Skip to main content

Detect imports that can be lazy

Project description

flake8-lazy

Actions Status Documentation Status

PyPI version PyPI platforms

flake8-lazy is a flake8 plugin that finds imports which can be made lazy in Python 3.15 (following PEP 810). See the post here for more on the development of this tool!

flake8-lazy helps keep import-time overhead low by detecting imports that can be declared as lazy in __lazy_modules__. For this package itself, flake8-lazy --help runs roughly twice as fast when using Python 3.15's new lazy import system.

Error messages will mention __lazy_modules__ since that is backward compatible with older Python versions, but the lazy keyword is supported too.

Quick run

There's a standalone flake8-lazy runner. If you use uv or pipx, you can run it from anywhere without installation:

uvx flake8-lazy <filenames>
# OR
pipx run flake8-lazy <filenames>

Try --format=lazy-modules to get copy-paste lines or even --apply to have the tool update your lazy modules automatically!

Install

python -m pip install flake8-lazy

Usually you would include this in some sort of dependency-group in your project, e.g. dev or lint.

flake8 will automatically discover the plugin.

Rule codes

1xx: Missing lazy declarations

  • LZY101: Missing lazy stdlib module in __lazy_modules__
  • LZY102: Missing lazy third-party or local module in __lazy_modules__

2xx: __lazy_modules__ validation

  • LZY201: __lazy_modules__ list is not sorted
  • LZY202: Module listed in __lazy_modules__ is never imported
  • LZY203: Module listed in __lazy_modules__ appears more than once
  • LZY204: __lazy_modules__ is assigned after importing modules it names
  • LZY205: Module name in __lazy_modules__ is relative (.name) instead of absolute

3xx: Native lazy keyword (Python 3.15+)

  • LZY301: Lazy import inside suppress(ImportError) is misleading
  • LZY302: Module is declared lazy by both lazy keyword and __lazy_modules__
  • LZY303: Module is imported both eagerly and lazily

4xx: Lazy import safety and semantics

  • LZY401: Module is declared lazy but accessed at the top level
  • LZY402: Module is an enclosing package for this file and should not be lazy

Basic example

__lazy_modules__ = ["argparse", "requests"]

import argparse
import requests


def main() -> None:
    parser = argparse.ArgumentParser()
    parser.add_argument("url")
    args = parser.parse_args()

    response = requests.get(args.url, timeout=5)
    print(response.status_code)

In this example, requests is only used inside main, so it can be lazy. The checker expects it in __lazy_modules__ and emits LZY102 until you add it. Running --help will not import requests, resulting in a more responsive app!

How detection works

flake8-lazy inspects module-scope imports and module runtime usage.

  • Counts top-level import and from ... import ... statements.
  • Currently treats annotation-only usage as lazy-capable (from __future__ import annotations if not using 3.14+).
  • Treats usage inside if typing.TYPE_CHECKING: as type-only.
  • Handles static sys.version_info checks
  • Skips from __future__ import ....
  • Requires exact module entries for nested imports.
  • Treats enclosing package names as non-lazy for a file. For example, in a/b/c.py, a and a.b should not be listed as lazy.

Nested import note:

import email.header

__lazy_modules__ = ["email"]  # Not enough

This emits LZY101; the required entry is "email.header". PEP 810 requires full module names.

Missing relative imports use f"{__spec__.parent}.name".

Enclosing package note:

# file: a/b/c.py
__lazy_modules__ = ["a", "a.b", "requests"]

# Python 3.15+ also applies
# lazy import a
# lazy import a.b

This emits LZY402 for a and a.b. Those are enclosing packages for the current file, so declaring them lazy is unnecessary and can be removed.

CLI

The project also provides a direct CLI runner:

flake8-lazy path/to/file.py another_file.py
# or
uvx flake8-lazy path/to/file.py another_file.py

The default output format matches flake8-style diagnostics:

path/to/file.py:12:0: LZY102 module 'numpy' should be listed in __lazy_modules__

You can also ask for a copy-pasteable recommendation instead:

flake8-lazy --format lazy-modules path/to/file.py
path/to/file.py: __lazy_modules__ = ["numpy", "pandas"]

This prints the sorted __lazy_modules__ value the checker recommends for each file when it differs from the file's current static __lazy_modules__ declaration. The command still exits with status code 1 if the file has any diagnostics.

To rewrite files in place with the recommended declaration, use --apply:

flake8-lazy --apply path/to/file.py another_file.py

--apply replaces an existing top-level __lazy_modules__ assignment when present. If there is no assignment yet, one is inserted near the top of the file after leading comments/docstrings (and after from __future__ import ... lines, to keep valid Python syntax).

The command exits with status code 1 if any error is found.

Authoring __lazy_modules__

Use a static, sorted list of strings:

__lazy_modules__ = [
    "argparse",
    "numpy",
    "pathlib",
]

Dynamic values are intentionally ignored for now.

Local development

Run tests:

nox -s tests
# or
uv run pytest

Run linting:

nox -s lint
# or
prek -a

Build docs:

nox -s docs --non-interactive

Serve docs locally:

nox -s docs

Bump the version:

uv version <new_version>

FAQ

Why is this not in Ruff?

It's really new, and it's a bit complex. Maybe someday it will be? :)

Why is there no config?

I don't like flake8's config file choices, so I've tried to make it work out of the box. Probably will get some eventually, though, like a list of modules that can't be lazy imported, or a 'simple' mode that requires every module to be lazy.

It's missing something!

Open an issue! If it's clear and detailed and in-scope, I might even be able to assign it to copilot!

Acknowledgements

GitHub Copilot in VS Code was used to help develop this package. The Scientific Python Development Guide template was used as a starting point.

Project details


Download files

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

Source Distribution

flake8_lazy-0.5.1.tar.gz (27.4 kB view details)

Uploaded Source

Built Distribution

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

flake8_lazy-0.5.1-py3-none-any.whl (21.4 kB view details)

Uploaded Python 3

File details

Details for the file flake8_lazy-0.5.1.tar.gz.

File metadata

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

File hashes

Hashes for flake8_lazy-0.5.1.tar.gz
Algorithm Hash digest
SHA256 23a33e543633f0a8746710ab39acf463779a31f39a0da52b0c1b91b31b8540d5
MD5 eabdf31de845abf2bcdce02d30ddb3ae
BLAKE2b-256 4b63fbe721d62450000b410ac9c0145f4ef9d22d0c06414a5ee16a1981b91700

See more details on using hashes here.

Provenance

The following attestation bundles were made for flake8_lazy-0.5.1.tar.gz:

Publisher: cd.yml on henryiii/flake8-lazy

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

File details

Details for the file flake8_lazy-0.5.1-py3-none-any.whl.

File metadata

  • Download URL: flake8_lazy-0.5.1-py3-none-any.whl
  • Upload date:
  • Size: 21.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for flake8_lazy-0.5.1-py3-none-any.whl
Algorithm Hash digest
SHA256 2827c59e574b7ea191bec54f32be9c6d0f3de5b7cbdaf4821907c3e7679a86be
MD5 e38c567661c0123d2df280d391435ec0
BLAKE2b-256 7260a5c26ac5061edf0a8c4c32757b6c326d9fb36da4990f914eb5107d362765

See more details on using hashes here.

Provenance

The following attestation bundles were made for flake8_lazy-0.5.1-py3-none-any.whl:

Publisher: cd.yml on henryiii/flake8-lazy

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 Pingdom Monitoring Sentry Error logging StatusPage Status page