rustytree-xarray 0.2.1__tar.gz → 0.4.0__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.
- rustytree_xarray-0.4.0/.github/dependabot.yml +73 -0
- {rustytree_xarray-0.2.1 → rustytree_xarray-0.4.0}/.github/workflows/ci.yml +35 -8
- {rustytree_xarray-0.2.1 → rustytree_xarray-0.4.0}/.github/workflows/release.yml +8 -8
- {rustytree_xarray-0.2.1 → rustytree_xarray-0.4.0}/CHANGELOG.md +327 -1
- {rustytree_xarray-0.2.1 → rustytree_xarray-0.4.0}/Cargo.lock +262 -109
- {rustytree_xarray-0.2.1 → rustytree_xarray-0.4.0}/Cargo.toml +34 -5
- {rustytree_xarray-0.2.1 → rustytree_xarray-0.4.0}/PKG-INFO +6 -5
- {rustytree_xarray-0.2.1 → rustytree_xarray-0.4.0}/README.md +1 -1
- {rustytree_xarray-0.2.1 → rustytree_xarray-0.4.0}/docs/architecture.md +13 -2
- {rustytree_xarray-0.2.1 → rustytree_xarray-0.4.0}/docs/usage.md +67 -10
- {rustytree_xarray-0.2.1 → rustytree_xarray-0.4.0}/notebooks/klot_demo.ipynb +289 -299
- {rustytree_xarray-0.2.1 → rustytree_xarray-0.4.0}/pyproject.toml +20 -4
- {rustytree_xarray-0.2.1 → rustytree_xarray-0.4.0}/python/rustytree/__init__.py +1 -1
- {rustytree_xarray-0.2.1 → rustytree_xarray-0.4.0}/python/rustytree/_array.py +20 -2
- {rustytree_xarray-0.2.1 → rustytree_xarray-0.4.0}/python/rustytree/backend.py +128 -57
- rustytree_xarray-0.4.0/src/array.rs +740 -0
- rustytree_xarray-0.4.0/src/dtype_dispatch.rs +82 -0
- {rustytree_xarray-0.2.1 → rustytree_xarray-0.4.0}/src/error.rs +0 -7
- {rustytree_xarray-0.2.1 → rustytree_xarray-0.4.0}/src/icechunk_store.rs +5 -0
- {rustytree_xarray-0.2.1 → rustytree_xarray-0.4.0}/src/lib.rs +52 -11
- {rustytree_xarray-0.2.1 → rustytree_xarray-0.4.0}/src/node.rs +4 -1
- rustytree_xarray-0.4.0/src/py_credentials.rs +393 -0
- {rustytree_xarray-0.2.1 → rustytree_xarray-0.4.0}/src/runtime.rs +10 -0
- {rustytree_xarray-0.2.1 → rustytree_xarray-0.4.0}/src/store.rs +96 -1
- {rustytree_xarray-0.2.1 → rustytree_xarray-0.4.0}/src/walk.rs +67 -14
- {rustytree_xarray-0.2.1 → rustytree_xarray-0.4.0}/tests/conftest.py +42 -3
- {rustytree_xarray-0.2.1 → rustytree_xarray-0.4.0}/tests/test_backend_entrypoint.py +239 -82
- rustytree_xarray-0.4.0/tests/test_chunks.py +238 -0
- rustytree_xarray-0.4.0/tests/test_codecs.py +80 -0
- {rustytree_xarray-0.2.1 → rustytree_xarray-0.4.0}/tests/test_eager_fetch.py +6 -9
- {rustytree_xarray-0.2.1 → rustytree_xarray-0.4.0}/tests/test_icechunk.py +48 -7
- {rustytree_xarray-0.2.1 → rustytree_xarray-0.4.0}/tests/test_lazy.py +20 -15
- {rustytree_xarray-0.2.1 → rustytree_xarray-0.4.0}/tests/test_phase1_scaffold.py +1 -1
- rustytree_xarray-0.4.0/tests/test_pickle.py +137 -0
- rustytree_xarray-0.4.0/tests/test_string_arrays.py +335 -0
- {rustytree_xarray-0.2.1 → rustytree_xarray-0.4.0}/tests/test_to_rust_source.py +84 -1
- {rustytree_xarray-0.2.1 → rustytree_xarray-0.4.0}/tests/test_walk.py +25 -1
- rustytree_xarray-0.2.1/src/array.rs +0 -300
- rustytree_xarray-0.2.1/src/dtype_dispatch.rs +0 -82
- rustytree_xarray-0.2.1/tests/test_chunks.py +0 -98
- {rustytree_xarray-0.2.1 → rustytree_xarray-0.4.0}/.gitignore +0 -0
- {rustytree_xarray-0.2.1 → rustytree_xarray-0.4.0}/LICENSE +0 -0
- {rustytree_xarray-0.2.1 → rustytree_xarray-0.4.0}/assets/logo-banner-dark.png +0 -0
- {rustytree_xarray-0.2.1 → rustytree_xarray-0.4.0}/assets/logo-banner-dark.svg +0 -0
- {rustytree_xarray-0.2.1 → rustytree_xarray-0.4.0}/assets/logo-banner-light.png +0 -0
- {rustytree_xarray-0.2.1 → rustytree_xarray-0.4.0}/assets/logo-banner-light.svg +0 -0
- {rustytree_xarray-0.2.1 → rustytree_xarray-0.4.0}/assets/logo.png +0 -0
- {rustytree_xarray-0.2.1 → rustytree_xarray-0.4.0}/docs/contributing.md +0 -0
- {rustytree_xarray-0.2.1 → rustytree_xarray-0.4.0}/docs/release-process.md +0 -0
- {rustytree_xarray-0.2.1 → rustytree_xarray-0.4.0}/src/glob.rs +0 -0
- {rustytree_xarray-0.2.1 → rustytree_xarray-0.4.0}/src/url.rs +0 -0
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# Dependabot version updates. Opens PRs on a schedule; nothing auto-merges —
|
|
2
|
+
# every PR is reviewed and merged by hand (including the grouped github-actions
|
|
3
|
+
# one). Security updates (CVE-triggered PRs) are enabled separately at the repo
|
|
4
|
+
# level, independent of this schedule.
|
|
5
|
+
#
|
|
6
|
+
# Deliberately NOT managed here (bumped by hand — coordinated, cross-manifest):
|
|
7
|
+
# - icechunk / typetag: FFI lockstep. The Python `icechunk` (pyproject) must
|
|
8
|
+
# share a minor with the Rust `icechunk` (`=2.1.0`) and `typetag`
|
|
9
|
+
# (`=0.2.22`) the crate links — the msgpack `Session` bytes format is
|
|
10
|
+
# coupled across the boundary, so a solo bump breaks `Session::from_bytes`
|
|
11
|
+
# (the 2.0.5→2.0.6 skew behind #46). Bump all three together manually.
|
|
12
|
+
# - ruff: pinned in pyproject AND hardcoded in ci.yml (`uvx ruff@0.15.8`),
|
|
13
|
+
# which Dependabot can't edit; a solo bump desyncs the lint gate and can
|
|
14
|
+
# churn formatting. Bump pyproject + both ci.yml literals together.
|
|
15
|
+
# - dtolnay/rust-toolchain@stable, pypa/gh-action-pypi-publish@release/v1:
|
|
16
|
+
# branch refs, not semver tags — Dependabot leaves them alone (no rule
|
|
17
|
+
# needed).
|
|
18
|
+
version: 2
|
|
19
|
+
|
|
20
|
+
updates:
|
|
21
|
+
# GitHub Actions — the `uses:` version pins in .github/workflows/* (exactly
|
|
22
|
+
# the manual toil of #54). One grouped PR per run.
|
|
23
|
+
- package-ecosystem: github-actions
|
|
24
|
+
directory: "/"
|
|
25
|
+
schedule:
|
|
26
|
+
interval: weekly
|
|
27
|
+
labels: ["dependencies"]
|
|
28
|
+
commit-message:
|
|
29
|
+
prefix: ci
|
|
30
|
+
groups:
|
|
31
|
+
actions:
|
|
32
|
+
patterns: ["*"]
|
|
33
|
+
|
|
34
|
+
# Rust crates (Cargo.toml / Cargo.lock).
|
|
35
|
+
- package-ecosystem: cargo
|
|
36
|
+
directory: "/"
|
|
37
|
+
schedule:
|
|
38
|
+
interval: weekly
|
|
39
|
+
labels: ["dependencies"]
|
|
40
|
+
commit-message:
|
|
41
|
+
prefix: deps
|
|
42
|
+
open-pull-requests-limit: 5
|
|
43
|
+
groups:
|
|
44
|
+
# Keep the zarrs family (zarrs, zarrs_storage, zarrs_object_store,
|
|
45
|
+
# zarrs_icechunk) moving together — they share a release cadence.
|
|
46
|
+
zarrs:
|
|
47
|
+
patterns: ["zarrs*"]
|
|
48
|
+
# pyo3 and numpy track matched 0.x minors (pyo3 0.28 ↔ numpy 0.28).
|
|
49
|
+
pyo3:
|
|
50
|
+
patterns: ["pyo3", "numpy"]
|
|
51
|
+
# Everything else: fold minor/patch into one PR; majors get their own.
|
|
52
|
+
cargo-minor:
|
|
53
|
+
update-types: ["minor", "patch"]
|
|
54
|
+
ignore:
|
|
55
|
+
- dependency-name: "icechunk" # FFI lockstep — bump by hand (see header)
|
|
56
|
+
- dependency-name: "typetag" # FFI lockstep — bump by hand (see header)
|
|
57
|
+
|
|
58
|
+
# Python deps (pyproject.toml: project deps + the `dev` extra).
|
|
59
|
+
- package-ecosystem: pip
|
|
60
|
+
directory: "/"
|
|
61
|
+
schedule:
|
|
62
|
+
interval: weekly
|
|
63
|
+
labels: ["dependencies"]
|
|
64
|
+
commit-message:
|
|
65
|
+
prefix: deps
|
|
66
|
+
open-pull-requests-limit: 5
|
|
67
|
+
groups:
|
|
68
|
+
# Fold minor/patch into one PR; majors get their own.
|
|
69
|
+
python:
|
|
70
|
+
update-types: ["minor", "patch"]
|
|
71
|
+
ignore:
|
|
72
|
+
- dependency-name: "icechunk" # FFI lockstep — bump by hand (see header)
|
|
73
|
+
- dependency-name: "ruff" # also hardcoded in ci.yml — bump by hand
|
|
@@ -15,19 +15,45 @@ env:
|
|
|
15
15
|
RUSTFLAGS: -D warnings
|
|
16
16
|
|
|
17
17
|
jobs:
|
|
18
|
-
#
|
|
18
|
+
# Python lint + format gate. Fast and dependency-free (ruff is a standalone
|
|
19
|
+
# binary; no extension build needed), so it runs in parallel with the cargo
|
|
20
|
+
# and pytest jobs and fails fast on style drift. The pinned version must
|
|
21
|
+
# match the `ruff` pin in pyproject's `dev` extra so local `ruff format`
|
|
22
|
+
# and this check agree.
|
|
23
|
+
lint:
|
|
24
|
+
name: ruff (lint + format)
|
|
25
|
+
runs-on: ubuntu-latest
|
|
26
|
+
steps:
|
|
27
|
+
- uses: actions/checkout@v7
|
|
28
|
+
- uses: astral-sh/setup-uv@v7
|
|
29
|
+
- run: uvx 'ruff@0.15.8' check .
|
|
30
|
+
- run: uvx 'ruff@0.15.8' format --check .
|
|
31
|
+
|
|
32
|
+
# Cargo gates run once on stable Rust. `cargo test` links libpython (see the
|
|
33
|
+
# test step), so Python is set up here too.
|
|
19
34
|
cargo:
|
|
20
35
|
name: cargo (fmt, clippy, test)
|
|
21
36
|
runs-on: ubuntu-latest
|
|
22
37
|
steps:
|
|
23
|
-
- uses: actions/checkout@
|
|
38
|
+
- uses: actions/checkout@v7
|
|
24
39
|
- uses: dtolnay/rust-toolchain@stable
|
|
25
40
|
with:
|
|
26
41
|
components: clippy, rustfmt
|
|
42
|
+
- uses: actions/setup-python@v6
|
|
43
|
+
with:
|
|
44
|
+
python-version: "3.12"
|
|
27
45
|
- uses: Swatinem/rust-cache@v2
|
|
28
46
|
- run: cargo fmt --check
|
|
29
47
|
- run: cargo clippy --all-targets --all-features --locked -- -D warnings
|
|
30
|
-
-
|
|
48
|
+
# Build tests WITHOUT `extension-module` so PyO3 links libpython: the
|
|
49
|
+
# credential-fetcher shim's `#[typetag::serde]` impls are force-retained
|
|
50
|
+
# by `inventory` and call `Python::attach`, so the test binary references
|
|
51
|
+
# libpython symbols that can't be dead-code-eliminated (they can under
|
|
52
|
+
# `extension-module`, which suppresses libpython linking for the cdylib).
|
|
53
|
+
- name: cargo test
|
|
54
|
+
run: cargo test --locked --no-default-features
|
|
55
|
+
env:
|
|
56
|
+
LD_LIBRARY_PATH: ${{ env.pythonLocation }}/lib
|
|
31
57
|
|
|
32
58
|
# Python integration: build the cdylib via maturin and run pytest against
|
|
33
59
|
# the supported interpreter range. Each matrix cell rebuilds the extension
|
|
@@ -41,16 +67,16 @@ jobs:
|
|
|
41
67
|
matrix:
|
|
42
68
|
python-version: ["3.12"]
|
|
43
69
|
steps:
|
|
44
|
-
- uses: actions/checkout@
|
|
70
|
+
- uses: actions/checkout@v7
|
|
45
71
|
- uses: dtolnay/rust-toolchain@stable
|
|
46
72
|
- uses: Swatinem/rust-cache@v2
|
|
47
73
|
with:
|
|
48
74
|
# Keyed by Python version so each matrix cell gets its own cache.
|
|
49
75
|
key: py-${{ matrix.python-version }}
|
|
50
|
-
- uses: actions/setup-python@
|
|
76
|
+
- uses: actions/setup-python@v6
|
|
51
77
|
with:
|
|
52
78
|
python-version: ${{ matrix.python-version }}
|
|
53
|
-
- uses: astral-sh/setup-uv@
|
|
79
|
+
- uses: astral-sh/setup-uv@v7
|
|
54
80
|
- name: Set up venv and install maturin
|
|
55
81
|
run: |
|
|
56
82
|
uv venv --python "${{ matrix.python-version }}"
|
|
@@ -59,8 +85,9 @@ jobs:
|
|
|
59
85
|
# Install the [dev] extras directly rather than `pip install -e ".[dev]"`,
|
|
60
86
|
# which would re-trigger a full cdylib rebuild on top of maturin develop
|
|
61
87
|
# below (we'd build the wheel twice — saves ~80s per matrix cell).
|
|
62
|
-
#
|
|
63
|
-
|
|
88
|
+
# These are the deps pytest needs; ruff is not among them — linting runs
|
|
89
|
+
# in the dedicated `lint` job above.
|
|
90
|
+
run: uv pip install --python .venv/bin/python pytest pytest-cov 'zarr>=3.0' 'icechunk>=2.1.0,<2.2' 'dask[distributed]>=2024.1'
|
|
64
91
|
- name: Build extension (maturin develop)
|
|
65
92
|
run: .venv/bin/maturin develop
|
|
66
93
|
- name: pytest
|
|
@@ -47,8 +47,8 @@ jobs:
|
|
|
47
47
|
python-version: ["3.12", "3.13"]
|
|
48
48
|
runs-on: ${{ matrix.platform.os }}
|
|
49
49
|
steps:
|
|
50
|
-
- uses: actions/checkout@
|
|
51
|
-
- uses: actions/setup-python@
|
|
50
|
+
- uses: actions/checkout@v7
|
|
51
|
+
- uses: actions/setup-python@v6
|
|
52
52
|
with:
|
|
53
53
|
python-version: ${{ matrix.python-version }}
|
|
54
54
|
- uses: dtolnay/rust-toolchain@stable
|
|
@@ -61,7 +61,7 @@ jobs:
|
|
|
61
61
|
target: ${{ matrix.platform.target }}
|
|
62
62
|
manylinux: ${{ matrix.platform.manylinux }}
|
|
63
63
|
args: --release --strip --out dist -i python${{ matrix.python-version }}
|
|
64
|
-
- uses: actions/upload-artifact@
|
|
64
|
+
- uses: actions/upload-artifact@v7
|
|
65
65
|
with:
|
|
66
66
|
name: wheel-${{ matrix.platform.target }}-py${{ matrix.python-version }}
|
|
67
67
|
path: dist/*.whl
|
|
@@ -71,12 +71,12 @@ jobs:
|
|
|
71
71
|
name: sdist
|
|
72
72
|
runs-on: ubuntu-latest
|
|
73
73
|
steps:
|
|
74
|
-
- uses: actions/checkout@
|
|
74
|
+
- uses: actions/checkout@v7
|
|
75
75
|
- uses: PyO3/maturin-action@v1
|
|
76
76
|
with:
|
|
77
77
|
command: sdist
|
|
78
78
|
args: --out dist
|
|
79
|
-
- uses: actions/upload-artifact@
|
|
79
|
+
- uses: actions/upload-artifact@v7
|
|
80
80
|
with:
|
|
81
81
|
name: sdist
|
|
82
82
|
path: dist/*.tar.gz
|
|
@@ -97,8 +97,8 @@ jobs:
|
|
|
97
97
|
group: release-${{ github.ref }}
|
|
98
98
|
cancel-in-progress: false
|
|
99
99
|
steps:
|
|
100
|
-
- uses: actions/checkout@
|
|
101
|
-
- uses: actions/download-artifact@
|
|
100
|
+
- uses: actions/checkout@v7
|
|
101
|
+
- uses: actions/download-artifact@v8
|
|
102
102
|
with:
|
|
103
103
|
path: dist
|
|
104
104
|
merge-multiple: true
|
|
@@ -128,7 +128,7 @@ jobs:
|
|
|
128
128
|
permissions:
|
|
129
129
|
id-token: write # mint short-lived OIDC tokens for trusted publishing
|
|
130
130
|
steps:
|
|
131
|
-
- uses: actions/download-artifact@
|
|
131
|
+
- uses: actions/download-artifact@v8
|
|
132
132
|
with:
|
|
133
133
|
path: dist
|
|
134
134
|
merge-multiple: true
|
|
@@ -11,6 +11,311 @@ release, that section is renamed to `[x.y.z] - YYYY-MM-DD` and a fresh
|
|
|
11
11
|
|
|
12
12
|
## [Unreleased]
|
|
13
13
|
|
|
14
|
+
## [0.4.0] - 2026-07-24
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
|
|
18
|
+
- Dependabot dependency automation ([#56]). A `.github/dependabot.yml` opens
|
|
19
|
+
weekly grouped version-update PRs for three ecosystems — `cargo`, `pip`
|
|
20
|
+
(pyproject), and `github-actions` — plus repo-level vulnerability alerts and
|
|
21
|
+
Dependabot security updates for CVE-triggered PRs. Nothing auto-merges; every
|
|
22
|
+
PR is reviewed by hand. `icechunk`/`typetag` (FFI lockstep) and `ruff` (also
|
|
23
|
+
pinned in `ci.yml`) are intentionally ignored and stay manual, coordinated
|
|
24
|
+
bumps.
|
|
25
|
+
|
|
26
|
+
### Changed
|
|
27
|
+
|
|
28
|
+
- Bump `zarrs` 0.22 → 0.23 ([#60]). A breaking upstream release: `DataType`
|
|
29
|
+
became a newtype over `Arc<dyn DataTypeTraits>` rather than an enum, so the
|
|
30
|
+
shared `for_each_supported_dtype!` dispatch is now an
|
|
31
|
+
`is::<Float64DataType>()` chain against the marker types in
|
|
32
|
+
`zarrs::array::data_type`. `ArraySubset` moved from the removed
|
|
33
|
+
`zarrs::array_subset` module into `zarrs::array`, and
|
|
34
|
+
`async_retrieve_array_subset_elements::<T>` (deprecated in 0.23, already
|
|
35
|
+
deleted upstream for 0.24) became `async_retrieve_array_subset::<Vec<T>>`.
|
|
36
|
+
The supported-dtype set, the read path, and the storage stack are otherwise
|
|
37
|
+
unchanged — `zarrs_storage` still resolves to a single 0.4.x, so
|
|
38
|
+
`zarrs_object_store` and `zarrs_icechunk` needed no bump and the icechunk
|
|
39
|
+
FFI lockstep is untouched.
|
|
40
|
+
- Fixes a dtype string that 0.23 would have silently corrupted: the walk
|
|
41
|
+
built `VarMeta::dtype` via `format!("{}", array.data_type())`, and 0.23's
|
|
42
|
+
`Display` renders *both* Zarr spellings when they differ (`float64` →
|
|
43
|
+
`"float64 / <f8"`), which `numpy.dtype()` rejects. Both walk sites now
|
|
44
|
+
route through `zarrs_dtype_to_numpy_str`, the same canonical mapper the
|
|
45
|
+
array handle already used.
|
|
46
|
+
- `zarrs_dtype_to_numpy_str` now reads the dtype's Zarr V3 name (identical
|
|
47
|
+
to the NumPy name for all thirteen dtypes we can name; pinned by a new
|
|
48
|
+
unit test) instead of maintaining a parallel hand-written table. The one
|
|
49
|
+
exception is `bytes`, reported as `object` to match zarr-python —
|
|
50
|
+
`np.dtype("bytes")` is `|S0`, which would mislabel a variable-length
|
|
51
|
+
binary array rather than fail. Dtypes we can neither read nor safely
|
|
52
|
+
rename (`string`, `numpy.datetime64`, `r32`, …) still surface as a
|
|
53
|
+
`TypeError` from `np.dtype()` at open, as before.
|
|
54
|
+
|
|
55
|
+
- CI: bump GitHub Actions off the deprecated Node 20 runtime ([#55], fixes #54).
|
|
56
|
+
`actions/checkout` v4 → v5, `actions/setup-python` v5 → v6,
|
|
57
|
+
`actions/upload-artifact` v4 → v6, `actions/download-artifact` v4 → v7 — the
|
|
58
|
+
lowest major of each that runs on Node 24 (verified via each tag's
|
|
59
|
+
`runs.using`; the artifact actions only moved to Node 24 at v6/v7, so a naïve
|
|
60
|
+
bump to v5 would still have been Node 20). No workflow behaviour change.
|
|
61
|
+
|
|
62
|
+
- Raise the NumPy floor `numpy>=1.24` → `numpy>=2.0` ([#71]). Variable-length
|
|
63
|
+
`string` arrays are surfaced as numpy 2's variable-width `StringDType`
|
|
64
|
+
(`np.dtype("T")`), which did not exist before 2.0. rustytree's other pins
|
|
65
|
+
(`zarr>=3`, recent `xarray`) already track numpy 2, so this only makes an
|
|
66
|
+
existing expectation explicit.
|
|
67
|
+
|
|
68
|
+
- Routine dependency and CI-action bumps via Dependabot ([#57], [#58], [#59],
|
|
69
|
+
[#64]) — the first batch after the automation ([#56]) landed. Cargo: `tokio`
|
|
70
|
+
1.52.3 → 1.53.0, `futures` 0.3.32 → 0.3.33, `zarrs_storage` 0.4.3 → 0.4.4
|
|
71
|
+
([#64]); `chrono` 0.4.44 → 0.4.45, `zarrs_icechunk` 0.5.0 → 0.5.1 ([#59]);
|
|
72
|
+
`cmov` 0.5.3 → 0.5.4 ([#57]). CI actions ([#58]): `actions/checkout` 5 → 7,
|
|
73
|
+
`astral-sh/setup-uv` 4 → 7, `actions/upload-artifact` 6 → 7, and
|
|
74
|
+
`actions/download-artifact` 7 → 8 — superseding the v5/v6/v7 pins the [#55]
|
|
75
|
+
entry above describes.
|
|
76
|
+
|
|
77
|
+
### Fixed
|
|
78
|
+
|
|
79
|
+
- Zarr v3 string arrays now open and read instead of crashing ([#71], fixes
|
|
80
|
+
#70). Both v3 string flavours failed before: a variable-length `string` array
|
|
81
|
+
raised `TypeError: data type 'string' not understood` (its name reached
|
|
82
|
+
`np.dtype()` unmapped), and a `fixed_length_utf32` array (numpy `<U…`,
|
|
83
|
+
xarray's default `str` encoding) raised `NotImplementedError` at read. This
|
|
84
|
+
became a production regression when raw2zarr's FM301 retrofit added
|
|
85
|
+
`sweep_mode`/`prt_mode`/`follow_mode` as vlen-`string` scalars to the KLOT
|
|
86
|
+
eager store and the Serbian OSN stores, forcing consumers to fall back to the
|
|
87
|
+
slower `engine="zarr"`. rustytree now mirrors `engine="zarr"` for both
|
|
88
|
+
flavours — `xr.testing.assert_identical` holds against it for all
|
|
89
|
+
valid-Unicode content (2-D chunking, interior NULs, non-BMP code points,
|
|
90
|
+
empty strings, and unwritten-chunk fill values all included):
|
|
91
|
+
- **vlen `string`** is *declared* `object` but its values *materialise* as
|
|
92
|
+
numpy-2 `StringDType`. That declared/read split is exactly what
|
|
93
|
+
`engine="zarr"` produces: xarray's CF-decode flattens a
|
|
94
|
+
`StringDType`-declared variable's values back to `object`, so declaring
|
|
95
|
+
`object` up-front is the only way to keep `StringDType` values. The `numpy`
|
|
96
|
+
crate can only build `object` arrays across the FFI boundary, so the read
|
|
97
|
+
returns `object` and `RustyBackendArray` casts to the dtype the new
|
|
98
|
+
`ZarrsArrayHandle.read_dtype` reports.
|
|
99
|
+
- **`fixed_length_utf32`** → fixed-width `<U{n}` (`n` code points). zarrs 0.23
|
|
100
|
+
opens this dtype (0.22 could not); rustytree decodes the native-endian
|
|
101
|
+
UTF-32 to `str` and builds the `<U` array. One documented divergence: lone
|
|
102
|
+
surrogate code points (U+D800–U+DFFF, malformed Unicode) are dropped rather
|
|
103
|
+
than preserved, because the decode goes through Rust `char`.
|
|
104
|
+
- `slice_nd`'s bound relaxed from `T: Copy` to `T: Clone` so the same slicer
|
|
105
|
+
serves the `String` buffers; zero-cost for the numeric arms.
|
|
106
|
+
|
|
107
|
+
- An empty selection no longer returns a stray element or panics ([#68], fixes
|
|
108
|
+
#65). `isel(time=slice(1, 1))` and friends returned one bogus element whenever
|
|
109
|
+
the offset was not a multiple of the chunk size, and raised
|
|
110
|
+
`pyo3_runtime.PanicException` when the selection sat at the end of a ragged
|
|
111
|
+
final chunk (e.g. `slice(13, 13)` on a shape-13 array with chunks of 4).
|
|
112
|
+
Raggedness was needed only for the panic — the wrong length happened on any
|
|
113
|
+
non-chunk-aligned offset, including on arrays whose shape divides evenly.
|
|
114
|
+
`slice_nd`'s walk was a do-while that pushed an element before it could test
|
|
115
|
+
the bounds; it is now driven by the output element count, so an empty request
|
|
116
|
+
runs zero iterations and totality is structural rather than a guard. The panic
|
|
117
|
+
mattered beyond the wrong length: `PanicException` derives from
|
|
118
|
+
`BaseException`, so it passed straight through ordinary `except Exception:`
|
|
119
|
+
handlers. Empty selections also short-circuit before the chunk-alignment step
|
|
120
|
+
now, so they no longer pull a chunk off the network to return nothing.
|
|
121
|
+
|
|
122
|
+
- A reversed slice is empty rather than an error ([#68]). `v.isel(n=slice(5, 3))`
|
|
123
|
+
and `v[5:3]` raised `ValueError: start 5 > stop 3`, where numpy, pandas and
|
|
124
|
+
`engine="zarr"` all return an empty array. `slice.indices()` reports a reversed
|
|
125
|
+
slice faithfully as `(5, 3, 1)`, and that pair reached `read_subset` unchanged;
|
|
126
|
+
the Python adapter now normalises it. `read_subset` keeps its stricter
|
|
127
|
+
`start <= stop` contract, so a genuinely malformed range still raises.
|
|
128
|
+
|
|
129
|
+
- Tests: the two opt-in KTWX smoke tests now skip when the directory exists but
|
|
130
|
+
holds no icechunk repository ([#60]). The guard tested `KTWX_PATH.exists()`,
|
|
131
|
+
so an empty leftover directory failed the guard's purpose and the tests
|
|
132
|
+
errored with `KeyError: group / not found in store` instead of skipping. The
|
|
133
|
+
shared `ktwx_repo_available()` helper in `tests/conftest.py` now mirrors
|
|
134
|
+
rustytree's own detector (`looks_like_icechunk_repo`): a `repo` manifest file
|
|
135
|
+
plus a `snapshots/` directory.
|
|
136
|
+
|
|
137
|
+
### Security
|
|
138
|
+
|
|
139
|
+
- Bump `pyo3` and `numpy` 0.28 → 0.29 ([#61]) to fix a **high**-severity
|
|
140
|
+
out-of-bounds read in PyO3's `PyIterator` `nth`/`nth_back` (RUSTSEC/GHSA)
|
|
141
|
+
plus a medium-severity missing `Sync` bound on `PyCFunction::new_closure`.
|
|
142
|
+
A clean bump — no source changes; the pyo3/numpy majors move in lockstep.
|
|
143
|
+
(Surfaced by the newly-enabled Dependabot security alerts.)
|
|
144
|
+
|
|
145
|
+
## [0.3.0] - 2026-07-04
|
|
146
|
+
|
|
147
|
+
### Added
|
|
148
|
+
|
|
149
|
+
- `group_filter` keyword for `open_datatree` ([#49]). An
|
|
150
|
+
`fnmatch`-style glob matched against every group path — only matching groups
|
|
151
|
+
(plus their ancestors and the root, so the tree stays connected) are loaded.
|
|
152
|
+
Mirrors xarray PR [pydata/xarray#11302](https://github.com/pydata/xarray/pull/11302):
|
|
153
|
+
matching follows `PurePosixPath.match` (right-anchored), so
|
|
154
|
+
`group_filter="*/sweep_0"` opens the lowest sweep of every volume, and group
|
|
155
|
+
names that literally contain glob metacharacters are reachable via character-
|
|
156
|
+
class escapes (`[*]`, `[?]`, `[[]`). Mutually exclusive with `group`. The Rust
|
|
157
|
+
walk prunes non-matching subtrees up front (conservative prefix predicate);
|
|
158
|
+
Python's `_filter_by_glob` remains the source of truth. `open_dataset` rejects
|
|
159
|
+
`group_filter` (a single Dataset has no defined target for a multi-match glob).
|
|
160
|
+
|
|
161
|
+
- Docs: a "Distributed compute (`dask.distributed` / Coiled)" section in
|
|
162
|
+
`docs/usage.md` ([#46]) covering the requirements for a remote cluster
|
|
163
|
+
(`spawn` workers, matching `rustytree` + `icechunk` in the worker environment)
|
|
164
|
+
and — mirroring icechunk's own guidance — how to avoid shipping secrets to
|
|
165
|
+
workers: use `from_env` / `refreshable` / `anonymous` credentials (secret-free
|
|
166
|
+
in the task graph) rather than `static` keys. Verified end-to-end that a
|
|
167
|
+
`from_env` session opened via rustytree pickles with no secret in the graph and
|
|
168
|
+
still computes on a distributed cluster.
|
|
169
|
+
|
|
170
|
+
- Picklable array handles for `dask.distributed` ([#44], fixes #44).
|
|
171
|
+
DataArrays opened with `engine="rustytree"` could not be computed under a
|
|
172
|
+
`dask.distributed` cluster — `.compute()` failed while distributed pickled
|
|
173
|
+
the task graph, because each lazy chunk held a native
|
|
174
|
+
`ZarrsArrayHandle` that raised `TypeError: cannot pickle
|
|
175
|
+
'rustytree._rustytree.ZarrsArrayHandle' object`. `ZarrsArrayHandle` now
|
|
176
|
+
implements `__reduce__`: for stores opened via an icechunk `Session` (both
|
|
177
|
+
local-filesystem and remote S3, the cases in #44) the handle carries the
|
|
178
|
+
session's own `as_bytes()` msgpack plus the array path, and a worker revives
|
|
179
|
+
it via icechunk's `Session::from_bytes` + `Array::async_open`. This mirrors
|
|
180
|
+
icechunk exactly — rustytree adds **no** credential handling of its own, so
|
|
181
|
+
credential exposure is precisely icechunk's: `from_env` / `anonymous` sessions
|
|
182
|
+
carry no secret into the task graph, while **static** credentials (and a
|
|
183
|
+
refreshable session's scattered `initial` creds) are embedded in the session
|
|
184
|
+
bytes and travel to every worker in the pickled graph — the same as
|
|
185
|
+
distributing an icechunk Session directly. Opening through an icechunk Session
|
|
186
|
+
with `from_env` / `anonymous` / `refreshable` credentials is the recommended
|
|
187
|
+
distributed pattern. Workers must use the `spawn` start-method (the
|
|
188
|
+
`dask.distributed` default); rustytree's tokio runtime is not fork-safe, so
|
|
189
|
+
fork-based multiprocessing after opening a store is unsupported. Vanilla
|
|
190
|
+
`s3://` / local Zarr stores are not yet picklable (no icechunk Session to
|
|
191
|
+
mirror) and now raise a clear, actionable error at pickle time instead of the
|
|
192
|
+
opaque default. New `tests/test_pickle.py` covers the pickle round-trip
|
|
193
|
+
(including deeply-nested arrays and multi-hop re-pickling), the not-picklable
|
|
194
|
+
and corrupt-state error paths, and an opt-in `distributed`-marked
|
|
195
|
+
`LocalCluster` compute.
|
|
196
|
+
|
|
197
|
+
- `numcodecs.zlib` codec support ([#41], fixes #42). Enables the `zlib`
|
|
198
|
+
feature on the `zarrs` dependency so rustytree can decode arrays whose
|
|
199
|
+
codec pipeline uses the non-standard `numcodecs.`-namespace codecs
|
|
200
|
+
zarr-python writes — e.g. the public `earthmover-public/goes-16`
|
|
201
|
+
arraylake dataset (and the ismip6 icechunk store reported in #42, where
|
|
202
|
+
enabling it gave a ~10× speedup over `engine="zarr"`), which chain
|
|
203
|
+
`bytes → numcodecs.shuffle → numcodecs.zlib`. zarrs's `zlib` codec is
|
|
204
|
+
documented byte-compatible
|
|
205
|
+
with zarr-python's; `numcodecs.shuffle` is always compiled, and
|
|
206
|
+
`gzip` / `blosc` / `zstd` / `crc32c` already come from zarrs's
|
|
207
|
+
default features. Verified bit-for-bit against zarr-python on a
|
|
208
|
+
GOES-16 `CMI_C01` slice. New non-gated `tests/test_codecs.py` writes
|
|
209
|
+
the shuffle+zlib pipeline to a local store and checks the decode
|
|
210
|
+
round-trips.
|
|
211
|
+
|
|
212
|
+
### Changed
|
|
213
|
+
|
|
214
|
+
- CI now runs `ruff check` and `ruff format --check` in a dedicated `lint` job,
|
|
215
|
+
and `ruff` is pinned (`>=0.15.8,<0.16`) in the `dev` extra so local and CI
|
|
216
|
+
formatting agree ([#50], fixes #48). Previously CI installed `ruff` but never
|
|
217
|
+
invoked it, so lint/format drift accumulated silently on `main`; this normalises
|
|
218
|
+
the drifted files (`backend.py`, `conftest.py`, `test_backend_entrypoint.py`,
|
|
219
|
+
`test_codecs.py`, `klot_demo.ipynb`) in one no-logic-change pass and gates
|
|
220
|
+
against recurrence. No runtime behaviour change.
|
|
221
|
+
|
|
222
|
+
- **Breaking:** `group=` is now exact-path only and no longer auto-detects glob
|
|
223
|
+
patterns ([#49]). Previously a `group=` value containing `*`, `?`, or `[`
|
|
224
|
+
silently switched to glob filtering; that overloaded behaviour is removed in
|
|
225
|
+
favour of the explicit `group_filter=` (see Added). This aligns with the final
|
|
226
|
+
design of xarray PR [pydata/xarray#11302](https://github.com/pydata/xarray/pull/11302),
|
|
227
|
+
which rejected the ambiguous overloaded form because group names may legitimately
|
|
228
|
+
contain glob metacharacters. Migrate `open_datatree(group="*/sweep_0")` to
|
|
229
|
+
`open_datatree(group_filter="*/sweep_0")`; `group` and `group_filter` are
|
|
230
|
+
mutually exclusive (passing both raises `ValueError`). A `group=` value with
|
|
231
|
+
glob characters is now looked up as a literal path.
|
|
232
|
+
|
|
233
|
+
- Docs: tidy the `notebooks/klot_demo.ipynb` demo — trim WHAT-narrating comments
|
|
234
|
+
and add an `xradar` prerequisite note ([#47]). No code change.
|
|
235
|
+
|
|
236
|
+
- Bump the pinned `icechunk` from 2.0.5 to 2.1.0 ([#46]). rustytree links the
|
|
237
|
+
`icechunk` Rust crate and round-trips sessions through
|
|
238
|
+
`Session::{as,from}_bytes`, so it can only open stores whose on-disk format
|
|
239
|
+
matches its pinned icechunk — 2.0.5 could not open repositories written by
|
|
240
|
+
icechunk 2.0.6+, failing in `Repository::open` with "the repository doesn't
|
|
241
|
+
exist" even though the store was present and the credentials valid. Bumping to
|
|
242
|
+
2.1.0 restores access to current icechunk stores; `typetag` moves to `=0.2.22`
|
|
243
|
+
to keep sharing icechunk's `inventory` credential-fetcher registry (the #41
|
|
244
|
+
`py_credentials` shim). The `dev` extra and CI now require
|
|
245
|
+
`icechunk>=2.1.0,<2.2` so the Python and Rust icechunk versions share a minor —
|
|
246
|
+
the msgpack session-bytes format is coupled across the FFI boundary, and a skew
|
|
247
|
+
is exactly what produced the "repository doesn't exist" failure. No rustytree
|
|
248
|
+
source changes were needed (the API surface is unchanged). Verified end-to-end
|
|
249
|
+
against a real credentialed AWS S3 icechunk 2.0.6 store: parity with
|
|
250
|
+
`engine="zarr"`, pickle round-trip, and a distributed `LocalCluster` compute
|
|
251
|
+
whose workers reopen the store from the pickled session.
|
|
252
|
+
|
|
253
|
+
### Fixed
|
|
254
|
+
|
|
255
|
+
- Unified the missing-group error type across backends ([#52], fixes #51).
|
|
256
|
+
A literal `group=` path that doesn't exist now raises `KeyError` on both
|
|
257
|
+
vanilla Zarr and icechunk stores — previously vanilla raised `RuntimeError`
|
|
258
|
+
(from the Rust walk) while icechunk raised `KeyError`, so callers couldn't
|
|
259
|
+
`except` one type. The Rust walk now maps zarrs' `GroupCreateError::MissingMetadata`
|
|
260
|
+
to `RustytreeError::NotFound` (→ `PyKeyError`); genuine storage/IO/corruption
|
|
261
|
+
errors still surface as `RuntimeError`.
|
|
262
|
+
|
|
263
|
+
- `open_dataset` and `open_datatree` now validate the `group`/`group_filter`
|
|
264
|
+
pair consistently ([#52]). Passing both to `open_dataset` raises the same
|
|
265
|
+
`ValueError` (mutually exclusive) as `open_datatree` instead of a
|
|
266
|
+
`NotImplementedError`; the shared validator (renamed
|
|
267
|
+
`_check_group_filter_mutex` → `_validate_group_filter` since it also rejects
|
|
268
|
+
an empty `group_filter`) runs first on both entry points.
|
|
269
|
+
|
|
270
|
+
- Docs and the `klot_demo` notebook still used the removed glob-`group=`
|
|
271
|
+
form (`group="*/sweep_0"`) that [#49] turned into a literal-path lookup —
|
|
272
|
+
the examples would now raise instead of filtering. Migrated `README.md`,
|
|
273
|
+
`docs/usage.md`, and `notebooks/klot_demo.ipynb` to `group_filter=`
|
|
274
|
+
([#50]).
|
|
275
|
+
|
|
276
|
+
- Spurious `SerializationWarning: variable '...' has multiple fill values`
|
|
277
|
+
and silently-broken masking for stores whose `_FillValue` is written in
|
|
278
|
+
raw Zarr wire form ([#43]). Some virtual/icechunk stores (e.g. the
|
|
279
|
+
source.coop ISMIP6 dataset) carry `_FillValue` as a base64-encoded
|
|
280
|
+
string (`'AAAAgB2vFUQ='`) alongside a numeric `missing_value`; xarray's
|
|
281
|
+
CF mask coder saw two distinct sentinels for the same value and warned,
|
|
282
|
+
and the base64 string masked nothing (a float array never equals a
|
|
283
|
+
`str`), so masking quietly fell back to `missing_value` alone.
|
|
284
|
+
`_RustyDataStore.get_variables` now mirrors xarray's zarr backend
|
|
285
|
+
(`ZarrStore.open_store_variable`): a base64 `str`/`bytes` `_FillValue`
|
|
286
|
+
is decoded to a numeric sentinel via `FillValueCoder.decode`, while an
|
|
287
|
+
already-numeric fill is left untouched. Verified byte-for-byte against
|
|
288
|
+
`engine="zarr"` on the ISMIP6 store (warnings 28 → 0, identical
|
|
289
|
+
`_FillValue` encoding and masking). Non-numeric list/tuple wire forms
|
|
290
|
+
(e.g. a complex `[real, imag]` fill) have no decoder branch and pass
|
|
291
|
+
through unchanged rather than raising. Network-free regression tests
|
|
292
|
+
cover the base64 decode, numeric pass-through, and list pass-through.
|
|
293
|
+
|
|
294
|
+
- Open arraylake / Earthmover icechunk sessions ([#41], fixes #40).
|
|
295
|
+
`xr.open_datatree(session.store, engine="rustytree")` raised
|
|
296
|
+
`ValueError: icechunk session: unknown error: unknown variant
|
|
297
|
+
PythonCredentialsFetcher, there are no variants` for sessions
|
|
298
|
+
created via arraylake. Such sessions store S3 credentials as
|
|
299
|
+
`Refreshable(Arc<dyn S3CredentialsFetcher>)` whose concrete fetcher
|
|
300
|
+
is `#[typetag::serde]`-registered only inside icechunk-python's
|
|
301
|
+
cdylib; rustytree links the vanilla `icechunk` crate, whose fetcher
|
|
302
|
+
registry is empty, so `Session::from_bytes` couldn't resolve the tag.
|
|
303
|
+
`xr.open_zarr` is unaffected because it uses the live in-process
|
|
304
|
+
store and never round-trips through bytes. New `src/py_credentials.rs`
|
|
305
|
+
re-registers `PythonCredentialsFetcher` (S3 / GCS / Azure) inside
|
|
306
|
+
rustytree's cdylib, mirroring icechunk-python's `get()`: serve the
|
|
307
|
+
scattered `initial` static credentials while fresh, else acquire the
|
|
308
|
+
GIL and re-run the embedded pickled credential callable to refresh —
|
|
309
|
+
so the common case and `scatter_initial_credentials=False` /
|
|
310
|
+
mid-walk credential expiry all keep working. `typetag` is pinned
|
|
311
|
+
`=0.2.21` to share icechunk's `inventory` registry. A Python-side
|
|
312
|
+
friendly error (`_rust_open_or_explain` in `backend.py`) is a safety
|
|
313
|
+
net for a future icechunk-python that renames the fetcher. CI now
|
|
314
|
+
builds tests with `--no-default-features` so PyO3 links libpython:
|
|
315
|
+
the inventory-retained fetcher impls reference `Python::attach`, so
|
|
316
|
+
their libpython symbols can no longer be dead-code-eliminated from
|
|
317
|
+
the test binary.
|
|
318
|
+
|
|
14
319
|
## [0.2.1] - 2026-05-23
|
|
15
320
|
|
|
16
321
|
### Fixed
|
|
@@ -530,7 +835,10 @@ below.
|
|
|
530
835
|
intentionally not normalized — relative-vs-absolute glob
|
|
531
836
|
semantics differ in `PurePosixPath.match`.
|
|
532
837
|
|
|
533
|
-
[Unreleased]: https://github.com/aladinor/rustytree/compare/v0.
|
|
838
|
+
[Unreleased]: https://github.com/aladinor/rustytree/compare/v0.4.0...HEAD
|
|
839
|
+
[0.4.0]: https://github.com/aladinor/rustytree/compare/v0.3.0...v0.4.0
|
|
840
|
+
[0.3.0]: https://github.com/aladinor/rustytree/compare/v0.2.1...v0.3.0
|
|
841
|
+
[0.2.1]: https://github.com/aladinor/rustytree/compare/v0.2.0...v0.2.1
|
|
534
842
|
[0.2.0]: https://github.com/aladinor/rustytree/compare/v0.1.0...v0.2.0
|
|
535
843
|
[0.1.0]: https://github.com/aladinor/rustytree/releases/tag/v0.1.0
|
|
536
844
|
[#1]: https://github.com/aladinor/rustytree/pull/1
|
|
@@ -558,3 +866,21 @@ below.
|
|
|
558
866
|
[#25]: https://github.com/aladinor/rustytree/pull/25
|
|
559
867
|
[#26]: https://github.com/aladinor/rustytree/pull/26
|
|
560
868
|
[#27]: https://github.com/aladinor/rustytree/pull/27
|
|
869
|
+
[#41]: https://github.com/aladinor/rustytree/pull/41
|
|
870
|
+
[#43]: https://github.com/aladinor/rustytree/pull/43
|
|
871
|
+
[#44]: https://github.com/aladinor/rustytree/pull/44
|
|
872
|
+
[#46]: https://github.com/aladinor/rustytree/pull/46
|
|
873
|
+
[#47]: https://github.com/aladinor/rustytree/pull/47
|
|
874
|
+
[#49]: https://github.com/aladinor/rustytree/pull/49
|
|
875
|
+
[#50]: https://github.com/aladinor/rustytree/pull/50
|
|
876
|
+
[#52]: https://github.com/aladinor/rustytree/pull/52
|
|
877
|
+
[#55]: https://github.com/aladinor/rustytree/pull/55
|
|
878
|
+
[#56]: https://github.com/aladinor/rustytree/pull/56
|
|
879
|
+
[#57]: https://github.com/aladinor/rustytree/pull/57
|
|
880
|
+
[#58]: https://github.com/aladinor/rustytree/pull/58
|
|
881
|
+
[#59]: https://github.com/aladinor/rustytree/pull/59
|
|
882
|
+
[#60]: https://github.com/aladinor/rustytree/pull/60
|
|
883
|
+
[#61]: https://github.com/aladinor/rustytree/pull/61
|
|
884
|
+
[#64]: https://github.com/aladinor/rustytree/pull/64
|
|
885
|
+
[#68]: https://github.com/aladinor/rustytree/pull/68
|
|
886
|
+
[#71]: https://github.com/aladinor/rustytree/pull/71
|