Pulls basic collection impl into BaseCollection, allowing Collection to add the semantics of "no semantics". This mirrors the implementation in tiledbsoma.
Makes Measurement and Experiment inherit from BaseCollection. Previous problems with this were due to missing __slots__.
Adds generic parameters to Measurement and Experiment that allow implementations to specify the exact types they provide, saving a lot of casting down the road.
Renames lots of TypeVars to be clearer about their purpose.
Adds overloads to add_new_collection for better type inference.
Tightens _Experimentish to only expect read accessors, not writers.
While these new changes add a bunch of generic slots to the base collection types and experiments and measurements, the experience from the perspective of a SOMA library user will be roughly the same. That is to say, it's a little scary here, but the end user will still see theimpl.Collection[ElementType]. Type inference when using composed objects is better as well:
some_exp = theimpl.Experiment.open(...)
obs = some_exp.obs
reveal_type(obs)
# BEFORE: somacore.DataFrame
# (i.e., the type system doesn't know what implementation
# of the abstract DataFrame this is; it only knows about
# the bare minimum DataFrame properties)
# AFTER: theimpl.DataFrame
ms = some_exp.ms
reveal_type(ms)
# BEFORE: somacore.Collection[somacore.Measurement]
# AFTER: theimpl.Collection[theimpl.Measurement]
some_meas = ms["whatever"]
reveal_type(ms)
# BEFORE: somacore.Measurement
# AFTER: theimpl.Measurement
some_meas.X
reveal_type(ms)
# BEFORE: somacore.Collection[somacore.NDArray]
# AFTER: theimpl.Collection[theimpl.NDArray]
There is no change at runtime; the actual types of the objects remain the same, but autocompletion, type checking, and other tooling has a much better idea of what is going on.
To show what this looks like on the tiledbsoma side, the diff is pretty small, but the key part is in io.py, where the cast(tiledbsoma.Measurement, ms[whatever]) no longer needs to happen, since the type system already knows it’s a tiledbsoma.Measurement. While that is the only change there specifically, there will be corresponding improvements in user code.
And just to reiterate: runtime behavior is identical, and any code which works now will continue to work, but static type inference is significantly improved.
Context: https://github.com/single-cell-data/TileDB-SOMA/issues/638 and https://github.com/single-cell-data/TileDB-SOMA/issues/540
BaseCollection
, allowingCollection
to add the semantics of "no semantics". This mirrors the implementation intiledbsoma
.Measurement
andExperiment
inherit fromBaseCollection
. Previous problems with this were due to missing__slots__
.Measurement
andExperiment
that allow implementations to specify the exact types they provide, saving a lot ofcast
ing down the road.add_new_collection
for better type inference._Experimentish
to only expect read accessors, not writers.While these new changes add a bunch of generic slots to the base collection types and experiments and measurements, the experience from the perspective of a SOMA library user will be roughly the same. That is to say, it's a little scary here, but the end user will still see
theimpl.Collection[ElementType]
. Type inference when using composed objects is better as well:There is no change at runtime; the actual types of the objects remain the same, but autocompletion, type checking, and other tooling has a much better idea of what is going on.
To show what this looks like on the
tiledbsoma
side, the diff is pretty small, but the key part is in io.py, where thecast(tiledbsoma.Measurement, ms[whatever])
no longer needs to happen, since the type system already knows it’s atiledbsoma.Measurement
. While that is the only change there specifically, there will be corresponding improvements in user code.And just to reiterate: runtime behavior is identical, and any code which works now will continue to work, but static type inference is significantly improved.