| |
| """ |
| Import-graph checker — verifies the one-way dependency direction. |
| |
| Run: python scripts/check_imports.py |
| |
| Exits 0 if every layer only imports from layers below it. |
| Exits 1 with a violation report otherwise. |
| """ |
|
|
| from __future__ import annotations |
|
|
| import importlib |
| import pkgutil |
| import sys |
| from pathlib import Path |
|
|
| |
| sys.path.insert(0, str(Path(__file__).resolve().parent.parent)) |
|
|
| |
| LAYER_RANK = { |
| "models": 0, |
| "utils": 1, |
| "config": 1, |
| "storage": 2, |
| "metrics": 2, |
| "providers": 3, |
| "pipeline": 4, |
| "orchestrator": 5, |
| "normalization": 5, |
| "confidence": 5, |
| "services": 6, |
| "api": 7, |
| } |
|
|
| |
| |
| |
| |
|
|
|
|
| def get_layer(module_name: str) -> int | None: |
| for prefix, rank in LAYER_RANK.items(): |
| if module_name == prefix or module_name.startswith(prefix + "."): |
| return rank |
| return None |
|
|
|
|
| def check_package(package_name: str) -> list[str]: |
| """Import every module in a package and check its imports.""" |
| violations: list[str] = [] |
| try: |
| pkg = importlib.import_module(package_name) |
| except ImportError as e: |
| print(f" SKIP {package_name}: {e}") |
| return violations |
|
|
| pkg_layer = get_layer(package_name) |
| if pkg_layer is None: |
| return violations |
|
|
| |
| if hasattr(pkg, "__path__"): |
| for _, modname, _ in pkgutil.walk_packages(pkg.__path__, prefix=f"{package_name}."): |
| try: |
| mod = importlib.import_module(modname) |
| except Exception as e: |
| print(f" SKIP {modname}: {e}") |
| continue |
|
|
| mod_layer = get_layer(modname) |
| if mod_layer is None: |
| continue |
|
|
| |
| for imported_name in list(vars(mod).keys()): |
| |
| pass |
|
|
| |
| for attr_name, attr_val in vars(mod).items(): |
| if not hasattr(attr_val, "__name__"): |
| continue |
| if not isinstance(attr_val, type(sys)): |
| continue |
| imported_module = getattr(attr_val, "__name__", "") |
| imported_layer = get_layer(imported_module) |
| if imported_layer is None: |
| continue |
| if imported_layer > mod_layer: |
| violations.append( |
| f" VIOLATION: {modname} (layer {mod_layer}) imports " |
| f"{imported_module} (layer {imported_layer})" |
| ) |
| return violations |
|
|
|
|
| def main() -> int: |
| print("Checking import graph for one-way dependency violations...") |
| print() |
| all_violations: list[str] = [] |
| for pkg in LAYER_RANK: |
| print(f"Checking {pkg}/...") |
| violations = check_package(pkg) |
| all_violations.extend(violations) |
|
|
| print() |
| if all_violations: |
| print(f"FOUND {len(all_violations)} violation(s):") |
| for v in all_violations: |
| print(v) |
| return 1 |
| else: |
| print("OK — no dependency-direction violations found.") |
| return 0 |
|
|
|
|
| if __name__ == "__main__": |
| sys.exit(main()) |
|
|