autolazy
Utilities for building lazily imported Python packages.
autolazy provides helper functions that make it easy to set up
lazy_loader-based packages.
It automatically discovers each submodule's public API by parsing __all__
declarations — no imports, no side effects, just AST analysis — and wires
everything up in a single call.
Installation
pip install autolazy
Requires Python 3.12+.
Quick start
Suppose you have a package like this:
mypkg/
├── __init__.py
├── audio.py # __all__ = ["AudioLoader", "AudioWriter"]
└── vision.py # __all__ = ["ImageLoader"]
Replace the usual eager imports in __init__.py with:
from autolazy import lazy_attach
__getattr__, __dir__, __all__ = lazy_attach(
package_name=__name__,
init_file_path=__file__,
submod_attrs=["audio", "vision"],
)
Now symbols are imported on first access:
import mypkg
mypkg.AudioLoader # imports audio.py only at this point
mypkg.ImageLoader # imports vision.py only at this point
Submodules themselves can also be exposed lazily:
__getattr__, __dir__, __all__ = lazy_attach(
package_name=__name__,
init_file_path=__file__,
submodules=["utils"], # exposed as mypkg.utils
submod_attrs=["audio"], # symbols exposed at mypkg level
)
API
lazy_attach
lazy_attach(
package_name: str,
init_file_path: str,
submodules: list[str] | None = None,
submod_attrs: list[str] | None = None,
) -> tuple[__getattr__, __dir__, __all__]
Scans each name in submod_attrs, extracts its __all__ via AST parsing,
and passes the result to lazy_loader.attach().
| Parameter | Description |
|---|---|
package_name |
The package name — pass __name__ |
init_file_path |
Path to __init__.py — pass __file__ |
submodules |
Submodules exposed as pkg.submod (not flattened) |
submod_attrs |
Submodules whose public symbols are flattened into the package namespace |
If a name in submod_attrs has no __all__ (or the file is missing), it
falls back to being treated as a plain submodule and a UserWarning is
emitted for missing files.
Dotted names (e.g. "sub.module") are resolved relative to the package
directory, so nested packages work out of the box.
parse_all
parse_all(path: Path) -> list[str]
Parses a Python source file and returns the names declared in __all__,
without importing the module. Supports all common patterns:
__all__ = ["a", "b"] # assignment
__all__ += ["c"] # augmented assignment
__all__.append("d") # append
__all__.extend(["e", "f"]) # extend
Non-string elements in __all__ are silently ignored. If multiple
assignments to __all__ exist, the last one wins (matching Python semantics).
sync_type_checking
Since lazy_attach builds the API at runtime, static type checkers like
Pylance/pyright can't see mypkg.AudioLoader. sync_type_checking generates an
if TYPE_CHECKING: block of literal imports (never executed, so lazy loading is
preserved) that keeps them in sync.
The easiest way is the CLI — it reads the arguments from your existing
lazy_attach(...) call, so there's nothing to configure:
python -m autolazy mypkg/__init__.py
It inserts (and, on re-runs, refreshes in place) a marker-delimited block:
# >>> autolazy: type-checking imports (auto-generated) >>>
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from .audio import AudioLoader, AudioWriter
from .vision import ImageLoader
__all__ = ["AudioLoader", "AudioWriter", "ImageLoader"]
# <<< autolazy: type-checking imports <<<
The literal __all__ marks the imports as re-exports, so ruff and pyright stay
quiet with no as aliases or # noqa. It sits under if TYPE_CHECKING:, so it
never runs — your runtime __all__ stays the one lazy_attach builds.
Imports and __all__ assignments that would exceed 88 characters are wrapped
with one name per line. Imports follow Ruff/isort's default order-by-type
natural ordering, while __all__ retains the public API's declaration order.
Writing preserves the existing file's LF or CRLF line endings, including on
Windows.
The same is available programmatically as sync_type_checking(init_file_path, submodules=..., submod_attrs=...) (pass write=False to get the block as a
string). See the guide for details.
How it compares to manual lazy_loader usage
Without autolazy you must maintain submod_attrs by hand:
# manual — must be kept in sync with each module's __all__
__getattr__, __dir__, __all__ = lazy.attach(
__name__,
submod_attrs={
"audio": ["AudioLoader", "AudioWriter"],
"vision": ["ImageLoader"],
},
)
With autolazy:
# automatic — __all__ is read from each file at import time
__getattr__, __dir__, __all__ = lazy_attach(
package_name=__name__,
init_file_path=__file__,
submod_attrs=["audio", "vision"],
)
Requirements
- Python >= 3.12
lazy-loader>= 0.4
License
MIT — see LICENSE.
Metadata
Release files for autolazy 1.1.6
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| autolazy-1.1.6.tar.gz | 9.4 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| autolazy-1.1.6-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 20.3 kB
Release files / autolazy-1.1.6.tar.gz
| Download URL | autolazy-1.1.6.tar.gz |
|---|---|
| Size | 9.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
361ab387791ce76bde73302586bf7cf642486d7daff2033daa1f411e5e573d88
|
|
BLAKE2b-256 checksum How to use checksums |
191fdf0e1bf791afa8af97303f86beffa755847553d7449a1172771b25141dbf
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.11.29 {"installer":{"name":"uv","version":"0.11.29","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Release files / autolazy-1.1.6-py3-none-any.whl
| Download URL | autolazy-1.1.6-py3-none-any.whl |
|---|---|
| Size | 10.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
cff9d6b57e9232305661ff086d2d2ad8e41dd8464a9124924858310ff6c20770
|
|
BLAKE2b-256 checksum How to use checksums |
283faa0ff0b4b9efdc5f74aec1dfedcfd0215374065f296e788ae74802f9cf77
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.11.29 {"installer":{"name":"uv","version":"0.11.29","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|