Initial extracted documentation set

This commit is contained in:
Rodin
2026-06-01 21:42:05 +00:00
commit 60ffec18e4
17 changed files with 1590 additions and 0 deletions
+127
View File
@@ -0,0 +1,127 @@
# 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 todays 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.