Public API and Exports¶
This page states which names Quivers exports, where each is imported
from, and what a contributor must do when adding, moving, or removing
one. The rules are enforced: tests/test_public_api.py checks the
import-time rules on every test run, and
tools/check_api_reference.py checks the rendered API reference after
mkdocs build in the docs job. A change that breaks a rule fails CI.
Terms¶
A module is public when no component of its dotted path begins with
an underscore: quivers.continuous.inline is public, while
quivers.continuous._ordered and quivers.dsl._grammar_data are
private. A package is a module with an __init__.py; a leaf module
is any other module. The public API is the union of the __all__
lists of the public packages, and a name is public exactly when it
appears in one of them.
Rules¶
-
Import from the package. The supported import path for a public name is the public package whose
__all__lists it, as infrom quivers.continuous import PlateDraw. A leaf-module path such asquivers.continuous.platerecords where the name is defined; it is not part of the contract, so a name may move between the leaf modules of its package without a breaking change. -
Every public module declares
__all__. The list names the module's public interface, whether defined in the module or re-exported from elsewhere. A module that holds implementation only declares__all__ = [], which states that it contributes nothing to the public API. Names absent from__all__are internal however they are spelled; other Quivers modules may import them, and user code should not. -
Packages re-export their leaf modules. For every public leaf module
Mdirectly inside a public packageP, each name inM.__all__is inP.__all__and is bound to the same object. A subpackage is a package in its own right, soPmay re-export a subpackage's names but need not. Two leaf modules of one package therefore cannot export different objects under one name. A package may bind a re-export lazily through a module-level__getattr__when an eager import would create a cycle. -
No private names in
__all__. An underscore-prefixed name is never listed, and a list holds no duplicates. -
Public signatures name public types. When a public function, or a public class's constructor or public method, annotates a parameter or return value with a Quivers type, that type is public. Every such annotation resolves at runtime under
typing.get_type_hints. A caller can thus construct every argument a public call accepts and name every value it returns without importing from an internal location. -
Anything the compiler builds, Python can build. Every
MorphismandContinuousMorphismsubclass the DSL compiler places in a compiled program is public, as are the step records aMonadicProgramis built from. Python code can therefore assemble any program the compiler produces, and the objects the compiler produces are a contract rather than an implementation detail. The test compiles every gallery example and checks the classes of the morphisms it finds. Thenn.Moduleparameter containers thatas_torch_modulewraps morphisms in are not morphisms and fall outside this rule. -
Every public name is documented. Each public name carries a docstring (for a constant or type alias, a string literal on the line after its assignment, which is where the documentation generator reads attribute docstrings) and renders in the API reference under
docs/api/, either through a:::directive on a package that exports it or on the module that defines it.tools/check_api_reference.pyreads the built site and fails on any public name without an anchor at one of those paths.
Deciding what is public¶
The rules fix how exports are spelled; this rubric decides which names a module lists. Export a name when a user of the Python API needs it to use a capability without the DSL:
- classes a user constructs, subclasses, or receives and inspects;
- functions a user calls;
- protocols and abstract base classes a user implements;
- registries and registration functions a user extends, such as family specifications and renderer hooks;
- exceptions a public call raises;
- constants that parameterize a public call or name one of its results, such as algebra singletons.
Keep internal the passes and helpers whose contract is "what another
Quivers module needs today": parser walkers, compiler mixins, lowering
and elaboration helpers, renderer internals, and generated-code
scaffolding. These belong in a module whose __all__ omits them, or in
an underscore-prefixed module.
Exceptions¶
The target runtime sources quivers/transpile/runtime_* are files
grafted into generated programs, read by path next to their .jl,
.scm, .js, and .stan siblings. They are data rather than modules
of the library, nothing imports them, and the rules above do not apply
to them. The test checks that no Quivers module imports one.
Changing the public API¶
Adding a public name is an addition and goes under Added in the changelog. Renaming or removing one, or changing a public signature incompatibly, goes under Changed or Removed, and while Quivers is below 1.0 it takes a minor version bump. Moving a name between leaf modules of one package changes nothing a user imports and needs no entry.
Building programs in Python¶
Rule 6 is what makes the DSL optional. The programs guide shows one program assembled from the exported builders, using the same step records and inline distributions the compiler emits.