Files
vaiola/pythonapp/Decider/Loader.py
Bacruru Sakaguchi 9e5e214944 initial commit
2025-09-12 17:10:13 +07:00

190 lines
12 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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. Возвращает значения
"""