* ci: test Python 3.11-3.14 and require Python 3.11 (#356) Python 3.10 reaches end of life in October 2026 and no PromptManager users run it; 3.14 is current and was untested. The test matrix runs 3.11-3.14 on Linux, Windows and macOS, bandit runs on 3.11, and requires-python is >=3.11 so the package no longer claims a version CI does not test. The packaging test drops its 3.10-only tomllib fallback. The required checks follow the matrix (py3.10 -> py3.14). * ci: narrow Python support to 3.12-3.14 (#356) ComfyUI recommends 3.13, names 3.12 as the fallback and 3.14 as working (its README); PromptManager supports exactly those. requires-python is >=3.12, the test matrix and the required checks drop 3.11, and bandit runs on 3.12.
83 lines
2.6 KiB
Python
83 lines
2.6 KiB
Python
"""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()
|