storage
blobmap.storage ¶
The narrow storage seam, over obstore.
List, get and conditional put is all blobmap needs. Keeping the surface this
small means partition and resolve
never import storage at all, and the MinIO integration tests are the same
suite with a different fixture rather than a second suite.
obstore gives S3, local disk and an in-memory backend through one API, with
atomic conditional writes: write-then-rename on POSIX, If-None-Match and
If-Match on S3.
Checked against obstore 0.11
LocalStore implements create-if-absent but not update-if-etag, so a
local repartition of an existing manifest falls back to overwrite with a
warning. The If-Match path is only really exercised against S3.
Attributes:
| Name | Type | Description |
|---|---|---|
Store |
TypeAlias
|
The type of a storage handle. An alias for |
MISSING |
tuple[type[BaseException], ...]
|
Exception types meaning "no such object". |
Example
from obstore.store import MemoryStore store = MemoryStore() etag = put_bytes(store, "a/b.json", b"hello", expect_absent=True) get_bytes(store, "a/b.json") b'hello' get_bytes(store, "not/here.json") is None True
Conflict ¶
Bases: RuntimeError
Someone else wrote this key since we read it.
Raised when a conditional write fails its precondition. This turns two jobs partitioning the same scope into an error you can retry rather than a silent last-writer-wins.
Entry
dataclass
¶
Entry(key: str, size: int, etag: str | None = None)
One object, as reported by a listing.
Attributes:
| Name | Type | Description |
|---|---|---|
key |
str
|
Full object key. |
size |
int
|
Size in bytes, exact rather than sampled. This is where compressed sizes come from, with no need to read any content. |
etag |
str | None
|
Entity tag, used for conditional writes. |
list_all ¶
list_all(store: Store, prefix: str = '') -> Iterator[Entry]
Yield every object under a prefix, with its real stored size.
This is the primary source for both structure and sizes. It reports compressed bytes exactly, needs no sampling, and never reads an object body. That last point matters: a v3 shard index lives inside the object, so introspecting it would trigger a restore on exactly the cold data we are trying not to touch.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
store
|
Store
|
A storage handle. |
required |
prefix
|
str
|
Key prefix to list. Empty lists everything. |
''
|
Yields:
| Type | Description |
|---|---|
Entry
|
One |
Entry
|
rather than materialised. |
Source code in src/blobmap/storage.py
83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 | |
list_dirs ¶
list_dirs(store: Store, prefix: str = '') -> list[str]
List immediate child prefixes, without recursing.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
store
|
Store
|
A storage handle. |
required |
prefix
|
str
|
Prefix to list under. Include the trailing slash. |
''
|
Returns:
| Type | Description |
|---|---|
list[str]
|
Common prefixes one level down. One delimited LIST, so this stays |
list[str]
|
cheap even above a store with hundreds of thousands of objects. |
Source code in src/blobmap/storage.py
106 107 108 109 110 111 112 113 114 115 116 117 118 | |
list_names ¶
list_names(store: Store, prefix: str = '') -> set[str]
Basenames of objects sitting directly under a prefix.
Used to detect a zarr store by looking for zarr.json or .zgroup
without listing the whole subtree.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
store
|
Store
|
A storage handle. |
required |
prefix
|
str
|
Prefix to inspect. Include the trailing slash. |
''
|
Returns:
| Type | Description |
|---|---|
set[str]
|
Basenames, excluding anything in nested prefixes. |
Source code in src/blobmap/storage.py
121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 | |
get_bytes ¶
get_bytes(store: Store, key: str) -> bytes | None
Read an object whole.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
store
|
Store
|
A storage handle. |
required |
key
|
str
|
Full object key. |
required |
Returns:
| Type | Description |
|---|---|
bytes | None
|
The object body, or |
bytes | None
|
an expected outcome here, not an error. |
Source code in src/blobmap/storage.py
138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 | |
head ¶
head(store: Store, key: str) -> Entry | None
Fetch metadata for one object without reading it.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
store
|
Store
|
A storage handle. |
required |
key
|
str
|
Full object key. |
required |
Returns:
| Type | Description |
|---|---|
Entry | None
|
An |
Entry | None
|
if the object does not exist. |
Source code in src/blobmap/storage.py
155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 | |
put_bytes ¶
put_bytes(
store: Store,
key: str,
body: bytes,
*,
etag: str | None = None,
expect_absent: bool = False
) -> str | None
Write an object, optionally conditionally.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
store
|
Store
|
A storage handle. |
required |
key
|
str
|
Full object key. |
required |
body
|
bytes
|
Bytes to write. |
required |
etag
|
str | None
|
Require the object to still have this etag, mapping to
|
None
|
expect_absent
|
bool
|
Require the object not to exist, mapping to
|
False
|
Returns:
| Type | Description |
|---|---|
str | None
|
The new etag, or |
Raises:
| Type | Description |
|---|---|
Conflict
|
If the precondition fails, meaning someone else wrote the key first. |
Note
Logs a warning and overwrites when the backend has no update-if-etag
support, which is the case for LocalStore in obstore 0.11.
Example
from obstore.store import MemoryStore store = MemoryStore() _ = put_bytes(store, "m.json", b"1", expect_absent=True) put_bytes(store, "m.json", b"2", expect_absent=True) Traceback (most recent call last): ... blobmap.storage.Conflict: m.json already exists
Source code in src/blobmap/storage.py
173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 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 | |