ooad1-9 Exercise 1: A Package the Checker Cannot See ========================================================= ex1.py (needs deps.py and the library package from the chapter folder; run from there): """Exercise 1: a new package slips past the dependency check, and how to close the hole. Run from the chapter folder: py ex1/ex1.py""" import contextlib import io import pathlib import shutil import sys import tempfile HERE = pathlib.Path(__file__).resolve().parent.parent sys.path.insert(0, str(HERE)) import deps # noqa: E402 def run_check(root: pathlib.Path) -> tuple[int, str]: buf = io.StringIO() with contextlib.redirect_stdout(buf): code = deps.check(root, show_edges=False) return code, buf.getvalue() def unknown_packages(root: pathlib.Path) -> list[str]: """The missing rule: every package that imports another must be listed in ALLOWED, or its imports are unchecked.""" return sorted({e["from"] for e in deps.collect(root) if e["from"] not in deps.ALLOWED and e["from"] != "(root)"}) with tempfile.TemporaryDirectory() as d: root = pathlib.Path(d) shutil.copytree(HERE / "library", root / "library") reporting = root / "library" / "reporting" reporting.mkdir() (reporting / "__init__.py").write_text('"""Reports."""\n', encoding="utf8") (reporting / "overdue.py").write_text( "from library.domain import Member\n" "from library.ui import cli # a report reaching up into the user interface: it should not\n\n\n" "def overdue(members: list[Member], today):\n" " return [(m.card, l.barcode) for m in members for l in m.open_loans() if l.due_on < today]\n", encoding="utf8") print("1. a new 'reporting' package imports the UI, and the checker as written says:") code, out = run_check(root) print(" " + out.strip().splitlines()[-1], f"(exit status {code})") print(" packages with imports that nothing checks:", unknown_packages(root)) print("2. the rule for reporting is written down (it may use domain and application only):") deps.ALLOWED["reporting"] = {"domain", "application"} code, out = run_check(root) for line in out.splitlines(): if "VIOLATION" in line or line.startswith("result"): print(" " + line.strip()) print("3. and the rule that would have caught step 1 by itself: unknown packages are an error") del deps.ALLOWED["reporting"] unknown = unknown_packages(root) print(" ERROR: no dependency rule exists for:", unknown, "-> add it to ALLOWED before merging" if unknown else "") Run with: py ex1/ex1.py Output: 1. a new 'reporting' package imports the UI, and the checker as written says: result: clean (exit status 0) packages with imports that nothing checks: ['reporting'] 2. the rule for reporting is written down (it may use domain and application only): VIOLATION reporting may not depend on ui: library\reporting\overdue.py:2 imports library.ui result: 1 violation(s) 3. and the rule that would have caught step 1 by itself: unknown packages are an error ERROR: no dependency rule exists for: ['reporting'] -> add it to ALLOWED before merging WHAT HAPPENED Step 1: deps.check only tests imports whose importing package appears in ALLOWED. 'reporting' is not in ALLOWED, so its import of the UI is never examined, there is no cycle, and the import is not 'deep', so all three rules pass: 'clean', exit status 0. The report is true (no rule was broken) and useless (no rule applied). Step 2: writing the package's rules (it may use domain and application) turns the same import into a reported violation with file and line. Step 3: the structural fix is to make a missing rule an error. Any package that imports another but has no entry in ALLOWED is listed and must be given rules before the change is accepted. In a real project, this goes inside check() so every run enforces it; here it is shown as a separate function to keep the chapter's script unchanged. WHY THIS WORKS AS AN ANSWER --------------------------- A check that is silent about what it did not examine will be trusted beyond its coverage. The same lesson as the empty-path case in the chapter: report what you analysed, and treat 'no rule for this' as a failure, not as a pass.