Skip to content

README

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.