atlas-python 0.13.0__tar.gz → 0.13.1__tar.gz
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_python-0.13.0 → atlas_python-0.13.1}/Cargo.lock +2 -2
- {atlas_python-0.13.0 → atlas_python-0.13.1}/Cargo.toml +1 -1
- {atlas_python-0.13.0 → atlas_python-0.13.1}/PKG-INFO +1 -1
- {atlas_python-0.13.0 → atlas_python-0.13.1}/atlas-python/Cargo.toml +1 -1
- {atlas_python-0.13.0 → atlas_python-0.13.1}/atlas-python/tests/test_xarray.py +64 -0
- {atlas_python-0.13.0 → atlas_python-0.13.1}/pyproject.toml +1 -1
- {atlas_python-0.13.0 → atlas_python-0.13.1}/python/atlas/xarray.py +81 -9
- {atlas_python-0.13.0 → atlas_python-0.13.1}/.github/workflows/atlas-python-docs.yaml +0 -0
- {atlas_python-0.13.0 → atlas_python-0.13.1}/.github/workflows/atlas-python-release.yaml +0 -0
- {atlas_python-0.13.0 → atlas_python-0.13.1}/.github/workflows/atlas-rust-release.yaml +0 -0
- {atlas_python-0.13.0 → atlas_python-0.13.1}/.github/workflows/ci.yaml +0 -0
- {atlas_python-0.13.0 → atlas_python-0.13.1}/.gitignore +0 -0
- {atlas_python-0.13.0 → atlas_python-0.13.1}/CONTRIBUTING.md +0 -0
- {atlas_python-0.13.0 → atlas_python-0.13.1}/LICENSE +0 -0
- {atlas_python-0.13.0 → atlas_python-0.13.1}/README.md +0 -0
- {atlas_python-0.13.0 → atlas_python-0.13.1}/atlas-python/.python-version +0 -0
- {atlas_python-0.13.0 → atlas_python-0.13.1}/atlas-python/LICENSE +0 -0
- {atlas_python-0.13.0 → atlas_python-0.13.1}/atlas-python/README.md +0 -0
- {atlas_python-0.13.0 → atlas_python-0.13.1}/atlas-python/benchmarks/README.md +0 -0
- {atlas_python-0.13.0 → atlas_python-0.13.1}/atlas-python/benchmarks/_common.py +0 -0
- {atlas_python-0.13.0 → atlas_python-0.13.1}/atlas-python/benchmarks/bench_collection.py +0 -0
- {atlas_python-0.13.0 → atlas_python-0.13.1}/atlas-python/benchmarks/generate_bench_charts.py +0 -0
- {atlas_python-0.13.0 → atlas_python-0.13.1}/atlas-python/docs/assets/bench_gridded.svg +0 -0
- {atlas_python-0.13.0 → atlas_python-0.13.1}/atlas-python/docs/assets/bench_profile.svg +0 -0
- {atlas_python-0.13.0 → atlas_python-0.13.1}/atlas-python/docs/benchmarks.md +0 -0
- {atlas_python-0.13.0 → atlas_python-0.13.1}/atlas-python/docs/examples.md +0 -0
- {atlas_python-0.13.0 → atlas_python-0.13.1}/atlas-python/docs/guides/attributes.md +0 -0
- {atlas_python-0.13.0 → atlas_python-0.13.1}/atlas-python/docs/guides/bulk-reads.md +0 -0
- {atlas_python-0.13.0 → atlas_python-0.13.1}/atlas-python/docs/guides/cloud-storage.md +0 -0
- {atlas_python-0.13.0 → atlas_python-0.13.1}/atlas-python/docs/guides/codecs-and-meta.md +0 -0
- {atlas_python-0.13.0 → atlas_python-0.13.1}/atlas-python/docs/guides/dask.md +0 -0
- {atlas_python-0.13.0 → atlas_python-0.13.1}/atlas-python/docs/guides/datasets-and-arrays.md +0 -0
- {atlas_python-0.13.0 → atlas_python-0.13.1}/atlas-python/docs/guides/dtypes.md +0 -0
- {atlas_python-0.13.0 → atlas_python-0.13.1}/atlas-python/docs/guides/durability.md +0 -0
- {atlas_python-0.13.0 → atlas_python-0.13.1}/atlas-python/docs/guides/shared-arrays.md +0 -0
- {atlas_python-0.13.0 → atlas_python-0.13.1}/atlas-python/docs/guides/stats.md +0 -0
- {atlas_python-0.13.0 → atlas_python-0.13.1}/atlas-python/docs/guides/xarray.md +0 -0
- {atlas_python-0.13.0 → atlas_python-0.13.1}/atlas-python/docs/index.md +0 -0
- {atlas_python-0.13.0 → atlas_python-0.13.1}/atlas-python/docs/installation.md +0 -0
- {atlas_python-0.13.0 → atlas_python-0.13.1}/atlas-python/docs/quickstart.md +0 -0
- {atlas_python-0.13.0 → atlas_python-0.13.1}/atlas-python/docs/reference/atlas.md +0 -0
- {atlas_python-0.13.0 → atlas_python-0.13.1}/atlas-python/docs/reference/dataset-view.md +0 -0
- {atlas_python-0.13.0 → atlas_python-0.13.1}/atlas-python/docs/reference/xarray-accessor.md +0 -0
- {atlas_python-0.13.0 → atlas_python-0.13.1}/atlas-python/docs/vs-zarr-netcdf.md +0 -0
- {atlas_python-0.13.0 → atlas_python-0.13.1}/atlas-python/examples/01_basics.py +0 -0
- {atlas_python-0.13.0 → atlas_python-0.13.1}/atlas-python/examples/02_xarray.py +0 -0
- {atlas_python-0.13.0 → atlas_python-0.13.1}/atlas-python/examples/03_dask_streaming.py +0 -0
- {atlas_python-0.13.0 → atlas_python-0.13.1}/atlas-python/examples/04_meta_formats.py +0 -0
- {atlas_python-0.13.0 → atlas_python-0.13.1}/atlas-python/examples/05_codecs.py +0 -0
- {atlas_python-0.13.0 → atlas_python-0.13.1}/atlas-python/examples/06_stats_scan.py +0 -0
- {atlas_python-0.13.0 → atlas_python-0.13.1}/atlas-python/examples/07_shared_arrays.py +0 -0
- {atlas_python-0.13.0 → atlas_python-0.13.1}/atlas-python/examples/08_object_store.py +0 -0
- {atlas_python-0.13.0 → atlas_python-0.13.1}/atlas-python/examples/09_missing_data.py +0 -0
- {atlas_python-0.13.0 → atlas_python-0.13.1}/atlas-python/mkdocs.yml +0 -0
- {atlas_python-0.13.0 → atlas_python-0.13.1}/atlas-python/src/attr.rs +0 -0
- {atlas_python-0.13.0 → atlas_python-0.13.1}/atlas-python/src/dataset.rs +0 -0
- {atlas_python-0.13.0 → atlas_python-0.13.1}/atlas-python/src/dtype.rs +0 -0
- {atlas_python-0.13.0 → atlas_python-0.13.1}/atlas-python/src/error.rs +0 -0
- {atlas_python-0.13.0 → atlas_python-0.13.1}/atlas-python/src/lib.rs +0 -0
- {atlas_python-0.13.0 → atlas_python-0.13.1}/atlas-python/src/logging.rs +0 -0
- {atlas_python-0.13.0 → atlas_python-0.13.1}/atlas-python/src/runtime.rs +0 -0
- {atlas_python-0.13.0 → atlas_python-0.13.1}/atlas-python/src/store.rs +0 -0
- {atlas_python-0.13.0 → atlas_python-0.13.1}/atlas-python/tests/GL_PR_BO_JLKU.nc +0 -0
- {atlas_python-0.13.0 → atlas_python-0.13.1}/atlas-python/tests/test_smoke.py +0 -0
- {atlas_python-0.13.0 → atlas_python-0.13.1}/examples/lifecycle.rs +0 -0
- {atlas_python-0.13.0 → atlas_python-0.13.1}/examples/sensor_fleet.rs +0 -0
- {atlas_python-0.13.0 → atlas_python-0.13.1}/examples/weather_store.rs +0 -0
- {atlas_python-0.13.0 → atlas_python-0.13.1}/python/atlas/__init__.py +0 -0
- {atlas_python-0.13.0 → atlas_python-0.13.1}/python/atlas/__init__.pyi +0 -0
- {atlas_python-0.13.0 → atlas_python-0.13.1}/python/atlas/py.typed +0 -0
- {atlas_python-0.13.0 → atlas_python-0.13.1}/python/atlas/store.py +0 -0
- {atlas_python-0.13.0 → atlas_python-0.13.1}/src/array.rs +0 -0
- {atlas_python-0.13.0 → atlas_python-0.13.1}/src/config.rs +0 -0
- {atlas_python-0.13.0 → atlas_python-0.13.1}/src/dataset/cache.rs +0 -0
- {atlas_python-0.13.0 → atlas_python-0.13.1}/src/dataset/mod.rs +0 -0
- {atlas_python-0.13.0 → atlas_python-0.13.1}/src/dataset/pending_attrs.rs +0 -0
- {atlas_python-0.13.0 → atlas_python-0.13.1}/src/dataset/view.rs +0 -0
- {atlas_python-0.13.0 → atlas_python-0.13.1}/src/error.rs +0 -0
- {atlas_python-0.13.0 → atlas_python-0.13.1}/src/lib.rs +0 -0
- {atlas_python-0.13.0 → atlas_python-0.13.1}/src/meta/mod.rs +0 -0
- {atlas_python-0.13.0 → atlas_python-0.13.1}/src/meta/persist.rs +0 -0
- {atlas_python-0.13.0 → atlas_python-0.13.1}/src/meta/schema.rs +0 -0
- {atlas_python-0.13.0 → atlas_python-0.13.1}/src/meta/store_meta.rs +0 -0
- {atlas_python-0.13.0 → atlas_python-0.13.1}/src/meta/type_index.rs +0 -0
- {atlas_python-0.13.0 → atlas_python-0.13.1}/src/pruning/bitmap.rs +0 -0
- {atlas_python-0.13.0 → atlas_python-0.13.1}/src/pruning/column.rs +0 -0
- {atlas_python-0.13.0 → atlas_python-0.13.1}/src/pruning/format.rs +0 -0
- {atlas_python-0.13.0 → atlas_python-0.13.1}/src/pruning/io.rs +0 -0
- {atlas_python-0.13.0 → atlas_python-0.13.1}/src/pruning/mod.rs +0 -0
- {atlas_python-0.13.0 → atlas_python-0.13.1}/src/pruning/value.rs +0 -0
- {atlas_python-0.13.0 → atlas_python-0.13.1}/src/schema/array.rs +0 -0
- {atlas_python-0.13.0 → atlas_python-0.13.1}/src/schema/attr.rs +0 -0
- {atlas_python-0.13.0 → atlas_python-0.13.1}/src/schema/dtype.rs +0 -0
- {atlas_python-0.13.0 → atlas_python-0.13.1}/src/schema/mod.rs +0 -0
- {atlas_python-0.13.0 → atlas_python-0.13.1}/src/store/bulk_read.rs +0 -0
- {atlas_python-0.13.0 → atlas_python-0.13.1}/src/store/durability.rs +0 -0
- {atlas_python-0.13.0 → atlas_python-0.13.1}/src/store/mod.rs +0 -0
- {atlas_python-0.13.0 → atlas_python-0.13.1}/tests/integration.rs +0 -0
|
@@ -71,7 +71,7 @@ dependencies = [
|
|
|
71
71
|
|
|
72
72
|
[[package]]
|
|
73
73
|
name = "atlas-python"
|
|
74
|
-
version = "0.13.
|
|
74
|
+
version = "0.13.1"
|
|
75
75
|
dependencies = [
|
|
76
76
|
"atlas-rust",
|
|
77
77
|
"ndarray",
|
|
@@ -86,7 +86,7 @@ dependencies = [
|
|
|
86
86
|
|
|
87
87
|
[[package]]
|
|
88
88
|
name = "atlas-rust"
|
|
89
|
-
version = "0.13.
|
|
89
|
+
version = "0.13.1"
|
|
90
90
|
dependencies = [
|
|
91
91
|
"array-format",
|
|
92
92
|
"chrono",
|
|
@@ -5,7 +5,7 @@ members = ["atlas-python"]
|
|
|
5
5
|
# Registry name on crates.io (`atlas` is taken). The library is still
|
|
6
6
|
# imported as `atlas` — see `[lib]` below.
|
|
7
7
|
name = "atlas-rust"
|
|
8
|
-
version = "0.13.
|
|
8
|
+
version = "0.13.1"
|
|
9
9
|
edition = "2024"
|
|
10
10
|
description = "Directory-based store for thousands of N-dimensional datasets local or remote using object storage."
|
|
11
11
|
license = "Apache-2.0"
|
|
@@ -530,6 +530,70 @@ def test_datetime_nat_default_fill():
|
|
|
530
530
|
assert "_FillValue" not in ds_back["time_obs"].attrs
|
|
531
531
|
|
|
532
532
|
|
|
533
|
+
def test_timedelta_roundtrip():
|
|
534
|
+
"""timedelta64 has no native atlas type; it's stored as int64 ns + a marker
|
|
535
|
+
attribute and restored to a duration dtype on read."""
|
|
536
|
+
deltas = (
|
|
537
|
+
np.array([1, 2, 3, 4], dtype="timedelta64[s]").astype("timedelta64[ns]")
|
|
538
|
+
)
|
|
539
|
+
arr = xr.DataArray(deltas, dims=["t"], attrs={"long_name": "sampling gap"})
|
|
540
|
+
ds = xr.Dataset({"gap": arr})
|
|
541
|
+
|
|
542
|
+
with tempfile.TemporaryDirectory() as d:
|
|
543
|
+
with atlas.Atlas.create(d) as store:
|
|
544
|
+
store.add_xarray_dataset(ds, "ds")
|
|
545
|
+
|
|
546
|
+
store2 = atlas.Atlas.open(d)
|
|
547
|
+
# Stored as int64 under the hood.
|
|
548
|
+
assert store2.open_dataset("ds").array_meta("gap")["dtype"] == "int64"
|
|
549
|
+
|
|
550
|
+
ds_back = store2.open_as_xarray_dataset("ds")
|
|
551
|
+
assert ds_back["gap"].dtype == np.dtype("timedelta64[ns]")
|
|
552
|
+
np.testing.assert_array_equal(ds_back["gap"].values, deltas)
|
|
553
|
+
# The internal marker never surfaces as a user-visible attribute.
|
|
554
|
+
assert "_pyatlas_timedelta" not in ds_back["gap"].attrs
|
|
555
|
+
assert ds_back["gap"].attrs == {"long_name": "sampling gap"}
|
|
556
|
+
|
|
557
|
+
|
|
558
|
+
def test_timedelta_nat_default_fill():
|
|
559
|
+
"""A timedelta var with NaT cells defaults to a NaT fill (recorded as null),
|
|
560
|
+
round-tripping with no spurious `_FillValue`."""
|
|
561
|
+
deltas = np.array([10, "NaT", 30, "NaT"], dtype="timedelta64[ns]")
|
|
562
|
+
ds = xr.Dataset({"gap": xr.DataArray(deltas, dims=["t"])})
|
|
563
|
+
|
|
564
|
+
with tempfile.TemporaryDirectory() as d:
|
|
565
|
+
with atlas.Atlas.create(d) as store:
|
|
566
|
+
store.add_xarray_dataset(ds, "ds")
|
|
567
|
+
|
|
568
|
+
store2 = atlas.Atlas.open(d)
|
|
569
|
+
assert store2.open_dataset("ds").array_stats("gap")["null_count"] == 2
|
|
570
|
+
ds_back = store2.open_as_xarray_dataset("ds")
|
|
571
|
+
assert ds_back["gap"].dtype == np.dtype("timedelta64[ns]")
|
|
572
|
+
np.testing.assert_array_equal(ds_back["gap"].values, deltas)
|
|
573
|
+
assert "_FillValue" not in ds_back["gap"].attrs
|
|
574
|
+
|
|
575
|
+
|
|
576
|
+
def test_failed_insert_rolls_back_dataset():
|
|
577
|
+
"""An insert that fails partway (unsupported dtype after a valid var) must
|
|
578
|
+
not leave a half-written dataset that a later flush would persist."""
|
|
579
|
+
ds = xr.Dataset(
|
|
580
|
+
{
|
|
581
|
+
"ok": xr.DataArray(np.arange(4, dtype=np.float32), dims=["x"]),
|
|
582
|
+
"bad": xr.DataArray(np.array([True, False, True, False]), dims=["x"]),
|
|
583
|
+
}
|
|
584
|
+
)
|
|
585
|
+
with tempfile.TemporaryDirectory() as d:
|
|
586
|
+
with atlas.Atlas.create(d) as store:
|
|
587
|
+
with pytest.raises(NotImplementedError):
|
|
588
|
+
store.add_xarray_dataset(ds, "ds")
|
|
589
|
+
# Rolled back: the name is gone from the in-memory store, so the
|
|
590
|
+
# __exit__ flush persists nothing for it.
|
|
591
|
+
assert "ds" not in store.list_datasets()
|
|
592
|
+
|
|
593
|
+
# And nothing was persisted to disk either.
|
|
594
|
+
assert "ds" not in atlas.Atlas.open(d).list_datasets()
|
|
595
|
+
|
|
596
|
+
|
|
533
597
|
def test_missing_strings_filled_with_warning():
|
|
534
598
|
"""A string var with None/NaN cells is written after substituting the string
|
|
535
599
|
fill (default ""), and a warning is emitted naming the count."""
|
|
@@ -36,6 +36,13 @@ if TYPE_CHECKING:
|
|
|
36
36
|
_COORDS_ATTR = "_pyatlas_coords"
|
|
37
37
|
_JSON_PREFIX = "json:"
|
|
38
38
|
|
|
39
|
+
# Per-array marker attribute for variables that were `timedelta64` on the way in.
|
|
40
|
+
# Atlas has no native duration type, so a timedelta is stored as int64
|
|
41
|
+
# nanoseconds (the same int64 view datetime64 uses) and tagged with this
|
|
42
|
+
# attribute; its value is the unit to reconstruct on read. The read path pops
|
|
43
|
+
# it so it never surfaces as a user-visible attribute.
|
|
44
|
+
_TIMEDELTA_ATTR = "_pyatlas_timedelta"
|
|
45
|
+
|
|
39
46
|
_NUMPY_TO_ATLAS = {
|
|
40
47
|
np.dtype("int8"): "int8",
|
|
41
48
|
np.dtype("int16"): "int16",
|
|
@@ -54,6 +61,11 @@ _NUMPY_TO_ATLAS = {
|
|
|
54
61
|
def _np_to_atlas_dtype(np_dtype: np.dtype) -> str:
|
|
55
62
|
if np_dtype in _NUMPY_TO_ATLAS:
|
|
56
63
|
return _NUMPY_TO_ATLAS[np_dtype]
|
|
64
|
+
# timedelta64 (any unit) has no native atlas type; it's stored as int64
|
|
65
|
+
# nanoseconds and tagged with `_TIMEDELTA_ATTR` so the read path restores
|
|
66
|
+
# the duration dtype — the datetime64 parallel.
|
|
67
|
+
if np_dtype.kind == "m":
|
|
68
|
+
return "int64"
|
|
57
69
|
# Object (Python str/bytes) and fixed-size byte/unicode strings all
|
|
58
70
|
# become variable-length atlas strings.
|
|
59
71
|
if np_dtype.kind in ("O", "S", "U"):
|
|
@@ -137,10 +149,17 @@ def _normalize_fill_value(value: Any, np_dtype: np.dtype) -> Any:
|
|
|
137
149
|
if value is None:
|
|
138
150
|
return None
|
|
139
151
|
if isinstance(value, np.ndarray) and value.ndim == 0:
|
|
140
|
-
|
|
152
|
+
if np_dtype.kind == "M":
|
|
153
|
+
value = value.view(np.int64).item()
|
|
154
|
+
elif np_dtype.kind == "m": # timedelta64 -> int64 nanoseconds
|
|
155
|
+
value = value.astype("timedelta64[ns]").view(np.int64).item()
|
|
156
|
+
else:
|
|
157
|
+
value = value.item()
|
|
141
158
|
elif isinstance(value, np.generic):
|
|
142
159
|
if isinstance(value, np.datetime64):
|
|
143
160
|
return value.astype("datetime64[ns]").view(np.int64).item()
|
|
161
|
+
if isinstance(value, np.timedelta64):
|
|
162
|
+
return value.astype("timedelta64[ns]").view(np.int64).item()
|
|
144
163
|
value = value.item()
|
|
145
164
|
if np_dtype.kind in ("O", "S", "U") and isinstance(value, bytes):
|
|
146
165
|
return value.decode("utf-8", errors="replace")
|
|
@@ -187,7 +206,7 @@ def _resolve_fill_value(
|
|
|
187
206
|
|
|
188
207
|
if np_dtype.kind == "f":
|
|
189
208
|
return float("nan")
|
|
190
|
-
if np_dtype.kind
|
|
209
|
+
if np_dtype.kind in ("M", "m"): # datetime64/timedelta64 -> NaT (i64::MIN sentinel)
|
|
191
210
|
return _NAT_INT64
|
|
192
211
|
if np_dtype.kind in ("O", "S", "U"): # string -> "" sentinel for missing cells
|
|
193
212
|
return ""
|
|
@@ -330,7 +349,8 @@ def _write_xarray_to_view(
|
|
|
330
349
|
# makes the on-disk file layout predictable.
|
|
331
350
|
for var_name in coord_names + [str(n) for n in ds.data_vars.keys()]:
|
|
332
351
|
var = ds[var_name]
|
|
333
|
-
|
|
352
|
+
np_dtype = np.dtype(var.dtype)
|
|
353
|
+
atlas_dtype = _np_to_atlas_dtype(np_dtype)
|
|
334
354
|
dims = [str(d) for d in var.dims]
|
|
335
355
|
shape = [int(s) for s in var.shape]
|
|
336
356
|
|
|
@@ -353,7 +373,7 @@ def _write_xarray_to_view(
|
|
|
353
373
|
var_attrs = dict(var.attrs)
|
|
354
374
|
resolved_fill = _resolve_fill_value(
|
|
355
375
|
var_name,
|
|
356
|
-
|
|
376
|
+
np_dtype,
|
|
357
377
|
var_attrs.pop("_FillValue", None),
|
|
358
378
|
fill_value,
|
|
359
379
|
)
|
|
@@ -367,6 +387,12 @@ def _write_xarray_to_view(
|
|
|
367
387
|
fill_value=resolved_fill,
|
|
368
388
|
)
|
|
369
389
|
|
|
390
|
+
# Tag timedelta64 arrays (stored as int64 ns) so the read path can
|
|
391
|
+
# restore the duration dtype. Values are normalised to ns below, so
|
|
392
|
+
# the recorded unit is always "ns".
|
|
393
|
+
if np_dtype.kind == "m":
|
|
394
|
+
view.set_array_attribute(var_name, _TIMEDELTA_ATTR, "ns")
|
|
395
|
+
|
|
370
396
|
# For string arrays, missing (None/NaN) cells can't be stored as null,
|
|
371
397
|
# so they're substituted with the resolved string fill (default "").
|
|
372
398
|
str_fill = resolved_fill if isinstance(resolved_fill, str) else ""
|
|
@@ -379,6 +405,10 @@ def _write_xarray_to_view(
|
|
|
379
405
|
# numpy datetime64 view to int64 without copying.
|
|
380
406
|
if block.dtype.kind == "M":
|
|
381
407
|
block = block.view(np.int64)
|
|
408
|
+
# timedelta64 -> int64 nanoseconds (normalise the unit first, then
|
|
409
|
+
# a zero-copy view). Restored to a duration on read via the marker.
|
|
410
|
+
elif block.dtype.kind == "m":
|
|
411
|
+
block = block.astype("timedelta64[ns]").view(np.int64)
|
|
382
412
|
elif atlas_dtype == "string":
|
|
383
413
|
block, n = _fill_missing_strings(block, str_fill)
|
|
384
414
|
n_filled_strings += n
|
|
@@ -428,6 +458,19 @@ def _atlas_to_numpy_dtype(atlas_dtype: str) -> np.dtype:
|
|
|
428
458
|
)
|
|
429
459
|
|
|
430
460
|
|
|
461
|
+
def _as_timedelta(arr: Any, unit: str) -> Any:
|
|
462
|
+
"""View an int64 array (numpy or dask) as ``timedelta64[unit]``.
|
|
463
|
+
|
|
464
|
+
A same-itemsize reinterpretation — the on-disk int64 nanoseconds become a
|
|
465
|
+
duration without copying. Dask-backed reads are transformed lazily so the
|
|
466
|
+
chunk-by-chunk read graph is preserved.
|
|
467
|
+
"""
|
|
468
|
+
td_dtype = np.dtype(f"timedelta64[{unit}]")
|
|
469
|
+
if _is_dask_array(arr):
|
|
470
|
+
return arr.map_blocks(lambda b: b.view(td_dtype), dtype=td_dtype)
|
|
471
|
+
return np.asarray(arr).view(td_dtype)
|
|
472
|
+
|
|
473
|
+
|
|
431
474
|
def _dask_chunks_for(shape: Sequence[int], chunk_shape: Sequence[int]) -> tuple:
|
|
432
475
|
"""Per-dim chunk-length tuples in the form dask expects."""
|
|
433
476
|
chunks: list[tuple[int, ...]] = []
|
|
@@ -573,15 +616,30 @@ def _view_to_xarray(view: "DatasetView", force_lazy: bool = False) -> "xr.Datase
|
|
|
573
616
|
):
|
|
574
617
|
continue
|
|
575
618
|
meta = view.array_meta(name)
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
619
|
+
# NaT sentinel (i64::MIN) on a datetime column, or on a timedelta column
|
|
620
|
+
# stored as int64 — self-describing in the data, so skip the attr.
|
|
621
|
+
is_timedelta = _TIMEDELTA_ATTR in per_var_attrs.get(name, {})
|
|
622
|
+
if fv == _NAT_INT64 and (
|
|
623
|
+
(meta is not None and meta["dtype"] == "timestamp_nanoseconds")
|
|
624
|
+
or is_timedelta
|
|
580
625
|
):
|
|
581
626
|
continue
|
|
582
627
|
per_var_attrs.setdefault(name, {})
|
|
583
628
|
per_var_attrs[name]["_FillValue"] = fv
|
|
584
629
|
|
|
630
|
+
# Restore timedelta64 arrays: stored as int64 ns + the `_TIMEDELTA_ATTR`
|
|
631
|
+
# marker. View the int64 data back as a duration and drop the marker so it
|
|
632
|
+
# doesn't surface as a user-visible attribute (i64::MIN reads back as NaT).
|
|
633
|
+
for name in array_names:
|
|
634
|
+
unit = per_var_attrs.get(name, {}).pop(_TIMEDELTA_ATTR, None)
|
|
635
|
+
if unit is None:
|
|
636
|
+
continue
|
|
637
|
+
target = data_vars if name in data_vars else coords
|
|
638
|
+
if name not in target:
|
|
639
|
+
continue
|
|
640
|
+
dims, arr, extra = target[name]
|
|
641
|
+
target[name] = (dims, _as_timedelta(arr, str(unit)), extra)
|
|
642
|
+
|
|
585
643
|
# Inject per-var attrs into the (dims, data, attrs) triples
|
|
586
644
|
def _with_attrs(name: str, triple: tuple) -> tuple:
|
|
587
645
|
dims, arr, _ = triple
|
|
@@ -664,9 +722,23 @@ def _write_xarray_new_dataset(
|
|
|
664
722
|
|
|
665
723
|
Both `atlas.add_xarray_dataset` and the `ds.atlas.write` accessor route through
|
|
666
724
|
this function.
|
|
725
|
+
|
|
726
|
+
The write is atomic: if populating the view fails partway (e.g. an
|
|
727
|
+
unsupported dtype), the just-created dataset is rolled back with
|
|
728
|
+
`delete_dataset` so a later `flush()`/`close()` can't persist a half-written
|
|
729
|
+
record. Nothing reaches disk until flush regardless, so this only cleans up
|
|
730
|
+
the in-memory store.
|
|
667
731
|
"""
|
|
668
732
|
view = atlas.create_dataset(name)
|
|
669
|
-
|
|
733
|
+
try:
|
|
734
|
+
_write_xarray_to_view(view, ds, chunks=chunks, fill_value=fill_value)
|
|
735
|
+
except BaseException:
|
|
736
|
+
try:
|
|
737
|
+
atlas.delete_dataset(name)
|
|
738
|
+
except Exception:
|
|
739
|
+
# Best-effort rollback; surface the original failure regardless.
|
|
740
|
+
pass
|
|
741
|
+
raise
|
|
670
742
|
|
|
671
743
|
|
|
672
744
|
# --- xarray accessor ----------------------------------------------------------
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{atlas_python-0.13.0 → atlas_python-0.13.1}/atlas-python/benchmarks/generate_bench_charts.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|