worldarray 0.5.0__tar.gz → 0.6.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 (37) hide show
  1. {worldarray-0.5.0 → worldarray-0.6.0}/CHANGELOG.md +17 -0
  2. {worldarray-0.5.0 → worldarray-0.6.0}/PKG-INFO +4 -3
  3. {worldarray-0.5.0 → worldarray-0.6.0}/README.md +3 -2
  4. {worldarray-0.5.0 → worldarray-0.6.0}/docs/spec.md +29 -10
  5. {worldarray-0.5.0 → worldarray-0.6.0}/pyproject.toml +1 -1
  6. {worldarray-0.5.0 → worldarray-0.6.0}/src/worldarray/__init__.py +1 -1
  7. {worldarray-0.5.0 → worldarray-0.6.0}/src/worldarray/context.py +36 -26
  8. {worldarray-0.5.0 → worldarray-0.6.0}/tests/test_access.py +20 -0
  9. {worldarray-0.5.0 → worldarray-0.6.0}/tests/test_write.py +2 -0
  10. {worldarray-0.5.0 → worldarray-0.6.0}/uv.lock +1 -1
  11. {worldarray-0.5.0 → worldarray-0.6.0}/.github/workflows/ci.yml +0 -0
  12. {worldarray-0.5.0 → worldarray-0.6.0}/.github/workflows/docs.yml +0 -0
  13. {worldarray-0.5.0 → worldarray-0.6.0}/.github/workflows/publish.yml +0 -0
  14. {worldarray-0.5.0 → worldarray-0.6.0}/.gitignore +0 -0
  15. {worldarray-0.5.0 → worldarray-0.6.0}/LICENSE +0 -0
  16. {worldarray-0.5.0 → worldarray-0.6.0}/docs/changelog.md +0 -0
  17. {worldarray-0.5.0 → worldarray-0.6.0}/docs/index.md +0 -0
  18. {worldarray-0.5.0 → worldarray-0.6.0}/mkdocs.yml +0 -0
  19. {worldarray-0.5.0 → worldarray-0.6.0}/src/worldarray/array.py +0 -0
  20. {worldarray-0.5.0 → worldarray-0.6.0}/src/worldarray/geometry.py +0 -0
  21. {worldarray-0.5.0 → worldarray-0.6.0}/src/worldarray/grid.py +0 -0
  22. {worldarray-0.5.0 → worldarray-0.6.0}/src/worldarray/levels.py +0 -0
  23. {worldarray-0.5.0 → worldarray-0.6.0}/src/worldarray/placement.py +0 -0
  24. {worldarray-0.5.0 → worldarray-0.6.0}/src/worldarray/resample.py +0 -0
  25. {worldarray-0.5.0 → worldarray-0.6.0}/src/worldarray/retry.py +0 -0
  26. {worldarray-0.5.0 → worldarray-0.6.0}/src/worldarray/store.py +0 -0
  27. {worldarray-0.5.0 → worldarray-0.6.0}/src/worldarray/units.py +0 -0
  28. {worldarray-0.5.0 → worldarray-0.6.0}/src/worldarray/write.py +0 -0
  29. {worldarray-0.5.0 → worldarray-0.6.0}/tests/conftest.py +0 -0
  30. {worldarray-0.5.0 → worldarray-0.6.0}/tests/fixtures/metadata.json +0 -0
  31. {worldarray-0.5.0 → worldarray-0.6.0}/tests/fixtures/write.json +0 -0
  32. {worldarray-0.5.0 → worldarray-0.6.0}/tests/test_geometry.py +0 -0
  33. {worldarray-0.5.0 → worldarray-0.6.0}/tests/test_grid.py +0 -0
  34. {worldarray-0.5.0 → worldarray-0.6.0}/tests/test_lost_wakeup.py +0 -0
  35. {worldarray-0.5.0 → worldarray-0.6.0}/tests/test_metadata.py +0 -0
  36. {worldarray-0.5.0 → worldarray-0.6.0}/tests/test_read.py +0 -0
  37. {worldarray-0.5.0 → worldarray-0.6.0}/tests/test_resample.py +0 -0
@@ -1,5 +1,22 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.6.0 (2026-10-23)
4
+
5
+ One new argument, at chunkmirage's request. No placement change and no unit table change;
6
+ nothing changes for a caller that does not pass it.
7
+
8
+ - `wa.shared_context(cache_bytes, threads=)`: `None` (the default) limits the decoding and
9
+ file I/O pools to `thread_budget()` as in 0.5, a positive number limits them to that many,
10
+ and `0` writes no limit, so tensorstore sizes them itself (every CPU on the node). Each
11
+ `(cache_bytes, threads)` is one watched context for the process, so a server can share
12
+ one cache pool across every store it opens at the thread count it chose, without building
13
+ and watching its own context. A pickled handle on such a context comes back on the same
14
+ one; `threads=None` still lets the other process size by its own job.
15
+ - Documented what a pool of one thread costs (spec Behaviour 7, `capped_context_spec`):
16
+ nothing on one-chunk blocks; on 8-chunk blocks, half the speed of 4 in a two-slot job
17
+ but only 7 to 19% slower in a one-slot one (c-elegans on /nrs), so a worker per slot
18
+ loses little at the default.
19
+
3
20
  ## 0.5.0 (2026-10-23)
4
21
 
5
22
  Two bugs that hung or crashed cellmap-analyze 0.6 on /nrs (salamander's and c-elegans'
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: worldarray
3
- Version: 0.5.0
3
+ Version: 0.6.0
4
4
  Summary: Read zarr, N5 and Neuroglancer precomputed volumes in world coordinates through tensorstore: correct voxel placement, pyramid levels, padded region reads, resampling. numpy + tensorstore only.
5
5
  Project-URL: Homepage, https://janeliascicomp.github.io/worldarray/
6
6
  Project-URL: Documentation, https://janeliascicomp.github.io/worldarray/
@@ -208,8 +208,9 @@ wa.open_pyramid(path, ...) -> Pyramid # .names .levels .by_name() .sele
208
208
  wa.metadata(path, level=None, units=None, open=True) -> ArrayMeta # open=False: from documents alone
209
209
  wa.detect(path, missing_ok=False) -> Node # format, array or group, raw attrs
210
210
  wa.open_tensorstore(path, level=None, context=None)
211
- wa.shared_context(cache_bytes=0) ; wa.capped_context(cache_bytes=0, concurrency=1, network=True) ; wa.Retry(...)
212
- wa.thread_budget() # the shared context's thread limit: OMP_NUM_THREADS, the job's slots, or CPUs
211
+ wa.shared_context(cache_bytes=0, threads=None) ; wa.capped_context(cache_bytes=0, concurrency=1, network=True) ; wa.Retry(...)
212
+ wa.thread_budget() # the shared context's default thread limit: OMP_NUM_THREADS, the job's slots, or CPUs;
213
+ # threads=N limits it to N, threads=0 leaves tensorstore's own pool sizes
213
214
  wa.list_chunk_keys(path, separator=".") # chunk indices from keys alone, no metadata needed
214
215
  wa.Box ; wa.units.TABLE
215
216
 
@@ -149,8 +149,9 @@ wa.open_pyramid(path, ...) -> Pyramid # .names .levels .by_name() .sele
149
149
  wa.metadata(path, level=None, units=None, open=True) -> ArrayMeta # open=False: from documents alone
150
150
  wa.detect(path, missing_ok=False) -> Node # format, array or group, raw attrs
151
151
  wa.open_tensorstore(path, level=None, context=None)
152
- wa.shared_context(cache_bytes=0) ; wa.capped_context(cache_bytes=0, concurrency=1, network=True) ; wa.Retry(...)
153
- wa.thread_budget() # the shared context's thread limit: OMP_NUM_THREADS, the job's slots, or CPUs
152
+ wa.shared_context(cache_bytes=0, threads=None) ; wa.capped_context(cache_bytes=0, concurrency=1, network=True) ; wa.Retry(...)
153
+ wa.thread_budget() # the shared context's default thread limit: OMP_NUM_THREADS, the job's slots, or CPUs;
154
+ # threads=N limits it to N, threads=0 leaves tensorstore's own pool sizes
154
155
  wa.list_chunk_keys(path, separator=".") # chunk indices from keys alone, no metadata needed
155
156
  wa.Box ; wa.units.TABLE
156
157
 
@@ -185,7 +185,8 @@ wa.open_tensorstore(path, level=None, context=None) -> tensorstore.TensorStore
185
185
  wa.corner_to_centre(corner, voxel_size, axes=None) ; wa.default_axes(ndim) ; wa.index_range(begin, end, voxel_size, translation)
186
186
  wa.units.TABLE # the unit tables, public: {"length": {...metres per unit}, "time": {...seconds per unit}}
187
187
  wa.select_level(levels, voxel_size, mode) ; wa.Box ; wa.Retry(max_attempts=10, timeout=None, base_delay=1.0)
188
- wa.shared_context(cache_bytes=0) ; wa.capped_context(cache_bytes=0, concurrency=1, network=True) ; wa.capped_context_spec(...)
188
+ wa.shared_context(cache_bytes=0, threads=None) ; wa.capped_context(cache_bytes=0, concurrency=1, network=True) ; wa.capped_context_spec(...)
189
+ # threads: None = thread_budget(), N = that many, 0 = tensorstore's own pool sizes (0.6)
189
190
  # network=False caps decoding and local file I/O only, leaving tensorstore's request pools alone
190
191
  wa.list_chunk_keys(path, separator=".", ndim=None) # chunk indices present under a path, from its keys alone: no
191
192
  # metadata document needed, so a directory of chunk files with no .zarray lists too
@@ -229,9 +230,11 @@ returned one would raise on inputs cellmap-analyze reads every day. A consumer t
229
230
  in integer Rois builds them from `corner`, `voxel_size` and `shape` in its own subclass,
230
231
  where it also chooses the integer scale factor that makes the corner whole.
231
232
 
232
- `shared_context(cache_bytes)` is one `tensorstore.Context` per budget for the process, so
233
- the levels of a pyramid and repeat opens share one cache pool; the default budget is 0. Its
234
- decoding and file I/O pools are sized by `thread_budget()` (0.5, Behaviour 7).
233
+ `shared_context(cache_bytes, threads=None)` is one `tensorstore.Context` per budget and
234
+ thread limit for the process, so the levels of a pyramid and repeat opens share one cache
235
+ pool; the default budget is 0. Its decoding and file I/O pools are sized by
236
+ `thread_budget()` (0.5), or by `threads` when given, `0` leaving tensorstore's own sizes
237
+ (0.6, Behaviour 7).
235
238
  `capped_context` is mesh-n-bone's worker-process context (every tensorstore concurrency
236
239
  resource limited, cache off); it returns a **new** context per call, as those workers
237
240
  inline the spec into every open today, and `capped_context_spec` exports the dict for
@@ -684,13 +687,27 @@ this document and that one differ, fix one of them.
684
687
  task (a 256^3 block write took 2.1 s against 1.3 s uncapped) but not a job of many
685
688
  workers, whose throughput its cores bound either way (four writers: 1.8 s against
686
689
  1.9 s). A process that wants more threads than its job has slots, a server under
687
- `bsub -n 1` say, asks for more slots or passes its own context (decided 2026-10-23,
688
- over chunkmirage's objection, once the watchdog had removed the hang risk).
690
+ `bsub -n 1` say, asks for more slots or says so: `shared_context(cache_bytes,
691
+ threads=N)` limits both pools to `N`, and `threads=0` writes no limit, leaving
692
+ tensorstore's own sizes (decided 2026-10-23, over chunkmirage's objection, once the
693
+ watchdog had removed the hang risk; the argument is 0.6, at chunkmirage's request, so
694
+ its server keeps one watched context per cache budget for every store it opens
695
+ instead of building its own). Each `(cache_bytes, threads)` is one context for the
696
+ process, made on first use and watched; `threads=None` and the number
697
+ `thread_budget()` returns are two contexts, as a pickle carries `None` for the other
698
+ process to size by its own job and a number as that number.
689
699
  `capped_context` sets `data_copy_concurrency`, `file_io_concurrency`,
690
700
  `gcs_request_concurrency`, `s3_request_concurrency` and `http_request_concurrency` to
691
701
  `concurrency` and the cache to `cache_bytes`, and returns a new context each call, so two
692
702
  handles never share one capped pool unless the caller passes the same object twice; a
693
- `concurrency` of 1 is safe under Behaviour 6's watchdog. Both are plain tensorstore contexts; a caller may pass its own, at construction or through
703
+ `concurrency` of 1 is safe under Behaviour 6's watchdog, but a read or write spanning
704
+ several chunks then handles them one at a time. On /nrs, from one process alternating
705
+ limits op by op (c-elegans, 2026-10-23): a one-chunk block (128^3 uint64) read in 18 to
706
+ 19 ms and wrote in 51 ms at both 1 and 4. An 8-chunk block (256^3, what a downsampling
707
+ step reads) read in 157 ms against 73 and wrote in 312 ms against 158 in a two-slot
708
+ job, but in a one-slot job read in 147 ms against 119 and wrote in 325 ms against 302
709
+ (and that job still used 1.3 cores, LSF not pinning it). Most of the gain is a core the
710
+ worker was not given, so a worker per slot, as a dask job runs, loses little at 1. Both are plain tensorstore contexts; a caller may pass its own, at construction or through
694
711
  `reopen(context=)` when a long-lived worker changes its concurrency (cellmap-analyze
695
712
  reopens tensorstore on every read today for that reason).
696
713
 
@@ -769,8 +786,8 @@ Behaviours 10 to 16 are 0.2; 17 to 19 are 0.3.
769
786
  unchanged, and a handle pickles by its arguments and its context's spec, so each worker
770
787
  process opens its own cheaply (a local path goes resolved against the working directory
771
788
  at open, so a worker that changes directory before unpickling, as cellmap-analyze's dask
772
- workers do, opens the same store; `reopen` uses it too), on the shared context when the handle had one, else on
773
- a context rebuilt once per process from the same spec, so a capped context stays
789
+ workers do, opens the same store; `reopen` uses it too), on the shared context when the handle had one (the same
790
+ `threads`, and the same cache budget), else on a context rebuilt once per process from the same spec, so a capped context stays
774
791
  capped (mesh-n-bone's per-process cache and thread caps).
775
792
 
776
793
  12. **Group attributes belong to everyone.** The package rewrites only the `multiscales`
@@ -1000,7 +1017,9 @@ nothing resolves) cancelled and retried, the warning naming `TimeoutError`, and
1000
1017
  after the last attempt; a capped context and a caller's context passed to an open both
1001
1018
  kicked by the watchdog, a dead context no longer watched, and a kick landing on a capped
1002
1019
  context's own pools; `thread_budget` from each variable, a bad value skipped, and the
1003
- shared context's limits. Opt-in (`WORLDARRAY_STRESS=1`, about a minute,
1020
+ shared context's limits. 0.6: `shared_context(threads=)` at `None`, `N` and `0`, one
1021
+ context per pair, a negative count refused, and a pickled handle coming back on the same
1022
+ shared context. Opt-in (`WORLDARRAY_STRESS=1`, about a minute,
1004
1023
  `tests/test_lost_wakeup.py`): worker processes aim tasks at the moment a one-thread pool's
1005
1024
  worker exits idle, so a plain context strands some for good (13 of about 7,000 on
1006
1025
  tensorstore 0.1.78; 40 to 44 of 12,000 to 15,000 with 16 workers on 0.1.75 and 0.1.85)
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "worldarray"
3
- version = "0.5.0"
3
+ version = "0.6.0"
4
4
  description = "Read zarr, N5 and Neuroglancer precomputed volumes in world coordinates through tensorstore: correct voxel placement, pyramid levels, padded region reads, resampling. numpy + tensorstore only."
5
5
  readme = "README.md"
6
6
  license = { file = "LICENSE" }
@@ -28,7 +28,7 @@ from .write import (
28
28
  parse_chunk_key,
29
29
  )
30
30
 
31
- __version__ = "0.5.0"
31
+ __version__ = "0.6.0"
32
32
 
33
33
  __all__ = [
34
34
  "ArrayMeta",
@@ -1,12 +1,14 @@
1
1
  """Tensorstore contexts: one shared per cache budget, or a capped one per call.
2
2
 
3
- ``shared_context(cache_bytes)`` is one ``tensorstore.Context`` per budget for the whole
4
- process, so the levels of a pyramid and repeat opens of an array draw on one cache pool.
5
- The default budget is 0: tensorstore's decoded-chunk cache is off, because every consumer
6
- either reads each region once or caches decoded chunks itself. Its decoding and file I/O
7
- pools are sized by ``thread_budget()``, from the variables a cluster job sets, where
8
- tensorstore alone would size them to every CPU on the node. Every context here, and every
9
- one a store is opened on, is watched for tensorstore's lost wakeup (``watch``).
3
+ ``shared_context(cache_bytes, threads=)`` is one ``tensorstore.Context`` per budget and
4
+ thread limit for the whole process, so the levels of a pyramid and repeat opens of an
5
+ array draw on one cache pool. The default budget is 0: tensorstore's decoded-chunk cache
6
+ is off, because every consumer either reads each region once or caches decoded chunks
7
+ itself. By default its decoding and file I/O pools are sized by ``thread_budget()``, from
8
+ the variables a cluster job sets, where tensorstore alone would size them to every CPU on
9
+ the node; ``threads=0`` leaves them at tensorstore's sizes (chunkmirage's server). Every
10
+ context here, and every one a store is opened on, is watched for tensorstore's lost wakeup
11
+ (``watch``).
10
12
 
11
13
  ``capped_context`` is mesh-n-bone's worker-process context: every tensorstore concurrency
12
14
  resource limited, so dozens of worker processes on one host do not multiply into thousands
@@ -26,7 +28,7 @@ from typing import Any
26
28
 
27
29
  import tensorstore as ts
28
30
 
29
- _shared: dict[int, ts.Context] = {}
31
+ _shared: dict[tuple[int, int | None], ts.Context] = {}
30
32
  _rebuilt: dict[str, ts.Context] = {}
31
33
  _lock = threading.Lock()
32
34
 
@@ -136,20 +138,23 @@ def thread_budget() -> int:
136
138
  return os.cpu_count() or 1
137
139
 
138
140
 
139
- def shared_context(cache_bytes: int = 0) -> ts.Context:
141
+ def shared_context(cache_bytes: int = 0, *, threads: int | None = None) -> ts.Context:
142
+ """The process's one context for ``cache_bytes`` and ``threads``, made on first use and
143
+ watched. ``threads`` limits its decoding and file I/O pools: ``None`` to
144
+ ``thread_budget()``, a positive number to that many, ``0`` not at all (tensorstore's own
145
+ sizes, every CPU on the node)."""
146
+ key = (int(cache_bytes), None if threads is None else int(threads))
147
+ if key[1] is not None and key[1] < 0:
148
+ raise ValueError(f"threads must be None, 0 or positive, not {threads!r}")
140
149
  with _lock:
141
- ctx = _shared.get(int(cache_bytes))
150
+ ctx = _shared.get(key)
142
151
  if ctx is None:
143
- limit = {"limit": thread_budget()}
144
- ctx = ts.Context(
145
- {
146
- "cache_pool": {"total_bytes_limit": int(cache_bytes)},
147
- "data_copy_concurrency": limit,
148
- "file_io_concurrency": limit,
149
- }
150
- )
151
- watch(ctx)
152
- _shared[int(cache_bytes)] = ctx
152
+ spec: dict = {"cache_pool": {"total_bytes_limit": key[0]}}
153
+ limit = thread_budget() if threads is None else key[1]
154
+ if limit:
155
+ spec["data_copy_concurrency"] = spec["file_io_concurrency"] = {"limit": limit}
156
+ ctx = watch(ts.Context(spec))
157
+ _shared[key] = ctx
153
158
  return ctx
154
159
 
155
160
 
@@ -157,7 +162,9 @@ def capped_context_spec(cache_bytes: int = 0, concurrency: int = 1, network: boo
157
162
  """The spec dict behind ``capped_context``, for callers building their own opens.
158
163
  ``network=False`` caps only decoding and local file I/O, leaving tensorstore's request
159
164
  pools alone (cellmap-analyze's choice for remote reads). A ``concurrency`` of 1 is safe:
160
- the context is watched (see ``KICK_EVERY``)."""
165
+ the context is watched (see ``KICK_EVERY``). It is not free: a read or write spanning
166
+ several chunks then decodes and stores them one at a time, half the speed of 4 on
167
+ 8-chunk blocks in a two-slot job, 7 to 19% slower in a one-slot one (spec, Behaviour 7)."""
161
168
  limit = {"limit": int(concurrency)}
162
169
  spec = {
163
170
  "data_copy_concurrency": limit,
@@ -179,12 +186,14 @@ def capped_context(cache_bytes: int = 0, concurrency: int = 1, network: bool = T
179
186
 
180
187
  def recipe_of(context: ts.Context) -> tuple[str, Any]:
181
188
  """How another process rebuilds ``context``: ``("shared", cache_bytes)`` for a shared
182
- context, else ``("spec", json)`` from the context's own spec, so a capped context stays
183
- capped across a pickle (Behaviour 11, mesh-n-bone's workers)."""
189
+ context at the default limit (the other process takes its own ``thread_budget()``),
190
+ ``("shared", (cache_bytes, threads))`` for one with ``threads`` given, else
191
+ ``("spec", json)`` from the context's own spec, so a capped context stays capped across
192
+ a pickle (Behaviour 11, mesh-n-bone's workers)."""
184
193
  with _lock:
185
- for cache_bytes, ctx in _shared.items():
194
+ for (cache_bytes, threads), ctx in _shared.items():
186
195
  if ctx is context:
187
- return ("shared", cache_bytes)
196
+ return ("shared", cache_bytes if threads is None else (cache_bytes, threads))
188
197
  spec = context.spec
189
198
  return ("spec", json.dumps(spec.to_json() if spec is not None else {}, sort_keys=True))
190
199
 
@@ -196,7 +205,8 @@ def from_recipe(recipe: tuple[str, Any] | None) -> ts.Context | None:
196
205
  return None
197
206
  kind, value = recipe
198
207
  if kind == "shared":
199
- return shared_context(int(value))
208
+ cache_bytes, threads = value if isinstance(value, (tuple, list)) else (value, None)
209
+ return shared_context(int(cache_bytes), threads=threads)
200
210
  with _lock:
201
211
  ctx = _rebuilt.get(value)
202
212
  if ctx is None:
@@ -301,3 +301,23 @@ def test_gce_guard_is_left_alone_on_a_google_machine(monkeypatch):
301
301
  monkeypatch.setattr(store, "_on_google_cloud", lambda: False)
302
302
  store.kvstore_specs("gs://bucket/vol")
303
303
  assert os.environ["GCE_METADATA_ROOT"] == "metadata.google.internal.invalid"
304
+
305
+
306
+ def test_a_shared_context_takes_the_callers_thread_count(monkeypatch):
307
+ monkeypatch.setenv("OMP_NUM_THREADS", "1")
308
+ monkeypatch.setattr(wa.context, "_shared", {})
309
+ default = wa.shared_context(7)
310
+ assert default.spec.to_json()["data_copy_concurrency"] == {"limit": 1}
311
+ four = wa.shared_context(7, threads=4).spec.to_json()
312
+ assert four["data_copy_concurrency"] == four["file_io_concurrency"] == {"limit": 4}
313
+ machine = wa.shared_context(7, threads=0)
314
+ assert machine is wa.shared_context(7, threads=0) and machine is not default
315
+ spec = machine.spec.to_json()
316
+ assert "data_copy_concurrency" not in spec and "file_io_concurrency" not in spec
317
+ assert spec["cache_pool"] == {"total_bytes_limit": 7}
318
+ assert machine in wa.context._watched
319
+ assert wa.context.recipe_of(machine) == ("shared", (7, 0))
320
+ assert wa.context.from_recipe(("shared", (7, 0))) is machine
321
+ assert wa.context.from_recipe(("shared", 7)) is default
322
+ with pytest.raises(ValueError):
323
+ wa.shared_context(threads=-1)
@@ -826,6 +826,8 @@ def test_a_pickled_handle_keeps_its_context(tmp_path):
826
826
  ) # one rebuilt context per spec
827
827
  shared = pickle.loads(pickle.dumps(wa.open(path)))
828
828
  assert shared.context is wa.shared_context(0)
829
+ machine = wa.shared_context(0, threads=0)
830
+ assert pickle.loads(pickle.dumps(wa.open(path, context=machine))).context is machine
829
831
 
830
832
 
831
833
  def test_codecs_of_a_path_and_the_fill_write_that_removes_a_key(tmp_path):
@@ -1247,7 +1247,7 @@ wheels = [
1247
1247
 
1248
1248
  [[package]]
1249
1249
  name = "worldarray"
1250
- version = "0.5.0"
1250
+ version = "0.6.0"
1251
1251
  source = { editable = "." }
1252
1252
  dependencies = [
1253
1253
  { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" },
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes