"""Packaging and repository hygiene contracts. These guard the metadata that ComfyUI-Manager, the Comfy Registry and ``pip install .`` consume, and the ignore rules that keep local secrets (``config.json`` holds the CivitAI API key) out of the repository. """ import os import re import unittest ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) # PEP 508 requirement: name, optional extras, optional version spec. _VERSION_CLAUSE = r"[<>=!~]=?\s*[A-Za-z0-9.*+!-]+\s*" _REQUIREMENT = re.compile( r"^[A-Za-z0-9][A-Za-z0-9._-]*" # distribution name r"(\[[A-Za-z0-9,._-]+\])?\s*" # optional extras r"(" + _VERSION_CLAUSE + r"(,\s*" + _VERSION_CLAUSE + r")*)?$" # version spec ) def _read(name): with open(os.path.join(ROOT, name), encoding="utf-8") as fh: return fh.read() def _pyproject(): import tomllib return tomllib.loads(_read("pyproject.toml")) class PyprojectContract(unittest.TestCase): def test_dependencies_are_valid_requirements(self): deps = _pyproject()["project"]["dependencies"] self.assertTrue(deps, "dependencies must not be empty") for dep in deps: self.assertRegex(dep, _REQUIREMENT, f"not a PEP 508 requirement: {dep!r}") def test_dependencies_match_requirements_txt(self): deps = set(_pyproject()["project"]["dependencies"]) wanted = { line.split("#", 1)[0].strip() for line in _read("requirements.txt").splitlines() if line.split("#", 1)[0].strip() } self.assertEqual(deps, wanted) def test_requires_python_floor_is_3_12(self): self.assertEqual(_pyproject()["project"]["requires-python"], ">=3.12") def test_build_system_is_declared(self): self.assertIsNotNone(_pyproject().get("build-system")) class IgnoreRulesContract(unittest.TestCase): def test_local_secrets_and_artifacts_are_ignored(self): patterns = { line.strip() for line in _read(".gitignore").splitlines() if line.strip() and not line.startswith("#") } for required in ( "config.json", "resume.sh", "*.backup_*", "promptmanager_logs/", ): self.assertIn(required, patterns) class LintConfigContract(unittest.TestCase): def test_flake8_reads_its_own_config_file(self): # flake8 ignores pyproject.toml, so the settings must live in .flake8. text = _read(".flake8") self.assertIn("max-line-length = 88", text) self.assertIn("E203", text) if __name__ == "__main__": unittest.main()