rustytree-xarray 0.3.0__tar.gz → 0.5.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.
Files changed (51) hide show
  1. rustytree_xarray-0.5.0/.github/dependabot.yml +73 -0
  2. {rustytree_xarray-0.3.0 → rustytree_xarray-0.5.0}/.github/workflows/ci.yml +7 -7
  3. {rustytree_xarray-0.3.0 → rustytree_xarray-0.5.0}/.github/workflows/release.yml +8 -8
  4. {rustytree_xarray-0.3.0 → rustytree_xarray-0.5.0}/CHANGELOG.md +178 -1
  5. {rustytree_xarray-0.3.0 → rustytree_xarray-0.5.0}/Cargo.lock +294 -140
  6. {rustytree_xarray-0.3.0 → rustytree_xarray-0.5.0}/Cargo.toml +5 -5
  7. {rustytree_xarray-0.3.0 → rustytree_xarray-0.5.0}/PKG-INFO +2 -2
  8. {rustytree_xarray-0.3.0 → rustytree_xarray-0.5.0}/docs/architecture.md +13 -2
  9. {rustytree_xarray-0.3.0 → rustytree_xarray-0.5.0}/pyproject.toml +4 -2
  10. {rustytree_xarray-0.3.0 → rustytree_xarray-0.5.0}/python/rustytree/__init__.py +1 -1
  11. {rustytree_xarray-0.3.0 → rustytree_xarray-0.5.0}/python/rustytree/_array.py +30 -3
  12. {rustytree_xarray-0.3.0 → rustytree_xarray-0.5.0}/python/rustytree/backend.py +9 -6
  13. rustytree_xarray-0.5.0/src/array.rs +752 -0
  14. rustytree_xarray-0.5.0/src/dtype_dispatch.rs +82 -0
  15. {rustytree_xarray-0.3.0 → rustytree_xarray-0.5.0}/src/lib.rs +6 -2
  16. {rustytree_xarray-0.3.0 → rustytree_xarray-0.5.0}/src/node.rs +8 -2
  17. {rustytree_xarray-0.3.0 → rustytree_xarray-0.5.0}/src/walk.rs +99 -33
  18. {rustytree_xarray-0.3.0 → rustytree_xarray-0.5.0}/tests/conftest.py +61 -0
  19. {rustytree_xarray-0.3.0 → rustytree_xarray-0.5.0}/tests/test_backend_entrypoint.py +47 -12
  20. rustytree_xarray-0.5.0/tests/test_chunks.py +238 -0
  21. {rustytree_xarray-0.3.0 → rustytree_xarray-0.5.0}/tests/test_eager_fetch.py +10 -24
  22. {rustytree_xarray-0.3.0 → rustytree_xarray-0.5.0}/tests/test_icechunk.py +9 -7
  23. {rustytree_xarray-0.3.0 → rustytree_xarray-0.5.0}/tests/test_lazy.py +20 -15
  24. {rustytree_xarray-0.3.0 → rustytree_xarray-0.5.0}/tests/test_phase1_scaffold.py +1 -1
  25. rustytree_xarray-0.5.0/tests/test_string_arrays.py +494 -0
  26. {rustytree_xarray-0.3.0 → rustytree_xarray-0.5.0}/tests/test_walk.py +25 -1
  27. rustytree_xarray-0.3.0/src/array.rs +0 -384
  28. rustytree_xarray-0.3.0/src/dtype_dispatch.rs +0 -82
  29. rustytree_xarray-0.3.0/tests/test_chunks.py +0 -98
  30. {rustytree_xarray-0.3.0 → rustytree_xarray-0.5.0}/.gitignore +0 -0
  31. {rustytree_xarray-0.3.0 → rustytree_xarray-0.5.0}/LICENSE +0 -0
  32. {rustytree_xarray-0.3.0 → rustytree_xarray-0.5.0}/README.md +0 -0
  33. {rustytree_xarray-0.3.0 → rustytree_xarray-0.5.0}/assets/logo-banner-dark.png +0 -0
  34. {rustytree_xarray-0.3.0 → rustytree_xarray-0.5.0}/assets/logo-banner-dark.svg +0 -0
  35. {rustytree_xarray-0.3.0 → rustytree_xarray-0.5.0}/assets/logo-banner-light.png +0 -0
  36. {rustytree_xarray-0.3.0 → rustytree_xarray-0.5.0}/assets/logo-banner-light.svg +0 -0
  37. {rustytree_xarray-0.3.0 → rustytree_xarray-0.5.0}/assets/logo.png +0 -0
  38. {rustytree_xarray-0.3.0 → rustytree_xarray-0.5.0}/docs/contributing.md +0 -0
  39. {rustytree_xarray-0.3.0 → rustytree_xarray-0.5.0}/docs/release-process.md +0 -0
  40. {rustytree_xarray-0.3.0 → rustytree_xarray-0.5.0}/docs/usage.md +0 -0
  41. {rustytree_xarray-0.3.0 → rustytree_xarray-0.5.0}/notebooks/klot_demo.ipynb +0 -0
  42. {rustytree_xarray-0.3.0 → rustytree_xarray-0.5.0}/src/error.rs +0 -0
  43. {rustytree_xarray-0.3.0 → rustytree_xarray-0.5.0}/src/glob.rs +0 -0
  44. {rustytree_xarray-0.3.0 → rustytree_xarray-0.5.0}/src/icechunk_store.rs +0 -0
  45. {rustytree_xarray-0.3.0 → rustytree_xarray-0.5.0}/src/py_credentials.rs +0 -0
  46. {rustytree_xarray-0.3.0 → rustytree_xarray-0.5.0}/src/runtime.rs +0 -0
  47. {rustytree_xarray-0.3.0 → rustytree_xarray-0.5.0}/src/store.rs +0 -0
  48. {rustytree_xarray-0.3.0 → rustytree_xarray-0.5.0}/src/url.rs +0 -0
  49. {rustytree_xarray-0.3.0 → rustytree_xarray-0.5.0}/tests/test_codecs.py +0 -0
  50. {rustytree_xarray-0.3.0 → rustytree_xarray-0.5.0}/tests/test_pickle.py +0 -0
  51. {rustytree_xarray-0.3.0 → rustytree_xarray-0.5.0}/tests/test_to_rust_source.py +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
@@ -24,8 +24,8 @@ jobs:
24
24
  name: ruff (lint + format)
25
25
  runs-on: ubuntu-latest
26
26
  steps:
27
- - uses: actions/checkout@v4
28
- - uses: astral-sh/setup-uv@v4
27
+ - uses: actions/checkout@v7
28
+ - uses: astral-sh/setup-uv@v7
29
29
  - run: uvx 'ruff@0.15.8' check .
30
30
  - run: uvx 'ruff@0.15.8' format --check .
31
31
 
@@ -35,11 +35,11 @@ jobs:
35
35
  name: cargo (fmt, clippy, test)
36
36
  runs-on: ubuntu-latest
37
37
  steps:
38
- - uses: actions/checkout@v4
38
+ - uses: actions/checkout@v7
39
39
  - uses: dtolnay/rust-toolchain@stable
40
40
  with:
41
41
  components: clippy, rustfmt
42
- - uses: actions/setup-python@v5
42
+ - uses: actions/setup-python@v7
43
43
  with:
44
44
  python-version: "3.12"
45
45
  - uses: Swatinem/rust-cache@v2
@@ -67,16 +67,16 @@ jobs:
67
67
  matrix:
68
68
  python-version: ["3.12"]
69
69
  steps:
70
- - uses: actions/checkout@v4
70
+ - uses: actions/checkout@v7
71
71
  - uses: dtolnay/rust-toolchain@stable
72
72
  - uses: Swatinem/rust-cache@v2
73
73
  with:
74
74
  # Keyed by Python version so each matrix cell gets its own cache.
75
75
  key: py-${{ matrix.python-version }}
76
- - uses: actions/setup-python@v5
76
+ - uses: actions/setup-python@v7
77
77
  with:
78
78
  python-version: ${{ matrix.python-version }}
79
- - uses: astral-sh/setup-uv@v4
79
+ - uses: astral-sh/setup-uv@v7
80
80
  - name: Set up venv and install maturin
81
81
  run: |
82
82
  uv venv --python "${{ matrix.python-version }}"
@@ -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@v4
51
- - uses: actions/setup-python@v5
50
+ - uses: actions/checkout@v7
51
+ - uses: actions/setup-python@v7
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@v4
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@v4
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@v4
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@v4
101
- - uses: actions/download-artifact@v4
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@v4
131
+ - uses: actions/download-artifact@v8
132
132
  with:
133
133
  path: dist
134
134
  merge-multiple: true
@@ -11,6 +11,166 @@ release, that section is renamed to `[x.y.z] - YYYY-MM-DD` and a fresh
11
11
 
12
12
  ## [Unreleased]
13
13
 
14
+ ## [0.5.0] - 2026-07-26
15
+
16
+ ### Added
17
+
18
+ - Eager-fetch small `object`-dtype (vlen `string`) scalars and self-named
19
+ 1-D coords during the walk's Phase C ([#77]). `xr.open_datatree(engine=
20
+ "rustytree")` was paying one serial lazy read per `object`-dtype variable
21
+ at open time — xarray's `_contains_datetime_like_objects` samples every
22
+ such variable to rule out `cftime.datetime`, independent of the existing
23
+ CF-time-like/dim-coord triggers Phase C already covers. Extends the walk's
24
+ parallel pre-fetch to cover this case (bounded by a 4096-element count cap
25
+ and an 8 MiB total-byte-size cap, since per-element size is unbounded for
26
+ strings unlike the existing fixed-width numeric cap), and adds the
27
+ matching `object` → declared-read-dtype cast on the eager path in
28
+ `backend.py` (mirroring the lazy path's existing cast from #71). Scoped to
29
+ scalars and self-named 1-D coords only, so an ordinary small string data
30
+ variable doesn't lose its dask laziness just because it's small. Verified
31
+ on the live `s3://nexrad-arco/KLOT` store: ~48% faster `open_datatree`
32
+ (8.223s → 4.241s mean over 4 trials), 42 serial lazy reads eliminated.
33
+
34
+ ### Changed
35
+
36
+ - Routine dependency and CI-action bumps via Dependabot ([#72], [#74],
37
+ [#75], [#76]). Cargo: `quinn-proto` 0.11.14 → 0.11.16 ([#72]); cargo-minor
38
+ group — `tokio` 1.53.0 → 1.53.1, `thiserror` 2.0.18 → 2.0.19, `async-trait`
39
+ 0.1.89 → 0.1.91, `serde` 1.0.228 → 1.0.229, `zarrs_storage` 0.4.4 → 0.4.5,
40
+ `serde_json` 1.0.150 → 1.0.151 ([#75]); `zarrs_object_store` 0.6.2 → 0.7.0
41
+ ([#76]). CI: `actions/setup-python` 6 → 7 ([#74]).
42
+
43
+ ## [0.4.0] - 2026-07-24
44
+
45
+ ### Added
46
+
47
+ - Dependabot dependency automation ([#56]). A `.github/dependabot.yml` opens
48
+ weekly grouped version-update PRs for three ecosystems — `cargo`, `pip`
49
+ (pyproject), and `github-actions` — plus repo-level vulnerability alerts and
50
+ Dependabot security updates for CVE-triggered PRs. Nothing auto-merges; every
51
+ PR is reviewed by hand. `icechunk`/`typetag` (FFI lockstep) and `ruff` (also
52
+ pinned in `ci.yml`) are intentionally ignored and stay manual, coordinated
53
+ bumps.
54
+
55
+ ### Changed
56
+
57
+ - Bump `zarrs` 0.22 → 0.23 ([#60]). A breaking upstream release: `DataType`
58
+ became a newtype over `Arc<dyn DataTypeTraits>` rather than an enum, so the
59
+ shared `for_each_supported_dtype!` dispatch is now an
60
+ `is::<Float64DataType>()` chain against the marker types in
61
+ `zarrs::array::data_type`. `ArraySubset` moved from the removed
62
+ `zarrs::array_subset` module into `zarrs::array`, and
63
+ `async_retrieve_array_subset_elements::<T>` (deprecated in 0.23, already
64
+ deleted upstream for 0.24) became `async_retrieve_array_subset::<Vec<T>>`.
65
+ The supported-dtype set, the read path, and the storage stack are otherwise
66
+ unchanged — `zarrs_storage` still resolves to a single 0.4.x, so
67
+ `zarrs_object_store` and `zarrs_icechunk` needed no bump and the icechunk
68
+ FFI lockstep is untouched.
69
+ - Fixes a dtype string that 0.23 would have silently corrupted: the walk
70
+ built `VarMeta::dtype` via `format!("{}", array.data_type())`, and 0.23's
71
+ `Display` renders *both* Zarr spellings when they differ (`float64` →
72
+ `"float64 / <f8"`), which `numpy.dtype()` rejects. Both walk sites now
73
+ route through `zarrs_dtype_to_numpy_str`, the same canonical mapper the
74
+ array handle already used.
75
+ - `zarrs_dtype_to_numpy_str` now reads the dtype's Zarr V3 name (identical
76
+ to the NumPy name for all thirteen dtypes we can name; pinned by a new
77
+ unit test) instead of maintaining a parallel hand-written table. The one
78
+ exception is `bytes`, reported as `object` to match zarr-python —
79
+ `np.dtype("bytes")` is `|S0`, which would mislabel a variable-length
80
+ binary array rather than fail. Dtypes we can neither read nor safely
81
+ rename (`string`, `numpy.datetime64`, `r32`, …) still surface as a
82
+ `TypeError` from `np.dtype()` at open, as before.
83
+
84
+ - CI: bump GitHub Actions off the deprecated Node 20 runtime ([#55], fixes #54).
85
+ `actions/checkout` v4 → v5, `actions/setup-python` v5 → v6,
86
+ `actions/upload-artifact` v4 → v6, `actions/download-artifact` v4 → v7 — the
87
+ lowest major of each that runs on Node 24 (verified via each tag's
88
+ `runs.using`; the artifact actions only moved to Node 24 at v6/v7, so a naïve
89
+ bump to v5 would still have been Node 20). No workflow behaviour change.
90
+
91
+ - Raise the NumPy floor `numpy>=1.24` → `numpy>=2.0` ([#71]). Variable-length
92
+ `string` arrays are surfaced as numpy 2's variable-width `StringDType`
93
+ (`np.dtype("T")`), which did not exist before 2.0. rustytree's other pins
94
+ (`zarr>=3`, recent `xarray`) already track numpy 2, so this only makes an
95
+ existing expectation explicit.
96
+
97
+ - Routine dependency and CI-action bumps via Dependabot ([#57], [#58], [#59],
98
+ [#64]) — the first batch after the automation ([#56]) landed. Cargo: `tokio`
99
+ 1.52.3 → 1.53.0, `futures` 0.3.32 → 0.3.33, `zarrs_storage` 0.4.3 → 0.4.4
100
+ ([#64]); `chrono` 0.4.44 → 0.4.45, `zarrs_icechunk` 0.5.0 → 0.5.1 ([#59]);
101
+ `cmov` 0.5.3 → 0.5.4 ([#57]). CI actions ([#58]): `actions/checkout` 5 → 7,
102
+ `astral-sh/setup-uv` 4 → 7, `actions/upload-artifact` 6 → 7, and
103
+ `actions/download-artifact` 7 → 8 — superseding the v5/v6/v7 pins the [#55]
104
+ entry above describes.
105
+
106
+ ### Fixed
107
+
108
+ - Zarr v3 string arrays now open and read instead of crashing ([#71], fixes
109
+ #70). Both v3 string flavours failed before: a variable-length `string` array
110
+ raised `TypeError: data type 'string' not understood` (its name reached
111
+ `np.dtype()` unmapped), and a `fixed_length_utf32` array (numpy `<U…`,
112
+ xarray's default `str` encoding) raised `NotImplementedError` at read. This
113
+ became a production regression when raw2zarr's FM301 retrofit added
114
+ `sweep_mode`/`prt_mode`/`follow_mode` as vlen-`string` scalars to the KLOT
115
+ eager store and the Serbian OSN stores, forcing consumers to fall back to the
116
+ slower `engine="zarr"`. rustytree now mirrors `engine="zarr"` for both
117
+ flavours — `xr.testing.assert_identical` holds against it for all
118
+ valid-Unicode content (2-D chunking, interior NULs, non-BMP code points,
119
+ empty strings, and unwritten-chunk fill values all included):
120
+ - **vlen `string`** is *declared* `object` but its values *materialise* as
121
+ numpy-2 `StringDType`. That declared/read split is exactly what
122
+ `engine="zarr"` produces: xarray's CF-decode flattens a
123
+ `StringDType`-declared variable's values back to `object`, so declaring
124
+ `object` up-front is the only way to keep `StringDType` values. The `numpy`
125
+ crate can only build `object` arrays across the FFI boundary, so the read
126
+ returns `object` and `RustyBackendArray` casts to the dtype the new
127
+ `ZarrsArrayHandle.read_dtype` reports.
128
+ - **`fixed_length_utf32`** → fixed-width `<U{n}` (`n` code points). zarrs 0.23
129
+ opens this dtype (0.22 could not); rustytree decodes the native-endian
130
+ UTF-32 to `str` and builds the `<U` array. One documented divergence: lone
131
+ surrogate code points (U+D800–U+DFFF, malformed Unicode) are dropped rather
132
+ than preserved, because the decode goes through Rust `char`.
133
+ - `slice_nd`'s bound relaxed from `T: Copy` to `T: Clone` so the same slicer
134
+ serves the `String` buffers; zero-cost for the numeric arms.
135
+
136
+ - An empty selection no longer returns a stray element or panics ([#68], fixes
137
+ #65). `isel(time=slice(1, 1))` and friends returned one bogus element whenever
138
+ the offset was not a multiple of the chunk size, and raised
139
+ `pyo3_runtime.PanicException` when the selection sat at the end of a ragged
140
+ final chunk (e.g. `slice(13, 13)` on a shape-13 array with chunks of 4).
141
+ Raggedness was needed only for the panic — the wrong length happened on any
142
+ non-chunk-aligned offset, including on arrays whose shape divides evenly.
143
+ `slice_nd`'s walk was a do-while that pushed an element before it could test
144
+ the bounds; it is now driven by the output element count, so an empty request
145
+ runs zero iterations and totality is structural rather than a guard. The panic
146
+ mattered beyond the wrong length: `PanicException` derives from
147
+ `BaseException`, so it passed straight through ordinary `except Exception:`
148
+ handlers. Empty selections also short-circuit before the chunk-alignment step
149
+ now, so they no longer pull a chunk off the network to return nothing.
150
+
151
+ - A reversed slice is empty rather than an error ([#68]). `v.isel(n=slice(5, 3))`
152
+ and `v[5:3]` raised `ValueError: start 5 > stop 3`, where numpy, pandas and
153
+ `engine="zarr"` all return an empty array. `slice.indices()` reports a reversed
154
+ slice faithfully as `(5, 3, 1)`, and that pair reached `read_subset` unchanged;
155
+ the Python adapter now normalises it. `read_subset` keeps its stricter
156
+ `start <= stop` contract, so a genuinely malformed range still raises.
157
+
158
+ - Tests: the two opt-in KTWX smoke tests now skip when the directory exists but
159
+ holds no icechunk repository ([#60]). The guard tested `KTWX_PATH.exists()`,
160
+ so an empty leftover directory failed the guard's purpose and the tests
161
+ errored with `KeyError: group / not found in store` instead of skipping. The
162
+ shared `ktwx_repo_available()` helper in `tests/conftest.py` now mirrors
163
+ rustytree's own detector (`looks_like_icechunk_repo`): a `repo` manifest file
164
+ plus a `snapshots/` directory.
165
+
166
+ ### Security
167
+
168
+ - Bump `pyo3` and `numpy` 0.28 → 0.29 ([#61]) to fix a **high**-severity
169
+ out-of-bounds read in PyO3's `PyIterator` `nth`/`nth_back` (RUSTSEC/GHSA)
170
+ plus a medium-severity missing `Sync` bound on `PyCFunction::new_closure`.
171
+ A clean bump — no source changes; the pyo3/numpy majors move in lockstep.
172
+ (Surfaced by the newly-enabled Dependabot security alerts.)
173
+
14
174
  ## [0.3.0] - 2026-07-04
15
175
 
16
176
  ### Added
@@ -704,7 +864,9 @@ below.
704
864
  intentionally not normalized — relative-vs-absolute glob
705
865
  semantics differ in `PurePosixPath.match`.
706
866
 
707
- [Unreleased]: https://github.com/aladinor/rustytree/compare/v0.3.0...HEAD
867
+ [Unreleased]: https://github.com/aladinor/rustytree/compare/v0.5.0...HEAD
868
+ [0.5.0]: https://github.com/aladinor/rustytree/compare/v0.4.0...v0.5.0
869
+ [0.4.0]: https://github.com/aladinor/rustytree/compare/v0.3.0...v0.4.0
708
870
  [0.3.0]: https://github.com/aladinor/rustytree/compare/v0.2.1...v0.3.0
709
871
  [0.2.1]: https://github.com/aladinor/rustytree/compare/v0.2.0...v0.2.1
710
872
  [0.2.0]: https://github.com/aladinor/rustytree/compare/v0.1.0...v0.2.0
@@ -742,3 +904,18 @@ below.
742
904
  [#49]: https://github.com/aladinor/rustytree/pull/49
743
905
  [#50]: https://github.com/aladinor/rustytree/pull/50
744
906
  [#52]: https://github.com/aladinor/rustytree/pull/52
907
+ [#55]: https://github.com/aladinor/rustytree/pull/55
908
+ [#56]: https://github.com/aladinor/rustytree/pull/56
909
+ [#57]: https://github.com/aladinor/rustytree/pull/57
910
+ [#58]: https://github.com/aladinor/rustytree/pull/58
911
+ [#59]: https://github.com/aladinor/rustytree/pull/59
912
+ [#60]: https://github.com/aladinor/rustytree/pull/60
913
+ [#61]: https://github.com/aladinor/rustytree/pull/61
914
+ [#64]: https://github.com/aladinor/rustytree/pull/64
915
+ [#68]: https://github.com/aladinor/rustytree/pull/68
916
+ [#71]: https://github.com/aladinor/rustytree/pull/71
917
+ [#72]: https://github.com/aladinor/rustytree/pull/72
918
+ [#74]: https://github.com/aladinor/rustytree/pull/74
919
+ [#75]: https://github.com/aladinor/rustytree/pull/75
920
+ [#76]: https://github.com/aladinor/rustytree/pull/76
921
+ [#77]: https://github.com/aladinor/rustytree/pull/77