diff --git a/decnet/topology/__init__.py b/decnet/topology/__init__.py index d9404e78..d40a2d5a 100644 --- a/decnet/topology/__init__.py +++ b/decnet/topology/__init__.py @@ -7,7 +7,6 @@ is marked as the DMZ (Internet-facing). Persisted via the repo pattern; deployed via :mod:`decnet.engine.deployer`. """ from decnet.topology.config import TopologyConfig, GeneratedTopology -from decnet.topology.generator import generate from decnet.topology.status import ( TopologyStatus, assert_transition, @@ -22,3 +21,14 @@ __all__ = [ "assert_transition", "TopologyStatusError", ] + + +def __getattr__(name: str): + # ponytail: lazy re-export — `generate` pulls generator→allocator→repository→the + # full SQLModel ORM (~38MB). Defer it so importing this package (which every worker + # does transitively via the CLI) doesn't drag the DB layer into DB-less workers. + if name == "generate": + from decnet.topology.generator import generate + + return generate + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") diff --git a/tests/topology/test_lazy_imports.py b/tests/topology/test_lazy_imports.py new file mode 100644 index 00000000..f7e53d64 --- /dev/null +++ b/tests/topology/test_lazy_imports.py @@ -0,0 +1,37 @@ +# SPDX-License-Identifier: AGPL-3.0-or-later +"""Guard: importing the topology package must not eagerly pull the generator +(which drags the full SQLModel ORM, ~38MB, into every worker via the CLI). + +See development/RELEASE-1.1.md (C2). +""" +import subprocess +import sys + + +def _import_and_report(stmt: str) -> set[str]: + """Run `stmt` in a fresh interpreter, return the set of decnet.* modules loaded.""" + code = ( + f"import sys\n{stmt}\n" + "print('\\n'.join(m for m in sys.modules if m.startswith('decnet')))" + ) + out = subprocess.run( + [sys.executable, "-c", code], capture_output=True, text=True, check=True + ) + return set(out.stdout.split()) + + +def test_topology_import_does_not_pull_generator(): + loaded = _import_and_report("import decnet.topology") + assert "decnet.topology.generator" not in loaded, ( + "topology/__init__ regressed to eager generator import — this pulls the " + "repository → SQLModel ORM into every DB-less worker" + ) + + +def test_generate_still_resolvable_lazily(): + loaded = _import_and_report("from decnet.topology import generate") + assert "decnet.topology.generator" in loaded # accessing it loads it + # and it's actually callable + from decnet.topology import generate + + assert callable(generate)