import os import platform import subprocess import urllib.request import zipfile import tarfile import re from typing import List try: from .Env import Env except ImportError: from Env import Env class StandalonePythonEnv(Env): def __init__(self, path: str, version: str): super().__init__() self.version = version self.path = path self.python_path = self._get_python_executable_path() self.pip_path = self._get_pip_executable_path() def _get_platform_info(self): system = platform.system().lower() arch = platform.machine().lower() if arch in ['x86_64', 'amd64']: arch = 'amd64' elif arch in ['i386', 'i686', 'x86']: arch = 'win32' if system == 'windows' else 'x86' return system, arch def _get_python_executable_path(self): system, _ = self._get_platform_info() if system == 'windows': return os.path.join(self.path, 'python.exe') return os.path.join(self.path, 'bin', 'python3') def _get_pip_executable_path(self): system, _ = self._get_platform_info() if system == 'windows': return os.path.join(self.path, 'Scripts', 'pip.exe') return os.path.join(self.path, 'bin', 'pip') def _download_and_extract(self): system, arch = self._get_platform_info() base_url = 'https://www.python.org/ftp/python' if system == 'windows': url = f'{base_url}/{self.version}/python-{self.version}-embed-{arch}.zip' extract_path = self.path zip_path = os.path.join(self.path, 'python.zip') os.makedirs(self.path, exist_ok=True) urllib.request.urlretrieve(url, zip_path) with zipfile.ZipFile(zip_path, 'r') as zip_ref: zip_ref.extractall(extract_path) os.remove(zip_path) elif system == 'linux': url = f'{base_url}/{self.version}/Python-{self.version}.tgz' tar_path = os.path.join(self.path, 'python.tgz') os.makedirs(self.path, exist_ok=True) urllib.request.urlretrieve(url, tar_path) with tarfile.open(tar_path, 'r:gz') as tar_ref: if hasattr(tarfile, 'data_filter'): tar_ref.extractall(self.path, filter='data') else: tar_ref.extractall(self.path) os.remove(tar_path) # Компиляция Python из исходников source_dir = os.path.join(self.path, f'Python-{self.version}') subprocess.run([ './configure', f'--prefix={self.path}', '--enable-optimizations', '--with-ensurepip=install' ], cwd=source_dir, check=True) subprocess.run(['make', '-j8'], cwd=source_dir, check=True) subprocess.run(['make', 'install'], cwd=source_dir, check=True) import shutil shutil.copy2(os.path.join(self.path, 'bin', 'pip3'), os.path.join(self.path, 'bin', 'pip')) pass else: raise NotImplementedError(f"Unsupported platform: {system}") def create(self): self._download_and_extract() def install_pkgs(self, pkgs: List[str], repo: str = None, extra :str = None): command = [self.pip_path, 'install'] + pkgs if repo: command.extend(["--index-url", repo]) if extra: command.extend(["--extra-index-url", extra]) subprocess.run(command, check=True) @staticmethod def get_available_versions(): import requests from bs4 import BeautifulSoup url = 'https://www.python.org/downloads/' response = requests.get(url) soup = BeautifulSoup(response.text, 'html.parser') versions = [] for release in soup.select('.release-list .release-number'): version_text = release.text.strip().split()[-1] if re.match(r'^\d+\.\d+\.\d+$', version_text): versions.append(version_text) return sorted(versions, key=lambda x: tuple(map(int, x.split('.'))), reverse=True) if __name__ == '__main__': env = StandalonePythonEnv(os.path.join('/tmp', 'build/test_standalone'), '3.12.9') env.create() env.install_pkgs(['requests', 'numpy'])