Skip to main content

geninit

Keeping __init__.py files up to date gets tedious once a package has more than a few modules. geninit takes care of generating those files while still letting you decide what belongs in your public API.

The geninit command itself requires Python 3.15 or newer, but can generate eager __init__.py files for projects that support older Python versions.

Installation

The simplest way to install geninit is as an isolated uv tool:

uv tool install --python 3.15 geninit

You can then run geninit from any project. If you only want to try it once, use uvx instead:

uvx --python 3.15 geninit --check

For a project that keeps its development tools in uv.lock, add geninit to the development dependency group:

uv add --dev geninit

Commands in the rest of this README can then be written as uv run --locked --no-sync geninit ....

Getting started

Add a [tool.geninit] table to your pyproject.toml and list the packages you want geninit to manage:

[project]
requires-python = ">=3.14"

[tool.geninit]
roots = ["src/acme"]

Run geninit from the directory containing pyproject.toml:

geninit

The first run creates the necessary __init__.py files. Later runs update the managed sections when modules or exports change.

You can also pass additional package roots on the command line:

geninit src/acme_plugins

Command-line roots are added to the roots from pyproject.toml.

Choosing what to export

By default, a normal child module is protected: the package exposes the module itself, but does not copy names from that module into the package namespace. Modules whose names start with an underscore are private by default.

Use these declarations in the handwritten part of __init__.py when you want different behavior:

__public__ = ("api",)
__protected__ = ("models",)
__flattened__ = ("implementation",)
__private__ = ("legacy",)

# <geninit>
# </geninit>
  • A public child exposes the module and the names listed in that child's __all__.
  • A protected child exposes only the module.
  • A flattened child exposes only the names listed in the child's __all__.
  • A private child is not exposed by the package.

For example, src/acme/api.py might contain:

__all__ = ("Client", "connect")


class Client: ...


def connect() -> Client:
    return Client()

With api declared public, users can then write either from acme import Client or from acme import api. Declaring api flattened instead would support the first form without adding api to the package's generated __all__ or propagating that child name through public parent packages. This controls the generated API; importing a child's contents may still cause Python to bind the child as a runtime package attribute.

The __public__, __protected__, __flattened__, __private__, and child __all__ declarations must contain literal strings, must be unique and must resolve to known symbols.

Managed files

geninit only replaces content between these markers:

# <geninit>
# </geninit>

New and empty __init__.py files are generated automatically. geninit will not overwrite a nonempty file unless it already contains exactly one matching pair of markers. To adapt an existing __init__.py file, add the empty marker pair where you want the generated section to appear, then run geninit.

Excluding files and forcing eager imports

exclude removes matching paths from discovery. eager forces selected modules to use ordinary eager imports:

[tool.geninit]
roots = ["src/acme"]
exclude = ["**/tests/**", "**/fixtures/**"]
eager = ["core.py", "plugins/builtin.py"]

Both options use paths relative to their package root.

geninit reads [project].requires-python to decide which import syntax is safe. If every supported version is Python 3.15 or newer, imports are lazy unless they match eager. If the project supports an earlier version, or the version constraint is missing, all generated imports are eager.

Checking without changing files

Use --check in CI or whenever you only want to verify that the generated files are current:

geninit --check

Use --diff to print the changes geninit would make:

geninit --diff

The command uses these exit codes:

  • 0 means every managed file was already current.
  • 1 means configuration, input, or generation failed.
  • 2 means files were changed, or --check/--diff found stale files.

The normal write command deliberately exits with status 2 after changing files. This makes generated changes visible in pre-commit hooks instead of letting a commit continue with unstaged output.

Using geninit with prek or pre-commit

For a uv-managed project, add this local hook to your pre-commit config:

[[repos]]
repo = "local"

[[repos.hooks]]
id = "geninit"
name = "Generate package initializers"
entry = "uv run --locked --no-sync geninit"
language = "system"
pass_filenames = false
always_run = true
stages = ["pre-commit"]

pass_filenames = false is important: geninit works from package roots rather than a list of staged files. always_run = true makes sure module deletions and configuration-only changes are noticed too.

If the hook updates anything, review the changes, stage them, and commit again.

If you are using ruff to format and lint your code, be sure to put geninit before ruff in pre-commit. Then, make sure ruff is available in the hook environment so that geninit can use the target project's Ruff configuration to format and sort the generated section.

CI example

A typical uv-based CI job can verify generated files before running the rest of the checks:

uv sync --locked
uv run --locked --no-sync geninit --check
uv run --locked --no-sync ruff format --check .
uv run --locked --no-sync ruff check .

If an initializer is stale, geninit --check exits with status 2 and the job fails without modifying the checkout.

Download files

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

Source Distribution

geninit-0.3.0.tar.gz (13.9 kB view details)

Uploaded Source

Built Distribution

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

geninit-0.3.0-py3-none-any.whl (17.2 kB view details)

Uploaded Python 3

File details

Details for the file geninit-0.3.0.tar.gz.

File metadata

  • Download URL: geninit-0.3.0.tar.gz
  • Upload date:
  • Size: 13.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.11.32 {"installer":{"name":"uv","version":"0.11.32","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for geninit-0.3.0.tar.gz
Algorithm Hash digest
SHA256 75ca6e650c5c3659c153e9d931b7e5842fa897560b39c94456eeb7a9c84e5daa
MD5 5e43415d5de369dd0ad4cad289f6bf5e
BLAKE2b-256 5097bb633232a9072e357879015914dd24ad85c305dec30117fc362f6e1b78a7

See more details on using hashes here.

File details

Details for the file geninit-0.3.0-py3-none-any.whl.

File metadata

  • Download URL: geninit-0.3.0-py3-none-any.whl
  • Upload date:
  • Size: 17.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.11.32 {"installer":{"name":"uv","version":"0.11.32","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for geninit-0.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 c24cc422db618ab141e68b220aa4a08a0838447d196afe69e6626109da9ff44f
MD5 98a4145de1aa6bd7606927d59b80b57f
BLAKE2b-256 127cf93bb629d100c232a5bcd1646c953b9119f571cce80adfc0649b4e0a1210

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

0.3.0 This release

2 files

0.2.0

2 files

0.1.3

2 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