Skip to content

liblaf/lazy-loader

liblaf-lazy-loader lets Python packages expose stub-driven lazy exports with both absolute imports and package-relative imports.

✨ Features

  • πŸ’€ Stub-driven lazy imports: Parse a sibling .pyi file and turn its import and from ... import ... statements into on-demand attribute loaders.
  • πŸ“¦ Module-friendly exports: Preserve __all__ and enrich dir() so interactive use and star exports stay aligned with the stub definition.
  • πŸ” Absolute and relative import support: Handle both local package imports and external modules, including aliased imports.
  • ⚑ Optional eager mode: Set EAGER_IMPORT=1 to resolve every declared export at import time when startup indirection is not wanted.
  • 🧭 Typed, tiny surface area: Ship as a typed package with no runtime dependencies and a small public API centered on attach_stub and LazyLoader.
  • πŸ”„ Drop-in attach_stub call: Support the familiar attach_stub(__name__, __file__) signature, with an optional trailing __package__ override when needed.

πŸ“¦ Installation

Note

liblaf-lazy-loader requires Python 3.12 or newer.

uv add liblaf-lazy-loader

πŸš€ Quick Start

In mypkg/__init__.py, wire the package up once:

from liblaf.lazy_loader import attach_stub

__getattr__, __dir__, __all__ = attach_stub(__name__, __file__)

If you need to pass an explicit package anchor, use the optional third argument:

__getattr__, __dir__, __all__ = attach_stub(__name__, __file__, __package__)

In the sibling mypkg/__init__.pyi, declare the exports you want to load lazily:

from . import cli
from ._config import Settings
from ._factory import make_settings
from rich import get_console
import rich.console as rich_console

__all__ = ["Settings", "cli", "get_console", "make_settings", "rich_console"]

With that wiring in place, Settings, cli, make_settings, get_console, and rich_console are imported only when first accessed. When the third argument is omitted entirely, attach_stub uses __name__ as the package anchor, which makes the two-argument form work as a drop-in replacement for lazy_loader.attach_stub in package __init__.py files. Passing None explicitly preserves None. The sibling .pyi file is part of the runtime configuration here, not only a type-checking aid.

🧩 Supported Stub Forms

The stub parser understands the explicit import forms that the test suite covers:

  • import rich
  • import rich.console as rich_console
  • from rich import get_console
  • from . import cli
  • from ._factory import make_settings
  • from ._factory import make_settings as build_settings

__all__ stays aligned with the stub definition, and dir() includes both declared exports and any names already materialized on the module.

⚑ Eager Import Mode

Lazy loading defers import errors until the first attribute access. During development or tests, set EAGER_IMPORT=1 before importing the package to resolve every declared export immediately.

The current test suite also covers EAGER_IMPORT=0 for normal lazy behavior and raises a ValueError when EAGER_IMPORT is set to an invalid boolean string.

🚧 Limitations and Errors

  • Accessing a name that is not declared in the stub raises AttributeError.
  • Import failures surface when the lazy attribute is accessed, or earlier if eager mode is enabled.
  • The package expects explicit import statements in the sibling stub file and uses that stub file at runtime.

πŸ” Compared With Alternatives

This project parses the sibling stub AST directly, so the runtime behavior is defined by the same file that type checkers read.

  • scientific-python/lazy-loader.attach_stub parses stubs into its older attach(...) API. In its current implementation, the stub visitor only accepts within-package from . import ... and from .foo import ... forms and raises ValueError for other patterns, so it cannot express absolute entries like import rich.console as rich_console in the stub. See the README and source.
  • etils.epy.lazy_api_imports records imports by temporarily wrapping builtins.__import__. Its underlying lazy import helper rejects relative imports with a ValueError, so it is not a drop-in fit for sibling package-relative exports. See the API docs and source.

⌨️ Local Development

Clone the repository, install all dependency groups with uv, and run the maintained nox test matrix:

gh repo clone liblaf/lazy-loader
cd lazy-loader
mise run install
nox

Build the documentation locally with:

mise run docs:build

🀝 Contributing

Issues and pull requests are welcome, especially around import edge cases, typing behavior, and documentation improvements.

PR Welcome

Contributors


πŸ“ License

Copyright Β© 2026 liblaf.
This project is MIT licensed.

About

πŸ¦₯ Python lazy-loading helpers that build module __getattr__, __dir__, and __all__ from .pyi stubs and import statements.

Topics

Resources

License

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages