From c28895ffddeaee14910febf28a9f7fbbcbd3f493 Mon Sep 17 00:00:00 2001 From: Abdessamad Derraz <3028866+Abdess@users.noreply.github.com> Date: Sun, 18 Oct 2026 05:18:56 +0200 Subject: [PATCH] fix: hold the scripts to stdlib and pyyaml The build scripts are documented as needing only the standard library and pyyaml, and the Windows downloader none of it, but CI installs both pyyaml and jsonschema's tree, so a stray import would pass everywhere it is tested. The suite now scans every import and runs the downloader with yaml unavailable. --- tests/test_optional_dependencies.py | 53 +++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/tests/test_optional_dependencies.py b/tests/test_optional_dependencies.py index f8491046..610f7a0d 100644 --- a/tests/test_optional_dependencies.py +++ b/tests/test_optional_dependencies.py @@ -52,5 +52,58 @@ class SuiteWithoutJsonschema(unittest.TestCase): self.assertEqual(completed.returncode, 0, completed.stdout + completed.stderr[-2000:]) +class ScriptsNeedOnlyPyyaml(unittest.TestCase): + """The build scripts need the standard library and pyyaml; jsonschema is + validate_schemas.py's alone. CI installs jsonschema with its whole tree + (attrs, referencing, rpds-py), so an import of any of them would pass + there and fail for a contributor following the documented setup.""" + + def test_no_script_imports_another_package(self): + import ast # noqa: PLC0415 + + scripts = Path(__file__).resolve().parents[1] / "scripts" + local = {p.stem for p in scripts.rglob("*.py")} | { + p.name for p in scripts.iterdir() if p.is_dir() + } | {"scripts", "__future__"} + allowed = {"yaml"} + found = [] + for path in sorted(scripts.rglob("*.py")): + for node in ast.walk(ast.parse(path.read_text(encoding="utf-8"))): + if isinstance(node, ast.Import): + names = [a.name.split(".")[0] for a in node.names] + elif isinstance(node, ast.ImportFrom) and node.level == 0 and node.module: + names = [node.module.split(".")[0]] + else: + continue + for name in names: + if name in sys.stdlib_module_names or name in local or name in allowed: + continue + if name == "jsonschema" and path.name == "validate_schemas.py": + continue + found.append(f"{path.relative_to(scripts)}: {name}") + self.assertEqual(found, []) + + +class TheDownloaderRunsWithoutPyyaml(unittest.TestCase): + """download.py is the documented Windows downloader, "stdlib only". It + stays so only because common.py imports yaml optionally: made a plain + import, every CI job (which installs pyyaml) would stay green.""" + + def test_help_runs_with_yaml_unimportable(self): + import subprocess # noqa: PLC0415 + + root = Path(__file__).resolve().parents[1] + code = ( + "import sys, runpy; sys.modules['yaml'] = None; " + "sys.path.insert(0, 'scripts'); sys.argv = ['download.py', '--help']; " + "runpy.run_path('scripts/download.py', run_name='__main__')" + ) + completed = subprocess.run( + [sys.executable, "-I", "-c", code], cwd=root, capture_output=True, + text=True, timeout=120, check=False, + ) + self.assertEqual(completed.returncode, 0, completed.stderr[-1500:]) + + if __name__ == "__main__": unittest.main()