API stability & deprecation policy¶
PySofra commits to a precise, machine-checked stability contract for its public surface. This page documents what is guaranteed, what is provisional, and how a future breaking change would be staged.
What is "public"¶
The public surface is everything re-exported from the top-level
pysofra namespace (28 names — see pysofra.__all__) plus the
documented public methods and attributes of pysofra.SofraTable. The
exact set is frozen in code by
tests/test_api_stability.py,
which fails CI if any documented name disappears, gains an undocumented
sibling, or has its call signature drift.
Anything reachable through a name starting with _ (e.g.
pysofra.core.table._rebuild, SofraTable._spec) is internal. It
may change between any two releases without notice. Please do not rely
on it; if you need a capability that only an internal exposes, please
open an issue so we can promote a public counterpart.
What the contract guarantees¶
The API-stability test suite locks in five structural and behavioural contracts:
| Contract | Pinned by |
|---|---|
| Top-level name set | EXPECTED_PUBLIC_NAMES (28 names) |
| Builder call signatures | test_tbl_one_signature_stable, etc. |
SofraTable method set |
EXPECTED_SOFRATABLE_METHODS (~45 methods) |
SofraTable attribute set |
EXPECTED_SOFRATABLE_ATTRIBUTES (9 attributes) |
SurveyDesign dataclass shape |
test_survey_design_dataclass_fields (6 fields) |
On top of the structural lock, four behavioural contracts are asserted on every test run:
- Builders return
SofraTable—tbl_one,tbl_summary,tbl_crossall yield aSofraTableinstance (not apandas.Styler, not aDataFrame, not a string). Downstream code can always chain.add_p().to_html()without type-sniffing. - Modifiers are copy-on-write — every chainable modifier
(
add_p,add_overall,add_smd,add_n,add_stat_label,add_significance_stars,bold_p,autofit, …) returns a newSofraTable. It is neverselfand neverNone. Pipelines never silently mutate an earlier result. - Public surface is docstring-complete — every name in
pysofra.__all__and every public method ofSofraTablecarries a non-empty docstring.help(...)and the rendered docs site never produce apologetic blanks. - No pysofra-originated deprecation warnings on a representative
build — a
tbl_one → add_p → add_overall → add_smd → to_html / to_markdown / to_latexpipeline emits zeroDeprecationWarningorPendingDeprecationWarningwhose source frame is inside thepysofrapackage. Nothing the user calls today is on a quiet removal timer.
Versioning¶
PySofra adheres to Semantic Versioning 2.0. The current release line is the 0.1.0a alpha series. While the foundation (immutable spec, multi-backend renderers, statistical abstractions) is locked down by the contract above, the package is explicitly pre-stable*: minor numeric defaults, prose wording, and non-public internals may change between alphas without further notice.
When 1.0.0 ships, the alpha contract becomes the long-term contract and the deprecation ladder below activates in full.
Deprecation ladder (post-1.0)¶
A breaking change to any name on the public surface will progress through three releases:
- Soft deprecation (X.Y.0) — the name continues to work but emits
a
DeprecationWarningon first call, pointing at the replacement. Documentation is updated. - Hard deprecation (X.(Y+1).0) — the name continues to work but
the warning is upgraded to
FutureWarningand is no longer suppressible by the default warning filter. The CHANGELOG entry pins the removal release. - Removal (X+1.0.0) — the name is removed;
EXPECTED_PUBLIC_NAMESis updated in the same commit; the major version bumps. There is never silent removal.
Equivalently: a user pinned to pysofra >= X.0, < X+1.0 will never see
their code stop working without first seeing a warning that names the
replacement.
Reporting an accidental break¶
If you see PySofra change a documented surface without going through
this ladder, please open an issue tagged api-break. We treat those as
release-blocking.