Skip to content

Contributing

nix develop            # or: pip install -e '.[dev]'
pytest -q
mypy

Layout

partition.py and resolve.py are pure -- no I/O, no storage imports. Keep them that way: it is what makes the interesting logic testable with hand-written dataclasses in milliseconds, and what keeps the semantics of the manifest format in one place.

storage.py is the only module that imports obstore. Three operations (list, get, conditional put) are all blobmap needs.

Tests

No mocks. MemoryStore and LocalStore are real implementations of the same API as S3Store, so the same suite runs against all three -- MinIO is one extra fixture param, not a second suite:

docker compose up -d
export BLOBMAP_TEST_S3_URL=s3://blobmap-test
export BLOBMAP_TEST_S3_ENDPOINT=http://localhost:9000
export AWS_ACCESS_KEY_ID=minioadmin AWS_SECRET_ACCESS_KEY=minioadmin
export AWS_REGION=us-east-1
pytest -q

tests/zarrgen.py writes synthetic stores as raw objects, with no zarr-python dependency. That is deliberate: blobmap parses this metadata itself, so the fixtures must not be generated by the library whose output they imitate. It also lets us produce layouts zarr-python will not easily write, such as a v3 array using v2-style flat chunk keys.

Diagrams and the call graph

docs/internals.md and docs/operations.md use mermaid, which renders on GitHub and in mkdocs without any build step. tests/test_docs.py checks every diagram is well formed, since a syntax error is otherwise invisible until someone opens the page.

Do not set colours. No fill:, no classDef, no style. Each renderer theming these differently is not a problem worth solving: GitHub light, GitHub dark and Material's palette toggle each override a different part, and mkdocs-material's CSS re-sets .mermaid text with higher specificity than a classDef color:, so a hardcoded fill ends up with the theme's own text colour over it. Letting the theme choose both is the only combination that is legible everywhere. A test enforces this.

Where a box needs emphasis, use structure instead: put it in a labelled subgraph, or mark the label with <b>.

The call graph is generated from the AST:

python tools/callgraph.py --entry partition_store --depth 3
python tools/callgraph.py --module resolve --private

The copy in internals.md is hand-laid-out for readability, so it is not byte-identical to the tool's output, but a test asserts the calls it shows still exist.

Docs

Docstrings are Google style and rendered by mkdocstrings:

pip install -e '.[docs]'
mkdocs serve

Every public function documents its arguments, its return value and anything it raises. Dataclasses use an Attributes: block covering every field.

Examples written as >>> are run by the test suite, since --doctest-modules is on by default. Prefer them over fenced code blocks wherever the example does not need a real backend, so the documentation cannot drift from the code. Use a fenced ```python block only when the example needs S3 or a database.

Storage handles

Anything holding a storage handle is annotated Store, an alias for obstore's ObjectStore defined in storage.py. Other modules import it from there rather than importing obstore, which keeps the dependency at one edge of the package. tests/test_typing.py enforces this, including a mypy probe that checks the annotation actually rejects a non-store.

Store is a closed union of obstore's own backends, so a wrapper around a store, for metrics or a read-only guard, will not satisfy it. If that becomes wanted, the move is to switch storage.py to calling store methods (store.get(key)) instead of module functions (obs.get(store, key)), which makes a structural Protocol possible.

Things that will bite

Changing how a blob id is derived is a breaking change: ids are the join key against blobtier's state table and, through it, against tape addresses. If a change can move an existing id, it belongs behind --force.

Manifest.SCHEMA_VERSION is checked strictly on read. Bump it and provide a migration rather than accepting both shapes.

The JSON Schema in schema.py is the contract, not a derived artifact. Change it and the dataclasses together: tests/test_schema.py cross-checks the hand-written walker against the real jsonschema library, so a keyword the walker does not implement fails the suite rather than being ignored. Every property needs a description; a test enforces that too.