atlas-python 0.9.0__cp310-abi3-win_amd64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- atlas/__init__.py +4 -0
- atlas/__init__.pyi +332 -0
- atlas/_atlas.pyd +0 -0
- atlas/py.typed +0 -0
- atlas/xarray.py +578 -0
- atlas_python-0.9.0.dist-info/METADATA +17 -0
- atlas_python-0.9.0.dist-info/RECORD +9 -0
- atlas_python-0.9.0.dist-info/WHEEL +4 -0
- atlas_python-0.9.0.dist-info/sboms/atlas-python.cyclonedx.json +8212 -0
atlas/__init__.py
ADDED
atlas/__init__.pyi
ADDED
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from typing import TYPE_CHECKING, Any, Optional, Sequence, Union
|
|
3
|
+
|
|
4
|
+
import numpy as np
|
|
5
|
+
from numpy.typing import NDArray
|
|
6
|
+
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
import xarray as xr
|
|
9
|
+
# `obstore` is an optional dependency — only needed if you want to
|
|
10
|
+
# open / create stores against S3, GCS, Azure, or HTTP backends.
|
|
11
|
+
# `pip install atlas-python[cloud]` pulls it in.
|
|
12
|
+
import obstore.store as _obstore_store
|
|
13
|
+
|
|
14
|
+
# Public alias for the polymorphic source argument accepted by
|
|
15
|
+
# `Atlas.create` / `Atlas.open`. Either a local filesystem path or an
|
|
16
|
+
# obstore-constructed store handle (`obstore.store.S3Store`,
|
|
17
|
+
# `obstore.store.GCSStore`, `obstore.store.AzureStore`,
|
|
18
|
+
# `obstore.store.LocalStore`, `obstore.store.HttpStore`).
|
|
19
|
+
AtlasSource = Union[str, "os.PathLike[str]", "_obstore_store.ObjectStore"]
|
|
20
|
+
|
|
21
|
+
__version__: str
|
|
22
|
+
|
|
23
|
+
class Atlas:
|
|
24
|
+
"""A directory-based store for many named datasets of N-dimensional arrays."""
|
|
25
|
+
|
|
26
|
+
@staticmethod
|
|
27
|
+
def create(
|
|
28
|
+
source: AtlasSource,
|
|
29
|
+
codec: str = "zstd",
|
|
30
|
+
meta_format: str = "json",
|
|
31
|
+
meta_compression: str = "none",
|
|
32
|
+
) -> "Atlas":
|
|
33
|
+
"""Create a new store.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
source: Either a local filesystem path (created with `mkdir -p`
|
|
37
|
+
semantics) or an [obstore](https://github.com/developmentseed/obstore)-
|
|
38
|
+
constructed store handle (`obstore.store.S3Store`,
|
|
39
|
+
`obstore.store.GCSStore`, `obstore.store.AzureStore`,
|
|
40
|
+
`obstore.store.LocalStore`, `obstore.store.HttpStore`).
|
|
41
|
+
Cloud credentials, region, endpoint and retry policy are
|
|
42
|
+
obstore's responsibility — atlas writes through the handle.
|
|
43
|
+
codec: Compression codec for new array blocks.
|
|
44
|
+
One of `"zstd"` (default), `"lz4"`, `"none"` / `"uncompressed"`.
|
|
45
|
+
meta_format: On-disk encoding for the metadata file.
|
|
46
|
+
`"json"` (default, written as `atlas.json`) or
|
|
47
|
+
`"msgpack"` / `"mp"` (written as `atlas.msgpack`, ~30-50%
|
|
48
|
+
smaller and faster to parse, but not human-readable).
|
|
49
|
+
meta_compression: Compression applied to the encoded metadata file.
|
|
50
|
+
`"none"` / `"uncompressed"` (default — filename has no extra
|
|
51
|
+
suffix), `"zstd"` (suffix `.zst`), or `"lz4"` (suffix `.lz4`).
|
|
52
|
+
Mostly useful for stores with thousands of datasets on a
|
|
53
|
+
high-latency object store.
|
|
54
|
+
"""
|
|
55
|
+
...
|
|
56
|
+
|
|
57
|
+
@staticmethod
|
|
58
|
+
def open(source: AtlasSource) -> "Atlas":
|
|
59
|
+
"""Open an existing store.
|
|
60
|
+
|
|
61
|
+
Accepts the same shapes as [`Atlas.create`][atlas.Atlas.create]:
|
|
62
|
+
a local filesystem path or an obstore-constructed store handle.
|
|
63
|
+
The codec, metadata format, and metadata compression are
|
|
64
|
+
auto-detected from the on-disk filename (`atlas.json`,
|
|
65
|
+
`atlas.msgpack`, `atlas.json.zst`, `atlas.msgpack.lz4`, etc.) —
|
|
66
|
+
no extra arguments required.
|
|
67
|
+
"""
|
|
68
|
+
...
|
|
69
|
+
|
|
70
|
+
def create_dataset(self, name: str) -> "DatasetView":
|
|
71
|
+
"""Create a new dataset. Raises if a dataset with this name already exists."""
|
|
72
|
+
...
|
|
73
|
+
|
|
74
|
+
def open_dataset(self, name: str) -> "DatasetView":
|
|
75
|
+
"""Open an existing dataset. Raises `KeyError` if not found."""
|
|
76
|
+
...
|
|
77
|
+
|
|
78
|
+
def delete_dataset(self, name: str) -> None:
|
|
79
|
+
"""Remove a dataset and tombstone its entries in every shared array file."""
|
|
80
|
+
...
|
|
81
|
+
|
|
82
|
+
def list_datasets(self) -> list[str]:
|
|
83
|
+
"""Names of all datasets in this store."""
|
|
84
|
+
...
|
|
85
|
+
|
|
86
|
+
def list_arrays(self) -> list[str]:
|
|
87
|
+
"""Distinct array names across all datasets."""
|
|
88
|
+
...
|
|
89
|
+
|
|
90
|
+
def dataset_exists(self, name: str) -> bool: ...
|
|
91
|
+
|
|
92
|
+
def __repr__(self) -> str: ...
|
|
93
|
+
|
|
94
|
+
def add_xr_dataset(
|
|
95
|
+
self,
|
|
96
|
+
ds: "xr.Dataset",
|
|
97
|
+
name: str,
|
|
98
|
+
chunks: Optional[dict[str, Sequence[int]]] = None,
|
|
99
|
+
) -> None:
|
|
100
|
+
"""Append an atlas dataset populated from an `xarray.Dataset`.
|
|
101
|
+
|
|
102
|
+
Per-variable attributes are flattened as `{var}.{attr}` alongside the
|
|
103
|
+
dataset attrs. Dask-backed variables are streamed chunk-by-chunk; their
|
|
104
|
+
chunk shape is used as the atlas chunk shape unless `chunks` overrides
|
|
105
|
+
it per-variable.
|
|
106
|
+
"""
|
|
107
|
+
...
|
|
108
|
+
|
|
109
|
+
def to_xarray(self, name: str) -> "xr.Dataset":
|
|
110
|
+
"""Open dataset `name` and return it as an `xarray.Dataset`.
|
|
111
|
+
|
|
112
|
+
Variables stored with `chunk_shape != shape` come back dask-backed (one
|
|
113
|
+
dask task per on-disk chunk); full-shape and 0-D variables come back
|
|
114
|
+
eager as numpy arrays.
|
|
115
|
+
"""
|
|
116
|
+
...
|
|
117
|
+
|
|
118
|
+
def to_xarray_many(
|
|
119
|
+
self,
|
|
120
|
+
names: Sequence[str],
|
|
121
|
+
concat_dim: str = "dataset",
|
|
122
|
+
parallel: bool = True,
|
|
123
|
+
) -> "xr.Dataset":
|
|
124
|
+
"""Open many datasets and stack them along `concat_dim` as one Dataset.
|
|
125
|
+
|
|
126
|
+
atlas-native equivalent of `xr.open_mfdataset(...)`. Each variable comes
|
|
127
|
+
back shape `(len(names), *original_shape)` as eager numpy. Wrap with
|
|
128
|
+
`.chunk(...)` downstream if you need dask laziness.
|
|
129
|
+
|
|
130
|
+
Implementation calls `Atlas.read_array_across` once per variable —
|
|
131
|
+
N per-dataset reads share one `RwLock::read` guard on the shared
|
|
132
|
+
physical file and dispatch concurrently on the tokio runtime.
|
|
133
|
+
|
|
134
|
+
Variable names + dtypes must match across all listed datasets.
|
|
135
|
+
Coordinates and dataset-level attrs are taken from the first dataset.
|
|
136
|
+
|
|
137
|
+
The `parallel` parameter is accepted for API compatibility but no
|
|
138
|
+
longer selects an implementation — the bulk path is always taken.
|
|
139
|
+
"""
|
|
140
|
+
...
|
|
141
|
+
|
|
142
|
+
def read_array_across(
|
|
143
|
+
self,
|
|
144
|
+
array: str,
|
|
145
|
+
dataset_names: Sequence[str],
|
|
146
|
+
start: Optional[Sequence[int]] = None,
|
|
147
|
+
shape: Optional[Sequence[int]] = None,
|
|
148
|
+
) -> list[Optional[NDArray[Any]]]:
|
|
149
|
+
"""Bulk-read the same slice of `array` across many datasets.
|
|
150
|
+
|
|
151
|
+
Returns a list of length `len(dataset_names)` — one numpy array per
|
|
152
|
+
dataset, or `None` for datasets that don't declare `array`. All reads
|
|
153
|
+
share one `RwLock::read` guard on the array's shared physical file and
|
|
154
|
+
dispatch concurrently on the tokio runtime via a `JoinSet` capped at
|
|
155
|
+
`num_cpus` in-flight tasks. Replaces N individual `read_array` calls
|
|
156
|
+
with a single Python ↔ Rust round-trip.
|
|
157
|
+
|
|
158
|
+
`start` / `shape` follow the same conventions as
|
|
159
|
+
`DatasetView.read_array`: omit both to read each dataset's full array.
|
|
160
|
+
|
|
161
|
+
For the common "stack and use as one array" pattern, prefer
|
|
162
|
+
[`read_array_across_stacked`] which skips the Python-side
|
|
163
|
+
`np.stack` copy.
|
|
164
|
+
"""
|
|
165
|
+
...
|
|
166
|
+
|
|
167
|
+
def read_array_across_stacked(
|
|
168
|
+
self,
|
|
169
|
+
array: str,
|
|
170
|
+
dataset_names: Sequence[str],
|
|
171
|
+
start: Optional[Sequence[int]] = None,
|
|
172
|
+
shape: Optional[Sequence[int]] = None,
|
|
173
|
+
) -> NDArray[Any]:
|
|
174
|
+
"""Stacked variant of `read_array_across`: returns one numpy ndarray
|
|
175
|
+
of shape `(len(dataset_names), *per_dataset_shape)` instead of a list.
|
|
176
|
+
|
|
177
|
+
The output buffer is pre-allocated in Rust; each parallel read writes
|
|
178
|
+
its row in as the task completes. Skips the ~N × per_dataset_size of
|
|
179
|
+
memory copies that `np.stack(read_array_across(...))` would do.
|
|
180
|
+
|
|
181
|
+
Errors if any listed dataset doesn't declare `array` (the stacked
|
|
182
|
+
representation has no positional "missing" sentinel).
|
|
183
|
+
"""
|
|
184
|
+
...
|
|
185
|
+
|
|
186
|
+
def flush(self) -> None:
|
|
187
|
+
"""Persist the in-memory atlas.json + every cached array file.
|
|
188
|
+
|
|
189
|
+
This is the single durability boundary; until called, no mutation
|
|
190
|
+
reaches disk (dropping the Atlas without flushing abandons every
|
|
191
|
+
pending write).
|
|
192
|
+
"""
|
|
193
|
+
...
|
|
194
|
+
|
|
195
|
+
def close(self) -> None:
|
|
196
|
+
"""Final flush; alias for `flush()`. Mirrors the context-manager exit."""
|
|
197
|
+
...
|
|
198
|
+
|
|
199
|
+
def compact(self) -> None:
|
|
200
|
+
"""Compact every cached array file in place (reclaim tombstoned space)."""
|
|
201
|
+
...
|
|
202
|
+
|
|
203
|
+
def __enter__(self) -> "Atlas": ...
|
|
204
|
+
def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None: ...
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
class DatasetView:
|
|
208
|
+
"""A handle to a single dataset within an `Atlas` store.
|
|
209
|
+
|
|
210
|
+
Holds the per-dataset array schemas and attributes. Mutations are buffered
|
|
211
|
+
in-memory until `flush()` is called.
|
|
212
|
+
"""
|
|
213
|
+
|
|
214
|
+
@property
|
|
215
|
+
def name(self) -> str: ...
|
|
216
|
+
|
|
217
|
+
def list_arrays(self) -> list[str]:
|
|
218
|
+
"""Names of all arrays defined in this dataset."""
|
|
219
|
+
...
|
|
220
|
+
|
|
221
|
+
def define_array(
|
|
222
|
+
self,
|
|
223
|
+
name: str,
|
|
224
|
+
dtype: str,
|
|
225
|
+
dims: Sequence[str],
|
|
226
|
+
shape: Sequence[int],
|
|
227
|
+
chunk_shape: Optional[Sequence[int]] = None,
|
|
228
|
+
fill_value: Optional[Any] = None,
|
|
229
|
+
) -> None:
|
|
230
|
+
"""Declare a new N-dimensional array.
|
|
231
|
+
|
|
232
|
+
Args:
|
|
233
|
+
name: Array name (no `/`, no leading `_`, non-empty).
|
|
234
|
+
dtype: e.g. `"float32"`, `"int64"`, `"uint8"`. See the module README for the full list.
|
|
235
|
+
dims: Named dimensions, one per axis.
|
|
236
|
+
shape: Logical shape, one entry per axis.
|
|
237
|
+
chunk_shape: Optional chunk shape; defaults to `shape` (a single chunk).
|
|
238
|
+
fill_value: Optional scalar returned for unwritten cells. Must match
|
|
239
|
+
the array dtype: a Python `int` for int/uint/timestamp arrays
|
|
240
|
+
(range-checked), a `float` (or `int`) for float arrays, a `bool`
|
|
241
|
+
for bool arrays, a `str` for string arrays. Raises `TypeError`
|
|
242
|
+
on a mismatch and `OverflowError` if the value is out of range.
|
|
243
|
+
"""
|
|
244
|
+
...
|
|
245
|
+
|
|
246
|
+
def write_array(
|
|
247
|
+
self,
|
|
248
|
+
name: str,
|
|
249
|
+
start: Sequence[int],
|
|
250
|
+
data: NDArray[Any],
|
|
251
|
+
) -> None:
|
|
252
|
+
"""Write a numpy array at the given starting index.
|
|
253
|
+
|
|
254
|
+
The numpy dtype must match the stored dtype and the array must be C-contiguous.
|
|
255
|
+
"""
|
|
256
|
+
...
|
|
257
|
+
|
|
258
|
+
def read_array(
|
|
259
|
+
self,
|
|
260
|
+
name: str,
|
|
261
|
+
start: Optional[Sequence[int]] = None,
|
|
262
|
+
shape: Optional[Sequence[int]] = None,
|
|
263
|
+
) -> Optional[NDArray[Any]]:
|
|
264
|
+
"""Read a full or partial array.
|
|
265
|
+
|
|
266
|
+
With `start` and `shape` omitted, returns the entire array. Returns `None`
|
|
267
|
+
if the array does not exist in this dataset.
|
|
268
|
+
"""
|
|
269
|
+
...
|
|
270
|
+
|
|
271
|
+
def read_arrays(
|
|
272
|
+
self,
|
|
273
|
+
names: Sequence[str],
|
|
274
|
+
start: Optional[Sequence[int]] = None,
|
|
275
|
+
shape: Optional[Sequence[int]] = None,
|
|
276
|
+
) -> dict[str, Optional[NDArray[Any]]]:
|
|
277
|
+
"""Bulk-read multiple arrays in one PyO3 call.
|
|
278
|
+
|
|
279
|
+
Returns `{name: ndarray | None}` — `None` for arrays not in this
|
|
280
|
+
dataset. Same `start` / `shape` apply to every array.
|
|
281
|
+
|
|
282
|
+
Fast path for per-dataset slice reads (e.g. inside a dask worker)
|
|
283
|
+
where `to_xarray(name).isel(...).load()` overhead would dominate the
|
|
284
|
+
actual I/O cost. Skips the xr.Dataset construction and per-chunk
|
|
285
|
+
dask graph that `to_xarray` builds. See the benchmarks for the
|
|
286
|
+
~3-4× speedup over `to_xarray` iteration on chunked storage.
|
|
287
|
+
"""
|
|
288
|
+
...
|
|
289
|
+
|
|
290
|
+
def delete_array(self, name: str) -> None:
|
|
291
|
+
"""Remove the array from this dataset (tombstone)."""
|
|
292
|
+
...
|
|
293
|
+
|
|
294
|
+
def array_meta(self, name: str) -> Optional[dict[str, Any]]:
|
|
295
|
+
"""Schema for `name` (`{"dtype", "shape", "chunk_shape", "dimension_names"}`),
|
|
296
|
+
or `None` if no array with that name exists in this dataset."""
|
|
297
|
+
...
|
|
298
|
+
|
|
299
|
+
def array_stats(self, name: str) -> Optional[dict[str, Any]]:
|
|
300
|
+
"""Persisted statistics or `None` if not yet computed.
|
|
301
|
+
|
|
302
|
+
After `flush()` returns `{"row_count", "null_count", "min", "max"}`.
|
|
303
|
+
"""
|
|
304
|
+
...
|
|
305
|
+
|
|
306
|
+
def array_fill_value(self, name: str) -> Optional[Any]:
|
|
307
|
+
"""The fill value passed to `define_array`, or `None` if the array
|
|
308
|
+
doesn't exist in this dataset or was defined without one."""
|
|
309
|
+
...
|
|
310
|
+
|
|
311
|
+
def attributes(self) -> dict[str, Any]:
|
|
312
|
+
"""All attributes as a dict."""
|
|
313
|
+
...
|
|
314
|
+
|
|
315
|
+
def set_attribute(
|
|
316
|
+
self,
|
|
317
|
+
key: str,
|
|
318
|
+
value: Any,
|
|
319
|
+
dtype: Optional[str] = None,
|
|
320
|
+
) -> None:
|
|
321
|
+
"""Set a typed attribute.
|
|
322
|
+
|
|
323
|
+
Type is inferred from the Python type by default. Pass `dtype` to force
|
|
324
|
+
a narrower variant (e.g. `dtype="int8"`).
|
|
325
|
+
"""
|
|
326
|
+
...
|
|
327
|
+
|
|
328
|
+
def get_attribute(self, key: str) -> Any:
|
|
329
|
+
"""Returns the attribute value or `None` if not set."""
|
|
330
|
+
...
|
|
331
|
+
|
|
332
|
+
def __repr__(self) -> str: ...
|
atlas/_atlas.pyd
ADDED
|
Binary file
|
atlas/py.typed
ADDED
|
File without changes
|