From 9e5e214944e6cbf2dbc913159cb362c66b6ab53a Mon Sep 17 00:00:00 2001 From: Bacruru Sakaguchi Date: Fri, 12 Sep 2025 17:10:13 +0700 Subject: [PATCH] initial commit --- main.py | 3 + modelspace/ModelPackage.py | 167 +++++++++++++++ modelspace/Repository.py | 57 ++++++ modelspace/__init__.py | 0 .../__pycache__/ModelPackage.cpython-313.pyc | Bin 0 -> 8659 bytes .../__pycache__/Repository.cpython-313.pyc | Bin 0 -> 3091 bytes .../__pycache__/__init__.cpython-313.pyc | Bin 0 -> 147 bytes pythonapp/Decider/ABS.py | 78 +++++++ pythonapp/Decider/Loader.py | 190 ++++++++++++++++++ pythonapp/Decider/__init__.py | 0 .../Decider/__pycache__/ABS.cpython-313.pyc | Bin 0 -> 5102 bytes .../__pycache__/Loader.cpython-313.pyc | Bin 0 -> 8207 bytes .../__pycache__/__init__.cpython-313.pyc | Bin 0 -> 154 bytes .../Decider/__pycache__/misc.cpython-313.pyc | Bin 0 -> 2098 bytes pythonapp/Decider/misc.py | 31 +++ pythonapp/Env/Env.py | 12 ++ pythonapp/Env/StandaloneEnv.py | 120 +++++++++++ pythonapp/Env/Venv.py | 45 +++++ pythonapp/Env/__init__.py | 0 pythonapp/Env/__pycache__/Env.cpython-313.pyc | Bin 0 -> 874 bytes .../__pycache__/StandaloneEnv.cpython-313.pyc | Bin 0 -> 8060 bytes .../Env/__pycache__/Venv.cpython-313.pyc | Bin 0 -> 3131 bytes .../Env/__pycache__/__init__.cpython-313.pyc | Bin 0 -> 150 bytes pythonapp/Instance/ABS.py | 79 ++++++++ pythonapp/Instance/Instance.py | 144 +++++++++++++ pythonapp/Instance/__init__.py | 0 .../Instance/__pycache__/ABS.cpython-313.pyc | Bin 0 -> 5279 bytes .../__pycache__/Instance.cpython-313.pyc | Bin 0 -> 14599 bytes .../__pycache__/__init__.cpython-313.pyc | Bin 0 -> 155 bytes pythonapp/Libs/ConfigDataClass.py | 53 +++++ pythonapp/Libs/__init__.py | 0 .../ConfigDataClass.cpython-313.pyc | Bin 0 -> 3611 bytes .../Libs/__pycache__/__init__.cpython-313.pyc | Bin 0 -> 151 bytes .../Libs/__pycache__/git.cpython-313.pyc | Bin 0 -> 757 bytes .../Libs/__pycache__/pip_api.cpython-313.pyc | Bin 0 -> 5114 bytes pythonapp/Libs/getpytorch.py | 54 +++++ pythonapp/Libs/git.py | 10 + pythonapp/Libs/pip_api.py | 138 +++++++++++++ pythonapp/__init__.py | 0 .../__pycache__/__init__.cpython-313.pyc | Bin 0 -> 146 bytes requirements.txt | 1 + shell/Handlers/ABS.py | 85 ++++++++ shell/Handlers/GlobalHandler.py | 27 +++ shell/Handlers/ModelSpaceHandler.py | 18 ++ shell/Handlers/PythonappHandler.py | 88 ++++++++ shell/Handlers/__init__.py | 0 .../Handlers/__pycache__/ABS.cpython-313.pyc | Bin 0 -> 3730 bytes .../__pycache__/GlobalHandler.cpython-313.pyc | Bin 0 -> 1571 bytes .../ModelSpaceHandler.cpython-313.pyc | Bin 0 -> 1178 bytes .../PythonappHandler.cpython-313.pyc | Bin 0 -> 5285 bytes .../__pycache__/__init__.cpython-313.pyc | Bin 0 -> 151 bytes shell/Interactive.py | 96 +++++++++ shell/Parser.py | 42 ++++ shell/__init__.py | 0 shell/__pycache__/Interactive.cpython-313.pyc | Bin 0 -> 3708 bytes shell/__pycache__/Parser.cpython-313.pyc | Bin 0 -> 1506 bytes shell/__pycache__/__init__.cpython-313.pyc | Bin 0 -> 142 bytes 57 files changed, 1538 insertions(+) create mode 100644 main.py create mode 100644 modelspace/ModelPackage.py create mode 100644 modelspace/Repository.py create mode 100644 modelspace/__init__.py create mode 100644 modelspace/__pycache__/ModelPackage.cpython-313.pyc create mode 100644 modelspace/__pycache__/Repository.cpython-313.pyc create mode 100644 modelspace/__pycache__/__init__.cpython-313.pyc create mode 100644 pythonapp/Decider/ABS.py create mode 100644 pythonapp/Decider/Loader.py create mode 100644 pythonapp/Decider/__init__.py create mode 100644 pythonapp/Decider/__pycache__/ABS.cpython-313.pyc create mode 100644 pythonapp/Decider/__pycache__/Loader.cpython-313.pyc create mode 100644 pythonapp/Decider/__pycache__/__init__.cpython-313.pyc create mode 100644 pythonapp/Decider/__pycache__/misc.cpython-313.pyc create mode 100644 pythonapp/Decider/misc.py create mode 100644 pythonapp/Env/Env.py create mode 100644 pythonapp/Env/StandaloneEnv.py create mode 100644 pythonapp/Env/Venv.py create mode 100644 pythonapp/Env/__init__.py create mode 100644 pythonapp/Env/__pycache__/Env.cpython-313.pyc create mode 100644 pythonapp/Env/__pycache__/StandaloneEnv.cpython-313.pyc create mode 100644 pythonapp/Env/__pycache__/Venv.cpython-313.pyc create mode 100644 pythonapp/Env/__pycache__/__init__.cpython-313.pyc create mode 100644 pythonapp/Instance/ABS.py create mode 100644 pythonapp/Instance/Instance.py create mode 100644 pythonapp/Instance/__init__.py create mode 100644 pythonapp/Instance/__pycache__/ABS.cpython-313.pyc create mode 100644 pythonapp/Instance/__pycache__/Instance.cpython-313.pyc create mode 100644 pythonapp/Instance/__pycache__/__init__.cpython-313.pyc create mode 100644 pythonapp/Libs/ConfigDataClass.py create mode 100644 pythonapp/Libs/__init__.py create mode 100644 pythonapp/Libs/__pycache__/ConfigDataClass.cpython-313.pyc create mode 100644 pythonapp/Libs/__pycache__/__init__.cpython-313.pyc create mode 100644 pythonapp/Libs/__pycache__/git.cpython-313.pyc create mode 100644 pythonapp/Libs/__pycache__/pip_api.cpython-313.pyc create mode 100644 pythonapp/Libs/getpytorch.py create mode 100644 pythonapp/Libs/git.py create mode 100644 pythonapp/Libs/pip_api.py create mode 100644 pythonapp/__init__.py create mode 100644 pythonapp/__pycache__/__init__.cpython-313.pyc create mode 100644 requirements.txt create mode 100644 shell/Handlers/ABS.py create mode 100644 shell/Handlers/GlobalHandler.py create mode 100644 shell/Handlers/ModelSpaceHandler.py create mode 100644 shell/Handlers/PythonappHandler.py create mode 100644 shell/Handlers/__init__.py create mode 100644 shell/Handlers/__pycache__/ABS.cpython-313.pyc create mode 100644 shell/Handlers/__pycache__/GlobalHandler.cpython-313.pyc create mode 100644 shell/Handlers/__pycache__/ModelSpaceHandler.cpython-313.pyc create mode 100644 shell/Handlers/__pycache__/PythonappHandler.cpython-313.pyc create mode 100644 shell/Handlers/__pycache__/__init__.cpython-313.pyc create mode 100644 shell/Interactive.py create mode 100644 shell/Parser.py create mode 100644 shell/__init__.py create mode 100644 shell/__pycache__/Interactive.cpython-313.pyc create mode 100644 shell/__pycache__/Parser.cpython-313.pyc create mode 100644 shell/__pycache__/__init__.cpython-313.pyc diff --git a/main.py b/main.py new file mode 100644 index 0000000..2721987 --- /dev/null +++ b/main.py @@ -0,0 +1,3 @@ +from shell.Interactive import Interactive + +Interactive().start() \ No newline at end of file diff --git a/modelspace/ModelPackage.py b/modelspace/ModelPackage.py new file mode 100644 index 0000000..1fb6cf7 --- /dev/null +++ b/modelspace/ModelPackage.py @@ -0,0 +1,167 @@ +import os.path +import shutil +import uuid +from dataclasses import dataclass, fields +from pathlib import Path +from typing import List + +from pythonapp.Libs.ConfigDataClass import Config + + +@dataclass +class PackageInfo(Config): + """Информация о пакете""" + uuid: str = "" + name: str = "" + description: str = "" + release_date: str = "" + package_type: str = "" # unet, vae, text encoder + lineage: str = "" # sd 1.5, sdxl, flux.1 + size_bytes: int = 0 + version: str = "" + quantization: str = "" # fp8, bf16 + dependencies: List[str] = None + resources: List[str] = None + + def __post_init__(self): + if self.dependencies is None: + self.dependencies = [] + if self.resources is None: + self.resources = [] + super().__post_init__() + + +class ModelPackage: + def __init__(self, package_path: str, file_paths: List[str] = None, package_info: PackageInfo = None): + self.path = Path(package_path) + + # Создаем директорию если она не существует + self.path.mkdir(parents=True, exist_ok=True) + + # Путь к файлу package.json + self.package_file = self.path / "package.json" + + # Загружаем существующую информацию из файла + self.info = PackageInfo(filename=str(self.package_file)) + + # Если package_info передан и не пустой, обновляем информацию + if package_info is not None: + # Обновляем только те поля, которые не определены + for field in fields(package_info): + field_name = field.name + field_value = getattr(package_info, field_name) + if field_value is not None and field_value != "" and field_name != "filename": + current_value = getattr(self.info, field_name) + if current_value is None or current_value == "" or current_value == 0 or len(current_value) == 0: + setattr(self.info, field_name, field_value) + + # Генерируем UUID если он не определен + if not self.info.uuid: + self.info.uuid = str(uuid.uuid4()) + + # Сохраняем информацию о пакете + self.info.save() + + # Создаем директорию files + self.files_path = self.path / "files" + self.files_path.mkdir(exist_ok=True) + + if file_paths: + # Перемещаем файлы в директорию files + for file_path in file_paths: + src = Path(file_path) + if src.exists(): + dst = self.files_path / src.name + if src.is_dir(): + shutil.copytree(src, dst) + else: + shutil.copy2(src, dst) + + @classmethod + def interactive(cls, package_path: str, pkg_uuid = None): + """Интерактивное создание пакета через консоль""" + print("Введите информацию о пакете:") + if os.path.exists(str(Path(package_path) / "package.json")): raise RuntimeError("package exists!") + package_info = PackageInfo(str(Path(package_path) / "package.json")) + package_info.name = input("Название: ").strip() + package_info.description = input("Описание: ").strip() + package_info.release_date = input("Дата выхода (YYYY-MM-DD): ").strip() + package_info.package_type = input("Тип пакета (unet, vae, text encoder): ").strip() + package_info.lineage = input("Линейка (sd 1.5, sdxl, flux.1): ").strip() + + # Размер в байтах + while True: + try: + size = int(input("Размер в байтах: ").strip()) + package_info.size_bytes = size + break + except ValueError: + print("Введите корректное число") + + package_info.version = input("Версия: ").strip() + package_info.quantization = input("Квантование (fp8, bf16): ").strip() + + # Ввод зависимостей + print("Зависимости (введите по одной, пустая строка для завершения):") + dependencies = [] + while True: + dep = input().strip() + if not dep: + break + dependencies.append(dep) + package_info.dependencies = dependencies + + # Ввод ресурсов + print("Ресурсы (введите по одному, пустая строка для завершения):") + resources = [] + while True: + resource = input().strip() + if not resource: + break + resources.append(resource) + package_info.resources = resources + + # Генерируем UUID случайным образом (не запрашиваем у пользователя) + package_info.uuid = pkg_uuid + if not package_info.uuid: + package_info.uuid = str(uuid.uuid4()) + print(f"Сгенерирован UUID: {package_info.uuid}") + + # Ввод путей к файлам + print("Пути к файлам и директориям (введите по одному, пустая строка для завершения):") + file_paths = [] + while True: + file_path = input().strip() + if not file_path: + break + file_paths.append(file_path) + + # Создаем пакет + return cls(package_path, file_paths, package_info) + + @property + def uuid(self) -> str: + """Возвращает UUID пакета""" + return self.info.uuid + + @property + def name(self) -> str: + """Возвращает название пакета""" + return self.info.name + + @property + def dependencies(self) -> List[str]: + """Возвращает список зависимостей пакета""" + return self.info.dependencies.copy() # Возвращаем копию для защиты от изменений + + @property + def provides(self) -> List[str]: + """Возвращает список ресурсов, предоставляемых пакетом (включая имя пакета)""" + provides_list = self.info.resources.copy() # Возвращаем копию + if self.info.name: # Добавляем имя пакета, если оно есть + provides_list.append(self.info.name) + return provides_list + +if __name__ == "__main__": + p = ModelPackage('/tmp/pkg') + pass \ No newline at end of file diff --git a/modelspace/Repository.py b/modelspace/Repository.py new file mode 100644 index 0000000..7f70a2a --- /dev/null +++ b/modelspace/Repository.py @@ -0,0 +1,57 @@ +import uuid +from dataclasses import dataclass +from pathlib import Path + +from modelspace.ModelPackage import ModelPackage +from pythonapp.Libs.ConfigDataClass import Config + + +@dataclass +class RepoConfig(Config): + """Конфигурация репозитория с сидом""" + seed: str = "" # UUID сида + + +class Repository: + def __init__(self, path: str): + self.path = Path(path) + # Создаем директорию если она не существует + self.path.mkdir(parents=True, exist_ok=True) + + self.config_file = self.path / "repo.json" + + # Создаем конфигурацию + self.config = RepoConfig( + filename=str(self.config_file), + autosave=True + ) + + # Проверяем и устанавливаем сид + if not self.config.seed: + self._generate_and_save_seed() + + # Создаем поддиректорию model-packages если она не существует + self.model_packages_path = self.path / "model-packages" + self.model_packages_path.mkdir(exist_ok=True) + + def _generate_and_save_seed(self) -> None: + """Генерирует новый UUID и сохраняет его в конфиг""" + self.config.seed = str(uuid.uuid4()) + self.config.save() # Сохраняем сразу после генерации + + @property + def seed(self) -> str: + """Возвращает текущий сид""" + return self.config.seed + + def add_model_package_interactive(self) -> ModelPackage: + """Добавляет новый пакет модели интерактивно""" + # Генерируем новый UUID + package_uuid = str(uuid.uuid4()) + + # Создаем путь к новому пакету + package_path = self.model_packages_path / package_uuid + + # Вызываем интерактивное создание пакета + package = ModelPackage.interactive(str(package_path), package_uuid) + return package diff --git a/modelspace/__init__.py b/modelspace/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/modelspace/__pycache__/ModelPackage.cpython-313.pyc b/modelspace/__pycache__/ModelPackage.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..eec243e870b04958e62fb6c2bd2bb1fee4bf6425 GIT binary patch literal 8659 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 literal 0 HcmV?d00001 diff --git a/modelspace/__pycache__/__init__.cpython-313.pyc b/modelspace/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4c80e2f282d96d4e53c907bcbab90946cfe85051 GIT binary patch literal 147 zcmey&%ge<81n)TxW`O9&AOZ#$p^VQgK*m&tbOudEzm*I{OhDdekkl source (installed/requested) + severity: ConflictSeverity + error_message: str + resolution_action: str = "" + + def __post_init__(self): + # Автоматически определяем источники, если не предоставлены + if not self.sources: + self.sources = {pkg: "unknown" for pkg in self.conflicting_packages} + + + +class DeciderState(enum.Enum): + SUCCESS = 0 + WARN = 30 + ERROR = 50 + FATAL = 100 + + + +class Decider: + @classmethod + def decide(cls, instance: Instance, + packages: list[RequirementInfo] | Path, + pip_path: str, + index_url: Optional[str] = None, + extra_index_urls: Optional[list[str]] = None + ) -> tuple[list[RequirementInfo], list[ConflictError], DeciderState]: + raise NotImplemented + + @classmethod + def load(cls, instance: Instance, packages: list[RequirementInfo] | Path, manual=False): + if isinstance(packages, Path): + opt, req = requirements_separator(packages) + packages = requirements_converter(opt, req) + p = instance.path + c = instance.config + installed = Loader.load_existent_requirements( + str(p / c.manual_requirements_path), str(p / c.requirements_dir), + str(p / c.pinned_packages_path), str(p / c.excluded_packages_path)) + if not manual: + packages = Loader.filter_reqs(packages, str(p / c.pinned_packages_path), str(p / c.excluded_packages_path)) + installed.extend(packages) + return installed, packages + +class SimpleDecider(Decider): + @classmethod + def decide(cls, instance: Instance, + packages: list[RequirementInfo] | Path, + pip_path: str, + index_url: Optional[str] = None, + extra_index_urls: Optional[list[str]] = None, + manual=False) -> tuple[list[RequirementInfo] | None, list[ConflictError], DeciderState]: + all_packages, packages = cls.load(instance, packages, manual) + result = pip_api.run_pip_install(pip_path, [p.requirement_str for p in all_packages], index_url, extra_index_urls, dry_run=True) + if result.exit_code != 0: return None, [], DeciderState.FATAL + return packages, [], DeciderState.SUCCESS diff --git a/pythonapp/Decider/Loader.py b/pythonapp/Decider/Loader.py new file mode 100644 index 0000000..d876112 --- /dev/null +++ b/pythonapp/Decider/Loader.py @@ -0,0 +1,190 @@ + +import os +from typing import Literal +from pathlib import Path +from dataclasses import dataclass + + +@dataclass +class RequirementInfo: + """ + Класс для представления информации о требовании к пакету. + + Attributes: + requirement_str (str): + Полная строка требования как в файле requirements.txt. + Пример: "requests>=2.25.0" или "numpy==1.21.0". + + package_name (str): + Имя пакета без указания версии. + Пример: "requests" или "numpy". + + significance_level (Literal["manual", "required", "optional"]): + Уровень значимости требования по убыванию важности: + - "manual": пакеты, установленные вручную + - "required": обязательные зависимости + - "optional": опциональные зависимости + + source_file (str): + Имя файла требований, из которого было извлечено это требование. + Пример: "core.req" или "dev.opt". + """ + requirement_str: str + package_name: str + significance_level: Literal["manual", "required", "optional"] + source_file: str + + @classmethod + def from_requirement_string(cls, requirement_str: str, + significance_level: Literal["manual", "required", "optional"], + source_file: str) -> "RequirementInfo | None": + """ + Создает экземпляр RequirementInfo из строки требования. + + Args: + requirement_str: Полная строка требования + significance_level: Уровень значимости требования + source_file: Имя файла-источника требования + + Returns: + Экземпляр RequirementInfo с извлеченным именем пакета + """ + # Извлекаем имя пакета (все до первого символа сравнения или пробела) + if requirement_str == '': return None + package_name = requirement_str.split()[0].strip() + for comparison_op in ["==", ">=", "<=", ">", "<", "!=", "~="]: + if comparison_op in package_name: + package_name = package_name.split(comparison_op)[0].strip() + break + + return cls( + requirement_str=requirement_str, + package_name=package_name, + significance_level=significance_level, + source_file=Path(source_file).name # Сохраняем только имя файла, не полный путь + ) + + def __str__(self) -> str: + """Строковое представление требования.""" + return self.requirement_str + + +class Loader: + @classmethod + def _load_req_file(cls, path: str | Path, significance_level: Literal["manual", "required", "optional"], key: str) -> list[RequirementInfo]: + with open(str(path), 'r') as f: + res = [] + # Читаем строки, игнорируем пустые и комментарии + packages = [ + line.strip() for line in f + if line.strip() and not line.strip().startswith('#') + ] + for pkg in packages: + res.append(RequirementInfo.from_requirement_string(pkg, significance_level, key)) + return res + + @classmethod + def filter_reqs(cls, req_list: list[RequirementInfo], pinned: str, excluded: str): + # Загружаем исключения из pinned файла + if pinned and os.path.exists(pinned): + pinned_packages = list(cls._load_req_file(pinned, "required", 'pinned')) + + # Удаляем исключенные пакеты + criteria = [c.package_name for c in pinned_packages] + req_list = [r for r in req_list if r.package_name not in criteria] + + # Загружаем исключения из файла excluded (удаляем точные совпадения) + if excluded and os.path.exists(excluded): + excluded_packages = list(cls._load_req_file(excluded, "required", 'excluded')) + + # Удаляем исключенные пакеты + criteria = [c.requirement_str for c in excluded_packages] + req_list = [r for r in req_list if r.requirement_str not in criteria] + + return req_list + + @classmethod + def load_existent_requirements(cls, + manual_reqs: str, + reqs_dir: str, + pinned: str, + excluded: str + ) -> list[RequirementInfo]: + """ + Загружает и обрабатывает требования из файлов. + + 1. Загружает все файлы из self.config.requirements_dir с расширением .req, создает из их строк + 2. Загружает все файлы из self.config.requirements_dir с расширением .opt в списки словаря opt по ключам, соответствующим именам файлов без расширения. + 3. Удаляет из req и opt все пакеты из файла pinned (версия пакета не важна) + 4. Удаляет из req и opt все пакеты из файла excluded (версия пакета должна полностью совпадать. Например если в требованиях есть torch>=2.7.0 удаляем именно такие строки) + 5. Загружает файл manual_req в один из списков словаря с ключом "_manual" + 6. Возвращает значения + Args: + manual_reqs: Путь к файлу списка пакетов, установленных вручную + reqs_dir: Путь к директории со списками пакетов + pinned: Путь к файлу с исключениями установки + excluded: Путь к файлу с исключениями проверки + + Returns: + Два словаря списков - req и opt + """ + + req_list: list[RequirementInfo] = [] + + # Загружаем .req файлы + for req_file in Path(reqs_dir).glob("*.req"): + req_list.extend(cls._load_req_file(req_file, "required", req_file.stem)) + + # Загружаем .opt файлы + for opt_file in Path(reqs_dir).glob("*.opt"): + req_list.extend(cls._load_req_file(opt_file, "optional", opt_file.stem)) + + req_list = cls.filter_reqs(req_list, pinned, excluded) + + # Загружаем manual_reqs файл + if manual_reqs and os.path.exists(manual_reqs): + req_list.extend(cls._load_req_file(manual_reqs, "manual", "manual")) + + # Возвращаем результаты + return req_list + + + + +""" +Теперь давай разработаем статический класс Resolver и его метод check integrity. Приведенного ниже алгоритма есть проблема, что он может игнорировать некоторые опциональное зависимости, но не сообщает об этом коду, который далее будет устанавливать пакеты и не удаляет их из self.config.requirements_dir. +ВНИМАНИЕ!!! Этот код не должен менять сами файлы. Он может только удалять пакеты из своих временных списков, но не из файлов. Также он должен быть полностью инкапсулирован от класса Instance. Все необходимые для его работы данные должны быть переданы непосредственно при вызове метода check_integrity. Данные класса instance, ровно, как и принадлежащие ему файлы не должны быть изменены в результате работы. Все что делает этот код - получает исходные данные и на их основе принимает решение и возвращает ответ, никак не трогая сами исходные данные. +В соответствии с этим классом должен быть изменен метод check_integrity класса Instance, но ничего более. +Метод всегда должен возвращать три переменных: список оставшихся пакетов, список ошибок на req, список ошибок на opt. Переделай check integrity класса instance с учетом этого. +Также pip dry run должен проводиться в venv окружении self.config.test_venv +его алгоритм: +ЭТАП A: Загрузка +1. Получает на вход список запрашиваемых пакетов в исходном виде, с версиями или без. И флаги required и interactive +2. Создает два словаря списков req и opt +3. Загружает файл self.config.manual_requirements в один из списков словаря с ключом "_manual" +4. Загружает все файлы из self.config.requirements_dir с расширением .req в списки словаря req по ключам, соответствующим именам файлов без расширения. +5. Повторяет пункт 4 для файлов .opt и словаря opt +6. Находит во всех списках пакеты из self.config.pinned_packages и удаляет их +7. Формирует из словарей списков req и opt соответствующие общие списки пакетов. + +""" + + +""" +Напиши статический класс Loader, статически реализующий следующий метод load_requirements +Аргументы: + manual_reqs - Путь к файлу списка пакетов, установленных вручную + reqs_dir - Путь к директории со списками пакетов, необходимых различным модулям программы + pinned - Путь к файлу с исключениями установки + excluded - Путь к файлу с исключениями проверки +Возвращает: dict[str, list[str]], dict[str, list[str]] + Два словаря списков - req и opt +Алгоритм работы: + 1. Создает два словаря списков req и opt + 2. Загружает все файлы из self.config.requirements_dir с расширением .req в списки словаря req по ключам, соответствующим именам файлов без расширения. + 3. Загружает все файлы из self.config.requirements_dir с расширением .opt в списки словаря opt по ключам, соответствующим именам файлов без расширения. + 4. Удаляет из req и opt все пакеты из файла pinned (версия пакета не важна) + 4. Удаляет из req и opt все пакеты из файла excluded (версия пакета должна полностью совпадать. Например если в требованиях есть torch>=2.7.0 удаляем именно такие строки) + 5. Загружает файл manual_req в один из списков словаря с ключом "_manual" + 6. Возвращает значения +""" \ No newline at end of file diff --git a/pythonapp/Decider/__init__.py b/pythonapp/Decider/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pythonapp/Decider/__pycache__/ABS.cpython-313.pyc b/pythonapp/Decider/__pycache__/ABS.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a15454da92fff339327847aa75560d63324b5540 GIT binary patch literal 5102 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 literal 0 HcmV?d00001 diff --git a/pythonapp/Decider/__pycache__/Loader.cpython-313.pyc b/pythonapp/Decider/__pycache__/Loader.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a2e12f6802b00c8445bad9aca1f3ad9ee91e560b GIT binary patch literal 8207 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 literal 0 HcmV?d00001 diff --git a/pythonapp/Decider/__pycache__/misc.cpython-313.pyc b/pythonapp/Decider/__pycache__/misc.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8e12a0d9286a02e4edde79bd188115dd969426e3 GIT binary patch literal 2098 zcmdT_PfQ$T6o2z~7Z!Gbg;Lg7X2C<3C>vo(=%OX%Ukb?D>@Y0>60+>hvQw9t?KeYh z8m5ifH8*NP!BeoJc20TRE>%8;B6~4M!opH{Z}MLFJ63`dGo&aeczk+ zz3=^gdsJOr2`IynPf{Ts;0HP=iKA#7O`x#~GRVv*j4*_esLhVDBOKw-=BAlx{yAJ7 zVrAY3S9v1H!n8Qe3%E}f%a#Kzj`~8P?98uo%vA&`e?kug7@NNovr_o>MAd5x8d2v} z-5S=DhV3NkwV9-rR^tp^S`-oA96m>{p?4L|z*Kow!A!f@!a5doMq#{gnKjuUn5-8r z)&UqTQM&hpbQQ;!*s*#57f7IUI*Vt;Un!mhLC|@81ew*vD;&lf0?=87jnbo?5Si;L zW_7kW6T`e@UKbUy?C}C9e6&>UV-Xa#v|EBPOimI0b=Ac}-V=i%_8B~_gpbZ9!4J3a#xD2X%8cs>atsTa9sNHTtya+W^rfLIOS+?1>2kp%_Zc$b~!^QcS`3OfUih|Q33 zn`s@oB@E#;Nw-{*Zdj6fLo+QiAlkfhWEam@Z5N~e&g3KFF^aY9-!O0?pH6NgWbDCc=8 zTJG&9YfJc-1zGGW@3%~0&?bI1Xiv}O_dpA+DZwH2b+B*-~n z()|>=q=5$dD-7d?eqAM&N(eRwP$-&Ps21VS-@4fUu*SQ@AG$m%z0198od>SwT~~89 zwBGT4c;m*Q)3s8$T={nIfj9hxH@p@4Ec|i!LHyI+&!6Az89V5acYEZ$9%biBY$vYo zd5wK%=8*uk^+zJA|2%5Wi4a~KIl|l_5^&m%4F2<_wOK-Y&OTjcrS>8#wZCMlkQRIuYUX@l-bgH@)$@d6i^9z8Lxl{$FyF%IA0dET5I{RpbE8O@33pUof{44|;dc%fC=}{u_S+f!eRr literal 0 HcmV?d00001 diff --git a/pythonapp/Decider/misc.py b/pythonapp/Decider/misc.py new file mode 100644 index 0000000..0ee982d --- /dev/null +++ b/pythonapp/Decider/misc.py @@ -0,0 +1,31 @@ +from pathlib import Path + +from pythonapp.Decider.Loader import RequirementInfo + + +def requirements_separator(req_file: Path | str): + try: + with open(req_file, 'r') as file: + lines = [line.strip() for line in file] + req = [] + opt = [] + req_bool = True + for line in lines: + if line.startswith("#"): req_bool = False + if req_bool: req.append(line) + else: opt.append(line) + except FileNotFoundError: + raise RuntimeError(f"File {req_file} not exists") + + return req, opt + +def requirements_converter(req: list[str], opt: list[str]) -> list[RequirementInfo]: + res: list[RequirementInfo] = [] + for line in req: + if line.startswith("#") or line == '': continue + res.append(RequirementInfo.from_requirement_string(line.split(" ")[0].strip(), 'required', 'requested')) + for line in opt: + if line.startswith("#") or line == '': continue + res.append(RequirementInfo.from_requirement_string(line.split(" ")[0].strip(), 'optional', 'requested')) + + return res \ No newline at end of file diff --git a/pythonapp/Env/Env.py b/pythonapp/Env/Env.py new file mode 100644 index 0000000..395582a --- /dev/null +++ b/pythonapp/Env/Env.py @@ -0,0 +1,12 @@ + + +class Env: + def __init__(self): + self.pip_path = "" + pass + + def create(self): + raise NotImplemented + + def install_pkgs(self, pkgs: list, repo: str = None, extra: str = None): + raise NotImplemented diff --git a/pythonapp/Env/StandaloneEnv.py b/pythonapp/Env/StandaloneEnv.py new file mode 100644 index 0000000..a73cf40 --- /dev/null +++ b/pythonapp/Env/StandaloneEnv.py @@ -0,0 +1,120 @@ +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']) \ No newline at end of file diff --git a/pythonapp/Env/Venv.py b/pythonapp/Env/Venv.py new file mode 100644 index 0000000..57e50ea --- /dev/null +++ b/pythonapp/Env/Venv.py @@ -0,0 +1,45 @@ +import subprocess +import os +from warnings import deprecated + +try: + from .Env import Env +except ImportError: + from Env import Env + + +class Venv(Env): + def __init__(self, path: str, python='python3'): + super().__init__() + self.path = path + self.pip_path = os.path.join(self.path, "bin", "pip") + self.python = python + self._check_pip() + + def _check_pip(self): + if not os.path.exists(self.pip_path): self.pip_path = os.path.join(self.path, "Scripts", "pip") + + def create(self): + command = [self.python, '-m', 'venv', self.path] + subprocess.run(command) + + def install_pkgs(self, pkgs: list, repo: str = None, extra :str = None): + self._check_pip() + command: list = [self.pip_path, "install"] + command.extend(pkgs) + if repo: command.extend(["--index-url", repo]) + if extra: command.extend(["--extra-index-url", extra]) + subprocess.run(command, check=True) + + #@deprecated + def install_req(self, req_file: str, extra_index_url=None): + self._check_pip() + command = [self.pip_path, "install", "-r", req_file] + if extra_index_url: command.extend(["--extra-index-url", extra_index_url]) + subprocess.run(command) + +if __name__ == '__main__': + env = Venv(os.path.join('/tmp', 'build/test_venv')) + env.create() + env.install_pkgs(['requests', 'numpy']) + diff --git a/pythonapp/Env/__init__.py b/pythonapp/Env/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pythonapp/Env/__pycache__/Env.cpython-313.pyc b/pythonapp/Env/__pycache__/Env.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bf7c5256fcb90f2a868074574c6584a057ca8c92 GIT binary patch literal 874 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 literal 0 HcmV?d00001 diff --git a/pythonapp/Instance/ABS.py b/pythonapp/Instance/ABS.py new file mode 100644 index 0000000..78209bc --- /dev/null +++ b/pythonapp/Instance/ABS.py @@ -0,0 +1,79 @@ +from dataclasses import dataclass +from pathlib import Path +from pythonapp.Libs.ConfigDataClass import Config + + +class InstanceFileNaming: + config_dir = '.vaiola' + main_config = 'instance.json' + requirements_dir = 'requirements.d' + manual_requirements = 'requirements.txt' + pinned_packages = 'pinned.txt' + excluded_packages = 'excluded.txt' + env_dir = 'env' + app_dir = 'app' + + +@dataclass +class InstanceConfig(Config): + instance_type: str = 'basic' + config_dir_rel_path: Path = Path(InstanceFileNaming.config_dir) + env_path: Path = Path(InstanceFileNaming.env_dir) + env_type: str = None + requirements_dir: Path = Path(InstanceFileNaming.config_dir) / InstanceFileNaming.requirements_dir + manual_requirements_path: Path = Path(InstanceFileNaming.config_dir) / InstanceFileNaming.manual_requirements + pinned_packages_path: Path = Path(InstanceFileNaming.config_dir) / InstanceFileNaming.pinned_packages + excluded_packages_path: Path = Path(InstanceFileNaming.config_dir) / InstanceFileNaming.excluded_packages + created: bool = False + app: Path = Path(InstanceFileNaming.app_dir) + app_installed: bool = None + app_extensions_dir: Path = None + app_models_dir: Path = None + app_output_dir: Path = None + app_input_dir: Path = None + app_user_dir: Path = None + +class Instance: + def __init__(self): + self.path: Path | None = None + self.config: InstanceConfig | None = None + + def insert_component_reqs(self, name: str, req_file: str | Path): + try: + with open('req_file', 'r') as file: + lines = [line.strip() for line in file] + req = [] + opt = [] + req_bool = True + for line in lines: + if line.startswith("#"): + req_bool = False + if req_bool: req.append(line) + else: opt.append(line) + + with open(self.path / self.config.requirements_dir / (name + '.req'), 'w') as file: + for line in req: file.write(line + '\n') + with open(self.path / self.config.requirements_dir / (name + '.opt'), 'w') as file: + for line in opt: file.write(line + '\n') + + except FileNotFoundError: + raise RuntimeError(f"Cannot update requirements for {name}, file {req_file} not exists") + raise NotImplemented + + def install_git_app(self, url: str, requirements_file_in_app_dir = 'requirements.txt', + extensions_dir: str = None, + models_dir: str = None, + output_dir: str = None, + input_dir: str = None, + user_dir: str = None, + ): + raise NotImplemented + + def install_reqs(self, name, req_file: Path): + raise NotImplemented + + def create(self): + raise NotImplemented + + def install_packages(self, pkgs: list, repo, extra, pin=False): + raise NotImplemented \ No newline at end of file diff --git a/pythonapp/Instance/Instance.py b/pythonapp/Instance/Instance.py new file mode 100644 index 0000000..cffea0e --- /dev/null +++ b/pythonapp/Instance/Instance.py @@ -0,0 +1,144 @@ +import os.path +import shutil +import tempfile + +from pythonapp.Decider.ABS import SimpleDecider +from pythonapp.Env.Venv import Venv, Env +from pythonapp.Env.StandaloneEnv import StandalonePythonEnv +from pythonapp.Libs.git import git +from pythonapp.Decider.misc import * +from pythonapp.Libs.pip_api import pip_api +from .ABS import InstanceConfig, InstanceFileNaming, Instance as ABSInstance +from ..Decider.Loader import Loader + + +class Instance(ABSInstance): + def __init__(self, path: str, env: str = 'venv', python = 'python3'): + self.path = Path(path) + self.config = InstanceConfig(os.path.join(self.path, InstanceFileNaming.main_config)) + self.config.env_type = self.config.env_type or env + self.config.created = (Path(self.path) / self.config.config_dir_rel_path).exists() + + + if self.config.env_type == 'venv': + self.env: Env = Venv(str(self.path / self.config.env_path), python) + elif self.config.env_type == 'standalone': + self.env: Env = StandalonePythonEnv(str(self.path / self.config.env_path), python) + + + def insert_component_reqs(self, name: str, req_file: str | Path): + try: + req, opt = requirements_separator(req_file) + os.makedirs(self.path / self.config.requirements_dir, exist_ok=True) + with open(self.path / self.config.requirements_dir / (name + '.req'), 'w') as file: + for line in req: file.write(line + '\n') + with open(self.path / self.config.requirements_dir / (name + '.opt'), 'w') as file: + for line in opt: file.write(line + '\n') + + except FileNotFoundError: + raise RuntimeError(f"Cannot update requirements for {name}, file {req_file} not exists") + + def _symlink_move_dir(self, orig_dir: Path, dest: Path, obj_name: Path): + cwd = os.getcwd() + os.chdir(self.path) + shutil.move(orig_dir / obj_name, dest) + os.symlink(os.path.relpath(orig_dir, dest), orig_dir / obj_name) + os.chdir(orig_dir) + with open(".gitignore", "a") as file: + file.write(str(obj_name) + '\n') + os.chdir(cwd) + + + def install_git_app(self, url: str, requirements_file_in_app_dir = 'requirements.txt', + extensions_dir: str = None, + models_dir: str = None, + output_dir: str = None, + input_dir: str = None, + user_dir: str = None, + ): + if os.path.exists(self.config.app): + raise RuntimeError("App installed previously. Multiapp instances is not supported") + with tempfile.TemporaryDirectory() as tmp_dir: + git.clone(url, tmp_dir) + try: + self.install_reqs('app', Path(tmp_dir) / requirements_file_in_app_dir) + except RuntimeError as e: raise TypeError(e) + + self.config.app_extensions_dir = Path(extensions_dir) if extensions_dir else None + self.config.app_models_dir = Path(models_dir) if models_dir else None + self.config.app_output_dir = Path(output_dir) if output_dir else None + self.config.app_input_dir = Path(input_dir) if input_dir else None + self.config.app_user_dir = Path(user_dir) if user_dir else None + self.config.save() + + git.clone(url, self.path / self.config.app) + + if self.config.app_extensions_dir: + self._symlink_move_dir(Path(os.path.dirname(self.config.app / self.config.app_extensions_dir)), + Path(os.path.basename(self.config.app / self.config.app_extensions_dir)), + Path(os.path.basename(self.config.app / self.config.app_extensions_dir))) + if self.config.app_models_dir: + self._symlink_move_dir(Path(os.path.dirname(self.config.app / self.config.app_models_dir)), + Path(os.path.basename(self.config.app / self.config.app_models_dir)), + Path(os.path.basename(self.config.app / self.config.app_models_dir))) + if self.config.app_output_dir: + self._symlink_move_dir(Path(os.path.dirname(self.config.app / self.config.app_output_dir)), + Path(os.path.basename(self.config.app / self.config.app_output_dir)), + Path(os.path.basename(self.config.app / self.config.app_output_dir))) + if self.config.app_input_dir: + self._symlink_move_dir(Path(os.path.dirname(self.config.app / self.config.app_input_dir)), + Path(os.path.basename(self.config.app / self.config.app_input_dir)), + Path(os.path.basename(self.config.app / self.config.app_input_dir))) + if self.config.app_user_dir: + self._symlink_move_dir(Path(os.path.dirname(self.config.app / self.config.app_user_dir)), + Path(os.path.basename(self.config.app / self.config.app_user_dir)), + Path(os.path.basename(self.config.app / self.config.app_user_dir))) + + + def install_reqs(self, name, req_file: Path): + packages, errors, state = SimpleDecider.decide(self, req_file, self.env.pip_path) + if not packages: raise RuntimeError("Cannot install packages due conflicts") + self.env.install_pkgs([p.requirement_str for p in packages]) + self.insert_component_reqs(name, req_file) + + + def create(self): + os.makedirs(self.path / self.config.config_dir_rel_path, exist_ok=True) + self.config.save() + self.env.create() + + + def install_packages(self, pkgs: list, repo: str = None, extra: str = None, pin=False): + packages = [RequirementInfo.from_requirement_string(p, 'manual', 'manual') for p in pkgs] + packages, errors, state = SimpleDecider.decide(self, packages, self.env.pip_path, manual=True) + pkgs = [p.requirement_str for p in packages] + print("manually install packages", pkgs) + self.env.install_pkgs(pkgs, repo, extra) + + for pkg in pkgs: self._write_unique_string(pkg, file_path=self.path / self.config.manual_requirements_path) + if pin: + for pkg in pkgs: self._write_unique_string(pkg, file_path=self.path / self.config.pinned_packages_path) + + def exclude_packages(self, pkgs: list[str]): + for pkg in pkgs: self._write_unique_string(pkg, file_path=self.path / self.config.excluded_packages_path) + + + def _write_unique_string(self, string, file_path): + existing_lines = set() + if file_path.exists(): + with open(file_path, 'r') as file: + existing_lines = set(line.strip() for line in file) + + with open(file_path, 'a') as file: + if string not in existing_lines: + file.write(string + '\n') + + def _delete_string(self, string, file_path): + with open(file_path, 'r') as file: + lines = file.readlines() + filtered_lines = [line for line in lines if line.rstrip('\n') != string] + with open(file_path, 'w') as file: + file.writelines(filtered_lines) + + + diff --git a/pythonapp/Instance/__init__.py b/pythonapp/Instance/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pythonapp/Instance/__pycache__/ABS.cpython-313.pyc b/pythonapp/Instance/__pycache__/ABS.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..dd43c172cea9f937d220a21eedb448831586e368 GIT binary patch literal 5279 zcmb^#TWlN0agTR=Q!lLeLfp3M0-><9x+Ni@v^6^v7*uAfF_dTGS;e0<=KkKiX1Z)cxtqo=8)&-56+= zyt&!k*}2)ddF^f0)j0^XU+(+k;%}UU{2e=$X4yfuvVdG6A`!VMa+af92yxSt=`2rq zhVxUxSu-_M|sgi1(By_ zQJ@ylOs%4Y+C(e0i#F;I?bIncsB6I)ap~?eimD})q%-$=-r zbYitm-k}&=QZveGJ(Vu4$W&UE6ippWJ!st3*Dh!X|pz)fjA<7#_qK5b9n)5!SP zpPEnS6Zp6pAIDSk>3ISlFXQ7xKB9~IVt&wcT@Ba4N7q#~-OSe3v$Yo5!qztI^a5=KOv(sy1JumC|X#0XC;@9gdMr(wiJ53;yZk|i1a#F7bhq(4u zR?NfNy#HI}tDX9mUG(W~cjWymqUTTc`A8}PSKCWAnR7z-4ih`{W zy$-#WMXhvNO6hLwN|!ZBQRR%HGJmeeF$g(Qx~895(XuNVo8Mtk>AH}7MU^N{GOv$> zXdOI@)+1;@(1;)eK<8DB>JYdRiju*gfe(#S0q3mMaT*o4%HOpnoGUTnG#oeMfVXYpJ4e zwB&6DELjwqH-jDd&f$&DQ-#h`#o$Z%k?6+Ad;$K!c?e_cKs>+M9myX!v2kFcaA2a? zJ(-{S!N%030{pu#<%QO(KyqobqdVVoe4}Ti&@)o(7|l<7e`7)}z`sMz3(Z%dp5(ztJ~V=o>3`jpxr?+&HsbfPdF=UI<(rEegveZ%b}s?Oai4*=+RYeZ3pLp@MIy z*mx*6vIcuOR0_1gW`~PHTdBP(FZiztMWL&-zdbj-4$IqXu!n>#!Q3nB;oCycP$cSb zSJ*{^z_#@+fZbtNGzAD||#|kkrPdoYRW_uKFWK@te~2Ph-&hXNk}@G(V1Fa$PZejDtpcJD)^m~got8R1gQ4l zSK${TLqxH_WfghFcHRv9#tBh`s4!zTxYR;Xs03*AN^A*Gv1nFoQCoEc96f58!Ch))YP5^qpb4XJv@VPVNb(^8|6d`1`otVYef@GvliGt z#G}l}42HcuEh>mMTvyQn>;l3R>UFP9zjb--PmtI8Ey;^d4+**%p{nz^D;~CE1QX1dsvQ=DD$NL0KByBUs=*;*Ih%nahK8pF zRd1`tt;fz|u2tjkzH_8(a@!^D%O>CY(zUM7n|iK?HxEBsY(Dn;@b&PAfe!+|X#HJy z-JEYecAqEi-ml%>wUH77`}VI*e$gCU7fPOe@1J__)U_iUp5B6|x6~e9pZuF=|5fqB z3m;s#F5C_dl>9C4FTJ;P&3oJ5bJtE<+O}Q9?O#v5FTW>W3x9m@qk|irCkycRpS&6V z%>So>g8#MrO!V`n+5Ckce$m>#KKgOsqrmmi-wxlHy4`X7gAtVFJogU}`v2vz?H~=o zysh=$_qs^Sakj_XO+63p`|$yPZJK@X&!$;^GNYPT?9q{$TfC?{LDPY^FMDHJ!$!s39w(Eu7klpzYrYr z@qcN40q{>f!Lc6x(?d4IPa^d#-ZAF3-m1fqTRz8Fm-SY=74aU&*mKrfhj_%FWAH?9 zyoI0E`4yVh>-Ut3j+!#jQBxw?Wu;Oc+A5`CvNrI7c^K&mYs4$$B0!@_9L2nIgQI8R z7O@%DECQ~#);b%@fRIV!SbbvQc@9Wu6u~Tl^9a5NK*h)Iw(AD4TP=}J#}^TwaX zv2%&>pi(xYo574|Lg(Qiqar`{dJIK0RuobF2GLTLYVXuEidK~`Vltv9#?gp{VmzD1 z7U9Mt^^ZM}Jdf+~6lt74zKd84K^#F60ESJ7*h|=d+)?2$P9O-wrLF^bhukxn%!kW1 zuQ_g&Jjw3>t0j+6=9N__{*Jlwwy z_m<6YFIz}=U)hS7jlh^4F$d}D`4=uKyE%Bw@HTjlLR3sc5VSb%E7I{5Y5s~lbKhd)ymtwJvY(io+ondYZGCo&z-^0%fie!< O4CsP literal 0 HcmV?d00001 diff --git a/pythonapp/Instance/__pycache__/Instance.cpython-313.pyc b/pythonapp/Instance/__pycache__/Instance.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e34067bf824500f57a532066cd41ecb47d41ffe6 GIT binary patch literal 14599 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 None: + """Загружает значения полей из файла""" + try: + if Path(self.filename).exists(): + with open(self.filename, 'r') as f: + data = json.load(f) + for field in fields(self): + if field.name in data and field.name != 'filename': + setattr(self, field.name, data[field.name]) + except Exception as e: + print(f"Error loading config: {e}") + + def save(self) -> None: + """Сохраняет текущие значения полей в файл""" + try: + # Преобразуем объект в словарь, исключая поле filename + data = asdict(self) + data.pop('filename', None) + + # Создаем директорию, если она не существует + Path(self.filename).parent.mkdir(parents=True, exist_ok=True) + + # Сохраняем в файл с кастомным сериализатором для Path объектов + with open(self.filename, 'w') as f: + json.dump(data, f, indent=4, default=self._json_serializer) + except Exception as e: + print(f"Error saving config: {e}") + + def _json_serializer(self, obj: Any) -> Any: + """Кастомный сериализатор для объектов, которые не могут быть сериализованы по умолчанию""" + if isinstance(obj, Path): + return str(obj) + raise TypeError(f"Object of type {type(obj).__name__} is not JSON serializable") \ No newline at end of file diff --git a/pythonapp/Libs/__init__.py b/pythonapp/Libs/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pythonapp/Libs/__pycache__/ConfigDataClass.cpython-313.pyc b/pythonapp/Libs/__pycache__/ConfigDataClass.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5a67c184c7223b4900a700143801d1821817c475 GIT binary patch literal 3611 zcmaJ^>vPoB6~9_(-}3qm#z2wH!Pw+GP9)1-n3F;gn=Oqmg&DwqUK@Udc(I zUmQpq2$|R<_Z%)k&GvQ0ml~#)(ZtjlH zJ@?*okB-hgzx$JyN=hOK+HYREIC?vT(0}N_YyN_=Gz`i#l90soBkcAJW?<~=_w})u z?c*@l=f{45uCe`rz90_vg>c9jbN%7I2#yG7h^;~ONb=VrDbVV3eYX}Kfukdg7`#tM zdqtLnQ;Ml1Gm2r5fMTSyq)7s4P0geX(MQ-W{V?HrVNzrW_e;ebO%}X39ak7Ey$;GW z8bUNu?2{PGZbn1gkYDmOz>37;z)%oIoHK%`a7ZeVLbd21hr^(S9W5e7hN4nnD7u}K zqRtF2@wj9pD8@*jC#$Emk^7K0L_%pTqw30-x|4*IaWiWuZ>UM85DV?t!r#(yP^Qr! z8d$zcEueY%5a@a#NA8L%e_9wQbqFQAH$?-1A1^b;g6i2*4ez(2jpS>8 zvtR?|pD6csA6U`)z{ZuHLsQdAvkFZ>d-@TILNBrO=e;dd89Tz(p(f;ID+QTP`vkNB zZ0Twzy8)${I+UnzJs}iCN)5anmw@QqCUJvY3~lpy?@xD2tj=|DBk(NRrmQ(9u#XDXMeB++YrktyZ!vm+v|S*ogdY=JoKYgEmn0ips%dyZgumm>gGoeBB%_I z$UrcBxmDGe-Kj#ek-#V37n&Md+z|v?K-h*>h*I~H~B3O0;r^Xs_OLmQ|oW=8|L{fHos+_Z?gHO z+5KPlT2$K&HWjO;Qm4mGjh&NjmW%mU7Rstlhd&I@Y|7Wo?w@b%vs?TAS=RrMMHP+n z<>Kvf@zK4~8j94ogH$nJm#?%cn&5<48;;f-_q|uOfRQkCQ-EW>eZtQ z1rW6ikws$Mw2X2kCJ_dgPJ2w9D^Y6rHfYMr;=b1Io``?N{0zJPVd1{^?LU!melSo;iW z9oXl*ebPQ^zyCweu-k#hNr#XGU@W@pG7uGj28il|Bc=O>52{I1$fgDJa84DDtPwOr z(6gq{C+*uScm=5pXH+pjBATJ;hN zZ~R%DZ|txeJ1nu&>U`Cz`PGfs0V{L>(|hF;04)Z#hp+w^Zik*kWO*!`8qYvrgqP)C zk1H8BOj0V#X$>1@M$=V2E6ex=Y7euD4yjNmr5Z5ne8zIa*=z<=esr0bLJ!jd#Li1q z%Q7U$)RHpgWNmodRO!|ZYCyws`Idg0VJaxqJ5YEReH$x3w&zZD?ZmF*sT*AFf4P9a z;Xzr%|LlXBm_NQKaQ@erb3KcQ_W7QNPVc&;jzr$MPcXgENwoM+j%r{E4ChXAKNwb9nQ fXx+b14SY-cq7Qvd%xladqH>9aiSL{VC*c193J6?a literal 0 HcmV?d00001 diff --git a/pythonapp/Libs/__pycache__/__init__.cpython-313.pyc b/pythonapp/Libs/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f1992551c1a72e97805f5cd48c074a9900ad5258 GIT binary patch literal 151 zcmey&%ge<81UB!sWq|0%AOZ#$p^VQgK*m&tbOudEzm*I{OhDdekkl<0K#e literal 0 HcmV?d00001 diff --git a/pythonapp/Libs/__pycache__/git.cpython-313.pyc b/pythonapp/Libs/__pycache__/git.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..78c7995726b66bb59c3b2f1ba62c798f220e4ff0 GIT binary patch literal 757 zcmZ8e&ubGw6n?X_KibqZB(^k-`E8o4~$yD&;+^Wg-(WHy;LJ^lIl>AIh+7PJ}|>a44;_3<>Os= ziUy5{S-#2aiQ(E3PhxSQ+YiH)g%)Y9zba8Mhd>zx;Nc7hNH@?yy8r<`LUnc-12kw> z_u+9fIKa{Bke8$f%3-C=%)q<>t}&EWnB-aHVrkQ&h_bP4@p4qKJd8M(n3b7pNnEl- zZsesXN%E2ZxqCRX&pX?*cBM+-gFf1FNY{~Tgk&_02$3y9(tKPdnqML0w4_P1 z(;?(Z%(zJ6EXs1d%>@;4m_};UaXs5MA@w4mLa=yLiii`!da55&&jYNZLEp?0G~rhj zyn=J9TN$R+uQ1rXC94ax#5LIZs^mW}xpuc6_a3{ZB>I`T?)D2KguX)ek8Pv=Z&0wb F`v;_Mq*nj{ literal 0 HcmV?d00001 diff --git a/pythonapp/Libs/__pycache__/pip_api.cpython-313.pyc b/pythonapp/Libs/__pycache__/pip_api.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3ea85f9a6bd49977856a3b8ee1f8a5e50eaf467f GIT binary patch literal 5114 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 literal 0 HcmV?d00001 diff --git a/pythonapp/Libs/getpytorch.py b/pythonapp/Libs/getpytorch.py new file mode 100644 index 0000000..9d83770 --- /dev/null +++ b/pythonapp/Libs/getpytorch.py @@ -0,0 +1,54 @@ +from typing import List +from dataclasses import dataclass +from .pip_api import pip_api + + + +@dataclass +class PyTorchInfo: + """Датакласс для хранения информации о версиях PyTorch компонентов""" + torch: List[str] + torchvision: List[str] + torchaudio: List[str] + + +class getpytorch: + """Класс для получения версий компонентов PyTorch""" + BASE_URL = "https://download.pytorch.org/whl/" + + def __init__(self, base_url: str = None): + self.base_url = base_url or self.BASE_URL + + def get_versions(self, api: str) -> PyTorchInfo: + """Получает версии всех компонентов PyTorch для указанного API""" + base_url = f"{self.base_url.rstrip('/')}/{api}" + + return PyTorchInfo( + torch=self.get_torch_versions(api), + torchvision=self.get_torchvision_versions(api), + torchaudio=self.get_torchaudio_versions(api), + ) + + def get_torch_versions(self, api: str) -> List[str]: + """Получает версии torch""" + return pip_api.get_pkg_versions('torch', f"{self.base_url.rstrip('/')}/{api}") + + def get_torchvision_versions(self, api: str) -> List[str]: + """Получает версии torchvision""" + return pip_api.get_pkg_versions('torchvision', f"{self.base_url.rstrip('/')}/{api}") + + def get_torchaudio_versions(self, api: str) -> List[str]: + """Получает версии torchaudio""" + return pip_api.get_pkg_versions('torchaudio', f"{self.base_url.rstrip('/')}/{api}") + + +if __name__ == "__main__": + # Пример использования + pytorch = getpytorch() + + api = input("API version (cu121): ") or "cu121" + versions = pytorch.get_versions(api) + + print(f"Все версии PyTorch: {versions.torch}") + print(f"Все версии torchvision: {versions.torchvision}") + print(f"Все версии torchaudio: {versions.torchaudio}") diff --git a/pythonapp/Libs/git.py b/pythonapp/Libs/git.py new file mode 100644 index 0000000..13723e1 --- /dev/null +++ b/pythonapp/Libs/git.py @@ -0,0 +1,10 @@ +import subprocess + + +class git: + @staticmethod + def clone(url, output_path = None, git = 'git'): + command = [git, 'clone', url] + if output_path: command.append(output_path) + result = subprocess.run(command) + return result \ No newline at end of file diff --git a/pythonapp/Libs/pip_api.py b/pythonapp/Libs/pip_api.py new file mode 100644 index 0000000..eae090c --- /dev/null +++ b/pythonapp/Libs/pip_api.py @@ -0,0 +1,138 @@ +import subprocess +from typing import List, Optional +import re +import subprocess +import shlex +from typing import List, Optional, Dict, Any, Union +from dataclasses import dataclass + +@dataclass +class PipResult: + """Результат выполнения команды pip.""" + exit_code: int + stdout: str + stderr: str + command: str + success: bool + + +class pip_api: + @staticmethod + def get_pkg_versions(package: str, index_url: Optional[str] = None) -> List[str]: + # Формируем базовую команду + command = [ + 'python3', '-m', 'pip', + 'install', + '--use-deprecated=legacy-resolver', # Для совместимости со старыми репозиториями + '--dry-run', + '--no-deps', + f'{package}==0.0.0.0' # Специально несуществующая версия + ] + + if index_url: + command.extend(['--index-url', index_url]) + + # Запускаем процесс + result = subprocess.run( + command, + capture_output=True, + text=True, + timeout=30 # Таймаут для избежания зависаний + ) + + #print(result.stdout) + #print(result.stderr) + # Парсим вывод для получения версий + if result.stderr: + match = re.search(r'from versions: (.+?)\)', result.stderr) + if match: + versions = match.group(1).split(', ') + return [v.strip() for v in versions if v.strip()] + + return [] + + @staticmethod + def run_pip_install( + pip_path: str, + packages: List[str], + index_url: Optional[str] = None, + extra_index_urls: Optional[List[str]] = None, + dry_run: bool = False + ) -> PipResult: + """ + Выполняет установку пакетов через pip с указанными параметрами. + + Args: + pip_path: Путь к исполняемому файлу pip + packages: Список пакетов для установки + index_url: Основной URL репозитория пакетов + extra_index_urls: Дополнительные URLs репозиториев пакетов + dry_run: Если True, команда только выводится, но не выполняется + + Returns: + PipResult: Объект с результатами выполнения команды + + Raises: + FileNotFoundError: Если pip_path не существует + ValueError: Если packages пуст + """ + # Проверка входных параметров + if not packages: + raise ValueError("Список пакетов не может быть пустым") + + # Формирование команды + command = [pip_path, "install"] + + if dry_run: + command.append('--dry-run') + + # Добавление основного index-url + if index_url: + command.extend(["--index-url", index_url]) + + # Добавление дополнительных index-urls + if extra_index_urls: + for url in extra_index_urls: + command.extend(["--extra-index-url", url]) + + # Добавление пакетов + command.extend(packages) + + # Преобразование команды в строку для вывода + command_str = " ".join(shlex.quote(arg) for arg in command) + + # Выполнение команды + try: + result = subprocess.run( + command, + capture_output=True, + text=True, + check=False # Не вызываем исключение при ненулевом коде возврата + ) + + return PipResult( + exit_code=result.returncode, + stdout=result.stdout, + stderr=result.stderr, + command=command_str, + success=result.returncode == 0 + ) + + except FileNotFoundError: + raise FileNotFoundError(f"Файл pip не найден по пути: {pip_path}") + except Exception as e: + return PipResult( + exit_code=-1, + stdout="", + stderr=str(e), + command=command_str, + success=False + ) + + + + + +if __name__ == "__main__": + versions = pip_api.get_pkg_versions("torch", "https://download.pytorch.org/whl/cu121") + print(versions) \ No newline at end of file diff --git a/pythonapp/__init__.py b/pythonapp/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pythonapp/__pycache__/__init__.cpython-313.pyc b/pythonapp/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..60a3fd205db9d30c8bfa559ab6eb7b188fee14d0 GIT binary patch literal 146 zcmey&%ge<81Rq{+%K*`jK?DpiLK&Y~fQ+dO=?t2Tek&P@n1H;`AgNo1`WgATsrpI9 ziP?$irO6qY`YEYp`eBZq`8kRD1(hWk`FV*21^V&vnR%Hd@$q^EmA5!-a`RJ4b5iY! VSb-*hY$ygXJ~A^hG8QodSpbG&BNG4s literal 0 HcmV?d00001 diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..3d90aaa --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +colorama \ No newline at end of file diff --git a/shell/Handlers/ABS.py b/shell/Handlers/ABS.py new file mode 100644 index 0000000..cd12217 --- /dev/null +++ b/shell/Handlers/ABS.py @@ -0,0 +1,85 @@ +from pathlib import Path + +from modelspace.Repository import Repository +global_repo = Repository(str(Path('..') / 'repo')) + + +class ExecutionError(RuntimeError): pass + + +class Handler: + syntax_error = SyntaxError + execution_error = ExecutionError + + def __init__(self): + self.forwarding_table: dict[str, Handler] = {} + self.handle_table: dict = {} + self.succeed = False + pass + + @staticmethod + def _check_arg(keys: dict, arg: str): + if not keys.get(arg, None): raise TypeError('Unfilled argument:', arg) + + + def handle(self, command: list[str], pos = 0): + self.succeed = False + if len(command) <= pos: raise self.syntax_error + verb = command[pos].lower() + + if verb in self.forwarding_table: + self.forwarding_table[verb].handle(command, pos + 1) + if self.forwarding_table[verb].succeed: + self.succeed = True + return + + elif verb in self.handle_table: + self.handle_table[verb](command, pos + 1) + if self.succeed: + return + + @staticmethod + def parse_arguments(args_list: list[str], expected_args: list[str], key_mode=False) -> tuple[dict[str, str | None], list[str]]: + """ + Парсит список аргументов согласно ожидаемым именам. + + Args: + args_list: список аргументов для обработки + expected_args: список ожидаемых имен аргументов + + Returns: + Кортеж из двух элементов: + - Словарь с ключами из expected_args и значениями аргументов (или None) + - Список необработанных аргументов + """ + # Инициализируем результат значениями None для всех ожидаемых аргументов + result: dict[str, str | None] = {arg: None for arg in expected_args} + + # Извлекаем именованные аргументы (в формате "имя:значение") + remaining_args = [] + + for arg in args_list: + if ':' in arg: + key, value = arg.split(':', 1) + if key in expected_args: + result[key] = value + else: + # Если имя аргумента не ожидается, добавляем в оставшиеся + remaining_args.append(arg) + else: + remaining_args.append(arg) + + # Заполняем оставшиеся аргументы по порядку + arg_index = 0 + if not key_mode: + for arg_name in expected_args: + if result[arg_name] is None and arg_index < len(remaining_args): + result[arg_name] = remaining_args[arg_index] + arg_index += 1 + + # Все оставшиеся аргументы добавляем в unsorted + unsorted = remaining_args[arg_index:] + + return result, unsorted + + diff --git a/shell/Handlers/GlobalHandler.py b/shell/Handlers/GlobalHandler.py new file mode 100644 index 0000000..4431c1d --- /dev/null +++ b/shell/Handlers/GlobalHandler.py @@ -0,0 +1,27 @@ +from shell.Handlers.ABS import Handler +from shell.Handlers.PythonappHandler import PythonappHandler +from shell.Handlers.ModelSpaceHandler import ModelSpaceHandler + + +class GlobalHandler(Handler): + def __init__(self): + super().__init__() + self.forwarding_table: dict[str, Handler] = { + 'pythonapp': PythonappHandler(), + 'modelspace': ModelSpaceHandler(), + } + self.handle_table: dict = { + 'tell': self._tell + + } + + def _tell(self, command: list[str], pos = 0): + command_str = '' + for word in command[pos:]: command_str += word + ' ' + print(command_str) + self.succeed = True + + def _exit(self, command: list[str], pos = 0): + raise KeyboardInterrupt + + diff --git a/shell/Handlers/ModelSpaceHandler.py b/shell/Handlers/ModelSpaceHandler.py new file mode 100644 index 0000000..d5d9c88 --- /dev/null +++ b/shell/Handlers/ModelSpaceHandler.py @@ -0,0 +1,18 @@ +from shell.Handlers.ABS import Handler, global_repo + + +class ModelSpaceHandler(Handler): + def __init__(self): + super().__init__() + self.forwarding_table: dict[str, Handler] = { + } + self.handle_table: dict = { + 'create_inter': self._create_inter + + } + + pass + + def _create_inter(self, command: list[str], pos=0): + global_repo.add_model_package_interactive() + self.succeed = True diff --git a/shell/Handlers/PythonappHandler.py b/shell/Handlers/PythonappHandler.py new file mode 100644 index 0000000..ecf7857 --- /dev/null +++ b/shell/Handlers/PythonappHandler.py @@ -0,0 +1,88 @@ +from shell.Handlers.ABS import Handler + +from pythonapp.Instance.Instance import Instance + + +class PythonappHandler(Handler): + + + + def __init__(self): + super().__init__() + self.forwarding_table: dict[str, Handler] = { + 'package': PackageHandler(self) + } + self.handle_table: dict = { + 'create': self._create, + 'load': self._load, + 'show': self._show, + 'activate': self._activate, + } + self._loaded_instances: dict[str, Instance] = {} + self._active_instance: Instance | None = None + + + def _load(self, command: list[str], pos = 0): + keys, args = self.parse_arguments(command[pos:], ['path', 'name']) + self._check_arg(keys, 'path') + if not keys['name']: keys['name'] = 'app' + i = Instance(path=str(keys['path'])) + if not i.config.created: raise self.execution_error("ACTIVATE INSTANCE: instance not exists") + self._loaded_instances[keys['name']] = i + self._active_instance = i + print(f"instance {keys['path']} loaded and activated. identified by {keys['name']}") + + def _activate(self, command: list[str], pos = 0): + keys, args = self.parse_arguments(command[pos:], ['name']) + self._check_arg(keys, 'name') + i = self._loaded_instances.get(command[1], None) + if i: + self._active_instance = i + else: + raise ValueError(f"pyapp {keys['name']} not loaded") + + def _show(self, command: list[str], pos = 0): + print("Environment type:", self._active_instance.config.env_type) + # TODO Add new config info (app section) + + def _create(self, command: list[str], pos = 0): + keys, args = self.parse_arguments(command[pos:], ['env', 'path', 'python']) + + + self.succeed = True + + pass + + @property + def active_instance(self): + return self._active_instance + + +class PackageHandler(Handler): + def __init__(self,parent: PythonappHandler): + super().__init__() + self.forwarding_table: dict[str, Handler] = {} + self.handle_table: dict = { + 'install': self._install, + 'exclude': self._exclude + } + self.parent = parent + + def _install(self, command: list[str], pos = 0): + if not self.parent.active_instance: raise self.execution_error("I don't have active instance yet!") + keys, args = self.parse_arguments(command[pos:], ['pin', 'index', 'extra'], key_mode=True) + if keys['pin']: pin = True + else: pin = False + self.parent.active_instance.install_packages(pkgs=args, repo=keys.get('index', None), extra=keys.get('extra', None), pin=pin) + + def _exclude(self, command: list[str], pos = 0): + if not self.parent.active_instance: raise self.execution_error("I don't have active instance yet!") + pkgs = command[pos:] + self.parent.active_instance.exclude_packages(pkgs) + + + + + self.succeed = True + + diff --git a/shell/Handlers/__init__.py b/shell/Handlers/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/shell/Handlers/__pycache__/ABS.cpython-313.pyc b/shell/Handlers/__pycache__/ABS.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f6500276f5dbee06fb475c5e702d6272f3f4d935 GIT binary patch literal 3730 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 a=lox}5hX6fAE5q!yGJQXc#KGIi~b+dZT27l literal 0 HcmV?d00001 diff --git a/shell/Handlers/__pycache__/GlobalHandler.cpython-313.pyc b/shell/Handlers/__pycache__/GlobalHandler.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ec360447c024bbb4c4f7f4fb699ce3c0383e2a42 GIT binary patch literal 1571 zcmZWp&u<$=6n^8~wKtC4CUHpy)@7?VC4m-n@D9z3*+O zSj+>CTep929%TUjqD5~QsnVH4Z=ruza6&Qmz!Pd)Q z8>?(a>z)_cjjWY>K?{qi+;t~x1fD>3=;n6Zj6$c?dSx44j2d2Gw;b0?jczweSL*4a za|e~rFsW4#%m$IMnb7KRnc0~#Y??y1bGEi>tY+yAmot>oroPEUuFr0ycGJ#^fjWG^ zME)XJrYc?wqBSQ-Q+ScZC@xFyriq?LU*{_nbLzwKM6u0xLZrk8LZt zo_Ej7X49EE1ikkmymj~_DNWC_JHLz314<_u8(?WmRz?l9dPu7L76B*;K&8iC5Lh{7 z$ac#U%H-S+{g`t>*9gi=4XsDwz9SlbxWQv*E%21V>69uolR9;cc!e&#U|kJx?goyO zoJ(X2RCd&rYBSpMs%z4@=WMjyreAG%UsP{hy%7aYRW>nHHAPWYPk=hz+7_oMh0&Ad zUXlcnd>_Tj9e8dQzsWr@$DWzxzsIeoEO@lSKSgNH%o+-PeH-{PipBqI<2N&Rl3g4AZAc4x#!a|x~^>e0t_-Ofzm-c%7F z)D{Z>gMgJh=0IJWNp)-hWL|K*gEi5Y6ew0%M%av&@WWW;WZQK;uVEQt1fz2FBg9+ z{rdKk$=RQ-JX}QVcJCo@duPw>>faW`C>}V01!bOKLDl#_lRaj!CG6AT1#f#TLXzAF zV^4^7E4H*(&kz^zn4|!`jFCd#mwxR0)10sSLdJm~dSQh8PI#pUzKoTIxF!&aNermo zK<36y?7Q5FW8ts0V-Lx!iYc#Mgq3r=y@bw^komA%1Y)BgLA@u5885$vqs)MjKOEJK z^N*K*oc!VL5ukclU`D08{ufZ~kVsycoL|Cz97+0Oy4M%eS3j}^0jMVWHu`^%N#m+Y zpVw=@%%ruG+kS@b%|s++&UW#e6Yt@Y9w*Bv4m8HtKD@OLv->djPqxG=53U^nPRZH- E0Qv@VrvLx| literal 0 HcmV?d00001 diff --git a/shell/Handlers/__pycache__/ModelSpaceHandler.cpython-313.pyc b/shell/Handlers/__pycache__/ModelSpaceHandler.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..89cfa5ae64a0271554639f3dd4ffbe9222abe4eb GIT binary patch literal 1178 zcmZuw%}*0S6rb7Mw%gM3AtJ~}Y=BEWv=HEi2*!hAwEp zBMC>n>3^Yr0)_-LG4bGuTTLZ$^u65{n&2dT^X7f@z4v>+ZDukl0%zmN*YX!i$S-`f zPpqXhZ-a6`EMn0|WSI)OtO+ekhR8Uvv|(cDn{-Z(VufBZaZy-oy<}!uwx3tibI*$W*mE()hq+7#tsz; zD<;f!a)*lKB*`UI-($bXJ*#FryjA-YOE@sv#lcqb*W3WHKo7{X3)dQ5C(oh8T+5Or zQ)HcZT06i3|7yJby1b{r?zLwjC769hr5(ZTfHSumaFL6tn5@?T3X_4w4H#3s+rD^h zi=taCvB2K)xXQ539F;DI@I_95(~#WTR!PPj&z6!giG5}F)ZIeaukgZ_v|rk#x>I%w zMZR0uSbFGtc0rc8=M`FD$ifwT^0haj3;st9Uv;%xMbPCei1R)2!_2%(9GOGM=5Rx! zbE$pf*c@xbNoM5m{>fL>qM(sr|KTR)LFwGX z>ILE$7M<~qt>7Lmw-VyLmeU1pK#keOBC8+`7|;L^X|2F^0(Y0IgseLb=S2wbfpBph zbM%@oC3cd{WT5c!hyp0m7Jljh0MygnU!C6~6l?tw11xjSU!xY<_IT4!90>?8JZAg&2}i9Tsdd;B42@06BrhSrvJz{p!j%V6F_zjBPWct)|?*0fQ+O!Y7 z182{kd+yo$^PQio&8n&j0!?oC_2f?~3Hb}YcujJd%}c<1N))1S?~(zIa#7^@cliN< z3QKq=79`X(AYtpQB2QvxAqkbkfi%uyTLew6t_WPdLLJ zD!MP5KL_SRn86sKoI)tCa8wv3-JHTVk{dD=l?p`|lg0u#;ci|LK`P$hXs|>Mvte?% zL>_SEp%Qt}l~=Tou}Vd5B*)ze(XbMtRm0>AN2}XNtkSNzJZDX2(pom_uIYIIR1=)5 zj}M$1ZqDW(ft=+&C8O|k+7LHPu0g+N9=4Yo^){ajljE+Pqo8NF8%#hxD0>!)Pmr<$ zElQ9BX?VNDl~$Ka6~0r1@A$Tl!e}Gp=_&Z;e_?{egt#5dYKfcLgdP*^K!WO;rQ4#B z(UP`kPG)Y|vX-z?Ga!YHlFaF>PHkCLQ|XkYs&@5dXRNz#cFlN(-qL6?m7Y*7?Yg1c zLDjV>sm!RPGOHogGi!$@8IWB4SD2cfgfBRlGVK~iSNBCR9_FSS<90|@6NYA*s%rAE z#OPi7Y}aIFO7FUEYOti~#AK=~sn2v>>FLiHT9-Mg8%CEqSF@}1)H}0tGy-PqhO!=S z!MJti7?5x8lCR~e#b92peZL_(X1Am&x9JpBWE~wcYif z4Llg|9`#MXtX{6(2uZC>uVFn*-aLgPTGqaId_jCFH*bP{Vh0nMsVUf*gbx{z2H~H< za0o#4DFS0FNO2srky?d_078ZD;*aCLK%0hVwj1=fT64O{u%K|BUKHPWMDHQDHwiC_ zfYO5?DFS;G=fWC}#}OXT74a08mWHLB0KOBv{V%=3pkC#g-SSW*&$iBuTprv3_TH>< zjw6?g@E_gfnNb4XJgbQk^z^IA8=_}BO28rHxGiQiYtk0e+LUe!5ENn@je?c=j-HD{ z{a1R1dZYbuWvC~9vG=X0htg;|V@37ZlxdmsAzwJ(9!1dW$tWCT)I)r-Gnz{3X)86J zf}ZPh(O88Y$!gTpRgF$ePeGSySEz|eJ#iBy6eFoEm=*;G&7{Xu695k8s7X7b&+3V3 zE0syBI;9y(_ku31#{p87rqb5u0os6~7G@)xF>UdtJ_i&Gn>Lp+G1Ns_qx(=1H_YBs z?g6+Ev}FDY$X&8l*HEa7R+uJSOi$F$>DpE#mJJlH2y?x zUyJOz-*@lhN4-l!D;>*YUx#=9qID^`a$?z7Z9JS0A6ekmA~pAB7iSBRSK;rGSU7Y# zf9Q1XjJ|qkJa=Gxt?Ot(Zpq6nOV&!?^2d4k$Xawico;a~{m@d|(#-OarIy^m_r4s; z4UYU_?8|trdi1G0Mw?-x+q?%s%Dm?*V7|+Hx_qDbAAE-ldRY&?;I%>DQ#=Xa9J~{! zqY%&Y&|^GL&|n0h*ta!7Mj^X9jrk(;H^8y@hTx%B=VDTv?gypk7!&3L**U;&biR^7 z(}7_uKwF`Ywjn{J*usQvIdG}Cq8ZbAF9XqQ&{YPEI@E702Zjf0v}{fQ*#R71)UVcE z*Z~>gnnL(MK761M?#PEbR&G7|=;8e1?$z+g1@6z4;kCB*r{Vp7t!-MGSxG)R@z7ZL z@bTNZ=5wpH=X2rn<>!n0#m;vG7)a56Y*Zli4!V&Ly8O1|X`w@uH1CX9gy7@zuk@y8 zQZ$oBSB+Y8S^ce;NMkUFZ6)mh&X)D`jEa3Rq3D71Fm#zXNANhu-*%(&4*rH|@(60b z3WQ~wT=o8y?n3LyeCx?V>$!aExkBrueCwsBa)0?&_)xz9qyID1yC7A(UjlZ8hdROE zK2|5h1Y3Y5u~29OxUgaxit!9kcF>$oBy>F)lS>G)kou+t@sqH#e1Ezy3VDWvMqt?T;aS?C_T%CZ+%a0hg9SGn z=p2k4VZbg{9d@*AvLzZPVX%qG=`MK|*;R@&{Au04ss@mW=seMx*f$D!C$2O3CHvymYo9J?zKLn=qf}!wZ zVmF`|t{HH-xgp;Wsieqo$q9@F5s7foC|)SG%ZnF^Agy56gi3%)h}OWc=VyQb{51-$ zpV%5w5N-@|v%r+x5MUnLZwRa^_eVo;!VN8HAoZ*GVwb{oqHp37SR1J^{ITGtd>o47 zAS*x&!w%}R31d2`$AT1L$)G|Je1J4hM^;@W2axYUuNSe9cQAkpi$?D{C}1`6-tCot zr&l2(zD&FdZLg5mx=*mG_!-p1cOKkXa~>O#;8np;EjNEs>6|pyD%%n_?gQJ~V*ti~ z=gvC?crT$Qozl|ZHL&M4D18y&&~!`^JEUr-oQ5dLzYkb~;#P1A0zr|`p!W?8W~c~h z=?AUB=H_G5t0rqZ^QN+pO0n!LSE$OXQa&&l0?CnVIzJ6Q^0>^0;WtbYT{{}0v3JZrFzrDe zODcL1$@h_9_;n&{FLLcj5Tg|5PZ5yx5|Vx(Ta#%G3Xxz+G2a9N$#k6$O3k^J&Tk-N zHgD8QQXjt}t|$*fFA4G+)w0y&2)={pIX?^lk_o(l>^klFj3 znhh=mo&D}_D?Y=nn9fSj&gaCji5Eg!KJ8Q@XK4anF<7#~Y+c|u?oXuSZ{+BUKsVQZ X|JDmgw(T#{Tm#p1|LRKu4`$_GtpqgN literal 0 HcmV?d00001 diff --git a/shell/Handlers/__pycache__/__init__.cpython-313.pyc b/shell/Handlers/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..beff7d751df873bdaf66b31ea4f09a79bd154425 GIT binary patch literal 151 zcmey&%ge<81Wh$NGC=fW5CH>>P{wB#AY&>+I)f&o-%5reCLr%KNa~iEenx(7s(w;& zVs>JBX>vxUeoAVYewd?Yeomr(aYkxRj=o1?UP?}CQL%n}d}dx|NqoFsLFFwDo80`A a(wtPgB37U=AZv<2jE~HWjEqIhKo$VfGb7Rf literal 0 HcmV?d00001 diff --git a/shell/Interactive.py b/shell/Interactive.py new file mode 100644 index 0000000..6dc38d1 --- /dev/null +++ b/shell/Interactive.py @@ -0,0 +1,96 @@ +import sys +from colorama import init, Fore, Style + +from shell.Handlers.GlobalHandler import GlobalHandler +from shell.Parser import Parser + +# Инициализация colorama для кроссплатформенной поддержки цветов +init() + + +class Interactive: + def __init__(self): + self.prompt = Interactive._get_colored_prompt() + self.parser = Parser() + self.handler = GlobalHandler() + + + @classmethod + def _get_colored_prompt(cls, prompt = "Vaiola> "): + """Создает градиентный цветной промпт 'Vaiola>'""" + colored_prompt = "" + + # Цвета для градиента (от ярко-белого к фиолетовому и обратно к белому) + colors = [ + Fore.LIGHTWHITE_EX, # V + Fore.MAGENTA, # a + Fore.MAGENTA, # i (немного менее яркий фиолетовый) + Fore.LIGHTMAGENTA_EX, # o (фиолетовый) + Fore.LIGHTMAGENTA_EX, # l (немного более яркий фиолетовый) + Fore.LIGHTWHITE_EX, # a (ярко-фиолетовый) + Fore.LIGHTWHITE_EX # > (ярко-белый) + ] + + # Применяем цвета к каждому символу промпта + for i, char in enumerate(prompt): + if i < len(colors): + colored_prompt += colors[i] + char + else: + colored_prompt += Fore.LIGHTWHITE_EX + char + + colored_prompt += Style.RESET_ALL + return colored_prompt + + def input(self): + args = [''] + while True: + new_args = self.parser.parse(input(self.prompt).strip()) + if len(new_args) == 0: + continue + args[len(args) - 1] += '\n' + new_args[0] if args[len(args) - 1] != '' else new_args[0] + args.extend(new_args[1:]) + if self.parser.in_quotes: + self.prompt = self._get_colored_prompt("\"____\"> ") + continue + else: + self.prompt = self._get_colored_prompt() + break + return args + + + + def start(self): + """Запускает интерактивную оболочку""" + while True: + try: + command = self.input() + try: + self.handler.handle(command) + except ValueError as e: + print(f"Error: {e}") + except RuntimeWarning: + print("Warning: last command failed") + + # # Выход из цикла по команде exit или quit + # if command.lower() in ['exit', 'quit']: + # print("До свидания!") + # break + + # # Парсим команду + # try: + # parsed = Parser.parse(command) + # print(f"Парсинг результата: {parsed}") + # # Здесь можно добавить обработку команд + # except ValueError as e: + # print(f"Ошибка: {e}") + + except KeyboardInterrupt: + print("\nGoodbye!") + break + except EOFError: + print("\nGoodbye!") + break + +# Запуск интерактивной оболочки +if __name__ == "__main__": + Interactive().start() \ No newline at end of file diff --git a/shell/Parser.py b/shell/Parser.py new file mode 100644 index 0000000..85d6d46 --- /dev/null +++ b/shell/Parser.py @@ -0,0 +1,42 @@ +class Parser: + def __init__(self): + self.in_quotes = False + + + def parse(self, command: str) -> list[str]: + tokens = [] + current_token = [] + + for char in command: + if char == '"': + if self.in_quotes: + # Завершаем токен внутри кавычек + tokens.append(''.join(current_token)) + current_token = [] + self.in_quotes = False + else: + # Начинаем новый токен в кавычках + if current_token: + # Если до кавычек были символы, добавляем их как отдельный токен + tokens.append(''.join(current_token)) + current_token = [] + self.in_quotes = True + elif char == ' ': + if self.in_quotes: + # Внутри кавычек пробелы добавляем к текущему токену + current_token.append(char) + else: + if current_token: + # Завершаем текущий токен, если он есть + tokens.append(''.join(current_token)) + current_token = [] + else: + # Любой символ, кроме кавычек и пробела + current_token.append(char) + + if current_token: + tokens.append(''.join(current_token)) + + return tokens + + diff --git a/shell/__init__.py b/shell/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/shell/__pycache__/Interactive.cpython-313.pyc b/shell/__pycache__/Interactive.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b1a40804e52498c1d9bce0776e23251b7c6d5102 GIT binary patch literal 3708 zcmb7HYitzP6~6P>r`Nk{d)YOHtOv(nPXuhrE3g!*ZEy_uk#@&RP>eJh?~d(l_u<}| zCAHl)6slwqZ80xFlt^);N(HG>NgGrift3F0kE)ufX?B^qDpJ)S{4+6CtL9J7o!wcl z9hazA_Pytx$GvyXJ>NNZ7aJSh2wK{GXZ%(pLVqU{tFcvL@jMWhkbnfL4`DLWebiwZ z(}x+%9A+`g5gyZLJ8Z{xlVEYY{_t2@Tgoaq9#%4yt)9fihJ; zAvEl;6s&WA)Vd)wOg1!Ioz!me#t=^p@(%0*hS4I9$*P#lXTVu0aRTSF6RKgajF@Z! z6Vp$a*c*=Vn*Ufu&WtMLt2pDu$ap?0M@AIs+tOGeIi8NB(p=8ZBFd+vw{W}BagIJ|2lFrS z)-wIw2sI#%*pN#zTik0=OJimA1l@^vy3hxF?E~$e_8aXZ?YeeDKgnso)=vQWk#?*Md)mh^p5(fYNNEVL{aps)otWI4(b#{e_u$ZL z2M-PPh&@LQ$3U#NXK*NH_)LCvMkFpdSI7cDRoP%Nat;$W7_NAa&@&{)`ulmi!6Y*Z z2A3eiyAmGeTrsG$!6wH69pqv;v*9KnF>!>^zAUc4h%&eXr2HJZ6KKJX+SV7nPo1c- zspJXkp76ZKf42X0|I$$MaJq+sc^y$ztXM4goXzf{{b>-jZkP>z8tOjXTa1-`{)^jZ zj$hb&=FPG{e6{HV{%T6^*rxlpo%cTShkw>t>e!)o?6|J#9eZ{E-t*okF4W2|c#tdf z-p2PM??iMbU-XuJ&uPx*Z~)G_EQ{4p_MnQg2$iG4D1qXjxw5U6*@lVN%?38Bpu>vS zcrD5;{iUpE=>WoOSua6bT!A*L31DGh5aODu)2ZP_V75Tj+eh!CO)NY^hsXsg`UarK z2^5>41=EJ0=hdYd1dYNdbQf?FRGfs+iUxL!fLLUm640}{&sM=vcXchP6;?-a5^pP- zV6)bRnoz4J1AqKq`gJv;rVY190aERQ7z!~|K74kTLSB^>6CgZ;{Y1=4cuX;zIr$BdPzpH% zV5x#6;zOgU9!cgVn}MzT0lE{Y>~AirMYim1E{E1#o<28S3U%wD?q96As{V5N+VmX1 zx5USEJ~qd{JQq4pJUH)bp4olo`?J)XZ^J`>YuV$!*fKM8VeJ*^TzJmI-R%5W`R$H7 zlfRGM`hgxD)Z#;P(V^J`S9f2JUEBMo&RyE;BU&=0zb@+@8Pvi2Y~Sg=E6&-b+2$*r z2cC^(U(4C))6*qiC;XMe9i{LtJ-qAs_vXR}X4s1ju;n9*ITzkdSY=NW7!@172rv?F zW*Li`01G1%T;v{BH0uhh@LQ!=#A{GnGF>Ve92$rqv;hs*K79+uZojgn6zJ9i-Jj2g zHWEn`BwE|_b?QBooAjT9iqiQ+Y6BD#{u2#m}ZHz`ueM(Ex` zpV5r13yQ71X^{@u!ix-03xxV?o7WcjXJ^qIrDbsw0NLdM! z9AUnHD$khp=8Jo4^{0-qb=fbU$~)duQK|d{GT-^;;qV@^68R?lHPVriZ=T<9ezoGQ z{kR8sBn2sifXk++zaaPD&?X&i`Ul$eSJeK*jwsuY?EkWVlcIu85a}1jX~cSpQ*+GP Yhpg|^K#2|MZ0OJI+64x&ZRR%r1+MfN&;S4c literal 0 HcmV?d00001 diff --git a/shell/__pycache__/Parser.cpython-313.pyc b/shell/__pycache__/Parser.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..55aae896b9d739680b7662eeb263573a030b6b9f GIT binary patch literal 1506 zcmb^xU2hXdaPGrrhx%}W9WbT}#&zOc5V#+JXhkDcqNsg1tK{OUl_IS!&S4*%@6x?9 z6!nECWP}i@P)O(t&J*D`^e>=FC=s0YrKymR`WCPRubn;HX{g$&QWCsEe9Z8{Fr6tj}~mL}D4{fC>gs z(GaL)h&iCLA$v$fy z#T()bFT`KK5N8I}-zBL^C;LSl?0N2AGNj$*(?3V6(vFY9Tk;{l;o}Ng#rKZ#JdgCh z=b7da{av0_`Oi%BHGXR+?jmw;p*H#>0R5N$%GnUBNb#F8G4Pc5CjTY05Gc6=*4c3-`xcIF?{*Xrxdjfpdx*-yjCsZ})` z*Z8e)WOOxs=g^MPBTqAvk290&nJ1a!&kq#;N&hGHMI6m2UG!aIrJ{WO(Yu zM=<*Ct5c9X7$*1K)^F&mh5My@rOvU9-20o!54KXHZIvc5Im*+lXhVFyU9)N7eD5Ko z=`FMzPA3U|9j4QlqzJiSQ$KKQ*K$2VDE}#$KCf7c~ jQItXSf{8P|n5QXDDnw>eNNh`jAiNe!!v2?lC{p+X5LF>x literal 0 HcmV?d00001 diff --git a/shell/__pycache__/__init__.cpython-313.pyc b/shell/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ca680dc8ca8a35b0ed28b4abf21dfc8f43cc781f GIT binary patch literal 142 zcmey&%ge<81oIPiWPs?$AOZ#$p^VQgK*m&tbOudEzm*I{OhDdekkl<*{fzwFRQ;sl z#O%cM(&UUx{gl)){V+$*{G3Gn;*8Xs9R2wC%)HE!_;|g7%3B;Zx%nxjIjMFr literal 0 HcmV?d00001