Maintaining the public API

The supported application contract begins at quantas.api. Internal packages may be imported by Quantas itself, but notebooks, the CLI, and future frontends should not depend on them.

Supported namespaces

The public package exposes domain namespaces lazily:

  • quantas.api.common;

  • quantas.api.elasticity;

  • quantas.api.seismic;

  • quantas.api.ha;

  • quantas.api.qha;

  • quantas.api.eos;

  • quantas.api.thermoelasticity;

  • quantas.api.profiles;

  • quantas.api.rendering;

  • quantas.api.registry;

  • quantas.api.interop.

import quantas remains lightweight. quantas.api loads one namespace only when it is first accessed.

What belongs in the public API

Good public objects are stable scientific contracts or operations:

  • passive Input, Options, and Result types;

  • enums and immutable requests needed to configure supported operations;

  • normalized readers and constructors;

  • workflow operations such as run, fit, or analyze_grid;

  • result accessors;

  • HDF5 read/write operations;

  • report, plot, and export builders;

  • registry and interoperability operations intended for applications.

The following normally remain internal:

  • concrete calculators, readers, exporters, and solvers;

  • implementation controllers;

  • CLI and renderer classes;

  • low-level HDF5 layout helpers;

  • convenience functions that expose unstable internal structures;

  • broad re-exports from quantas.modules.

Facade pattern

A public facade imports internal objects under private aliases and exposes a small wrapper with a technical docstring:

from quantas.modules.example.api import run_example as _run
from quantas.modules.example.models import (
    ExampleInput as Input,
    ExampleOptions as Options,
    ExampleResult as Result,
)

def run(
    input_data: Input | str | Path,
    options: Options | None = None,
    observer: Observer | None = None,
) -> ResultData:
    """Run the supported Example workflow."""
    return _run(input_data, options=options, observer=observer)

__all__ = ["Input", "Options", "Result", "run"]

The wrapper is not pointless duplication. It fixes names, type hints, docstrings, and the supported boundary while internal organization remains free to change.

__all__ is the contract

Every public namespace defines an explicit __all__. The curated API reference is generated from that inventory, and tests require each public name to be documented exactly once.

Do not rely on from module import * behaviour or on names that happen to be present in a module global namespace.

Adding a public symbol

Before promotion, answer:

  1. Is the name scientifically meaningful and stable?

  2. Can it be represented without exposing a concrete calculator or archive implementation?

  3. Are units, shapes, exceptions, and lifecycle documented?

  4. Does it work without Click, Rich, or Matplotlib?

  5. Does it require a capability entry in the registry?

  6. Is it needed by notebooks, a frontend, or another supported public operation?

Then update, in the same change:

  1. the public facade and __all__;

  2. the API reference page;

  3. public-surface tests;

  4. representative usage tests;

  5. registry descriptors when applications need discovery;

  6. the CLI if the operation is command-line accessible;

  7. migration or deprecation documentation when replacing an existing name.

Capability registry

quantas.api.registry describes supported operations without importing all modules eagerly. A quantas.api.registry.ModuleDescriptor records:

  • stable module name and title;

  • public namespace import path;

  • result payload key, when applicable;

  • supported capabilities;

  • public operation names;

  • public input, options, and result type aliases.

Add a capability only when the corresponding operation is part of the supported API. Modules are not required to advertise identical lifecycles.

Result accessors

Single-shot facades expose get_result to validate the generic envelope and return the typed payload. This is preferable to asking users to know a private results dictionary key.

A new payload type or result key must be updated consistently in:

  • the module contract;

  • the public accessor;

  • the capability descriptor;

  • HDF5 read/write code;

  • registry-based file opening;

  • API and round-trip tests.

Versioning policy

The Python package version governs the public API. Persisted text and HDF5 formats have independent schema versions because files may outlive the import surface.

Before the first stable 2.0 release, reviewed breaking corrections may be made without maintaining deprecated aliases. After a stable public release, incompatible removal requires a documented migration and deprecation period.

Application and CLI equivalence

The CLI must call public API operations. It may perform frontend tasks before and after the call, but it must not use a different calculator or reconstruct a scientific result independently. Representative tests should compare API and CLI results for exact or tolerance-controlled equality.

Contract verification

The tests freeze:

  • public namespace names;

  • exact __all__ inventories;

  • lazy imports;

  • absence of calculators/readers/exporters/renderers from the supported surface;

  • registry capability mappings;

  • metadata-based HDF5 dispatch;

  • API-reference coverage;

  • representative workflows through public facades.

Treat every addition to __all__ as a reviewed API decision.