zarr_indexing.testing.stateful
zarr_indexing.testing.stateful ¶
A stateful property test for indexing an array through LazyArray.
ChainedIndexingStateMachine composes indexing steps onto a LazyArray
wrapping your array — lazy[...], lazy.oindex[...], lazy.vindex[...],
each step applied to the view the last one produced — while applying the same
steps to a NumPy array holding the same values. After every step the view must
still agree with that model three ways: its shape, its result(), and the
assembly of its parts().
Point it at an array by subclassing and overriding make_source:
from zarr_indexing.testing import ChainedIndexingStateMachine, state_machine_test
class MyArrayIndexing(ChainedIndexingStateMachine):
def make_source(self, data):
array = my_format.create(shape=data.shape, dtype=data.dtype)
array[:] = data
return array
TestMyArrayIndexing = state_machine_test(MyArrayIndexing)
data, partitionings, and readers are class attributes; override any of
them to widen or narrow what is drawn. The base class needs no make_source at
all — left alone it wraps the NumPy array itself, which is a useful smoke test
of this package but says nothing about yours.
What it is checking
The parts invariant is the one with teeth.
Partition documents
out[part.out_selection] = part.view.result() as the assembly procedure, so
this checks that literally: a part's values must arrive at exactly the shape its
out_selection addresses — not merely a shape that broadcasts into it — land
there, and cover the view once. Checking through result() alone would prove
only that result() is self-consistent.
The choose_reader rule draws a reader and applies it to the view, so the
execution strategy becomes part of the chain. Every reader listed by a subclass
must preserve the NumPy model for its source. The universal basic_reader is
always exercised, even when a subclass lists only specialized readers; with no
declared readers it is the sole strategy drawn.
Requires the testing extra (pip install zarr-indexing[testing]).
DEFAULT_DATA
module-attribute
¶
The values the source holds by default: distinct, so a misplaced cell shows.
DEFAULT_PARTITIONINGS
module-attribute
¶
DEFAULT_PARTITIONINGS: tuple[Any, ...] = (
None,
(2, 2, 2),
(7, 5, 4),
(3, 2, 3),
((3, 3, 1), (2, 2, 1), (3, 1)),
(4, 3, 3),
)
Partitionings to read under: a single whole-array part, uniform boxes of several shapes (some of which do not divide the extent), and explicit per-axis sizes. Boxes that straddle whatever the source declares are deliberate — they cost extra I/O but must not change an answer.
DEFAULT_SETTINGS
module-attribute
¶
DEFAULT_SETTINGS = settings(
max_examples=250,
stateful_step_count=10,
deadline=None,
suppress_health_check=[
HealthCheck.data_too_large,
HealthCheck.filter_too_much,
HealthCheck.too_slow,
],
)
Enough examples to find a defect reachable only through a narrow chain, at a few seconds per run when there is nothing to find. Every step is followed by checks that each materialize the whole view, so the budget buys examples rather than long chains — a chain runs out of axes to index within a few steps anyway.
filter_too_much is suppressed because a chain that reaches a rank-0 view
leaves only repartition enabled, so a run that opens there is discarded.
__all__
module-attribute
¶
__all__ = [
"DEFAULT_DATA",
"DEFAULT_PARTITIONINGS",
"DEFAULT_SETTINGS",
"ChainedIndexingStateMachine",
"apply_selection",
"outer_selection",
"repartition",
"state_machine_test",
]
ChainedIndexingStateMachine ¶
Bases: RuleBasedStateMachine
Indexing steps composed onto one LazyArray, against NumPy as the model.
Subclass and override make_source to point it at your own array. See the
module docstring for the shape of that subclass and for what the invariants
check.
Attributes:
-
data(Any) –The values the source holds, and the model every step is checked against. Any shape and dtype NumPy supports; every axis must be non-empty.
-
partitionings(Sequence[Any]) –Drawn once per run, before any indexing:
with_partsis a pure setter that carries through composition untouched and is read only when a view resolves, so choosing it up front reaches the same states choosing it mid-chain does, and spends the whole step budget on indexing. -
readers(Sequence[Reader] | None) –Execution strategies
choose_readermay draw. Every listed reader must preserve the model for the source.basic_readeris always included;Nonemeans the reader already carried by the constructed view.
Source code in src/zarr_indexing/testing/stateful.py
191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 | |
__init__ ¶
Source code in src/zarr_indexing/testing/stateful.py
basic ¶
choose_partitioning ¶
Fix how the read is broken up, before any indexing.
Source code in src/zarr_indexing/testing/stateful.py
choose_reader ¶
Read the rest of the chain through another conforming strategy.
Source code in src/zarr_indexing/testing/stateful.py
make_source ¶
Build the array under test, holding data.
Called once per machine class and cached, not once per example: an example is cheap and a source may not be. The machine only ever reads, so the same object serves every run — but it must therefore not be mutated by anything else while the test runs.
The default returns data itself, so an unsubclassed machine exercises
this package against NumPy.
Source code in src/zarr_indexing/testing/stateful.py
orthogonal ¶
parts_tile_the_view ¶
The documented assembly, run literally.
Every part's values arrive at exactly the shape its out_selection
addresses — not merely a shape that broadcasts into it — and together
the parts cover the view once.
Source code in src/zarr_indexing/testing/stateful.py
repartition ¶
Re-box a chain that has run out of axes to index.
Something must stay enabled once the view is rank-0 or empty, or Hypothesis has no move to make and abandons the run. Re-boxing is the useful thing to do there: it changes nothing the invariants may see, and a rank-0 view read through every partitioning is exactly the state a collapsed correlated selection reaches.
Source code in src/zarr_indexing/testing/stateful.py
result_matches_the_model ¶
slices_only ¶
An oindex step carrying only slices is not a fancy selection.
It narrows the view's own axes and composes like basic indexing. Drawn as its own rule so that narrowing an existing index array by slices — a distinct code path from narrowing it with coordinates — stays exercised at full weight.
Source code in src/zarr_indexing/testing/stateful.py
the_view_has_the_models_shape ¶
vectorized ¶
apply_selection ¶
apply_selection(
array: Any,
selection: tuple[Any, ...],
mode: SelectionMode,
) -> Any
Apply a selection to a NumPy array in the given mode — the model a view is checked against.
NumPy's own semantics are basic and vectorized indexing, so only the
orthogonal mode needs building (see outer_selection).
Source code in src/zarr_indexing/testing/stateful.py
outer_selection ¶
Apply an orthogonal selection to a NumPy array: the outer product of its axes.
NumPy has no operator for this, so the model is built from numpy.ix_.
Scalar integers are basic indices — NumPy applies them first and drops the
axis — so they are peeled off before the outer product is formed.
Source code in src/zarr_indexing/testing/stateful.py
repartition ¶
Apply one of partitionings to a view.
The three partitioning spellings are three named methods, so a list holding a mix of them needs a dispatch somewhere. Choosing among them is what a test harness drawing from that list is doing, so it lives here rather than being pushed back into the public API as a type-inspecting parameter.
Source code in src/zarr_indexing/testing/stateful.py
state_machine_test ¶
state_machine_test(
machine: type[RuleBasedStateMachine],
*,
config: settings = DEFAULT_SETTINGS,
) -> Any
The pytest-collectable TestCase for a machine, with settings applied.
Hypothesis builds a fresh TestCase per state-machine class, so settings
set on a base class do not reach a subclass's; this applies them where they
land. Assign the result to a module-level name beginning with Test.