9. The CLI's import budget, measured
9. The CLI's import budget, measured [tested: tests/tier1/test_library_behaviour.py::test_cli_deferred_imports_and_hashing_duck_typing]¶
import nanopnp.cli costs 55.6 ms cumulative (python -X importtime), and a cold subprocess
that imports it and exits takes 0.082 s. import ngsolve alone is ~370 ms, and the physics and
mesh modules at module scope would take the package's own share from 56 ms to 424 ms. A sweep
dispatching a job array pays that per process, which is what the deferred-import rule of
CLAUDE.md buys. import numpy alone is ~67 ms, which is why core/hashing.py detects arrays by
duck-typing rather than importing NumPy [tested: tests/tier1/test_library_behaviour.py::test_cli_deferred_imports_and_hashing_duck_typing].
Two mechanisms keep it there, and both are load-bearing rather than tidy:
-
The stage registry holds descriptions beside
"module:attribute"target strings, andnanopnp.core.stages.createis the only function that resolves one.nanopnp stage --listtherefore answers from data. Asserted in a subprocess: aftermain(["stage", "--list"])no module matchingngsolve*ornetgen*is insys.modules[tested: tests/tier1/test_cli.py::test_ver32_stage_list_imports_no_stage_module]. -
The IF-02 exit-code table is keyed on
f"{cls.__module__}:{cls.__qualname__}"strings, andclassifywalkstype(error).__mro__looking each name up. Classifying an error therefore imports no exception module — which matters because the table names classes insolve,mesh,chargeandpost, and a table of class objects would import all four at CLI start. Asserted the same way, on the table's own module list [tested: tests/tier1/test_cli.py::test_ver32_classifying_imports_no_exception_module].
The MRO walk is not incidental: it is what makes a subclass of a classified exception inherit its
exit code, so a narrower gate error added later is still a 4 rather than falling to 1.