Status: index of cross-cutting type designs. Scope: types and small dataclasses that are consumed by more than one of the major designs (reader, catalog, geotoolz operators) and therefore deserve their own home rather than being defined inside whichever design happens to need them first. Audience: anyone touching a type that flows between layers.
Primer for newcomers¶
ELI5. Some Lego pieces have unique shapes that only fit in one castle. Others are the standard 2×4 brick that fits in everything. This directory is for the standard 2×4 bricks of the design — small, reusable, with one shape that lots of places need.
What’s a “type” in this directory?¶
What it is. A type in this directory is a Python construct — usually a Protocol, occasionally a @dataclass — that defines a shape (what attributes/methods/fields a value must have) without owning the implementation.
Types live here when more than one design references them.
How it works. Three flavours show up:
typing.Protocol— structural typing. Any class with the right method signatures satisfies it; no inheritance required. Used forCredentialand the reader Protocols (GeoData/GeoDataBase/AsyncGeoData). The static type-checker (mypy/ty) verifies conformance at the call site.@dataclass— auto-generated__init__/__repr__/__eq__. Used forGeoSlice. Oftenfrozen=Trueto make instances immutable and hashable.Concrete subclasses of a Protocol —
AzureSASCredential, etc. Live alongside the Protocol they implement, in the same design doc.
What this means for us. Code that takes a Protocol parameter (def f(reader: GeoData)) accepts any conforming object — no shared base class, no inheritance dance.
Code that takes a @dataclass parameter (def f(slice_: GeoSlice)) gets all the dataclass machinery (immutable fields, equality, repr) for free.
The patterns are deliberately small and uniform across the directory.
When does a type land here vs in a design subdir?¶
What it is. The criterion for promoting a type to plans/types/.
Three conditions, all of which must hold.
How it works. A type lives here when (1) it’s a public surface — users construct or pattern-match against it directly, (2) it’s consumed by more than one of the major designs, and (3) it’s small enough to specify in one document.
Types that fail any of these stay scoped to their owning design — e.g., GeoData / GeoDataBase / AsyncGeoData live in georeader/reader_protocol.md because they’re the subject of that design, not just incidental to it.
Same logic kept GeoCatalog in geodatabase and Operator in geotoolz.
What this means for us. This directory grows slowly.
Most “types” stay in their owning subdir; the ones that end up here are the ones that flow between layers (GeoSlice, Credential).
The bytestore.md doc is here too but is not a Protocol of our own — it documents the decision to defer cloud byte transport to upstream obspec (see bytestore.md).
What goes here¶
A type lands in this directory when all three are true:
It’s a public surface — users construct or pattern-match against it directly, not just an internal helper.
It’s consumed by more than one design — moves between layers (reader → catalog → operator) rather than being scoped to a single subsystem.
It’s small enough to specify in one document — a dataclass, a Protocol, or a small family of related primitives. Big subsystems (the reader Protocol surface, the catalog Protocol) get their own design dirs.
The georeader-side types that aren’t here, and why:
GeoTensor— already a real implemented type ingeoreader/geotensor.py; documented in Tutorial Ch. 1. No design doc needed.GeoData/GeoDataBase/AsyncGeoData— all live in Reader reconciliation because they’re the subject of that design, not just incidental to it.GeoCatalogProtocol — lives in Geodatabase for the same reason.obspec.AsyncStore— the cloud-byte transport surface. It’s not ours — we defer to upstreamobspec(DevSeed). The note atbytestore.mddocuments this decision and the smallgeotoolz.io.open_store(url)helper we ship.
If a type starts in another design and grows into something multiple designs reference, promote it here — the cleanup is the same shape as the GeoSlice promotion that motivated this directory.
Current designs¶
| Design | Type(s) covered |
|---|---|
geoslice.md | GeoSlice dataclass + the sampler/stitch family (random_sampler, grid_sampler, stitch) that produces and consumes GeoSlice. |
credentials.md | Credential Protocol + per-cloud subclasses (AzureSASCredential, AzureManagedIdentityCredential, AWSStaticCredential, AWSProfileCredential, GCSServiceAccountCredential) + from_config(...) factory + to_obstore_*_store() adapter helpers. Replaces the env-var-soup pattern that every project currently re-implements. |
bytestore.md | One-page passthrough note: cloud byte transport is obspec.AsyncStore (upstream, not ours). We ship a small geotoolz.io.open_store(url) factory and nothing else. Not a Protocol of our own. |
Future candidates¶
These are types that might land here as the geotoolz ecosystem grows. Listed for orientation, not commitment:
OperatorProtocol — if a shared base class forgeotoolzoperators turns out to be reused by other libraries (e.g., a siblingxr_toolz-shaped library), it’d live here. Today it’s scoped togeotoolz.md.A
ChiporWindowreconciliation type — ifGeoSlice,rasterio.windows.Window, andslices.create_windowsoutputs end up needing a unified shape.A
Sensor/Missionmetadata struct — if sensor-preset operators (geotoolz.md §1.2) need to share a structured description of band layout, calibration constants, etc. Today this lives ad-hoc inside each reader (e.g.,BANDS_S2,BANDS_S2_L2A).
When a candidate becomes a real design, it lands here as a sibling to geoslice.md.
Conventions¶
One file per type family. A “type family” can include a small number of closely-coupled types — e.g.,
GeoSliceplus the three samplers andstitchthat produce/consume it — but not unrelated types stuffed together for filing convenience.Same design-doc skeleton as the other designs: Status / Scope / Motivation / Goals / Non-goals / Constraints / The type itself / Connections to other designs / Open questions / Alternatives.
Keep concrete enough to implement. The whole point of pulling a type out is that it gets the same attention as a subsystem — meaning a real Protocol or dataclass spec, not a sketch.
Open questions, gotchas, and warnings¶
The cross-cutting types are the load-bearing ones — when they’re wrong, every downstream design ripples. Things to watch:
Credential.apply()per-call vsapply_to_os_environ()mutation. The design distinguishes pure (returns a dict) vs mutating (writesos.environ) modes. Per-reader isolation only works if every reader consumes the per-call dict; the env-var mode is a backwards-compat hatch, not the recommended path. Audit each reader on the reconciliation path to make sure it accepts the per-call dict.obspecAPI stability.obspecis the upstream Protocol (DevSeed);obstoreis the reference implementation. Both are pre-1.0. Ourgeotoolz.io.open_store(...)returnsobspec.AsyncStore, so any breaking change there ripples throughAsyncGeoTIFFReader. Pin a minor range and bump deliberately.GeoSlicetime-axis convention.GeoSlice.intervalis apd.Interval; the slice can be spatial-only (no interval) or spatial+temporal. Document the discriminated-union behaviour clearly so samplers and stitchers don’t trip over the optional time dimension.Sampler API stability.
random_geo_sampler/grid_geo_samplerare bigger than they look — they coupleGeoSlice, the catalog, and the inference loop. Treat their signatures as v0.1 public API; bump_v2if the shape needs to change rather than mutating in place.stitch_predictionsreduction semantics. Average vs first-write-wins vs max-vote — pick a default, document the alternatives, expose amethod=knob. Without this, every user reinvents stitching.DuckDB credential bridge. Reading cloud GeoParquet from DuckDB needs
httpfsconfigured with credentials. TheCredentialProtocol should provide ato_duckdb_secret()adapter so users don’t configure auth twice. Out-of-scope for v0.1, in-scope for the geodatabase Phase 2 design — flag the dependency.Future-candidate types are not commitments.
OperatorProtocol,Chip/Window,Sensor/Missionare listed for orientation. Each lands here only when a second design references it. Resist promoting speculative types — they’re cheap to add later, expensive to retract.