exportify is a CLI tool and library for managing Python package exports: generating __init__.py files with lazy imports (using lateimport), managing __all__ declarations, and validating import consistency.
Exportify was previously developed as an internal dev tool to assist with our CodeWeaver project; it slowly grew in function until it didn't make sense to keep it as part of that project. Here it is for anyone to use.
Exportify solves the problem of managing consistency and updates in export patterns across a codebase. It ensures module and package-level __all__ exports are consistent, accurate, and complete. It can also validate
Exportify offers a simple rule-based system for enforcing __all__ and __init__ patterns across a codebase with optional per-file overrides. Comes with sane defaults.
- Lazy
__init__.pygeneration — generates__init__.pyfiles using a lazy__getattr__pattern powered bylateimport, keeping package imports fast and circular-import-free - YAML-driven rule engine — declarative rules control which symbols are exported, with priority ordering and pattern matching
- Export propagation — symbols exported from submodules automatically propagate up the package hierarchy
- Code preservation — manually written code above the
# === MANAGED EXPORTS ===sentinel is preserved across regeneration - Validation — checks that lazy import calls are well-formed and that
__all__declarations are consistent - Cache — SHA-256-based analysis cache for fast incremental updates
Easiest to install with uv:
uv tool install exportifyor pipx:
pipx install exportifyPython 3.12+ required.
# Create a default config file (.exportify/config.yaml)
exportify init
# Check current state — runs all checks by default
exportify check
# Bootstrap __init__.py files for packages that don't have one
exportify generate
# Preview what fix would change without writing anything
exportify fix --dry-run
# Sync exports and __all__ to match rules
exportify fix
# Show overall export/import health
exportify status| Document | Description |
|---|---|
| Getting Started | Step-by-step tutorial for new projects |
| Document | Description |
|---|---|
| CLI Reference | Complete command reference with all flags |
| Rule Engine | Rule syntax, priorities, match criteria, provenance |
| Configuration | Initializing and configuring exportify |
| Document | Description |
|---|---|
| Troubleshooting & FAQ | Common issues and answers |
| Contributing | Development setup and how to contribute |
| Document | Description |
|---|---|
| Caching | Cache implementation and API |
| Overload Handling | @overload decorator support |
| Provenance Support | Symbol provenance in rules |
| Schema Versioning | Config schema version management |
Rules live in .exportify/config.yaml (created by exportify init or written manually). Exportify searches for the config file in this order:
EXPORTIFY_CONFIGenvironment variable (any path).exportify/config.yaml.exportify/config.yml.exportify.yamlin the current working directory.exportify.ymlexportify.yamlexportify.yml
schema_version: "1.0"
rules:
- name: "exclude-private"
priority: 1000
match:
name_pattern: "^_"
action: exclude
- name: "include-public-classes"
priority: 700
match:
name_pattern: "^[A-Z]"
member_types: [class]
action: include
propagate: root
- name: "include-public-functions"
priority: 700
match:
name_pattern: "^[a-z]"
member_types: [function]
action: include
propagate: parent| Priority | Purpose |
|---|---|
| 1000 | Absolute exclusions (private, dunders) |
| 900–800 | Infrastructure/framework exclusions |
| 700 | Primary export rules (classes, functions) |
| 600–500 | Import handling |
| 300–400 | Special cases |
| 0–200 | Defaults/fallbacks |
See the Rule Engine docs for the full rule syntax including logical combinations, match criteria, and advanced propagation options.
none— export only in the defining moduleparent— export in the defining module and its direct parentroot— export all the way to the package rootcustom— specify explicit target module
Exportify generates __init__.py files using the lazy __getattr__ pattern from lateimport:
# SPDX-FileCopyrightText: 2026 Your Name
#
# SPDX-License-Identifier: MIT
# === MANAGED EXPORTS ===
# This section is automatically generated. Manual edits below this line will be overwritten.
from __future__ import annotations
from typing import TYPE_CHECKING
from types import MappingProxyType
from lateimport import create_late_getattr
if TYPE_CHECKING:
from mypackage.core import MyClass
from mypackage.utils import helper_function
_dynamic_imports: MappingProxyType[str, tuple[str, str]] = MappingProxyType({
"MyClass": (__spec__.parent, "core"),
"helper_function": (__spec__.parent, "utils"),
})
__getattr__ = create_late_getattr(_dynamic_imports, globals(), __name__)
__all__ = ("MyClass", "helper_function")
def __dir__() -> list[str]:
"""List available attributes for the package."""
return list(__all__)Important
To use exportify for lazy __init__ management, you must add lateimport as a runtime dependency.
Add a # === MANAGED EXPORTS === sentinel to an existing __init__.py to protect manually written code above it:
"""My package."""
from .compat import legacy_function # kept across regeneration
# === MANAGED EXPORTS ===
# ... generated section below (managed by exportify)Everything above the sentinel is left untouched on every fix or generate run.
| Command | Description |
|---|---|
exportify check |
Check exports and __all__ declarations for consistency |
exportify fix |
Sync exports and __all__ to match rules |
exportify generate |
Bootstrap new __init__.py files for packages missing one |
exportify status |
Show current export/import health status |
exportify doctor |
Run health checks and provide actionable advice |
exportify init |
Initialize exportify with a default config file |
exportify clear-cache |
Clear the analysis cache |
See the full CLI reference for all flags and options.
- Python 3.12+
lateimport(installed automatically)
Dual-licensed under MIT and Apache 2.0. See LICENSE-MIT and LICENSE-Apache-2.0.