Python Package Layout
This document describes the standard package layout for Python packages in this repository. The goal is to keep the public interface clear while isolating implementation details.
Core Rule
Expose the public interface of a package from its __init__.py.
Place concrete implementations under an internal/ subpackage.
Callers should normally import from the package itself rather than from internal/.
Standard Structure
A typical package should look like this:
my_package/
├── __init__.py
└── internal/
├── __init__.py
├── widget.py
└── helpers.py
The package-level __init__.py defines the supported public API.
The internal/ directory holds the implementation modules.
The package-level __init__.py is the only supported external stability boundary for normal callers.
Internal module paths are implementation details and may be reorganized freely.
Public Interface Pattern
The package-level __init__.py should re-export the symbols that form the public API.
For example:
from .internal.widget import Widget
from .internal.helpers import make_widget
__all__ = ["Widget", "make_widget"]
With this pattern, external code writes imports such as:
from my_package import Widget, make_widget
External users should not need to import from my_package.internal during normal use.
Internal modules also should not add re-export shims just to preserve other internal module paths unless a task explicitly requires that compatibility.
Implementation Pattern
Implementation logic should live under internal/.
That includes:
concrete classes,
helper functions,
implementation-specific utilities,
and backend-specific adapters.
Modules inside internal/ may depend on one another as needed.
Code outside the package should avoid importing directly from internal/ unless there is a deliberate testing or development reason.
When refactoring, prefer importing concrete implementations directly from the module that defines them rather than routing internal imports through compatibility modules.
Rules For __init__.py
The package-level __init__.py should:
re-export the intended public API,
define
__all__when appropriate,and contain only minimal metadata, aliases, constants, and import wiring.
The package-level __init__.py should not:
contain substantial implementation logic,
contain long algorithm implementations,
become a catch-all utility module,
or expose internal helper symbols unless they are intentionally public.
Module Size And Cohesion
Implementation modules should stay small enough to be easy to scan, summarize, and move. Prefer one primary runtime class, one schema family, one validation family, or one tightly related helper cluster per file. As a rule of thumb, modules larger than roughly 400 to 600 lines should be reviewed for splitting unless there is a clear cohesion reason to keep them together. Splits should follow domain seams rather than arbitrary utility extraction so the resulting files remain easy to name and navigate.
Internal API Stability
The repository should preserve the package-level public API exported from __init__.py.
It does not need to preserve internal/ module paths during refactors unless a task explicitly requires that stability.
Re-export shims inside internal/ are discouraged because they create accidental internal APIs and make future refactors harder.
Lint And Typing Suppressions
Prefer fixing lint and typing issues in code rather than suppressing them.
When a suppression is necessary, it must be attached to the narrowest possible scope.
Use line-local Ruff suppressions such as # noqa: PLR0911 on the exact line that needs the exception.
Use line-local Pyright suppressions such as # pyright: ignore[reportPrivateUsage] on the exact line that needs the exception.
Use line-local mypy suppressions such as # type: ignore[arg-type] on the exact line that needs the exception.
Do not use file-wide suppressions such as # ruff: noqa, file-level # pyright: directives, or broad mypy ignores unless the entire file truly requires the exception.
If a broad suppression is unavoidable, document why directly in that file.
Facade modules, dispatch modules, and compatibility modules follow the same rule.
Their role in preserving imports or organizing behavior does not justify broad suppressions by default.
When To Use This Pattern
Use this layout for new Python packages in this repository. Use it when refactoring older packages into a cleaner public interface. Use it unless a task explicitly requires a different structure.
Subpackages should follow the same pattern.
For example, buffalo_wings.airfoil and buffalo_wings.wing should expose their supported public APIs from their own __init__.py files.
Shared Buffalo-wide numeric aliases such as FloatArray should come from
buffalo_core.typing.
Project-specific aliases that are unique to Wings may still live in dedicated
public modules such as buffalo_wings.type_aliases.
Module And Signature Conventions
New typed runtime modules should include from __future__ import annotations near the top of the file.
This keeps annotations deferred, reduces forward-reference quoting, and aligns with the repository’s strict typing style.
Existing modules should adopt it when they are already being edited and the file meaningfully uses annotations.
Avoid repo-wide churn-only edits whose only effect is adding the future import to otherwise stable files.
For dataclasses rendered through Sphinx autodoc, prefer brief class docstrings plus attribute docstrings on the dataclass fields themselves.
This keeps field documentation attached to the generated attribute entries instead of concentrating it in one class-level Attributes section.
For @property methods rendered through autodoc, prefer an attribute-style summary and semantic notes without a Returns section unless the repository explicitly requires otherwise.
Use keyword-only parameters intentionally in public APIs and nontrivial internal helpers when call-site clarity matters.
Keep primary data inputs positional when that is the natural reading of the API.
Prefer keyword-only parameters for boolean flags, mode selectors, and semantically important optional controls.
This is especially useful when a positional call would leave values such as True or False ambiguous to the reader.
Avoid adding * mechanically to every function because unnecessary verbosity can make simple helpers harder to use.
Migration Guidance
When refactoring an existing package into this layout:
Identify the symbols that should remain public.
Move concrete implementations into
internal/.Re-export the public symbols from the package-level
__init__.py.Add or update tests so public imports are validated through the package-level interface.
Definition Of Success
A package follows this layout successfully when:
external imports go through the package-level
__init__.py,implementation logic lives under
internal/,the public API is easy to identify,
internal reorganization does not require downstream import changes,
and
__init__.pyremains small and focused.