Skip to main content

Lifeguard for Lazy Imports

A fast static analysis tool to aid adoption of Lazy Imports in Python.

Lifeguard

What are Lazy Imports?

In Python, every import statement executes immediately when a module is loaded. This overhead is incurred regardless of whether that import is actually used. PEP 810 introduces explicit Lazy Imports to Python, which defer the actual loading of a module until the imported name is first accessed. Lazy Imports can significantly reduce memory usage, startup times, and import overhead, especially in large codebases with deep dependency trees.

However, some Python patterns depend on imports executing immediately. For example:

  • Module-level side effects — a module that registers a handler or modifies global state at import time will behave differently if that import is deferred.
  • The registry pattern — a module that registers itself (e.g., adding to a global dict) when imported will silently fail to register under Lazy Imports.
  • sys.modules manipulation — code that reads or writes sys.modules assumes prior imports have already executed.
  • Metaclasses and __init_subclass__ — class creation side effects may depend on imports being resolved.

Adapting an existing codebase to use Lazy Imports can be a daunting task, especially at scale. Lifeguard identifies these incompatible patterns so you can adopt Lazy Imports with confidence.

How does Lifeguard work?

Lifeguard analyzes Python source files for a given project in parallel. It walks each module's AST to detect effects and maps Lazy-Imports-incompatible effects to errors. The analyzer takes a conservative approach towards its analysis: any module that cannot be programmatically determined to be safe to import lazily is marked unsafe by default. This means Lifeguard will err on the side of marking potentially compatible modules as incompatible, leaving potential performance optimizations on the table in favor of production safety.

For a deeper look at the analysis pipeline and architecture, see docs/architecture.md.

Project Stage: Beta

Lifeguard is in active development. We are aiming to be ready for general use by the Python 3.15 final release.

Items on our roadmap

  • We are preparing GitHub actions to fully support external contributors.
  • We plan to release to PyPI.
  • We've tested and support Python 3.12 and 3.14. Other versions may also work. We do not yet support the lazy keyword added in PEP 810 — but we fully intend to support this ahead of the 3.15 release.
  • We are actively developing a standalone linter output mode to help users identify which specific lines in their codebase are incompatible with Lazy Imports.
  • We plan to add support for easy ingestion of Lifeguard's output to drive Lazy Imports enablement for advanced users (see Using the Output).

Prerequisites

  • Rust (nightly) — the crate uses unstable features. Install via rustup and set with rustup default nightly.
  • Git — clone with submodules: git clone --recurse-submodules https://github.com/facebook/Lifeguard.git

If you already cloned without --recurse-submodules, run git submodule update --init --recursive.

Quick Start

The fastest way to try Lifeguard is the run-tree subcommand, which analyzes every .py file under a directory. No additional setup needed.

cargo run -- run-tree <INPUT_DIR> <OUTPUT_PATH>

For example, using the bundled sample project:

cargo run -- run-tree testdata/sample_project output.json

For a full walkthrough including interpreting the output, see GETTING_STARTED.md.

Running Lifeguard

For larger projects where you need more control, you can generate a source DB — a JSON file that tells Lifeguard the full set of Python files in your project and their module paths (see Input Format for details). Follow these steps:

  1. Generate the source DB. We provide a subcommand to start this file for you, but you may need to tune it by hand. (As the project matures, we hope to make this process smoother.)
cargo run -- gen-source-db <INPUT_DIR> <OUTPUT_PATH>

Optionally, if your project has library dependencies, you can point Lifeguard at your site-packages by adding a lifeguard section to your pyproject.toml:

[lifeguard]
site_packages = "/path/to/site-packages"

You can find out your site-packages path via python -m site. The gen-source-db subcommand reads this section automatically when generating the source DB.

Note: The script may not discover all of your project's dependencies. If Lifeguard reports missing modules, you may need to manually add entries to the generated source DB.

  1. Run Lifeguard in one of two modes:
    • Default: Prints a high-level analysis of your codebase (% of compatible files, top errors, etc.) and writes the JSON output to OUTPUT_PATH.
    cargo run -- <DB_PATH> <OUTPUT_PATH>
    
    • Verbose mode: Also writes a human-readable report showing which specific lines in each module cause incompatibility.
    cargo run -- <DB_PATH> <OUTPUT_PATH> --verbose-output <VERBOSE_OUTPUT_PATH>
    

Example Verbose Output:

## example.module.foo
### Errors
  Line 17 - ImportedModuleAssignment sys
  Line 38 - UnsafeFunctionCall example.demo.unsafe_method

Input Format

In some modes, Lifeguard requires a source DB — a JSON file mapping Python module paths to their locations on disk. The format is:

{
  "build_map": {
      "foo/bar.py": "/local/usr/disk/foo/bar.py",
      "example/__init__.py": "/local/usr/disk/third-party/example/__init__.py"
  }
}

You can generate this automatically using cargo run -- gen-source-db (see Running Lifeguard), or create it by hand.

Output Format

Lifeguard writes a JSON file with two fields:

{
    "LAZY_ELIGIBLE": {
        "module1": [],
        "module2": ["module3", "module4"],
        "module5": [],
    },
    "LOAD_IMPORTS_EAGERLY": ["module5", "module99", "module100"]
}

LAZY_ELIGIBLE

A dictionary mapping modules that are safe for Lazy Imports to a list of their dependencies that must be imported eagerly. For example:

  • "module1": []module1 is fully safe for Lazy Imports with no restrictions.
  • "module2": ["module3", "module4"]module2 is safe for Lazy Imports, but only if module3 and module4 have already been imported.

Important: Modules that do not appear as keys in this dictionary have been analyzed as unsafe for Lazy Imports.

LOAD_IMPORTS_EAGERLY

A set of modules where all imports within the module must be loaded eagerly. Lazy Imports is essentially temporarily disabled for these modules. Note the distinction: other modules can still lazily import a module in the LOAD_IMPORTS_EAGERLY set, but when that module does load, its own import statements must execute immediately rather than being deferred.

This set is only used for specific corner cases:

  • Custom finalizers (__del__) — unpredictable execution timing means imports must be available at finalization.
  • exec() calls — dynamic code execution negates static analysis guarantees.
  • sys.modules access — reading or writing sys.modules could depend on prior imports having already executed.

For more details, see docs/load_imports_eagerly.md.

Using the Output

As a standalone linter

Lifeguard can be used as a standalone linter to identify which specific lines in your codebase are incompatible with Lazy Imports. Run the analyzer with --verbose-output to get a human-readable report showing per-module errors with line numbers (see Running Lifeguard). This lets you treat Lifeguard like a linter: run it in CI or locally, review the flagged lines, and fix them. In this manner, Lifeguard is used as a guide to safely enable Lazy Imports.

To drive a lazy import loader

The JSON output is designed to drive a lazy import loader's filter function. In Python 3.15, importlib.util.lazy_import accepts a filter callback that controls which imports are deferred and which are loaded eagerly. Lifeguard's output provides the data needed to build this filter — using LAZY_ELIGIBLE to identify safe modules and their constraints, and LOAD_IMPORTS_EAGERLY to identify modules that need all imports resolved upfront.

We plan to provide tooling for easy ingestion of Lifeguard's output ahead of the Python 3.15 release. This is a work in progress.

Implementation

Lifeguard is implemented in Rust. We leverage ruff for AST traversal and re-use several crates from pyrefly. We also extend .pyi stub files to annotate known side effects in third-party libraries — for example, marking that a particular module-level function call in a dependency has observable behavior. These stubs are stored in the resources/ folder. See resources/stubs/stubs.md for details on how effect annotations work alongside standard type stubs.

License

By contributing to Lifeguard, you agree that your contributions will be licensed under the LICENSE file in the root directory of this source tree.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

lifeguard_lazy_imports-0.1.0.tar.gz (837.5 kB view details)

Uploaded Source

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

lifeguard_lazy_imports-0.1.0-py3-none-win_arm64.whl (3.5 MB view details)

Uploaded Python 3Windows ARM64

lifeguard_lazy_imports-0.1.0-py3-none-win_amd64.whl (3.6 MB view details)

Uploaded Python 3Windows x86-64

lifeguard_lazy_imports-0.1.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (4.1 MB view details)

Uploaded Python 3manylinux: glibc 2.17+ x86-64

lifeguard_lazy_imports-0.1.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (4.0 MB view details)

Uploaded Python 3manylinux: glibc 2.17+ ARM64

lifeguard_lazy_imports-0.1.0-py3-none-macosx_11_0_arm64.whl (3.8 MB view details)

Uploaded Python 3macOS 11.0+ ARM64

lifeguard_lazy_imports-0.1.0-py3-none-macosx_10_12_x86_64.whl (3.9 MB view details)

Uploaded Python 3macOS 10.12+ x86-64

File details

Details for the file lifeguard_lazy_imports-0.1.0.tar.gz.

File metadata

  • Download URL: lifeguard_lazy_imports-0.1.0.tar.gz
  • Upload date:
  • Size: 837.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for lifeguard_lazy_imports-0.1.0.tar.gz
Algorithm Hash digest
SHA256 be6f1c1a9957b22188efd2a06db22aa1038a875736f9713a8fcbb9a5d07cda3d
MD5 94eb31b6afd7fa6110b332e954e77181
BLAKE2b-256 48a17dddaf42356f99be6ef3491639feb514bd2106ffdf8b9eadd6668f3684e2

See more details on using hashes here.

Provenance

The following attestation bundles were made for lifeguard_lazy_imports-0.1.0.tar.gz:

Publisher: publish_to_pypi.yml on facebook/Lifeguard

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file lifeguard_lazy_imports-0.1.0-py3-none-win_arm64.whl.

File metadata

File hashes

Hashes for lifeguard_lazy_imports-0.1.0-py3-none-win_arm64.whl
Algorithm Hash digest
SHA256 c8845c296a583d17c65f0dfee8b7ca3847a60eb2d84a5112018167727981b673
MD5 e5a211fe7c3282c1ace9a25ac423281d
BLAKE2b-256 33bf588f2ded68c33e67de0ec46ff379e813f103dd7162fdb30f5c0dc9547bbc

See more details on using hashes here.

Provenance

The following attestation bundles were made for lifeguard_lazy_imports-0.1.0-py3-none-win_arm64.whl:

Publisher: publish_to_pypi.yml on facebook/Lifeguard

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file lifeguard_lazy_imports-0.1.0-py3-none-win_amd64.whl.

File metadata

File hashes

Hashes for lifeguard_lazy_imports-0.1.0-py3-none-win_amd64.whl
Algorithm Hash digest
SHA256 7a8bc4d02fd1f2125e9b3a1f0d77ad75088e5b112c24c1b8d8af3a43bdbe8b66
MD5 f7b4ddbd6292da39b53e2408e288a9e7
BLAKE2b-256 dfcf63d89181bd316b100282c2b11deb76f3d350e9f55e4fbc4069e4bc02d5a8

See more details on using hashes here.

Provenance

The following attestation bundles were made for lifeguard_lazy_imports-0.1.0-py3-none-win_amd64.whl:

Publisher: publish_to_pypi.yml on facebook/Lifeguard

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file lifeguard_lazy_imports-0.1.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for lifeguard_lazy_imports-0.1.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 20fa0d99b9a31142d08f51522c73fccdcc59bb9fa1da1e63c440b8f812a40773
MD5 7eead4893171905fd559afd1bbcfa063
BLAKE2b-256 c1248f6090d7559c39e37d272b736b51d77a8d59538c0e0d5c60c0b1cba3a892

See more details on using hashes here.

Provenance

The following attestation bundles were made for lifeguard_lazy_imports-0.1.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl:

Publisher: publish_to_pypi.yml on facebook/Lifeguard

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file lifeguard_lazy_imports-0.1.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.

File metadata

File hashes

Hashes for lifeguard_lazy_imports-0.1.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 223b1bc3c96b9bc6ac72a65373a997106be6518a763cec81f778a4af0278de10
MD5 bee4d709f810991ca056c34e628fa1d8
BLAKE2b-256 b4a15379953e22225f6f5711d1a7220fed606ce63edb42a73b99139059c2895c

See more details on using hashes here.

Provenance

The following attestation bundles were made for lifeguard_lazy_imports-0.1.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl:

Publisher: publish_to_pypi.yml on facebook/Lifeguard

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file lifeguard_lazy_imports-0.1.0-py3-none-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for lifeguard_lazy_imports-0.1.0-py3-none-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 e5ada42720f98ca1a36ed2c3031e5d8f6d512337c0d36b6490f8c4f6a78997c4
MD5 2514c848331bcf9d9ee8b34cc206e340
BLAKE2b-256 7a7e175e7d325d4e2b64f43694dea77acb7d9be2f8e060ec950ab9ebef865aa9

See more details on using hashes here.

Provenance

The following attestation bundles were made for lifeguard_lazy_imports-0.1.0-py3-none-macosx_11_0_arm64.whl:

Publisher: publish_to_pypi.yml on facebook/Lifeguard

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file lifeguard_lazy_imports-0.1.0-py3-none-macosx_10_12_x86_64.whl.

File metadata

File hashes

Hashes for lifeguard_lazy_imports-0.1.0-py3-none-macosx_10_12_x86_64.whl
Algorithm Hash digest
SHA256 9eb58110e9036a1a29935ba1e6e6a457675e580e8e2ec5b38b753ce76bbf4cd7
MD5 c33b9d14586f4064c7b5e98fc5809ec4
BLAKE2b-256 4358b50854693c08155e1797777faad39ce643451eccdcff947dd71b98e95456

See more details on using hashes here.

Provenance

The following attestation bundles were made for lifeguard_lazy_imports-0.1.0-py3-none-macosx_10_12_x86_64.whl:

Publisher: publish_to_pypi.yml on facebook/Lifeguard

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page