● 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.
This commit is contained in:
Dr.Lt.Data
2025-10-04 08:55:59 +09:00
parent 1ab2b1aeb3
commit 2866193baf
29 changed files with 10024 additions and 1 deletions

View File

@@ -0,0 +1,180 @@
"""
Test policy priority and conflicts (Priority 2)
Tests that user policies override base policies correctly
"""
import json
import subprocess
from pathlib import Path
import pytest
@pytest.fixture
def conflicting_policies(temp_policy_dir, temp_user_policy_dir):
"""Create conflicting base and user policies"""
# Base policy
base_content = {
"numpy": {
"apply_first_match": [
{
"type": "skip",
"reason": "Base policy skip"
}
]
}
}
base_file = temp_policy_dir / "pip-policy.json"
base_file.write_text(json.dumps(base_content, indent=2))
# User policy (should override)
user_content = {
"numpy": {
"apply_first_match": [
{
"type": "force_version",
"version": "1.26.0",
"reason": "User override"
}
]
}
}
user_file = temp_user_policy_dir / "pip-policy.user.json"
user_file.write_text(json.dumps(user_content, indent=2))
return base_file, user_file
@pytest.mark.unit
def test_user_policy_overrides_base_policy(
conflicting_policies,
mock_manager_util,
mock_context,
mock_subprocess_success
):
"""
Test user policy completely replaces base policy
Priority: 2 (Important)
Purpose:
Verify that user policy completely overrides base policy
at the package level (not section-level merge).
"""
import sys
# Path setup handled by conftest.py
from comfyui_manager.common.pip_util import get_pip_policy
policy = get_pip_policy()
# Verify user policy replaced base policy
assert "numpy" in policy
assert "apply_first_match" in policy["numpy"]
assert len(policy["numpy"]["apply_first_match"]) == 1
# Should be force_version (user), not skip (base)
assert policy["numpy"]["apply_first_match"][0]["type"] == "force_version"
assert policy["numpy"]["apply_first_match"][0]["version"] == "1.26.0"
# Base policy skip should be completely gone
assert not any(
item["type"] == "skip"
for item in policy["numpy"]["apply_first_match"]
)
@pytest.fixture
def first_match_policy(temp_policy_dir):
"""Create policy with multiple apply_first_match entries"""
policy_content = {
"pkg": {
"apply_first_match": [
{
"condition": {
"type": "installed",
"package": "numpy"
},
"type": "force_version",
"version": "1.0"
},
{
"type": "force_version",
"version": "2.0"
},
{
"type": "skip"
}
]
}
}
policy_file = temp_policy_dir / "pip-policy.json"
policy_file.write_text(json.dumps(policy_content, indent=2))
return policy_file
@pytest.fixture
def mock_first_match_subprocess(monkeypatch):
"""Mock subprocess for first match test"""
call_sequence = []
installed_packages = {
"numpy": "1.26.0"
}
def mock_run(cmd, **kwargs):
call_sequence.append(cmd)
# pip freeze
if "freeze" in cmd:
output = "\n".join([f"{pkg}=={ver}" for pkg, ver in installed_packages.items()])
return subprocess.CompletedProcess(cmd, 0, output, "")
# pip install
if "install" in cmd and "pkg" in cmd:
if "pkg==1.0" in cmd:
installed_packages["pkg"] = "1.0"
return subprocess.CompletedProcess(cmd, 0, "", "")
return subprocess.CompletedProcess(cmd, 0, "", "")
monkeypatch.setattr("subprocess.run", mock_run)
return call_sequence, installed_packages
@pytest.mark.integration
def test_first_match_stops_at_first_satisfied(
first_match_policy,
mock_manager_util,
mock_context,
mock_first_match_subprocess
):
"""
Test apply_first_match stops at first satisfied condition
Priority: 2 (Important)
Purpose:
Verify that in apply_first_match, only the first policy
with a satisfied condition is executed (exclusive execution).
"""
import sys
# Path setup handled by conftest.py
from comfyui_manager.common.pip_util import PipBatch
call_sequence, installed_packages = mock_first_match_subprocess
with PipBatch() as batch:
result = batch.install("pkg")
# Verify installation succeeded
assert result is True
# First condition satisfied (numpy installed), so version 1.0 applied
install_calls = [cmd for cmd in call_sequence if "install" in cmd and "pkg" in cmd]
assert len(install_calls) > 0
assert "pkg==1.0" in install_calls[0]
assert "pkg==2.0" not in str(call_sequence) # Second policy not applied