From 675ae8a62cca78e92e6bf008f53af1c164807eaf Mon Sep 17 00:00:00 2001 From: Nose Date: Wed, 1 Apr 2026 14:14:29 -0700 Subject: [PATCH] new hyrdusupdate --- scripts/hydrusnetwork.py | 143 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 141 insertions(+), 2 deletions(-) diff --git a/scripts/hydrusnetwork.py b/scripts/hydrusnetwork.py index e9ce052..e965a83 100644 --- a/scripts/hydrusnetwork.py +++ b/scripts/hydrusnetwork.py @@ -692,12 +692,146 @@ IMPORT_NAME_OVERRIDES = { } +def normalize_python_command(python_cmd: Optional[Sequence[str] | str]) -> list[str]: + if python_cmd is None: + return [sys.executable] + if isinstance(python_cmd, (str, Path)): + return [str(python_cmd)] + return [str(part) for part in python_cmd] + + +def get_python_version_info(python_cmd: Optional[Sequence[str] | str] = None) -> Optional[tuple[int, int, int]]: + cmd = normalize_python_command(python_cmd) + try: + result = subprocess.run( + cmd + [ + "-c", + "import sys; print('.'.join(str(part) for part in sys.version_info[:3]))", + ], + stdout=subprocess.PIPE, + stderr=subprocess.DEVNULL, + text=True, + check=True, + ) + version_text = (result.stdout or "").strip() + major, minor, micro = version_text.split(".", 2) + return (int(major), int(minor), int(micro)) + except Exception: + return None + + +def is_hydrus_repo(repo_root: Path) -> bool: + return (repo_root / "setup_venv.py").exists() and (repo_root / "hydrus_client.py").exists() + + +def select_hydrus_python_command() -> tuple[list[str], tuple[int, int, int]]: + candidates: list[list[str]] = [] + + if os.name == "nt" and shutil.which("py"): + for version in ("3.12", "3.11", "3.10", "3.13", "3.14"): + candidates.append(["py", f"-{version}"]) + + candidates.append([sys.executable]) + + for name in ("python3.12", "python3.11", "python3.10", "python3", "python"): + resolved = shutil.which(name) + if resolved: + candidates.append([resolved]) + + seen: set[tuple[str, ...]] = set() + best_fallback: Optional[tuple[list[str], tuple[int, int, int]]] = None + + for candidate in candidates: + normalized = tuple(candidate) + if normalized in seen: + continue + seen.add(normalized) + + version = get_python_version_info(candidate) + if version is None or version < (3, 10): + continue + + if version < (3, 13): + return candidate, version + + if best_fallback is None or version < best_fallback[1]: + best_fallback = (candidate, version) + + if best_fallback is not None: + return best_fallback + + version = get_python_version_info([sys.executable]) + if version is None: + raise RuntimeError("Could not determine a usable Python interpreter for Hydrus") + return [sys.executable], version + + +def build_hydrus_install_targets( + req_path: Path, + repo_root: Path, + python_version: tuple[int, int, int], +) -> list[str]: + if not is_hydrus_repo(repo_root) or python_version < (3, 13): + return ["-r", str(req_path)] + + overrides = { + "pyside6": "PySide6==6.10.1", + "qtpy": "QtPy==2.4.3", + "opencv-python-headless": "opencv-python-headless==4.13.0.90", + "numpy": "numpy==2.4.1", + } + targets: list[str] = [] + seen_packages: set[str] = set() + + for raw_line in req_path.read_text(encoding="utf-8").splitlines(): + line = raw_line.strip() + if not line or line.startswith("#"): + continue + + package_name = re.split(r"[<>=!~\[]", line, maxsplit=1)[0].strip().lower() + if package_name in overrides: + line = overrides[package_name] + + targets.append(line) + seen_packages.add(package_name) + + if os.name == "nt" and "pywin32" not in seen_packages: + targets.append("pywin32") + + logging.info( + "Using Hydrus compatibility dependency set for Python %s.%s.%s", + python_version[0], + python_version[1], + python_version[2], + ) + return targets + + def ensure_repo_venv( repo_root: Path, venv_name: str = ".venv", recreate: bool = False, purpose: Optional[str] = None, ) -> Path: + python_cmd: list[str] + python_version: Optional[tuple[int, int, int]] + if is_hydrus_repo(repo_root): + python_cmd, python_version = select_hydrus_python_command() + if python_version is not None: + logging.info( + "Using Python %s.%s.%s for Hydrus venv creation via %s", + python_version[0], + python_version[1], + python_version[2], + " ".join(python_cmd), + ) + if python_version >= (3, 13): + logging.info( + "Hydrus is running on a newer Python; installer will apply compatibility package overrides." + ) + else: + python_cmd = [sys.executable] + venv_dir = repo_root / str(venv_name) if venv_dir.exists(): if recreate: @@ -711,7 +845,7 @@ def ensure_repo_venv( logging.info("Creating venv at %s for %s", venv_dir, purpose) else: logging.info("Creating venv at %s", venv_dir) - subprocess.run([sys.executable, "-m", "venv", str(venv_dir)], check=True) + subprocess.run(python_cmd + ["-m", "venv", str(venv_dir)], check=True) venv_py = get_python_in_venv(venv_dir) if not venv_py: @@ -727,6 +861,10 @@ def install_requirements_into_venv( req_path: Path, reinstall: bool = False, ) -> None: + python_version = get_python_version_info([str(venv_py)]) + if python_version is None: + raise RuntimeError(f"Could not determine python version for {venv_py}") + logging.info( "Installing dependencies from %s into venv (reinstall=%s)", req_path, @@ -751,10 +889,11 @@ def install_requirements_into_venv( "pip", "install", "--disable-pip-version-check", + "--prefer-binary", ] if reinstall: cmd.extend(["--upgrade", "--force-reinstall"]) - cmd.extend(["-r", str(req_path)]) + cmd.extend(build_hydrus_install_targets(req_path, repo_root, python_version)) subprocess.run(cmd, cwd=str(repo_root), check=True) logging.info("Dependencies installed successfully")