moczarr.store
Store layer: obstore-backed access to hive store roots and leaves.
One backend (obstore) by design — the walk's termination rule ("no digit children ⇒ nothing finer exists") and the debris model both lean on strongly-consistent, uncached delimiter-LIST semantics reaching this code unmediated, so a second backend would be a second correctness surface (rationale on the plan thread, espg/moczarr#1).
Leaf functions take (store_root, leaf) with leaf a store-relative
path (from :func:moczarr.convention.leaf_path or :func:walk_leaves), so
every access — local or S3 — goes through one store handle and a missing
leaf is uniformly a clean GET miss, never a backend-dependent error.
Postures, per the design's D9 discipline:
- The manifest is the bootstrap: absent reads
None, malformed raises — there is no degraded mode without it. - Cache tiers (root MOC) are tolerant: garbage reads as absent with a debug
log, and the caller degrades to :func:
walk_leaves— never wrong answers. - The commit stamp gates completeness: an unstamped or malformed-stamp leaf
is debris and reads
None. Presence requires the stamp; absence (a clean LIST/GET miss) is trustworthy on its own. - A PRESENT-but-corrupt bitmap sidecar raises (see
moczarr.coverage). - A stamp naming
currentis a pointer (zagg spec §1.5, versioned leaves): the arrays and the in-leaf sidecar are under that version subgroup, and :func:leaf_data_prefixis the one place that says so. The stamp read itself stays on the stable root, as do the leaf's siblings at its node. A pointer over a missing or unstamped version is debris (the open paths, which never read the version's stamp, catch a missing one only — see :func:_dangling_pointer).
bitmap_and(store_root, leaf, aoi, *, store=None, **store_kwargs)
Exact cell-level intersection via a leaf's coverage (bitmap or full).
Reads the stamp once. aoi is a morton cover — packed uint64
words, decimal strings, or an object exposing __morton_moc__()
(mortie's Moc). encoding: "full" short-circuits to MOC
membership against the shard's own id — no sidecar GET, no expansion.
The "bitmap" path pays the one sidecar GET. None when the leaf
carries neither (box-only, debris, absent) — the caller falls back to
the box verdict. An empty array is a definitive miss: both encodings
are exact, not conservative.
Source code in src/moczarr/store.py
448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 | |
leaf_data_prefix(leaf, stamp)
Where a leaf's arrays and in-leaf sidecar live, given its ROOT stamp.
The one reader rule of zagg spec §1.5 ("Versioned leaves"): a root stamp
naming current makes the stable {id}.zarr prefix a pointer —
every array, chunk and in-leaf sidecar key is under {leaf}/{current}/
— and a stamp without it is a legacy leaf whose arrays sit at the root.
No spec token marks the difference and a store legitimately mixes
both, so the question is asked per leaf, of the stamp every reader has
already fetched: following the pointer costs no request.
Pure name arithmetic — nothing is read, so a pointer naming a version
that is not there (a corrupted leaf, §1.5) is not detected here; the
caller's read under the returned prefix misses, which is its debris
answer. stamp=None (debris, or an absent leaf) returns the root.
What still lives beside the STABLE root, never under the version: the
leaf's siblings at its node (the stats sidecar, granules.json, the
§4.6 column) and the root zarr.json itself — the stamp read stays on
the root, which mirrors the version's stamp.
Raises ValueError for a current that is not a version name
(§4.2: a single path component beginning run-): that stamp claims a
version and cannot be half-trusted, and composing a key from it could
address something outside the leaf.
Source code in src/moczarr/store.py
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 | |
load_root_coverage(store_root, *, store=None, **store_kwargs)
The store-root coverage envelope, or None when unusable.
Tolerant (the root MOC is a regenerable cache): a missing object,
unparsable JSON, or an unknown spec/encoding all read as absent — with a
debug log so the degradation is discoverable — and the caller falls back
to :func:walk_leaves.
Source code in src/moczarr/store.py
203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 | |
open_object_store(path, *, anonymous=False, **kwargs)
Open an obstore store at path (s3://... or a local directory).
Read-only posture: a missing local directory raises FileNotFoundError
(the writer creates stores; the reader never does). anonymous=True
skips request signing for public buckets (the source.coop case);
kwargs pass through to S3Store.from_url (region=... etc.).
Ambient credentials (no explicit keys/endpoint, no anonymous):
obstore's native chain reads env vars then falls back to EC2 instance
metadata — it cannot see AWS profiles/SSO, so a laptop with
AWS_PROFILE set gets an IMDS HostUnreachable, not a credential
error. When boto3 is importable and resolves credentials, prefer its
resolver via Boto3CredentialProvider (which carries the session's
region into the store config itself). Environments without boto3, or
where boto3 resolves nothing, keep the bare store — env/IMDS behavior
unchanged.
Source code in src/moczarr/store.py
126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 | |
read_commit(store_root, leaf, *, store=None, **store_kwargs)
A leaf's commit stamp, or None for debris / absent leaves.
One GET of the leaf's root zarr.json (the leaf is vanilla zarr v3 by
convention, so the root group metadata is that one object; no zarr
machinery needed to check completeness). A missing object, a missing
stamp, and a malformed (non-mapping) stamp are the same answer: not
complete. A present-but-unparsable zarr.json raises — that leaf
claims to exist and cannot be half-trusted.
Source code in src/moczarr/store.py
223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 | |
read_commits(store_root, leaves, *, store=None, concurrency=32, **store_kwargs)
Commit stamps for many leaves, batched (issue #5).
One result per input leaf, aligned by position, each with
:func:read_commit semantics — None for debris/absent, a raise for
a present-but-unparsable zarr.json (:func:read_leaf_metas is the
shared substrate).
Source code in src/moczarr/store.py
377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 | |
read_coverage_bitmap(store_root, leaf, *, stamp=None, coverage=None, store=None, **store_kwargs)
A leaf's exact occupied cell words from its bitmap sidecar, or None.
None for anything without a "bitmap" sidecar to read: debris, a
box-only envelope, encoding: "full" (there IS no sidecar — the shard
id is the exact MOC; :func:bitmap_and short-circuits on it), or a
missing sidecar object. A present-but-corrupt sidecar raises (decoder's
posture). The shard id comes from the leaf basename via the frozen
first-_ split; cell_order from the envelope.
The sidecar sits beside the arrays, so on a versioned leaf it is under
the version the root stamp names (:func:leaf_data_prefix). Pass an
already-read root stamp to skip the stamp GET. coverage — an
already-parsed envelope — still overrides the one read off the stamp,
but no longer skips that GET by itself: the envelope does not carry the
pointer, the stamp does.
Source code in src/moczarr/store.py
405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 | |
read_json(store, key)
GET+parse one small JSON object; None when it does not exist.
Parse errors propagate — tolerant callers wrap this per their tier's posture.
Source code in src/moczarr/store.py
176 177 178 179 180 181 182 183 184 185 186 187 188 189 | |
read_leaf_coverage(store_root, leaf, *, store=None, **store_kwargs)
A leaf's coverage envelope off its commit stamp, or None.
Source code in src/moczarr/store.py
398 399 400 401 402 | |
read_leaf_metas(store_root, leaves, *, store=None, concurrency=32, **store_kwargs)
Raw root zarr.json payloads for many leaves, batched (issue #5).
The substrate :func:read_commits maps over, exposed so a caller that
needs MORE than the stamp from the same object — the pyramid layer reads
the D11 role/zagg_overview root attrs alongside it (zagg spec
§4.3) — never pays a second GET. One result per input leaf, aligned by
position: None for an absent object, the parsed payload otherwise; a
present-but-unparsable zarr.json raises (that leaf claims to exist
and cannot be half-trusted). concurrency bounds the in-flight GETs
(obstore.get_async behind a semaphore); None or 1 keeps the
serial path for debugging.
Source code in src/moczarr/store.py
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 369 370 371 372 373 374 | |
read_manifest(store_root, *, store=None, **store_kwargs)
The store's validated morton_hive.json; None when absent.
Loud on malformed content (bad JSON or a failed :func:parse_manifest):
the manifest is the reader's bootstrap, so garbage here is an error, not
a degradable cache.
Source code in src/moczarr/store.py
192 193 194 195 196 197 198 199 200 | |
walk_columns(store_root, *, store=None, concurrency=None, path_grouping=1, **store_kwargs)
Yield the store-relative path of every §4.6 column artifact (issue #36).
The column twin of :func:walk_leaves — same walk, same contracts
(fallback/verification path, stamped and debris objects alike, set
identical between the serial and concurrent forms, order not guaranteed)
— but yielding the *.pyramid.zarr objects the name seam classifies
as columns. This is the read-the-columns discovery §4.6 prescribes: the
manifest's pyramid block MAY lag a config-only change, so "which
leaves actually carry columns" is answerable only from the columns
themselves. Completeness stays the caller's check
(:func:read_commit — an unstamped column prefix is debris), and a
column's classification is its role/zagg_column attrs
(:func:moczarr.column.read_column_record), never the name alone.
The suffix is the seam at every depth, so on a staged store this
yields two §4.6 kinds interleaved: the leaf columns at
shard_order nodes, and the issue-#384 stage columns a staged
sweep leaves at its dispatch nodes (the same artifact shape under an
ancestor node's prefix). A caller tells them apart by node order, read
off the path. :func:moczarr.column.read_column_record and
:func:moczarr.column.open_column address leaf columns only — both
are keyed by shard id and refuse an ancestor-node id — so a stage column
is discoverable here and not readable through that pair today
(espg/moczarr#36b). The split is deliberate rather than an oversight:
§4.6 makes stage-column existence at a given ancestor order
orchestration, never contract ("a reader binds to the ladder artifacts
of §4.4, not to stage columns"), so a path-addressed reader for them is
a design step this surface does not take on spec.
Source code in src/moczarr/store.py
610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 | |
walk_leaves(store_root, *, store=None, concurrency=None, path_grouping=1, **store_kwargs)
Yield the store-relative path of every leaf zarr — the discovery walk.
The fallback/verification path (never the hot path): one delimiter-LIST
per digit node — no digit children means nothing finer exists (LIST is
strongly consistent and object stores have no empty prefixes, so absence
is definitive). Yields stamped and debris leaves alike — completeness is
the caller's check (:func:read_commit), matching the tiered postures.
A §4.6 leaf column (*.pyramid.zarr) is never yielded: the name
seam excludes it, and it has to, because a column carries a commit stamp
of its own and would pass that completeness check. Columns have their
own discovery walk (:func:walk_columns, issue #36).
path_grouping is the manifest's digit-chunking (spec §6.1) — child
classification depends on it, so callers walking a grouped store must
pass the manifest's value (readers hold the manifest before any path
work; D10).
concurrency > 1 batches the LISTs breadth-parallel, one tree level
at a time (obstore.list_with_delimiter_async behind a semaphore).
The yielded SET is identical to the serial walk's; the ORDER may differ
(level-by-level vs depth-first) — callers sort, per the contract.
Source code in src/moczarr/store.py
570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 | |
warn_if_stale(store_root, shard, envelope)
O7 lazy staleness detection for one opened, commit-stamped leaf.
Call with POSITIVE evidence of a committed shard (an opened leaf with a
stamp) that the root MOC does not list. Usually benign — a run in
progress writes the root MOC only at end of run — so this warns ONCE per
store per process with the context, returns whether stale, and never
auto-walks. envelope may be None (no root MOC at all): that is
absence, not staleness, and reads False. A malformed envelope cannot
vouch for the shard and counts as stale.
Source code in src/moczarr/store.py
657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 | |