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 project consistency — runs all checks by default
exportify check
# Sync exports and __all__ to match your rules
# Creates missing __init__.py files and updates existing ones
exportify sync
# Preview what sync would change without writing anything
exportify sync --dry-run
# Run health checks and show current status
exportify doctor| 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 sync run.
| Command | Description |
|---|---|
exportify init |
Initialize project configuration |
exportify check |
Validate exports and __all__ consistency |
exportify sync |
Align project code with export rules |
exportify undo |
Restore files from the last sync run |
exportify doctor |
Run system health checks |
exportify cache clear |
Clear the analysis cache |
exportify cache stats |
Show cache statistics |
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.