# Module Design Design a small, stable public surface. Keep implementation modules movable behind it. ## Why Python makes it easy to publish internals by accident: - every file is importable - helpers become de facto API once users depend on them - refactors turn into breaking changes when file layout becomes the contract Mature libraries push back on that. They usually: - publish one obvious import surface - re-export supported names deliberately - keep internal modules non-authoritative - use `__all__` when the boundary needs to be explicit That buys two things: - simpler imports for callers - freedom to reorganize internals later ## The pattern 1. Decide what callers should import. 2. Re-export those names from the package boundary. 3. Keep implementation details in internal modules. 4. Use `__all__` when you want an explicit contract. 5. Treat internal file layout as private unless you intentionally publish it. ## When to use Use this when: - a package spans multiple modules - internals will evolve faster than the public API - you want callers thinking in domain concepts, not filenames - compatibility matters across releases ## When not to use Do not build a facade when: - the package is tiny and direct imports are already clear - the abstraction boundary is still moving fast - re-exporting would turn `__init__.py` into a junk drawer A curated surface is not the same as a flat surface. Keep structure where the concepts are meaningfully different. ## Preferred shapes ### Package facade over internal modules ```python # mypkg/__init__.py from ._client import Client from ._errors import AppError from ._models import Item __all__ = ["Client", "AppError", "Item"] ``` Why this works: - callers learn one stable import path - internals can move without import churn - the package advertises its real contract ### Explicit module contract ```python __all__ = ["Number", "Complex", "Real", "Rational", "Integral"] ``` This says: these names are supported; everything else is implementation detail. ## Counterexamples ### File layout becomes the API by accident ```python from mypkg.utils import helper_a from mypkg.impl_v2 import thing from mypkg.more_helpers import other_thing ``` Refactoring internal modules now breaks users. ### Everything dumped into `__init__.py` If `__init__.py` exports fifty unrelated names, you did not create a clean facade. You created autocomplete noise. ### Public API mirrors the folder tree too literally If callers need to know today’s internal layout to use the library, the boundary is still underdesigned. ## Source signals ### CPython - `Lib/numbers.py:8-23` warns that published ABC APIs are hard to change and should be designed carefully. - `Lib/numbers.py:35` publishes a narrow `__all__` rather than treating every helper as public. - `Lib/operator.py:13-15` and `Lib/smtplib.py:55-58` do the same in stdlib modules with mixed public/internal names. ### Pytest - `src/pytest/__init__.py:6-80` builds the top-level `pytest` API by importing from many `_pytest.*` internals. - `src/pytest/__init__.py:98-186` then pins that facade with an explicit `__all__`. ### HTTPX - `httpx/__init__.py:1-12` re-exports the package surface from internal modules such as `._client`, `._exceptions`, and `._models`. - `httpx/__init__.py:29-100` defines the supported top-level export list explicitly. - `httpx/__init__.py:103-106` rewrites exported objects’ `__module__` to `httpx`, reinforcing the facade instead of leaking internal filenames. ### Click - `src/click/__init__.py:10-75` exposes the package through re-exports. - `src/click/__init__.py:77-126` keeps compatibility shims and deprecations at the boundary instead of freezing old internal layout forever. ## Bottom line Make the public API intentional. Callers should depend on your concepts, not your current file tree.