Skip to content

Give a hand-built object a public way onto the API surface - #101

Merged
timtreis merged 6 commits into
mainfrom
feat/public-stamp
Sep 20, 2026
Merged

timtreis merged 6 commits into
mainfrom
feat/public-stamp

Conversation

@timtreis

Copy link
Copy Markdown
Member

io.stamp records the schema version and the resolution on an AnnData that did not come from a reader here: a published h5ad, another pipeline's output, a subset assembled in a notebook, or a matrix of learned embeddings with its metadata alongside. io.validate reported whether an object satisfied the contract; nothing public established it, so docs/tutorials/10_differential_features.ipynb imported mantispy._core.schema.stamp to do it.

It refuses a resolution whose required obs columns are absent, instead of stamping and pushing the failure into whichever tool runs next.

It also fills the feature-annotation columns var does not already have, by parsing the feature names; columns already present are untouched. This matters for embeddings: a name that is not a CellProfiler feature name parses as empty, and without those columns a stamped embedding failed validation on ten counts, so io.write — which validates first — could not write it at all. The test covers that round trip, and it caught a second failure, where the annotation has to stay categorical or h5py rejects an object array of NaN.

The private _core.schema.stamp is unchanged and still used internally, where several callers legitimately record the version before obs is complete.

Closes #37, and removes one of the six private imports listed in #38.

Verified: 800 tests pass, mypy, ruff and the formatters clean.

@review-notebook-app

Copy link
Copy Markdown

Check out this pull request on  ReviewNB

See visual diffs & provide feedback on Jupyter Notebooks.


Powered by ReviewNB

@codecov-commenter

codecov-commenter commented Sep 20, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 89.50%. Comparing base (16a425c) to head (d341a14).
⚠️ Report is 3 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #101      +/-   ##
==========================================
+ Coverage   89.05%   89.50%   +0.44%     
==========================================
  Files          80       81       +1     
  Lines        5884     6267     +383     
==========================================
+ Hits         5240     5609     +369     
- Misses        644      658      +14     
Files with missing lines Coverage Δ
src/mantispy/_core/features.py 96.15% <100.00%> (+0.36%) ⬆️
src/mantispy/io/__init__.py 90.90% <100.00%> (ø)
src/mantispy/io/_profiles.py 96.60% <100.00%> (+0.30%) ⬆️

... and 1 file with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

io.stamp records the schema version and the resolution on an AnnData that did
not come from a reader here: a published h5ad, another pipeline's output, or
a matrix of learned embeddings with its metadata. io.validate reported
whether an object satisfied the contract, but nothing public established it,
so tutorial 10 reached into mantispy._core.schema to do it.

It refuses a resolution whose required obs columns are absent, rather than
stamping and pushing the failure into whichever tool ran next, and it fills
the feature-annotation columns var does not already have by parsing the
feature names. Names that are not CellProfiler feature names parse as empty,
which is what a learned embedding is: without those columns a stamped
embedding failed validation on ten counts and io.write, which validates
first, could not write it at all.

Closes #37.
The feature-name parser does not leave a name it cannot read alone. It reads
openphenom_nahualX_17, the name JUMP-Lite ships, as the nahualX group of an
openphenom object, so stamping an embedding gave it feature families named
after the model's own tensors, which is what tl.feature_signature and
pl.effect_sizes group by.

_core.features.empty_annotation supplies the schema's annotation columns with
nothing in them, for features that are not CellProfiler measurements. The
test now uses the names JUMP-Lite actually ships; it passed before only
because it used emb_0, whose object happens to parse as missing while its
feature group does not. A second test pins that a parsed annotation survives
being stamped again.
Sphinx resolves the role against the public module, so the reference had
nowhere to point and the docs build, which treats warnings as errors, failed.
Three words are also less work for the reader than following a link.
@read-the-docs-community

read-the-docs-community Bot commented Sep 20, 2026

Copy link
Copy Markdown

Documentation build overview

📚 mantispy | 🛠️ Build #34664455 | 📁 Comparing 58d46d5 against latest (16a425c)

  🔍 Preview build  

7 files changed · + 1 added · ± 6 modified

+ Added

± Modified

The well default silently demoted a re-stamped cell-resolution subset, and
tl.aggregate then takes the non-cell branch, fills Metadata_CellCount with NaN
and so disables its own min_cells filter. Passing no resolution now keeps
what is recorded. The obs check moved above the copy, so a rejected call no
longer duplicates the matrix first, and the note says who checks X's dtype.
@timtreis
timtreis merged commit 1ab81a9 into main Sep 20, 2026
6 of 7 checks passed
@timtreis
timtreis deleted the feat/public-stamp branch September 20, 2026 23:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

No public way to stamp a hand-built AnnData

2 participants