Source code for dau_core._lazy

"""Import a package's exports on first use.

Every model in this stack is a ccflow model, and importing ccflow imports
csp, pandas and the rest of its stack: about 0.9 s. A package whose
``__init__`` imports its model modules therefore pays all of it just to be
imported -- including when hydra's searchpath plugin imports the config
package to discover what is installed, which is the slow step in composing
the registry and wants none of those names.

So a package declares which submodule provides each exported name and this
builds the ``__getattr__`` that imports the one submodule the name needs.
The names stay importable exactly as before (``from dau_core import
RegisterOffset``), and a type checker reads them from the ``TYPE_CHECKING``
block the package keeps beside the declaration.
"""

from __future__ import annotations

from collections.abc import Callable
from importlib import import_module

__all__ = ("lazy_exports",)


[docs] def lazy_exports(package: str, exports: dict[str, tuple[str, ...]]) -> tuple[Callable[[str], object], list[str]]: """A module ``__getattr__`` and ``__all__`` for ``{submodule: names}``. Returns both so the two cannot disagree: a name exported by nothing is not reachable, and a name declared here and missing from its submodule raises on first access rather than at import, which a guard test is what turns back into an import-time error. """ providers = {} for module, names in exports.items(): for name in names: if name in providers: raise ValueError(f"{package}: {name!r} is exported by both {providers[name]!r} and {module!r}") providers[name] = module def getattr_(name: str) -> object: module = providers.get(name) if module is None: raise AttributeError(f"module {package!r} has no attribute {name!r}") return getattr(import_module(f"{package}.{module}"), name) return getattr_, sorted(providers)