Skip to main content
Archived

This project has been archived by its maintainers, and is no longer receiving any updates.

lateimport

Lazy import utilities for Python — defer module loading until an imported object is actually used.

PyPI Python License CI codecov

Status: Stable. Feature complete. Open to suggestions for API improvements and will fix bugs.

Overview

lateimport provides two complementary tools:

  • lateimport — an explicit proxy for deferring imports in application code
  • create_late_getattr — a factory for the __getattr__ hook in package __init__.py files, for use with dispatch-table-based lazy loading (e.g. as generated by Exportify)

We developed lateimport for CodeWeaver, and we liked it so much we decided to ship it as its own package for other projects. It has been rigorously tested and has complete test coverage.

Dependency Free. Lightweight.

  • lateimport is purely stdlib -- zero outside dependencies.
  • Small package -- the whole implementation is ~280 lines of code. For that you get modern typing and type checker support, and truly lazy imports.

Why?

There are a lot of late/lazy/delayed import solutions out there. Lateimport's main differences:

  • Complete type checking support
  • Actually lazy. Most lazy patterns delay until the lazy object is in scope. lateimport delays imports until they are actually directly accessed (i.e. you access an attribute or call it).
  • Get linting and automation with Exportify. Exportify is a CLI development tool that lets you declaratively set rules for import and export patterns, and validate all lazy imports from lateimport. You can say goodbye to broken lazy imports and updating __all__.

Requirements

Python 3.12+

Installation

pip install lateimport

Usage

Explicit lazy imports

Use lateimport when you want to defer a heavy import in application code. The module is not imported until the returned proxy is actually used — called, subscripted, passed to isinstance, etc.

from lateimport import lateimport

# No import happens here
numpy = lateimport("numpy")
pandas = lateimport("pandas", "DataFrame")  # defers pandas.DataFrame

# Import triggered on first use
result = numpy.array([1, 2, 3])
df = pandas({"a": [1, 2]})

Proxies are thread-safe and cache the resolved object after first access.

proxy = lateimport("heavy.module", "ExpensiveClass")

proxy.is_resolved()   # False
instance = proxy()    # heavy.module imported, ExpensiveClass resolved
proxy.is_resolved()   # True

Type annotations

LateImport[T] is a generic class. Annotating a variable with the resolved type gives you full type-checking and autocompletion downstream — the type checker knows that calling or accessing the proxy produces a T.

from __future__ import annotations

from typing import TYPE_CHECKING

from lateimport import LateImport, lateimport

if TYPE_CHECKING:
    import numpy as np
    from pandas import DataFrame

# Type checker knows ndarray() returns np.ndarray
ndarray: LateImport[np.ndarray] = lateimport("numpy", "ndarray")
arr = ndarray([1, 2, 3])  # arr: np.ndarray

# type[DataFrame] means calling the proxy produces a DataFrame instance
DataFrame: LateImport[type[DataFrame]] = lateimport("pandas", "DataFrame")
df = DataFrame({"col": [1, 2, 3]})  # df: DataFrame

[!IMPORTANT] The TYPE_CHECKING guard keeps the imports from executing at runtime — you get static type information without the import cost, which is the whole point.

For types that are always available no guard is needed:

from pathlib import Path
from lateimport import LateImport, lateimport

MkPath: LateImport[type[Path]] = lateimport("pathlib", "Path")
p = MkPath("/tmp/out")  # p: Path

Package-level lazy __getattr__

For packages using a dispatch-table pattern (e.g. generated by Exportify, create_late_getattr creates a __getattr__ hook that imports attributes on demand:

# mypackage/__init__.py
from types import MappingProxyType
from lateimport import create_late_getattr
# NOTE: for IDE support, import any dynamic imports in a TYPE_CHECKING block:
from typing import TYPE_CHECKING:
    from mypackage.core.models import MyClass
    from mypackage.utils.helpers import my_function
    from mypackage import SubModule

_dynamic_imports = MappingProxyType({
    "MyClass":      ("mypackage.core",   "models"),
    "my_function":  ("mypackage.utils",  "helpers"),
    # for the current package you can just use __spec__.parent:
    "SubModule":    (__spec__.parent,        "__module__"),
})

__getattr__ = create_late_getattr(_dynamic_imports, globals(), __name__)

__all__ = ("MyClass", "my_function", "SubModule")

# Make sure dir calls use __all__ and not globals:
__dir__ = lambda: list(__all__)

The dispatch tuple is (package, submodule):

  • Normal attributes: imports package.submodule and returns getattr(submodule, attr_name)
  • "__module__": imports the submodule itself as the attribute (import_module(f".{attr_name}", package=package))

Resolved attributes are cached in globals() so subsequent accesses are direct.

API

lateimport(module_name, *attrs) -> LateImport[T]

Create a lazy proxy for module_name. Optional attrs are an attribute chain traversed after import. The function is generic — annotate the variable as LateImport[T] to propagate the resolved type to callers.

lateimport("os")                    # proxy for the os module
lateimport("os.path", "join")       # proxy for os.path.join
lateimport("mypackage", "A", "B")   # proxy for mypackage.A.B

class LateImport[T]

The generic proxy class returned by lateimport. Annotate the variable with LateImport[T] to tell the type checker what type the proxy resolves to — __call__ and _resolve() are typed to return T. Supports call, attribute access, dir(), and repr(). Thread-safe.

Method Description
is_resolved() True if the import has been triggered
_resolve() Force immediate resolution and return the object (typed as T)

create_late_getattr(dynamic_imports, module_globals, module_name)

Create a __getattr__ function for package-level lazy loading.

Parameter Type Description
dynamic_imports MappingProxyType[str, tuple[str, str]] Dispatch table
module_globals dict[str, object] globals() of the calling module
module_name str __name__ of the calling module

INTROSPECTION_ATTRIBUTES

frozenset of dunder attribute names that are resolved immediately rather than proxied (e.g. __doc__, __name__, __module__). Available for reference when implementing custom proxy patterns.

License

MIT OR Apache-2.0 — see LICENSE-MIT and LICENSE-Apache-2.0.

Metadata

Release files for lateimport 1.0.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for lateimport 1.0.1
File Size Uploaded
lateimport-1.0.1.tar.gz 39.4 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for lateimport 1.0.1
File Interpreter ABI Platform
lateimport-1.0.1-py3-none-any.whl Python 3 none any Details

Total release size: 56.9 kB

Release files / lateimport-1.0.1.tar.gz

Download URL lateimport-1.0.1.tar.gz
Size 39.4 kB
Tags Source
SHA-256 checksum
How to use checksums
04c774c177278d2e565791339dc8761285710c46698b757886531a506f629b20
BLAKE2b-256 checksum
How to use checksums
bac97a9feb36256e8fb54133b501759fbe0c81fb844a3776e5f182b19b0ef4f7
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.7

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Mar 27, 2026.

Transparency log

Release files / lateimport-1.0.1-py3-none-any.whl

Download URL lateimport-1.0.1-py3-none-any.whl
Size 17.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
5be6b538ac874349e5942eca26b7916c762548ae52729e5f37b0d9862654f145
BLAKE2b-256 checksum
How to use checksums
fcae789dfaf78162bb484064c38e5e470e1bd746733e3722020366421834ade6
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.7

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Mar 27, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

1.0.1 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page