"""Executable dependency rules for the service/algorithm/database layers.""" import ast from pathlib import Path APP_ROOT = Path(__file__).parents[2] / "app" def _app_imports(path: Path) -> set[str]: tree = ast.parse(path.read_text(encoding="utf-8"), filename=str(path)) imports: set[str] = set() package_parts = ("app", *path.relative_to(APP_ROOT).parent.parts) for node in ast.walk(tree): if isinstance(node, ast.Import): imports.update(alias.name for alias in node.names if alias.name.startswith("app.")) elif isinstance(node, ast.ImportFrom): if node.level: parent_count = node.level - 1 if parent_count >= len(package_parts): continue base_parts = package_parts[: len(package_parts) - parent_count] if node.module: imports.add(".".join((*base_parts, *node.module.split(".")))) else: imports.update( ".".join((*base_parts, alias.name.split(".")[0])) for alias in node.names ) elif node.module and node.module.startswith("app."): imports.add(node.module) return imports def _forbidden_imports(root: Path, prefixes: tuple[str, ...]) -> list[str]: violations: list[str] = [] for path in sorted(root.rglob("*.py")): for module in sorted(_app_imports(path)): if module.startswith(prefixes): violations.append(f"{path.relative_to(APP_ROOT)} -> {module}") return violations def test_algorithms_are_independent_of_application_and_io_layers(): violations = _forbidden_imports( APP_ROOT / "algorithms", ("app.api", "app.auth", "app.infra", "app.native", "app.services"), ) assert violations == [] def test_infrastructure_does_not_depend_on_application_or_algorithms(): violations = _forbidden_imports( APP_ROOT / "infra", ("app.api", "app.algorithms", "app.services"), ) assert violations == [] def test_services_do_not_execute_sql_directly(): violations = [] for path in sorted((APP_ROOT / "services").rglob("*.py")): source = path.read_text(encoding="utf-8") if ".cursor(" in source or ".execute(" in source: violations.append(str(path.relative_to(APP_ROOT))) assert violations == [] def test_services_do_not_depend_on_the_http_network_facade(): violations = _forbidden_imports( APP_ROOT / "services", ("app.services.tjnetwork",), ) assert violations == [] def test_algorithm_packages_use_explicit_business_names(): legacy_names = { "burst_location", "cleaning", "health", "isolation", "leakage", "sensor", "simulation", "water_demand", } present_names = { path.name for path in (APP_ROOT / "algorithms").iterdir() if path.is_dir() and any(path.glob("*.py")) } assert present_names.isdisjoint(legacy_names) def test_application_code_does_not_open_direct_psycopg_connections(): violations = [] direct_calls = ("psycopg.connect(", "Connection.connect(", "AsyncConnection.connect(") for path in sorted(APP_ROOT.rglob("*.py")): source = path.read_text(encoding="utf-8") if any(call in source for call in direct_calls): violations.append(str(path.relative_to(APP_ROOT))) assert violations == []