ComfyUI-Manager/tests/common/pip_util/analyze_dependencies.py
Dr.Lt.Data 2866193baf ● feat: Draft pip package policy management system (not yet integrated)
Add comprehensive pip dependency conflict resolution framework as draft implementation. This is self-contained and does not affect existing
ComfyUI Manager functionality.

Key components:
- pip_util.py with PipBatch class for policy-driven package management
- Lazy-loaded policy system supporting base + user overrides
- Multi-stage policy execution (uninstall → apply_first_match → apply_all_matches → restore)
- Conditional policies based on platform, installed packages, and ComfyUI version
- Comprehensive test suite covering edge cases, workflows, and platform scenarios
- Design and implementation documentation

Policy capabilities (draft):
- Package replacement (e.g., PIL → Pillow, opencv-python → opencv-contrib-python)
- Version pinning to prevent dependency conflicts
- Dependency protection during installations
- Platform-specific handling (Linux/Windows, GPU detection)
- Pre-removal and post-restoration workflows

Testing infrastructure:
- Pytest-based test suite with isolated environments
- Dependency analysis tools for conflict detection
- Coverage for policy priority, edge cases, and environment recovery

Status: Draft implementation complete, integration with manager workflows pending.
2025-10-04 08:55:59 +09:00

262 lines
7.7 KiB
Python
Executable File

#!/usr/bin/env python3
"""
Dependency Tree Analyzer for pip_util Tests
Usage:
python analyze_dependencies.py [package]
python analyze_dependencies.py --all
python analyze_dependencies.py --update-context
Examples:
python analyze_dependencies.py requests
python analyze_dependencies.py python-dateutil
python analyze_dependencies.py --all
"""
import subprocess
import sys
from typing import Dict, List, Tuple, Optional
from pathlib import Path
PIP = "./test_venv/bin/pip"
def check_venv():
"""Check if test venv exists"""
if not Path(PIP).exists():
print("❌ Test venv not found!")
print(" Run: ./setup_test_env.sh")
sys.exit(1)
def get_installed_packages() -> Dict[str, str]:
"""Get currently installed packages"""
result = subprocess.run(
[PIP, "freeze"],
capture_output=True,
text=True,
check=True
)
packages = {}
for line in result.stdout.strip().split('\n'):
if '==' in line:
pkg, ver = line.split('==', 1)
packages[pkg] = ver
return packages
def analyze_package_dry_run(
package: str,
constraints: Optional[List[str]] = None
) -> Tuple[List[Tuple[str, str]], Dict[str, str]]:
"""
Analyze what would be installed with --dry-run
Returns:
- List of (package_name, version) tuples in install order
- Dict of current_version → new_version for upgrades
"""
cmd = [PIP, "install", "--dry-run", "--ignore-installed", package]
if constraints:
cmd.extend(constraints)
result = subprocess.run(cmd, capture_output=True, text=True)
# Parse "Would install" line
would_install = []
for line in result.stdout.split('\n'):
if 'Would install' in line:
packages_str = line.split('Would install')[1].strip()
for pkg_str in packages_str.split():
parts = pkg_str.split('-', 1)
if len(parts) == 2:
would_install.append((parts[0], parts[1]))
# Check against current installed
installed = get_installed_packages()
changes = {}
for pkg, new_ver in would_install:
if pkg in installed:
old_ver = installed[pkg]
if old_ver != new_ver:
changes[pkg] = (old_ver, new_ver)
return would_install, changes
def get_available_versions(package: str, limit: int = 10) -> Tuple[str, List[str]]:
"""
Get available versions from PyPI
Returns:
- Latest version
- List of available versions (limited)
"""
result = subprocess.run(
[PIP, "index", "versions", package],
capture_output=True,
text=True
)
latest = None
versions = []
for line in result.stdout.split('\n'):
if 'LATEST:' in line:
latest = line.split('LATEST:')[1].strip()
elif 'Available versions:' in line:
versions_str = line.split('Available versions:')[1].strip()
versions = [v.strip() for v in versions_str.split(',')[:limit]]
return latest, versions
def print_package_analysis(package: str, with_pin: bool = False):
"""Print detailed analysis for a package"""
print(f"\n{'='*80}")
print(f"Package: {package}")
print(f"{'='*80}")
installed = get_installed_packages()
# Get latest version
latest, available = get_available_versions(package)
if latest:
print(f"\n📦 Latest version: {latest}")
print(f"📋 Available versions: {', '.join(available[:5])}")
# Scenario 1: Without constraints
print(f"\n🔍 Scenario A: Install without constraints")
print(f" Command: pip install {package}")
would_install, changes = analyze_package_dry_run(package)
if would_install:
print(f"\n Would install {len(would_install)} packages:")
for pkg, ver in would_install:
if pkg in changes:
old_ver, new_ver = changes[pkg]
print(f"{pkg:25} {old_ver:15}{new_ver:15} ⚠️ UPGRADE")
elif pkg in installed:
print(f"{pkg:25} {ver:15} (already installed)")
else:
print(f"{pkg:25} {ver:15} ✨ NEW")
# Scenario 2: With pin constraints (if dependencies exist)
dependencies = [pkg for pkg, _ in would_install if pkg != package]
if dependencies and with_pin:
print(f"\n🔍 Scenario B: Install with pin constraints")
# Create pin constraints for all current dependencies
constraints = []
for dep in dependencies:
if dep in installed:
constraints.append(f"{dep}=={installed[dep]}")
if constraints:
print(f" Command: pip install {package} {' '.join(constraints)}")
would_install_pinned, changes_pinned = analyze_package_dry_run(
package, constraints
)
print(f"\n Would install {len(would_install_pinned)} packages:")
for pkg, ver in would_install_pinned:
if pkg in constraints:
print(f"{pkg:25} {ver:15} 📌 PINNED")
elif pkg in installed:
print(f"{pkg:25} {ver:15} (no change)")
else:
print(f"{pkg:25} {ver:15} ✨ NEW")
# Show what was prevented
prevented = set(changes.keys()) - set(changes_pinned.keys())
if prevented:
print(f"\n ✅ Pin prevented {len(prevented)} upgrade(s):")
for pkg in prevented:
old_ver, new_ver = changes[pkg]
print(f"{pkg:25} {old_ver:15} ❌→ {new_ver}")
def analyze_all_test_packages():
"""Analyze all packages used in tests"""
print("="*80)
print("ANALYZING ALL TEST PACKAGES")
print("="*80)
test_packages = [
("requests", True),
("python-dateutil", True),
]
for package, with_pin in test_packages:
print_package_analysis(package, with_pin)
print(f"\n{'='*80}")
print("ANALYSIS COMPLETE")
print(f"{'='*80}")
def print_current_environment():
"""Print current test environment"""
print("="*80)
print("CURRENT TEST ENVIRONMENT")
print("="*80)
installed = get_installed_packages()
print(f"\nTotal packages: {len(installed)}\n")
# Group by category
test_packages = ["urllib3", "certifi", "charset-normalizer", "six", "attrs", "packaging"]
framework = ["pytest", "iniconfig", "pluggy", "Pygments"]
print("Test packages:")
for pkg in test_packages:
if pkg in installed:
print(f" {pkg:25} {installed[pkg]}")
print("\nTest framework:")
for pkg in framework:
if pkg in installed:
print(f" {pkg:25} {installed[pkg]}")
other = set(installed.keys()) - set(test_packages) - set(framework)
if other:
print("\nOther packages:")
for pkg in sorted(other):
print(f" {pkg:25} {installed[pkg]}")
def main():
"""Main entry point"""
check_venv()
if len(sys.argv) == 1:
print("Usage: python analyze_dependencies.py [package|--all|--env]")
print("\nExamples:")
print(" python analyze_dependencies.py requests")
print(" python analyze_dependencies.py --all")
print(" python analyze_dependencies.py --env")
sys.exit(0)
command = sys.argv[1]
if command == "--all":
analyze_all_test_packages()
elif command == "--env":
print_current_environment()
elif command.startswith("--"):
print(f"Unknown option: {command}")
sys.exit(1)
else:
# Analyze specific package
print_package_analysis(command, with_pin=True)
if __name__ == "__main__":
main()