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 ADDED
@@ -0,0 +1,4 @@
1
+ from ._atlas import Atlas, DatasetView, __version__, init_tracing
2
+ from . import xarray as _xarray # noqa: F401 — registers the `ds.atlas` accessor
3
+
4
+ __all__ = ["Atlas", "DatasetView", "__version__", "init_tracing"]
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