worldarray 0.10.1__tar.gz → 0.10.2__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 (38) hide show
  1. {worldarray-0.10.1 → worldarray-0.10.2}/.github/workflows/ci.yml +16 -0
  2. {worldarray-0.10.1 → worldarray-0.10.2}/CHANGELOG.md +14 -0
  3. {worldarray-0.10.1 → worldarray-0.10.2}/PKG-INFO +1 -1
  4. {worldarray-0.10.1 → worldarray-0.10.2}/docs/spec.md +10 -4
  5. {worldarray-0.10.1 → worldarray-0.10.2}/pyproject.toml +1 -1
  6. {worldarray-0.10.1 → worldarray-0.10.2}/src/worldarray/__init__.py +1 -1
  7. {worldarray-0.10.1 → worldarray-0.10.2}/src/worldarray/store.py +13 -3
  8. {worldarray-0.10.1 → worldarray-0.10.2}/tests/test_access.py +4 -0
  9. {worldarray-0.10.1 → worldarray-0.10.2}/uv.lock +1 -1
  10. {worldarray-0.10.1 → worldarray-0.10.2}/.github/workflows/docs.yml +0 -0
  11. {worldarray-0.10.1 → worldarray-0.10.2}/.github/workflows/publish.yml +0 -0
  12. {worldarray-0.10.1 → worldarray-0.10.2}/.gitignore +0 -0
  13. {worldarray-0.10.1 → worldarray-0.10.2}/LICENSE +0 -0
  14. {worldarray-0.10.1 → worldarray-0.10.2}/README.md +0 -0
  15. {worldarray-0.10.1 → worldarray-0.10.2}/docs/changelog.md +0 -0
  16. {worldarray-0.10.1 → worldarray-0.10.2}/docs/index.md +0 -0
  17. {worldarray-0.10.1 → worldarray-0.10.2}/mkdocs.yml +0 -0
  18. {worldarray-0.10.1 → worldarray-0.10.2}/src/worldarray/array.py +0 -0
  19. {worldarray-0.10.1 → worldarray-0.10.2}/src/worldarray/context.py +0 -0
  20. {worldarray-0.10.1 → worldarray-0.10.2}/src/worldarray/geometry.py +0 -0
  21. {worldarray-0.10.1 → worldarray-0.10.2}/src/worldarray/grid.py +0 -0
  22. {worldarray-0.10.1 → worldarray-0.10.2}/src/worldarray/levels.py +0 -0
  23. {worldarray-0.10.1 → worldarray-0.10.2}/src/worldarray/placement.py +0 -0
  24. {worldarray-0.10.1 → worldarray-0.10.2}/src/worldarray/resample.py +0 -0
  25. {worldarray-0.10.1 → worldarray-0.10.2}/src/worldarray/retry.py +0 -0
  26. {worldarray-0.10.1 → worldarray-0.10.2}/src/worldarray/units.py +0 -0
  27. {worldarray-0.10.1 → worldarray-0.10.2}/src/worldarray/write.py +0 -0
  28. {worldarray-0.10.1 → worldarray-0.10.2}/tests/conftest.py +0 -0
  29. {worldarray-0.10.1 → worldarray-0.10.2}/tests/fixtures/metadata.json +0 -0
  30. {worldarray-0.10.1 → worldarray-0.10.2}/tests/fixtures/write.json +0 -0
  31. {worldarray-0.10.1 → worldarray-0.10.2}/tests/test_axes_order.py +0 -0
  32. {worldarray-0.10.1 → worldarray-0.10.2}/tests/test_geometry.py +0 -0
  33. {worldarray-0.10.1 → worldarray-0.10.2}/tests/test_grid.py +0 -0
  34. {worldarray-0.10.1 → worldarray-0.10.2}/tests/test_lost_wakeup.py +0 -0
  35. {worldarray-0.10.1 → worldarray-0.10.2}/tests/test_metadata.py +0 -0
  36. {worldarray-0.10.1 → worldarray-0.10.2}/tests/test_read.py +0 -0
  37. {worldarray-0.10.1 → worldarray-0.10.2}/tests/test_resample.py +0 -0
  38. {worldarray-0.10.1 → worldarray-0.10.2}/tests/test_write.py +0 -0
@@ -3,6 +3,8 @@ on:
3
3
  push:
4
4
  branches: [main]
5
5
  pull_request:
6
+ schedule:
7
+ - cron: "0 6 * * 1" # weekly, so a new dependency release is tried without a push
6
8
  jobs:
7
9
  test:
8
10
  strategy:
@@ -20,3 +22,17 @@ jobs:
20
22
  - run: uv run ruff format --check src tests
21
23
  - run: uv run pytest -q
22
24
  - run: uv run mkdocs build --strict
25
+ # what a fresh install gets: the newest release of every dependency, not the lock's
26
+ newest:
27
+ strategy:
28
+ matrix:
29
+ os: [ubuntu-latest, macos-latest]
30
+ runs-on: ${{ matrix.os }}
31
+ steps:
32
+ - uses: actions/checkout@v4
33
+ - uses: astral-sh/setup-uv@v5
34
+ with:
35
+ python-version: "3.12"
36
+ - run: uv sync --group dev --upgrade
37
+ - run: uv pip list | grep -E "^(tensorstore|numpy) "
38
+ - run: uv run pytest -q
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.10.2 (2026-11-13)
4
+
5
+ 0.10.1's fix, on tensorstore 0.1.86. No placement change, no unit table change, and no
6
+ default changes.
7
+
8
+ - A `gs://` open with a stale Google login falls back to the public https URL on
9
+ tensorstore 0.1.86 too. 0.10.1 knew the failed login by the source locations in
10
+ tensorstore's error, which 0.1.86 leaves out, so with the newest tensorstore (what a
11
+ fresh install gets) the open still raised. The token endpoint's own error code
12
+ (`invalid_grant`, and the other OAuth 2.0 codes) is now matched as well. On 0.1.86 a
13
+ token endpoint out of reach is raised as any unreachable host is, not passed over.
14
+ - CI also runs the tests on the newest release of every dependency, weekly and on every
15
+ pull request; 0.10.1 passed on the lock's tensorstore 0.1.78.
16
+
3
17
  ## 0.10.1 (2026-11-13)
4
18
 
5
19
  A fix for `gs://` with a stale Google login. No placement change, no unit table change,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: worldarray
3
- Version: 0.10.1
3
+ Version: 0.10.2
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/
@@ -540,10 +540,13 @@ this document and that one differ, fix one of them.
540
540
  is read as that unit on every axis; tensorstore is given only the array members of
541
541
  `attributes.json`, so attributes it would reject do not stop the open. The next way is tried only when the error
542
542
  starts `PERMISSION_DENIED` or `UNAUTHENTICATED`, or came from getting an access token,
543
- which tensorstore marks with its `internal/oauth2/` code among the error's source
544
- locations (0.10.1): Google refuses an expired gcloud login with HTTP 400 `invalid_grant`,
543
+ which tensorstore up to 0.1.85 marks with its `internal/oauth2/` code among the error's
544
+ source locations (0.10.1), and which is otherwise known by the token endpoint's own error
545
+ code, an OAuth 2.0 one (RFC 6749 §5.2) as the body's string `error`, where a store's error
546
+ body holds an object (0.10.2: tensorstore 0.1.86 leaves source locations out): Google refuses an expired gcloud login with HTTP 400 `invalid_grant`,
545
547
  which starts neither (cellmap-flow, on David's Mac), and a token endpoint out of reach
546
- leaves no token either, so the public URL is worth trying. When the last way fails too
548
+ leaves no token either, so the public URL is worth trying; from 0.1.86 an unreachable
549
+ token endpoint carries no such mark and is raised as any unreachable host is. When the last way fails too
547
550
  after a refusal, its error carries the first refusal as its cause, so a stale login is
548
551
  still in the traceback of a private bucket's 403. The way that worked is remembered per
549
552
  (driver, bucket) for the process. Format probing runs all probe keys under one way before
@@ -1058,7 +1061,10 @@ chunkmirage derives from raw attributes carries a `layered` note beside the pack
1058
1061
  fallback; detection ignores the directory suffix and finds a precomputed scale key.
1059
1062
  0.10.1: a failed login counts as a refusal and is the cause of a later failure; end to
1060
1063
  end, a `gs://` open whose token refresh is answered 400 `invalid_grant` (an https proxy
1061
- playing www.googleapis.com under a test CA) reads through the public URL.
1064
+ playing www.googleapis.com under a test CA) reads through the public URL. 0.10.2: the
1065
+ same on tensorstore 0.1.86's messages, and CI runs the suite on the newest release of
1066
+ every dependency as well as the lock's, weekly and on every pull request: 0.10.1 passed
1067
+ on the lock's tensorstore 0.1.78 and failed on 0.1.86, which a fresh install got.
1062
1068
 
1063
1069
  Reads: every statement in Behaviour 1, 2, 3 and 9; a document-less group probed through
1064
1070
  `s0`, integer and `"first"` levels, `detect(missing_ok=True)`, `index_box`; funlib keys on
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "worldarray"
3
- version = "0.10.1"
3
+ version = "0.10.2"
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.10.1"
31
+ __version__ = "0.10.2"
32
32
 
33
33
  __all__ = [
34
34
  "ArrayMeta",
@@ -158,12 +158,22 @@ class Refused(Exception):
158
158
  """A read was refused for want of credentials (not a missing key, not a network error)."""
159
159
 
160
160
 
161
+ # An OAuth 2.0 token endpoint's error codes (RFC 6749, 5.2), the string "error" member of the
162
+ # body it answers; a store's own error bodies hold an object there. Quotes may come escaped.
163
+ _TOKEN_ERROR = re.compile(
164
+ r'error\\*"\s*:\s*\\*"(?:invalid_grant|invalid_client|invalid_request|invalid_scope'
165
+ r'|unauthorized_client|unsupported_grant_type)\\*"'
166
+ )
167
+
168
+
161
169
  def login_failed(error: BaseException) -> bool:
162
170
  """``error`` came from getting an access token, before any request reached the store: a
163
171
  stale Google login (HTTP 400 ``invalid_grant``, which starts neither refusal code), a
164
- rejected client, or a token endpoint out of reach. tensorstore names its oauth2 code
165
- among the error's source locations."""
166
- return "tensorstore/internal/oauth2/" in str(error)
172
+ rejected client, or (up to tensorstore 0.1.85) a token endpoint out of reach. Up to
173
+ 0.1.85 tensorstore names its oauth2 code among the error's source locations; 0.1.86
174
+ leaves source locations out, so the token endpoint's own error code is matched too."""
175
+ message = str(error)
176
+ return "tensorstore/internal/oauth2/" in message or bool(_TOKEN_ERROR.search(message))
167
177
 
168
178
 
169
179
  def is_refused(error: BaseException) -> bool:
@@ -116,6 +116,10 @@ _STALE_LOGIN = ( # tensorstore's error for gcloud's login after a reauth policy
116
116
 
117
117
 
118
118
  def test_a_failed_login_is_a_refusal_and_the_cause_of_a_later_one():
119
+ # tensorstore 0.1.86 leaves source locations out; the token endpoint's error code remains
120
+ login = r"Bad Request [HTTP code 400] [http_response_body='{\"error\": \"invalid_grant\"}']"
121
+ store_400 = r"Bad Request [HTTP code 400] [http_response_body='{\"error\": {\"code\": 400}}']"
122
+ assert store.is_refused(ValueError(login)) and not store.is_refused(ValueError(store_400))
119
123
  unreachable = (
120
124
  "UNAVAILABLE: CURL error Could not resolve host [source locations='"
121
125
  "tensorstore/internal/curl/curl_transport.cc:475\n"
@@ -1247,7 +1247,7 @@ wheels = [
1247
1247
 
1248
1248
  [[package]]
1249
1249
  name = "worldarray"
1250
- version = "0.10.1"
1250
+ version = "0.10.2"
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