diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml
new file mode 100644
index 0000000..b499bb3
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug_report.yml
@@ -0,0 +1,62 @@
+name: Bug report
+description: Something is broken or behaves unexpectedly.
+title: "[Bug]: "
+labels: ["bug"]
+body:
+ - type: markdown
+ attributes:
+ value: |
+ Thanks for taking the time to file a bug. Please fill in what you can —
+ a minimal reproducible example is the single most helpful thing.
+ - type: textarea
+ id: what-happened
+ attributes:
+ label: What happened?
+ description: What did you do, what did you expect, and what happened instead?
+ placeholder: I called KaguyaTC(...) and expected ..., but got ...
+ validations:
+ required: true
+ - type: textarea
+ id: reproduce
+ attributes:
+ label: Minimal reproducible example
+ description: The smallest snippet that triggers the bug. It will be rendered as Python.
+ render: python
+ placeholder: |
+ import astrofetch as af
+
+ moondata = af.KaguyaTC(products=["dtm"], bbox=(-26.3, -50.6, -25.5, -49.7))
+ next(iter(moondata))
+ validations:
+ required: true
+ - type: textarea
+ id: traceback
+ attributes:
+ label: Traceback or error output
+ description: Paste the full traceback if there is one. It will be rendered as a code block.
+ render: shell
+ - type: input
+ id: version
+ attributes:
+ label: AstroFetch version
+ description: "Output of: python -c \"import astrofetch; print(astrofetch.__version__)\""
+ placeholder: "0.1.0"
+ validations:
+ required: true
+ - type: input
+ id: environment
+ attributes:
+ label: Python and OS
+ description: Python version and operating system.
+ placeholder: "Python 3.12 on macOS 15"
+ validations:
+ required: true
+ - type: checkboxes
+ id: checks
+ attributes:
+ label: Before submitting
+ options:
+ - label: I searched existing issues and this is not a duplicate.
+ required: true
+ - label: The example above reproduces the problem on a clean environment.
+ required: false
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
new file mode 100644
index 0000000..d675436
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -0,0 +1,8 @@
+blank_issues_enabled: false
+contact_links:
+ - name: Questions and discussion
+ url: https://github.com/TechnicToms/AstroFetch/discussions
+ about: Ask a question or discuss an idea before filing an issue.
+ - name: Contributing guide
+ url: https://github.com/TechnicToms/AstroFetch/blob/main/CONTRIBUTING.md
+ about: How to set up, test, and open a pull request.
diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml
new file mode 100644
index 0000000..5d56847
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/feature_request.yml
@@ -0,0 +1,54 @@
+name: Feature request
+description: Suggest an enhancement or new capability.
+title: "[Feature]: "
+labels: ["enhancement"]
+body:
+ - type: markdown
+ attributes:
+ value: |
+ Thanks for the idea! For anything substantial, it helps to discuss the
+ approach here before a large PR. Note the current phase in the
+ [roadmap](https://github.com/TechnicToms/AstroFetch/blob/main/AGENTS.md#roadmap).
+ - type: textarea
+ id: problem
+ attributes:
+ label: What problem does this solve?
+ description: The use case or pain point motivating the request.
+ placeholder: When training on lunar patches I need ...
+ validations:
+ required: true
+ - type: textarea
+ id: proposal
+ attributes:
+ label: Proposed solution
+ description: What would the API or behavior look like? A code sketch is welcome.
+ render: python
+ validations:
+ required: true
+ - type: textarea
+ id: alternatives
+ attributes:
+ label: Alternatives considered
+ description: Other approaches you weighed, and why this one.
+ - type: dropdown
+ id: scope
+ attributes:
+ label: Roughly which area?
+ options:
+ - Data path (STAC / COG / grid / cache)
+ - New instrument dataset
+ - New body module
+ - Transforms / samplers
+ - Docs / examples
+ - Other
+ validations:
+ required: true
+ - type: checkboxes
+ id: checks
+ attributes:
+ label: Before submitting
+ options:
+ - label: I searched existing issues and this is not a duplicate.
+ required: true
+ - label: I would be willing to help implement this.
+ required: false
diff --git a/.github/ISSUE_TEMPLATE/new_dataset.yml b/.github/ISSUE_TEMPLATE/new_dataset.yml
new file mode 100644
index 0000000..013bb6e
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/new_dataset.yml
@@ -0,0 +1,71 @@
+name: New dataset or body
+description: Propose a new instrument dataset or a new planetary body module.
+title: "[Data]: "
+labels: ["dataset"]
+body:
+ - type: markdown
+ attributes:
+ value: |
+ New instrument datasets and body modules are exactly the contributions
+ we want. AstroFetch's quantitative path needs Cloud Optimized GeoTIFFs
+ (COGs) discoverable via a STAC API. Rendered-only WMS/WMTS layers can be
+ added as a clearly-labeled non-quantitative source. See the "Adding a
+ new instrument dataset" section of
+ [CONTRIBUTING.md](https://github.com/TechnicToms/AstroFetch/blob/main/CONTRIBUTING.md).
+ - type: input
+ id: body
+ attributes:
+ label: Planetary body
+ placeholder: "Moon / Mars / ..."
+ validations:
+ required: true
+ - type: input
+ id: instrument
+ attributes:
+ label: Probe and instrument
+ placeholder: "LRO / LROC WAC"
+ validations:
+ required: true
+ - type: dropdown
+ id: source-type
+ attributes:
+ label: Data source type
+ options:
+ - STAC API with COG assets (quantitative — preferred)
+ - Standalone COG / mosaic (no STAC)
+ - WMS / WMTS rendered tiles (non-quantitative)
+ - Other / not sure
+ validations:
+ required: true
+ - type: textarea
+ id: source
+ attributes:
+ label: Where does the data live?
+ description: STAC catalog root and collection id, or the COG / service URL(s).
+ placeholder: |
+ STAC root: https://stac.astrogeology.usgs.gov/api/
+ Collection: kaguya_terrain_camera_usgs_dtms
+ COG asset key(s): dtm, orthoimage
+ validations:
+ required: true
+ - type: textarea
+ id: products
+ attributes:
+ label: Products and units
+ description: Which products (bands/assets), their physical units, and any scale/offset or nodata quirks.
+ placeholder: "DTM (metres, nodata -32767); orthoimage (radiance, DN * scale)."
+ - type: input
+ id: projection
+ attributes:
+ label: Projection / CRS
+ description: Native CRS of the rasters, if known (equirectangular, polar stereographic, IAU code).
+ placeholder: "IAU_2015:30110 (equirectangular); polar items use stereographic"
+ - type: checkboxes
+ id: checks
+ attributes:
+ label: Before submitting
+ options:
+ - label: I confirmed the data is openly accessible (no login / paywall).
+ required: true
+ - label: I searched existing issues and this is not a duplicate.
+ required: true
diff --git a/AGENTS.md b/AGENTS.md
index 7127a8e..de0424e 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -19,7 +19,7 @@ src/astrofetch/
tiles.py # secondary rendered mode: USGS WMS, Moon Trek WMTS
moon/
layers.py # layer registry (name -> STAC collection + read config) + Body/Probe/Instrument catalog (MOON)
- datasets.py # instrument dataset classes (InstrumentDataset, KaguyaTC, LROCWAC) + IntersectionDataset
+ datasets.py # instrument dataset classes (InstrumentDataset, KaguyaTC, KaguyaTCImagery) + IntersectionDataset
tests/
unit/ # network fully mocked, runs in CI
live/ # hits real endpoints, manual trigger only
@@ -39,7 +39,7 @@ tests/
- Python 3.10+, src layout, uv-managed: `uv sync` installs the package plus the `dev` group (or `pip install -e .` for the package alone).
- Lint: `uv run ruff check` and `uv run ruff format`
-- Types: `uv run mypy src` (config in pyproject.toml; keep new code typed)
+- Types: `uv run ty check` (config in pyproject.toml; keep new code typed)
- Tests: `uv run pytest` (unit only; the `live` marker is deselected by default). Live endpoint tests: `uv run pytest tests/live -m live` (never run these in CI or in loops; they hit real government servers).
- Docs: `uv run --group docs mkdocs serve`
@@ -93,12 +93,12 @@ Widely-adopted defaults that keep the codebase consistent. When in doubt, match
Check the current phase before proposing work; for example, do not build Phase 2 datasets and transforms while Phase 1 (STAC sampler MVP) is incomplete. Everything is a thin layer above existing archive tooling, never a mirror of any archive.
-**Current phase: Phase 0 (scaffolding) — the per-instrument API surface is real and stable; the data path returns synthetic placeholder tensors until the Phase 1 STAC sampler lands.**
+**Current phase: Phase 1 (STAC sampler). Phase 0 scaffolding is complete. `InstrumentDataset.read` now fetches the real COGs covering a window from the USGS ARD catalog, reprojects them onto a common geographic grid, applies scale/offset, mosaics overlapping items, and caches the result — no more synthetic tensors.**
### Phase 0: Scaffolding (weekend 1)
- Repo setup: pyproject.toml (hatchling or setuptools), src layout, MIT or Apache-2.0 license, CITATION.cff.
-- CI: GitHub Actions running ruff, mypy (lenient at first), pytest on 3.10 through 3.12.
+- CI: GitHub Actions running ruff, ruff format, `ty` type checking, and pytest on 3.10 through 3.14.
- Testing policy established early: unit tests mock all network calls; a separate, manually triggered "live" test suite hits real endpoints.
- Placeholder docs (mkdocs-material) and a README with the one-line pitch and the target API sketch.
@@ -111,23 +111,23 @@ The single most important deliverable. Everything else builds on it.
- `astrofetch.data.stac`: query the USGS ARD catalog root with pystac-client, filter by collection and bbox.
- `astrofetch.data.raster`: windowed COG reads via rasterio, honoring scale/offset to return physical values, resampling to a requested resolution.
- `astrofetch.data.grid`: define a common target grid (equirectangular, IAU 2015 Moon), reproject and stack layers into a (C, H, W) float tensor with a per-channel metadata record.
-- The sampler plugs into `InstrumentDataset.read(bbox)`, replacing the Phase 0 synthetic placeholder. Public API target (already the shipped Phase 0 surface):
+- The sampler plugs into `InstrumentDataset.read(bbox)`, replacing the Phase 0 synthetic placeholder. Shipped public API:
```python
import astrofetch as af
-bbox = (-60.0, 5.0, -55.0, 10.0)
-moondata = af.KaguyaTC(products=["dtm"], bbox=bbox, resolution=100) & af.LROCWAC(
+bbox = (-26.3, -50.6, -25.5, -49.7) # a Kaguya TC USGS DTM footprint
+moondata = af.KaguyaTC(products=["dtm"], bbox=bbox, resolution=100) & af.KaguyaTCImagery(
bbox=bbox, resolution=100
)
sample = next(iter(moondata))
sample["image"] # torch.Tensor (C, H, W), physical values
sample["mask"] # torch.BoolTensor (C, H, W), validity (nodata gaps)
-sample["layers"] # ["kaguya_tc_dtm", "lroc_wac"], plus bbox/crs/resolution
+sample["layers"] # ["kaguya_tc_dtm", "kaguya_tc_image"], plus bbox/crs/resolution
```
- Local disk cache keyed by (collection, item, window, resolution), transparent and clearable.
-Exit criteria: the quickstart notebook fetches a Reiner Gamma patch with two layers and plots it, end to end, on a clean machine.
+Exit criteria: `KaguyaTC(products=["dtm", "ortho"], bbox=...)` fetches a real, coregistered two-layer patch from the USGS ARD catalog on a clean machine (covered by `tests/live`). A plotting quickstart notebook is a nice-to-have follow-up.
### Phase 2: Datasets and transforms (2 to 3 weekends)
@@ -135,6 +135,7 @@ Exit criteria: the quickstart notebook fetches a Reiner Gamma patch with two lay
- Samplers that respect spatial autocorrelation for train/val/test splits (block splitting, not random pixels).
- Transforms: per-channel normalization stats, nodata masking, polar/equatorial projection handling made explicit.
- Secondary access mode behind the same interface: WMS/WMTS rendered mode, clearly labeled non-quantitative.
+- LRO WAC global mosaic: the USGS ARD STAC catalog has no LRO WAC collection, so add it here from a non-STAC source (a public COG mosaic or WMS/WMTS), behind the same instrument-dataset interface as a new `LROCWAC` class.
Exit criteria: `DataLoader` trains a toy model on random lunar patches without custom user code.
diff --git a/CITATION.cff b/CITATION.cff
index a840b15..314cdd9 100644
--- a/CITATION.cff
+++ b/CITATION.cff
@@ -8,6 +8,8 @@ type: software
authors:
- family-names: Sander
given-names: Tom
+ - family-names: Arnaut
+ given-names: Mirza
license: Apache-2.0
version: 0.1.0
date-released: "2026-07-08"
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..7f820f6
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,109 @@
+# Contributing to AstroFetch
+
+Thanks for your interest in AstroFetch — a PyTorch-friendly, ML-ready gateway to
+planetary science data. Contributions are welcome, and **new body modules (Mars
+first) and new instrument datasets are especially wanted**.
+
+This file is the quick-start for contributors. The design rules, architecture,
+and phased roadmap live in [`AGENTS.md`](AGENTS.md) — read it before a
+non-trivial change; it is the source of truth for how the codebase is meant to
+fit together.
+
+## Ways to contribute
+
+- **Report a bug** or **request a feature** with the [issue templates](https://github.com/TechnicToms/AstroFetch/issues/new/choose).
+- **Add an instrument dataset** (a new STAC-backed sensor) or a **new body module**.
+- **Improve docs, examples, or tests.**
+- Not sure where to start? Open a discussion issue first — for anything
+ substantial, please float the idea before writing a large PR.
+
+## Development setup
+
+AstroFetch uses [uv](https://docs.astral.sh/uv/) for everything. You do not need
+to manage a virtualenv by hand.
+
+```bash
+git clone https://github.com/TechnicToms/AstroFetch.git
+cd AstroFetch
+uv sync # create the venv and install the package + dev/test groups
+```
+
+Common commands:
+
+```bash
+uv run pytest # unit tests only — no network (the CI default)
+uv run ruff check # lint
+uv run ruff format # format (the single source of truth for layout)
+uv run ty check # type check
+uv run --group docs mkdocs serve # preview the docs site at localhost:8000
+```
+
+Live endpoint tests hit real government servers and are **deselected by
+default**. Run them deliberately, one at a time, when verifying an endpoint —
+never in a loop:
+
+```bash
+uv run pytest tests/live -m live
+```
+
+## Making a change
+
+1. **Branch** off `main` (`git switch -c my-change`).
+2. **Write the code and its tests together.** Every new public function needs a
+ NumPy-style docstring with a runnable example and at least one unit test.
+3. **Keep unit tests network-free.** Mock STAC responses and read small local
+ GeoTIFFs; never point a unit test at a live service. If you add or change an
+ endpoint or query, add or update the corresponding fixture.
+4. **Run the full local gate before pushing:**
+ ```bash
+ uv run ruff check && uv run ruff format --check && uv run ty check && uv run pytest
+ ```
+5. **Open a PR** into `main` with a clear description. If you reimplemented
+ anything instead of wrapping existing archive tooling, justify it there
+ (see design rule 1 in `AGENTS.md`).
+
+## Coding conventions
+
+These are enforced in CI; the short version:
+
+- **Formatting is automated.** `ruff format` owns layout, quotes, and line
+ length — do not hand-align or fight it. Run it before every commit.
+- **Imports** are sorted and grouped (stdlib, third-party, first-party) by
+ ruff's isort rules; no wildcard or unused imports.
+- **Type hints** on every public function; keep `ty check` clean.
+- **Naming** follows PEP 8 (`snake_case`, `PascalCase`, `UPPER_SNAKE_CASE`).
+- **Conventional commits** (`feat:`, `fix:`, `docs:`, `test:`, `refactor:`,
+ `chore:`).
+- **No print statements** in library code; use the `astrofetch` logger. Raise
+ `EndpointError` for remote failures and `ValueError` for bad user input.
+
+Non-negotiable architecture rules (endpoint URLs live only in
+`data/endpoints.py`, the quantitative COG path and rendered tile path never mix,
+the cache is disposable, and so on) are spelled out in `AGENTS.md` — please skim
+them so review can focus on the substance of your change.
+
+## Adding a new instrument dataset
+
+The typical shape of such a change:
+
+1. Add the dataset class in `src/astrofetch/moon/datasets.py` (subclass
+ `InstrumentDataset`, declare its `probe`, `instrument`, STAC `collection`,
+ and `all_products` map of product → `Product(layer, asset)`).
+2. Register its layers and wire it into the `MOON` catalog in
+ `src/astrofetch/moon/layers.py`.
+3. Export it from `src/astrofetch/moon/__init__.py` and `astrofetch/__init__.py`.
+4. Add unit tests (mocked reads) and, if useful, a single live check.
+
+A new **body** (e.g. Mars) is a new sibling module under `src/astrofetch/`, not
+edits scattered through the Moon code — the body-agnostic `data/` layer is
+reused as-is.
+
+## Code of conduct
+
+Be kind, be constructive, assume good faith. Planetary science is a small,
+collaborative community; let's keep it welcoming.
+
+## License
+
+By contributing, you agree that your contributions are licensed under the
+project's [Apache 2.0](LICENSE) license.
diff --git a/README.md b/README.md
index 2503eee..bd5c1a9 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,7 @@
+
+
+
+
# AstroFetch
PyTorch-friendly, ML-ready access to planetary science data, starting with the Moon.
@@ -14,9 +18,9 @@ rehosted, everything is fetched on demand and cached. It is, roughly, what
[TorchGeo](https://github.com/microsoft/torchgeo) is for Earth observation, pointed at
the Moon (and Mars next).
-> **Status: Phase 0 scaffolding.** The API surface is real and stable; the data path
-> currently returns synthetic placeholder tensors until the STAC sampler lands in Phase 1.
-> See the [roadmap](#roadmap).
+> **Status: Phase 1 (STAC sampler).** The data path is real: a request fetches the
+> Cloud Optimized GeoTIFFs covering the window from the USGS ARD catalog, reprojects
+> them onto a common grid, and returns physical values. See the [roadmap](#roadmap).
## Install
@@ -32,16 +36,26 @@ channels over the overlapping region:
```python
import astrofetch as af
-bbox = (-60.0, 5.0, -55.0, 10.0) # west, south, east, north (degrees)
+bbox = (-26.3, -50.6, -25.5, -49.7) # west, south, east, north (degrees)
-moondata = af.KaguyaTC(products=["dtm"], bbox=bbox, resolution=100) & af.LROCWAC(
+moondata = af.KaguyaTC(products=["dtm"], bbox=bbox, resolution=100) & af.KaguyaTCImagery(
bbox=bbox, resolution=100
)
for sample in moondata:
sample["image"] # torch.Tensor (C, H, W), one channel per layer
sample["mask"] # torch.BoolTensor (C, H, W), validity (nodata gaps)
- sample["layers"] # ["kaguya_tc_dtm", "lroc_wac"], plus bbox/crs/resolution
+ sample["layers"] # ["kaguya_tc_dtm", "kaguya_tc_image"], plus bbox/crs/resolution
+```
+
+Each dataset is **map-style**: index it (`moondata[0]`), take its `len()`, and
+shuffle or split it like any `torch` dataset. `patch_size` sets the output size —
+images are `(C, patch_size, patch_size)`, default 256 — and `length` sets how
+many random patches make up one epoch:
+
+```python
+moondata = af.KaguyaTC(products=["dtm"], bbox=bbox, patch_size=128, length=500, seed=0)
+moondata[0]["image"].shape # torch.Size([1, 128, 128]); moondata[i] is reproducible
```
It plugs directly into a PyTorch training loop — samples are plain dicts, so
@@ -65,7 +79,7 @@ for probe in MOON.probes.values():
for instrument in probe.instruments.values():
print(probe.name, "/", instrument.name, "->", sorted(instrument.products))
-MOON.probes["lro"].instruments["lroc_wac"].dataset #
+MOON.probes["kaguya"].instruments["tc_imagery"].dataset # KaguyaTCImagery
```
## Design principles
@@ -87,9 +101,9 @@ MOON.probes["lro"].instruments["lroc_wac"].dataset # =1.24", "torch>=2.0"]
+dependencies = [
+ "numpy>=1.24",
+ "torch>=2.0",
+ "pystac-client>=0.7",
+ "rasterio>=1.3",
+]
[project.urls]
Homepage = "https://github.com/TechnicToms/AstroFetch"
diff --git a/src/astrofetch/__init__.py b/src/astrofetch/__init__.py
index 2d36259..e93d6e4 100644
--- a/src/astrofetch/__init__.py
+++ b/src/astrofetch/__init__.py
@@ -6,8 +6,15 @@
"""
from astrofetch import moon
-from astrofetch.moon import LROCWAC, MOON, IntersectionDataset, KaguyaTC
+from astrofetch.moon import MOON, IntersectionDataset, KaguyaTC, KaguyaTCImagery
__version__ = "0.1.0"
-__all__ = ["MOON", "IntersectionDataset", "KaguyaTC", "LROCWAC", "moon", "__version__"]
+__all__ = [
+ "MOON",
+ "IntersectionDataset",
+ "KaguyaTC",
+ "KaguyaTCImagery",
+ "moon",
+ "__version__",
+]
diff --git a/src/astrofetch/data/__init__.py b/src/astrofetch/data/__init__.py
new file mode 100644
index 0000000..2ccc52e
--- /dev/null
+++ b/src/astrofetch/data/__init__.py
@@ -0,0 +1,6 @@
+"""Body-agnostic data access: STAC search, windowed COG reads, gridding, cache.
+
+Moon-specific wiring (which collection backs which product) lives under
+``astrofetch/moon``; everything here is body-neutral and reused as new bodies
+are added.
+"""
diff --git a/src/astrofetch/data/cache.py b/src/astrofetch/data/cache.py
new file mode 100644
index 0000000..6e545a9
--- /dev/null
+++ b/src/astrofetch/data/cache.py
@@ -0,0 +1,73 @@
+"""Disposable local cache for reprojected layer windows.
+
+Never load-bearing (AGENTS rule 4): every entry is re-fetchable from the archive
+and safe to delete at any time. A key is a hash of the layer id and the exact
+target grid, so an identical request skips the network on a warm cache while any
+change to bbox, size, or CRS misses cleanly.
+"""
+
+from __future__ import annotations
+
+import hashlib
+import os
+from pathlib import Path
+
+import numpy as np
+
+from astrofetch.data.grid import TargetGrid
+
+
+def default_cache_dir() -> Path:
+ """Cache root: ``$ASTROFETCH_CACHE`` if set, else an XDG cache subdir."""
+ override = os.environ.get("ASTROFETCH_CACHE")
+ if override:
+ return Path(override)
+ base = os.environ.get("XDG_CACHE_HOME", str(Path.home() / ".cache"))
+ return Path(base) / "astrofetch"
+
+
+class WindowCache:
+ """Filesystem cache of ``(image, mask)`` arrays keyed by layer and grid.
+
+ Args:
+ root: cache directory; defaults to :func:`default_cache_dir`.
+ """
+
+ def __init__(self, root: str | Path | None = None) -> None:
+ self.root = Path(root) if root is not None else default_cache_dir()
+
+ def _key(self, layer: str, grid: TargetGrid) -> str:
+ west, south, east, north = grid.bbox
+ raw = (
+ f"{layer}|{grid.crs}|{grid.width}x{grid.height}"
+ f"|{west:.8f},{south:.8f},{east:.8f},{north:.8f}"
+ )
+ return hashlib.sha256(raw.encode()).hexdigest()[:16]
+
+ def _path(self, key: str) -> Path:
+ return self.root / f"{key}.npz"
+
+ def get(self, layer: str, grid: TargetGrid) -> tuple[np.ndarray, np.ndarray] | None:
+ """Return the cached ``(image, mask)`` for ``layer``/``grid``, or None."""
+ path = self._path(self._key(layer, grid))
+ if not path.exists():
+ return None
+ with np.load(path) as data:
+ return data["image"], data["mask"]
+
+ def put(self, layer: str, grid: TargetGrid, image: np.ndarray, mask: np.ndarray) -> None:
+ """Store ``(image, mask)`` for ``layer``/``grid``, replacing any entry."""
+ self.root.mkdir(parents=True, exist_ok=True)
+ path = self._path(self._key(layer, grid))
+ # Write to a temp file then atomically replace, so an interrupted write
+ # never leaves a half-written cache entry that a later read would trust.
+ tmp = self.root / f"{path.stem}.{os.getpid()}.tmp.npz"
+ np.savez(tmp, image=image, mask=mask)
+ os.replace(tmp, path)
+
+ def clear(self) -> None:
+ """Delete every cache entry. The cache is disposable; this is always safe."""
+ if not self.root.exists():
+ return
+ for entry in self.root.glob("*.npz"):
+ entry.unlink()
diff --git a/src/astrofetch/data/endpoints.py b/src/astrofetch/data/endpoints.py
new file mode 100644
index 0000000..e7156a3
--- /dev/null
+++ b/src/astrofetch/data/endpoints.py
@@ -0,0 +1,11 @@
+"""External archive endpoints — the ONLY place URLs live (AGENTS rule 2).
+
+Every COG asset href is discovered through the STAC API at request time, so the
+API root below is the single external URL AstroFetch hard-codes. When a service
+moves (as QuickMap's domain once did), this is the only file to change.
+"""
+
+from __future__ import annotations
+
+STAC_API_ROOT = "https://stac.astrogeology.usgs.gov/api/"
+"""USGS Astrogeology Analysis Ready Data STAC API root (pystac-client entry)."""
diff --git a/src/astrofetch/data/grid.py b/src/astrofetch/data/grid.py
new file mode 100644
index 0000000..af8e840
--- /dev/null
+++ b/src/astrofetch/data/grid.py
@@ -0,0 +1,78 @@
+"""Target grid definition and unit helpers (body-agnostic).
+
+A :class:`TargetGrid` is the common raster every layer is reprojected onto so
+that channels coregister exactly. Phase 1 uses a plate-carree (equirectangular)
+grid in the IAU 2015 Moon geographic CRS; ``data/raster.py`` reprojects each
+source COG — whatever its native projection, equirectangular or polar
+stereographic — onto it. Keeping the grid geographic means the ``bbox`` a caller
+passes and the ``crs`` a sample advertises are the same coordinate system.
+"""
+
+from __future__ import annotations
+
+import math
+from dataclasses import dataclass
+
+from affine import Affine
+from rasterio.transform import from_bounds
+
+BBox = tuple[float, float, float, float]
+"""(west, south, east, north) in degrees."""
+
+GEOGRAPHIC_CRS = "IAU_2015:30100"
+"""Ocentric IAU 2015 Moon geographic CRS (degrees); the target grid CRS."""
+
+MOON_RADIUS_M = 1737400.0
+"""IAU 2015 Moon sphere radius in metres (IAU code 30100; see any ARD COG WKT)."""
+
+
+@dataclass(frozen=True)
+class TargetGrid:
+ """A fixed output raster: a bbox rasterized to ``width`` x ``height`` pixels.
+
+ Args:
+ bbox: (west, south, east, north) in degrees, west < east, south < north.
+ width: output width in pixels.
+ height: output height in pixels.
+ crs: grid CRS; defaults to the IAU 2015 Moon geographic CRS.
+ """
+
+ bbox: BBox
+ width: int
+ height: int
+ crs: str = GEOGRAPHIC_CRS
+
+ def __post_init__(self) -> None:
+ west, south, east, north = self.bbox
+ if not (west < east and south < north):
+ raise ValueError(f"invalid bbox (west, south, east, north): {self.bbox}")
+ if self.width <= 0 or self.height <= 0:
+ raise ValueError(f"grid size must be positive, got {self.width}x{self.height}")
+
+ @property
+ def transform(self) -> Affine:
+ """Affine mapping pixel (col, row) to grid CRS coordinates."""
+ west, south, east, north = self.bbox
+ return from_bounds(west, south, east, north, self.width, self.height)
+
+
+def meters_to_degrees(meters: float, latitude: float) -> tuple[float, float]:
+ """Approximate the (dlon, dlat) degree span of a ground distance in metres.
+
+ Spherical arc length on the IAU 2015 Moon sphere, used only to size a
+ sampling window from ``patch_size * resolution``; the exact pixel geometry
+ is fixed later by :class:`TargetGrid`, so this need only be close. Longitude
+ degrees shrink with ``cos(latitude)``.
+
+ Args:
+ meters: ground distance in metres.
+ latitude: latitude in degrees where the span is measured.
+
+ Returns:
+ (dlon, dlat) span in degrees.
+ """
+ dlat = math.degrees(meters / MOON_RADIUS_M)
+ coslat = math.cos(math.radians(latitude))
+ # Guard the poles, where a bounded ground distance spans unbounded longitude.
+ dlon = dlat / coslat if coslat > 1e-6 else 360.0
+ return dlon, dlat
diff --git a/src/astrofetch/data/raster.py b/src/astrofetch/data/raster.py
new file mode 100644
index 0000000..a9e44e8
--- /dev/null
+++ b/src/astrofetch/data/raster.py
@@ -0,0 +1,77 @@
+"""Windowed COG reads reprojected onto a :class:`TargetGrid` (rasterio).
+
+Reads only the region of a Cloud Optimized GeoTIFF that covers the grid — via
+HTTP range requests against the right overview level — reprojects it onto the
+grid's CRS and resolution, applies the band's scale/offset to recover physical
+values, and returns the data with a boolean validity mask (``False`` where the
+source had nodata or simply does not cover the grid).
+
+All map-projection and resampling math is delegated to rasterio/GDAL (AGENTS
+rule 1): this module wires it up, it does not reimplement it.
+"""
+
+from __future__ import annotations
+
+import numpy as np
+import rasterio
+from rasterio.enums import Resampling
+from rasterio.errors import RasterioIOError
+from rasterio.vrt import WarpedVRT
+
+from astrofetch.data.grid import TargetGrid
+from astrofetch.errors import EndpointError
+
+_FILL = np.float32(0.0)
+"""Value written into invalid pixels; always paired with a False mask entry."""
+
+
+def read_window(
+ href: str,
+ grid: TargetGrid,
+ band: int = 1,
+ resampling: Resampling = Resampling.bilinear,
+) -> tuple[np.ndarray, np.ndarray]:
+ """Read one COG band, reprojected onto ``grid``, in physical units.
+
+ Args:
+ href: COG URL or local path.
+ grid: output grid; defines CRS, size, and extent.
+ band: 1-based band index to read.
+ resampling: resampling used when reprojecting to the grid.
+
+ Returns:
+ ``(image, mask)`` where ``image`` is a ``(grid.height, grid.width)``
+ float32 array of physical values (scale/offset applied) with invalid
+ pixels set to 0, and ``mask`` is a same-shaped bool array, ``True``
+ where the pixel is valid.
+
+ Raises:
+ EndpointError: the COG could not be opened or read.
+ """
+ try:
+ with rasterio.open(href) as src:
+ nodata = src.nodata
+ scale = float(src.scales[band - 1])
+ offset = float(src.offsets[band - 1])
+ with WarpedVRT(
+ src,
+ crs=grid.crs,
+ transform=grid.transform,
+ width=grid.width,
+ height=grid.height,
+ resampling=resampling,
+ src_nodata=nodata,
+ nodata=nodata,
+ ) as vrt:
+ raw = vrt.read(band)
+ except RasterioIOError as exc:
+ raise EndpointError(href, f"could not read COG: {exc}") from exc
+
+ if nodata is not None:
+ mask = raw != nodata
+ else:
+ mask = np.ones(raw.shape, dtype=bool)
+
+ image = raw.astype(np.float32) * np.float32(scale) + np.float32(offset)
+ image[~mask] = _FILL
+ return image, mask
diff --git a/src/astrofetch/data/stac.py b/src/astrofetch/data/stac.py
new file mode 100644
index 0000000..2a444d0
--- /dev/null
+++ b/src/astrofetch/data/stac.py
@@ -0,0 +1,85 @@
+"""STAC search against the USGS ARD catalog (pystac-client), politely.
+
+Wraps pystac-client with a retrying, backed-off HTTP session — AGENTS rule 5:
+never hammer archive servers — and normalizes failures into :class:`EndpointError`
+so callers never have to know pystac-client's exception surface. A single client
+is reused across searches (one polite connection pool, not one per request).
+"""
+
+from __future__ import annotations
+
+from functools import cache
+
+from pystac_client import Client
+from pystac_client.exceptions import APIError
+from pystac_client.stac_api_io import StacApiIO
+from urllib3.util.retry import Retry
+
+from astrofetch.data.endpoints import STAC_API_ROOT
+from astrofetch.data.grid import BBox
+from astrofetch.errors import EndpointError
+
+_TIMEOUT_S = 30
+"""Per-request timeout; a stuck archive should fail, not hang a dataloader."""
+
+_RETRY = Retry(
+ total=4,
+ backoff_factor=0.5,
+ status_forcelist=(429, 500, 502, 503, 504),
+ allowed_methods=frozenset({"GET", "POST"}),
+)
+"""Exponential backoff on rate-limit and transient server errors."""
+
+
+@cache
+def catalog(root: str = STAC_API_ROOT) -> Client:
+ """Open (and memoize) the STAC API client for ``root``.
+
+ Raises:
+ EndpointError: the catalog root could not be opened.
+ """
+ try:
+ return Client.open(root, stac_io=StacApiIO(max_retries=_RETRY, timeout=_TIMEOUT_S))
+ except APIError as exc:
+ raise EndpointError(root, f"could not open STAC catalog: {exc}") from exc
+
+
+def find_asset_hrefs(
+ collection: str,
+ asset_key: str,
+ bbox: BBox,
+ max_items: int = 20,
+ root: str = STAC_API_ROOT,
+) -> list[str]:
+ """Return hrefs of one asset across items of ``collection`` intersecting ``bbox``.
+
+ Args:
+ collection: STAC collection id to search.
+ asset_key: asset key to pull from each matching item, e.g. ``"dtm"``.
+ bbox: (west, south, east, north) in degrees.
+ max_items: cap on items returned; bounds the request volume per read.
+ root: STAC API root; defaults to the configured USGS ARD catalog.
+
+ Returns:
+ Asset hrefs, one per intersecting item (possibly empty if none overlap).
+
+ Raises:
+ EndpointError: the search failed or a matched item lacks ``asset_key``.
+ """
+ client = catalog(root)
+ try:
+ search = client.search(collections=[collection], bbox=list(bbox), max_items=max_items)
+ items = list(search.items())
+ except APIError as exc:
+ raise EndpointError(collection, f"STAC search failed: {exc}") from exc
+
+ hrefs = []
+ for item in items:
+ asset = item.assets.get(asset_key)
+ if asset is None:
+ raise EndpointError(
+ collection,
+ f"item {item.id} has no asset {asset_key!r}; available: {sorted(item.assets)}",
+ )
+ hrefs.append(asset.href)
+ return hrefs
diff --git a/src/astrofetch/errors.py b/src/astrofetch/errors.py
new file mode 100644
index 0000000..6afafb0
--- /dev/null
+++ b/src/astrofetch/errors.py
@@ -0,0 +1,26 @@
+"""AstroFetch exception hierarchy.
+
+Public errors live here so callers can ``except astrofetch.errors.EndpointError``
+without importing internal modules.
+"""
+
+from __future__ import annotations
+
+
+class AstroFetchError(Exception):
+ """Base class for every error AstroFetch raises on purpose."""
+
+
+class EndpointError(AstroFetchError):
+ """A remote archive endpoint failed or returned something unusable.
+
+ Carries the offending endpoint (a collection id, URL, or asset href) and
+ always points back at ``astrofetch/data/endpoints.py`` — the single place
+ external URLs are configured — so a moved or flaky service is easy to trace.
+ """
+
+ def __init__(self, endpoint: str, message: str) -> None:
+ self.endpoint = endpoint
+ super().__init__(
+ f"{endpoint}: {message} (endpoint URLs are configured in astrofetch/data/endpoints.py)"
+ )
diff --git a/src/astrofetch/moon/__init__.py b/src/astrofetch/moon/__init__.py
index 80be111..567d295 100644
--- a/src/astrofetch/moon/__init__.py
+++ b/src/astrofetch/moon/__init__.py
@@ -1,10 +1,10 @@
"""Lunar data access: instrument datasets, layer registry, discovery catalog."""
from astrofetch.moon.datasets import (
- LROCWAC,
InstrumentDataset,
IntersectionDataset,
KaguyaTC,
+ KaguyaTCImagery,
)
from astrofetch.moon.layers import LAYERS, MOON, Body, Instrument, LayerSpec, Probe
@@ -16,7 +16,7 @@
"InstrumentDataset",
"IntersectionDataset",
"KaguyaTC",
- "LROCWAC",
+ "KaguyaTCImagery",
"LayerSpec",
"Probe",
]
diff --git a/src/astrofetch/moon/datasets.py b/src/astrofetch/moon/datasets.py
index 0283852..850244a 100644
--- a/src/astrofetch/moon/datasets.py
+++ b/src/astrofetch/moon/datasets.py
@@ -1,31 +1,50 @@
"""Torch dataset classes over lunar instrument data.
-Phase 0 scaffolding: the API surface is real, the data path is a placeholder.
-Instrument datasets will be backed by the STAC sampler (Phase 1); until then
-``read`` yields correctly-shaped random tensors so the training loop contract —
-``for sample in KaguyaTC(...) & LROCWAC(...)`` — can be exercised end to end.
+Each instrument dataset samples random windows inside its ``bbox`` and, for each
+window, reads its product COGs from the USGS ARD STAC catalog, reprojects them
+onto a common target grid, and stacks them into a coregistered ``(C, H, W)``
+tensor. Combine instruments with ``&`` to stack their channels over the region
+they share.
+
+The Phase 1 read path is real: :mod:`astrofetch.data.stac` finds the COG assets
+covering a window, :mod:`astrofetch.data.raster` reprojects and reads them, and
+:class:`astrofetch.data.cache.WindowCache` memoizes the result on disk.
"""
from __future__ import annotations
from collections.abc import Iterator
-from typing import ClassVar
+from typing import ClassVar, NamedTuple
+import numpy as np
import torch
-from torch.utils.data import IterableDataset
+from torch.utils.data import Dataset
+
+from astrofetch.data import raster, stac
+from astrofetch.data.cache import WindowCache
+from astrofetch.data.grid import GEOGRAPHIC_CRS, TargetGrid, meters_to_degrees
BBox = tuple[float, float, float, float]
"""(west, south, east, north) in degrees, IAU 2015 Moon."""
-CRS = "IAU_2015:30100"
-"""Ocentric IAU 2015 Moon CRS carried by every sample."""
+CRS = GEOGRAPHIC_CRS
+"""Ocentric IAU 2015 Moon geographic CRS carried by every sample."""
+
+
+class Product(NamedTuple):
+ """One user-facing product: its sample-dict layer id and its STAC asset key."""
+ layer: str
+ asset: str
-class _WindowedDataset(IterableDataset):
- """Shared sampling loop: draw random windows, delegate to ``read``.
+
+class _WindowedDataset(Dataset[dict]):
+ """Map-style dataset: index ``i`` deterministically samples one window.
Subclasses set ``bbox``, ``resolution``, ``patch_size``, ``length``,
- ``seed``, and ``layers``, and implement ``read``.
+ ``seed``, and ``layers``, and implement ``read``. ``dataset[i]`` and
+ iteration both work, and because each index maps to a fixed window the
+ dataset supports ``DataLoader`` shuffling, samplers, and ``random_split``.
"""
bbox: BBox
@@ -34,8 +53,9 @@ class _WindowedDataset(IterableDataset):
length: int
seed: int | None
layers: list[str]
+ _cached_seed_base: int | None = None
- def read(self, bbox: BBox, generator: torch.Generator | None = None) -> dict:
+ def read(self, bbox: BBox) -> dict:
"""Read one window as a sample dict.
Returns:
@@ -44,12 +64,16 @@ def read(self, bbox: BBox, generator: torch.Generator | None = None) -> dict:
"""
raise NotImplementedError
+ def __getitem__(self, index: int) -> dict:
+ if index < 0:
+ index += self.length
+ if not 0 <= index < self.length:
+ raise IndexError(f"index out of range for length {self.length}")
+ return self.read(self._sample_bbox(index))
+
def __iter__(self) -> Iterator[dict]:
- generator = torch.Generator()
- if self.seed is not None:
- generator.manual_seed(self.seed)
- for _ in range(self.length):
- yield self.read(self._sample_bbox(generator), generator)
+ for index in range(self.length):
+ yield self[index]
def __len__(self) -> int:
return self.length
@@ -57,38 +81,58 @@ def __len__(self) -> int:
def __and__(self, other: _WindowedDataset) -> IntersectionDataset:
return IntersectionDataset(self, other)
- def _sample_bbox(self, generator: torch.Generator) -> BBox:
+ @property
+ def _seed_base(self) -> int:
+ # Fixed per instance: an explicit seed makes samples reproducible across
+ # instances; without one, each instance still gives a stable dataset[i].
+ if self._cached_seed_base is None:
+ if self.seed is not None:
+ self._cached_seed_base = self.seed
+ else:
+ self._cached_seed_base = int(torch.randint(0, 2**31 - 1, (1,)).item())
+ return self._cached_seed_base
+
+ def _sample_bbox(self, index: int) -> BBox:
+ generator = torch.Generator()
+ generator.manual_seed((self._seed_base * 1_000_003 + index) % (2**63 - 1))
west, south, east, north = self.bbox
+ span_lon, span_lat = east - west, north - south
+ # Window ground size from patch_size * resolution, converted to degrees
+ # at the region's centre latitude; clamp to the bbox so it stays inside.
+ center_lat = (south + north) / 2.0
+ win_lon, win_lat = meters_to_degrees(self.patch_size * self.resolution, center_lat)
+ win_lon, win_lat = min(win_lon, span_lon), min(win_lat, span_lat)
u, v = torch.rand(2, generator=generator).tolist()
- # Placeholder: a degenerate point bbox; Phase 1 sizes the window from
- # patch_size * resolution on the target grid.
- lon = west + u * (east - west)
- lat = south + v * (north - south)
- return (lon, lat, lon, lat)
+ west0 = west + u * (span_lon - win_lon)
+ south0 = south + v * (span_lat - win_lat)
+ return (west0, south0, west0 + win_lon, south0 + win_lat)
class InstrumentDataset(_WindowedDataset):
- """Iterable dataset of patches from a single instrument.
+ """Map-style dataset of patches from a single instrument.
- Samples random bounding boxes within ``bbox`` and yields sample dicts:
- ``image`` is a (C, H, W) float tensor with one channel per requested
- product, ``mask`` a same-shaped bool validity tensor (orbital swaths do
- not cover everything), plus ``layers``/``bbox``/``crs``/``resolution``
- provenance. Combine instruments with ``&`` to stack their channels over
- the overlapping region.
+ Each index deterministically samples a bounding box within ``bbox`` and
+ returns a sample dict: ``image`` is a (C, H, W) float tensor with one
+ channel per requested product (physical values) where ``H = W =
+ patch_size``, ``mask`` a same-shaped bool validity tensor (orbital swaths
+ do not cover everything), plus ``layers``/``bbox``/``crs``/``resolution``
+ provenance. Combine instruments with ``&`` to stack their channels over the
+ overlapping region.
Args:
products: product names to stack, e.g. ``["dtm"]``; defaults to all
products the instrument offers.
bbox: region to sample from as (west, south, east, north) degrees.
- resolution: target resolution in meters per pixel.
+ resolution: target resolution in metres per pixel.
patch_size: output height and width in pixels.
length: number of patches per epoch.
seed: RNG seed for reproducible sampling.
+ max_items: cap on STAC items mosaicked per layer per window.
+ cache: window cache; defaults to the shared on-disk cache.
Example:
- >>> moondata = LROCWAC(bbox=(-60.0, 5.0, -55.0, 10.0))
- >>> for sample in moondata:
+ >>> moondata = KaguyaTC(products=["dtm"], bbox=(-26.4, -50.7, -25.4, -49.6))
+ >>> for sample in moondata: # doctest: +SKIP
... sample["image"] # torch.Tensor (C, H, W)
"""
@@ -98,8 +142,11 @@ class InstrumentDataset(_WindowedDataset):
instrument: ClassVar[str]
"""Human-readable instrument name."""
- all_products: ClassVar[dict[str, str]]
- """Product name -> layer identifier (see ``astrofetch.moon.layers``)."""
+ collection: ClassVar[str]
+ """USGS ARD STAC collection id backing this instrument's products."""
+
+ all_products: ClassVar[dict[str, Product]]
+ """Product name -> (layer id, STAC asset key)."""
def __init__(
self,
@@ -109,6 +156,8 @@ def __init__(
patch_size: int = 256,
length: int = 1000,
seed: int | None = None,
+ max_items: int = 20,
+ cache: WindowCache | None = None,
) -> None:
if products is None:
products = list(self.all_products)
@@ -124,41 +173,82 @@ def __init__(
if not (west < east and south < north):
raise ValueError(f"invalid bbox (west, south, east, north): {bbox}")
self.products = list(products)
- self.layers = [self.all_products[p] for p in products]
+ self.layers = [self.all_products[p].layer for p in products]
self.bbox = bbox
self.resolution = resolution
self.patch_size = patch_size
self.length = length
self.seed = seed
-
- def read(self, bbox: BBox, generator: torch.Generator | None = None) -> dict:
- # Placeholder for the Phase 1 STAC sampler: query the USGS ARD
- # catalog, window-read COGs, reproject, and stack.
- image = torch.rand(len(self.layers), self.patch_size, self.patch_size, generator=generator)
+ self.max_items = max_items
+ self.cache = cache if cache is not None else WindowCache()
+
+ def read(self, bbox: BBox) -> dict:
+ grid = TargetGrid(bbox=bbox, width=self.patch_size, height=self.patch_size, crs=CRS)
+ images, masks = [], []
+ for product in self.products:
+ spec = self.all_products[product]
+ image, mask = self._read_layer(spec, grid)
+ images.append(image)
+ masks.append(mask)
return {
- "image": image,
- "mask": torch.ones_like(image, dtype=torch.bool),
+ "image": torch.from_numpy(np.stack(images)).to(torch.float32),
+ "mask": torch.from_numpy(np.stack(masks)).to(torch.bool),
"layers": list(self.layers),
"bbox": bbox,
"crs": CRS,
"resolution": self.resolution,
}
+ def _read_layer(self, spec: Product, grid: TargetGrid) -> tuple[np.ndarray, np.ndarray]:
+ cached = self.cache.get(spec.layer, grid)
+ if cached is not None:
+ return cached
+ hrefs = stac.find_asset_hrefs(self.collection, spec.asset, grid.bbox, self.max_items)
+ image, mask = _mosaic(hrefs, grid)
+ self.cache.put(spec.layer, grid, image, mask)
+ return image, mask
+
+
+def _mosaic(hrefs: list[str], grid: TargetGrid) -> tuple[np.ndarray, np.ndarray]:
+ """Reproject each COG onto ``grid`` and fill invalid pixels from later items.
+
+ Earlier items win where they have data; later items fill only the gaps.
+ With no items, returns an all-invalid (zero) window — the mask tells the
+ truth about coverage rather than fabricating data.
+ """
+ image = np.zeros((grid.height, grid.width), dtype=np.float32)
+ valid = np.zeros((grid.height, grid.width), dtype=bool)
+ for href in hrefs:
+ layer_image, layer_mask = raster.read_window(href, grid)
+ fill = layer_mask & ~valid
+ image[fill] = layer_image[fill]
+ valid |= layer_mask
+ return image, valid
+
class KaguyaTC(InstrumentDataset):
- """Kaguya (SELENE) Terrain Camera: stereo-derived DTM and orthomosaic."""
+ """Kaguya (SELENE) Terrain Camera: USGS stereo-derived DTM and orthoimage."""
probe = "Kaguya (SELENE)"
- instrument = "Terrain Camera"
- all_products = {"dtm": "kaguya_tc_dtm", "ortho": "kaguya_tc_ortho"}
+ instrument = "Terrain Camera (USGS DTM)"
+ collection = "kaguya_terrain_camera_usgs_dtms"
+ all_products = {
+ "dtm": Product("kaguya_tc_dtm", "dtm"),
+ "ortho": Product("kaguya_tc_ortho", "orthoimage"),
+ }
+
+class KaguyaTCImagery(InstrumentDataset):
+ """Kaguya (SELENE) Terrain Camera: stereoscopic radiance imagery.
-class LROCWAC(InstrumentDataset):
- """LRO Wide Angle Camera: global morphologic mosaic."""
+ Raw Terrain Camera observations (16-bit DN scaled to radiance), distinct
+ from the USGS-derived DTM products in :class:`KaguyaTC`.
+ """
- probe = "Lunar Reconnaissance Orbiter"
- instrument = "LROC Wide Angle Camera"
- all_products = {"mosaic": "lroc_wac"}
+ probe = "Kaguya (SELENE)"
+ instrument = "Terrain Camera (imagery)"
+ collection = "kaguya_terrain_camera_stereoscopic_uncontrolled_observations"
+ all_products = {"image": Product("kaguya_tc_image", "image")}
class IntersectionDataset(_WindowedDataset):
@@ -196,9 +286,9 @@ def __init__(self, first: _WindowedDataset, second: _WindowedDataset) -> None:
self.seed = first.seed if first.seed is not None else second.seed
self.layers = first.layers + second.layers
- def read(self, bbox: BBox, generator: torch.Generator | None = None) -> dict:
- left = self.first.read(bbox, generator)
- right = self.second.read(bbox, generator)
+ def read(self, bbox: BBox) -> dict:
+ left = self.first.read(bbox)
+ right = self.second.read(bbox)
return {
"image": torch.cat([left["image"], right["image"]]),
"mask": torch.cat([left["mask"], right["mask"]]),
diff --git a/src/astrofetch/moon/layers.py b/src/astrofetch/moon/layers.py
index b5057ff..8e0dd5f 100644
--- a/src/astrofetch/moon/layers.py
+++ b/src/astrofetch/moon/layers.py
@@ -1,18 +1,23 @@
"""Layer registry and discovery catalog for lunar data.
The registry (``LAYERS``) is the single place where user-facing layer
-identifiers map to their provenance and, in Phase 1, USGS ARD STAC
-collections. The catalog (``MOON``) arranges the same information as a
+identifiers map to their provenance and their backing USGS ARD STAC collection
+and asset. The catalog (``MOON``) arranges the same information as a
Body -> Probe -> Instrument hierarchy for discovery; its nodes hold specs and
dataset *classes*, never dataset instances. Flat imports
-(``from astrofetch.moon import LROCWAC``) remain the primary path.
+(``from astrofetch.moon import KaguyaTC``) remain the primary path.
"""
from __future__ import annotations
from dataclasses import dataclass
-from astrofetch.moon.datasets import CRS, LROCWAC, InstrumentDataset, KaguyaTC
+from astrofetch.moon.datasets import (
+ CRS,
+ InstrumentDataset,
+ KaguyaTC,
+ KaguyaTCImagery,
+)
@dataclass(frozen=True)
@@ -32,12 +37,15 @@ class LayerSpec:
"""Product name within the instrument, e.g. ``"dtm"``."""
collection: str
- """USGS ARD STAC collection id; placeholder until the Phase 1 sampler."""
+ """USGS ARD STAC collection id backing this layer."""
+
+ asset: str
+ """STAC asset key read from each item, e.g. ``"dtm"``."""
@dataclass(frozen=True)
class Instrument:
- """Catalog node: one instrument, its products, and its dataset class."""
+ """Catalog node: one instrument dataset, its products, and its class."""
name: str
products: dict[str, LayerSpec]
@@ -47,7 +55,7 @@ class Instrument:
@dataclass(frozen=True)
class Probe:
- """Catalog node: one probe and the instruments it carries."""
+ """Catalog node: one probe and the instrument datasets it carries."""
name: str
instruments: dict[str, Instrument]
@@ -62,24 +70,24 @@ class Body:
probes: dict[str, Probe]
-def _spec(dataset: type[InstrumentDataset], product: str, collection: str) -> LayerSpec:
+def _spec(dataset: type[InstrumentDataset], product: str) -> LayerSpec:
+ entry = dataset.all_products[product]
return LayerSpec(
- name=dataset.all_products[product],
+ name=entry.layer,
probe=dataset.probe,
instrument=dataset.instrument,
product=product,
- collection=collection,
+ collection=dataset.collection,
+ asset=entry.asset,
)
-# Collection ids are placeholders until Phase 1 pins the real USGS ARD
-# collections; endpoint URLs will live in data/endpoints.py, never here.
LAYERS: dict[str, LayerSpec] = {
spec.name: spec
for spec in (
- _spec(KaguyaTC, "dtm", "placeholder:kaguya_tc_dtm"),
- _spec(KaguyaTC, "ortho", "placeholder:kaguya_tc_ortho"),
- _spec(LROCWAC, "mosaic", "placeholder:lroc_wac_mosaic"),
+ _spec(KaguyaTC, "dtm"),
+ _spec(KaguyaTC, "ortho"),
+ _spec(KaguyaTCImagery, "image"),
)
}
@@ -87,7 +95,7 @@ def _spec(dataset: type[InstrumentDataset], product: str, collection: str) -> La
def _instrument(dataset: type[InstrumentDataset]) -> Instrument:
return Instrument(
name=dataset.instrument,
- products={product: LAYERS[layer] for product, layer in dataset.all_products.items()},
+ products={product: LAYERS[entry.layer] for product, entry in dataset.all_products.items()},
dataset=dataset,
)
@@ -96,8 +104,13 @@ def _instrument(dataset: type[InstrumentDataset]) -> Instrument:
name="Moon",
crs=CRS,
probes={
- "kaguya": Probe(name=KaguyaTC.probe, instruments={"tc": _instrument(KaguyaTC)}),
- "lro": Probe(name=LROCWAC.probe, instruments={"lroc_wac": _instrument(LROCWAC)}),
+ "kaguya": Probe(
+ name=KaguyaTC.probe,
+ instruments={
+ "tc_dtm": _instrument(KaguyaTC),
+ "tc_imagery": _instrument(KaguyaTCImagery),
+ },
+ ),
},
)
"""Discovery catalog for the Moon: enumerate probes, instruments, products."""
diff --git a/tests/live/test_endpoints_live.py b/tests/live/test_endpoints_live.py
index fb4e378..f2c4213 100644
--- a/tests/live/test_endpoints_live.py
+++ b/tests/live/test_endpoints_live.py
@@ -6,18 +6,46 @@
uv run pytest tests/live -m live
-Phase 0 ships no network code yet, so this module only establishes the policy
-and the ``live`` marker. Real STAC/COG connectivity checks land with the
-Phase 1 sampler.
+They exercise the Phase 1 STAC/COG path against the USGS ARD catalog over a
+small, known-covered lunar region, so they stay fast and single-shot.
"""
+from __future__ import annotations
+
+from pathlib import Path
+
import pytest
+import astrofetch as af
+from astrofetch.data import stac
+from astrofetch.data.cache import WindowCache
+
+# A small box with known Kaguya TC USGS DTM coverage (near -50 deg latitude).
+_COVERED_BBOX = (-26.3, -50.6, -25.5, -49.7)
+
@pytest.mark.live
-def test_live_marker_is_registered() -> None:
- """Placeholder asserting the live suite is wired up.
+def test_stac_catalog_returns_covering_cogs() -> None:
+ """The USGS ARD catalog is reachable and returns DTM COGs for a known box."""
+ hrefs = stac.find_asset_hrefs(af.KaguyaTC.collection, "dtm", _COVERED_BBOX, max_items=3)
+ assert hrefs
+ assert all(href.endswith(".tif") for href in hrefs)
+
- Replace with a real USGS ARD STAC reachability check in Phase 1.
- """
- pytest.skip("no live endpoints until the Phase 1 STAC sampler lands")
+@pytest.mark.live
+def test_fetches_a_real_two_layer_patch(tmp_path: Path) -> None:
+ """End to end: a coregistered (2, H, W) DTM+ortho patch of real physical data."""
+ moondata = af.KaguyaTC(
+ products=["dtm", "ortho"],
+ bbox=_COVERED_BBOX,
+ resolution=50.0,
+ patch_size=64,
+ length=1,
+ seed=3,
+ cache=WindowCache(tmp_path),
+ )
+ sample = next(iter(moondata))
+ assert sample["image"].shape == (2, 64, 64)
+ assert sample["layers"] == ["kaguya_tc_dtm", "kaguya_tc_ortho"]
+ # At least some pixels are valid over a covered region.
+ assert bool(sample["mask"].any())
diff --git a/tests/unit/test_cache.py b/tests/unit/test_cache.py
new file mode 100644
index 0000000..b487190
--- /dev/null
+++ b/tests/unit/test_cache.py
@@ -0,0 +1,63 @@
+"""Unit tests for the disposable window cache."""
+
+from __future__ import annotations
+
+from pathlib import Path
+
+import numpy as np
+import pytest
+
+from astrofetch.data.cache import WindowCache, default_cache_dir
+from astrofetch.data.grid import TargetGrid
+
+
+def _grid(width: int = 8) -> TargetGrid:
+ return TargetGrid(bbox=(0.0, 0.0, 1.0, 1.0), width=width, height=8)
+
+
+def test_roundtrips_image_and_mask(tmp_path: Path) -> None:
+ cache = WindowCache(tmp_path)
+ image = np.arange(64, dtype=np.float32).reshape(8, 8)
+ mask = image > 30
+
+ assert cache.get("kaguya_tc_dtm", _grid()) is None
+ cache.put("kaguya_tc_dtm", _grid(), image, mask)
+ cached = cache.get("kaguya_tc_dtm", _grid())
+
+ assert cached is not None
+ got_image, got_mask = cached
+ np.testing.assert_array_equal(got_image, image)
+ np.testing.assert_array_equal(got_mask, mask)
+
+
+def test_distinct_grids_do_not_collide(tmp_path: Path) -> None:
+ cache = WindowCache(tmp_path)
+ image = np.ones((8, 8), dtype=np.float32)
+ cache.put("layer", _grid(width=8), image, image.astype(bool))
+
+ # Same layer, different grid width -> different key -> miss.
+ assert cache.get("layer", _grid(width=16)) is None
+
+
+def test_clear_removes_entries(tmp_path: Path) -> None:
+ cache = WindowCache(tmp_path)
+ image = np.zeros((8, 8), dtype=np.float32)
+ cache.put("layer", _grid(), image, image.astype(bool))
+
+ cache.clear()
+
+ assert cache.get("layer", _grid()) is None
+ assert not list(tmp_path.glob("*.npz"))
+
+
+def test_put_leaves_no_temp_files(tmp_path: Path) -> None:
+ cache = WindowCache(tmp_path)
+ image = np.zeros((8, 8), dtype=np.float32)
+ cache.put("layer", _grid(), image, image.astype(bool))
+
+ assert not list(tmp_path.glob("*.tmp.npz"))
+
+
+def test_default_dir_honors_env(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
+ monkeypatch.setenv("ASTROFETCH_CACHE", str(tmp_path / "custom"))
+ assert default_cache_dir() == tmp_path / "custom"
diff --git a/tests/unit/test_datasets.py b/tests/unit/test_datasets.py
index c3f5cc2..1c0ba5d 100644
--- a/tests/unit/test_datasets.py
+++ b/tests/unit/test_datasets.py
@@ -1,13 +1,48 @@
+"""Unit tests for the instrument datasets.
+
+The read path is real (STAC search + COG reproject + cache), so these tests
+stub :mod:`astrofetch.data.stac` and :mod:`astrofetch.data.raster` — no network,
+no real COGs — and redirect the cache to a temp dir. A deterministic fake read
+lets us assert shapes, layer wiring, reproducibility, and composition.
+"""
+
+from __future__ import annotations
+
+from pathlib import Path
+
+import numpy as np
import pytest
import torch
from torch.utils.data import DataLoader
import astrofetch as af
from astrofetch.moon import LAYERS, MOON
+from astrofetch.moon import datasets as ds
SAMPLE_KEYS = {"image", "mask", "layers", "bbox", "crs", "resolution"}
+def _fake_find_asset_hrefs(
+ collection: str, asset: str, bbox: tuple, max_items: int = 20, root: str | None = None
+) -> list[str]:
+ return [f"{collection}|{asset}"]
+
+
+def _fake_read_window(href, grid, band=1, resampling=None):
+ # Deterministic in (href, window): identical requests read identical data,
+ # which is what makes seeded sampling reproducible under mocking.
+ value = float(hash((href, grid.bbox)) % 997)
+ image = np.full((grid.height, grid.width), value, dtype=np.float32)
+ return image, np.ones((grid.height, grid.width), dtype=bool)
+
+
+@pytest.fixture(autouse=True)
+def _mock_reads(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
+ monkeypatch.setenv("ASTROFETCH_CACHE", str(tmp_path / "cache"))
+ monkeypatch.setattr(ds.stac, "find_asset_hrefs", _fake_find_asset_hrefs)
+ monkeypatch.setattr(ds.raster, "read_window", _fake_read_window)
+
+
def test_instrument_yields_sample_dicts() -> None:
moondata = af.KaguyaTC(
products=["dtm", "ortho"],
@@ -33,10 +68,52 @@ def test_products_default_to_all() -> None:
assert moondata.layers == ["kaguya_tc_dtm", "kaguya_tc_ortho"]
+def test_read_queries_stac_by_collection_and_asset(monkeypatch: pytest.MonkeyPatch) -> None:
+ calls: list[tuple[str, str]] = []
+
+ def _spy(collection, asset, bbox, max_items=20, root=None):
+ calls.append((collection, asset))
+ return [f"{collection}|{asset}"]
+
+ monkeypatch.setattr(ds.stac, "find_asset_hrefs", _spy)
+ next(iter(af.KaguyaTC(products=["ortho"], patch_size=8, length=1, seed=0)))
+ assert calls == [("kaguya_terrain_camera_usgs_dtms", "orthoimage")]
+
+
+def test_supports_indexing() -> None:
+ moondata = af.KaguyaTC(products=["dtm"], patch_size=8, length=5, seed=0)
+ sample = moondata[0]
+ assert set(sample) == SAMPLE_KEYS
+ assert sample["image"].shape == (1, 8, 8)
+ # Indexing is deterministic and negative indices work.
+ assert torch.equal(moondata[0]["image"], moondata[0]["image"])
+ assert torch.equal(moondata[-1]["image"], moondata[4]["image"])
+
+
+def test_index_out_of_range_raises() -> None:
+ moondata = af.KaguyaTC(products=["dtm"], patch_size=8, length=3, seed=0)
+ with pytest.raises(IndexError):
+ moondata[3]
+
+
+def test_patch_size_sets_image_dimensions() -> None:
+ moondata = af.KaguyaTCImagery(patch_size=48, length=1, seed=0)
+ assert moondata[0]["image"].shape == (1, 48, 48)
+
+
+def test_shuffled_dataloader_covers_every_index() -> None:
+ moondata = af.KaguyaTC(products=["dtm"], patch_size=8, length=6, seed=0)
+ loader = DataLoader(moondata, batch_size=2, shuffle=True)
+ total = sum(batch["image"].shape[0] for batch in loader)
+ assert total == 6
+
+
def test_seed_is_reproducible() -> None:
- kwargs = dict(products=["mosaic"], patch_size=16, length=2, seed=42)
- first = [s["image"] for s in af.LROCWAC(**kwargs)]
- second = [s["image"] for s in af.LROCWAC(**kwargs)]
+ def sampler() -> af.KaguyaTCImagery:
+ return af.KaguyaTCImagery(products=["image"], patch_size=16, length=2, seed=42)
+
+ first = [s["image"] for s in sampler()]
+ second = [s["image"] for s in sampler()]
for a, b in zip(first, second, strict=True):
assert torch.equal(a, b)
@@ -44,29 +121,29 @@ def test_seed_is_reproducible() -> None:
def test_intersection_stacks_channels() -> None:
moondata = af.KaguyaTC(
products=["dtm"], bbox=(-30.0, -10.0, -20.0, 0.0), patch_size=16, length=4, seed=1
- ) & af.LROCWAC(bbox=(-25.0, -15.0, -15.0, 5.0), patch_size=16, length=6, seed=1)
+ ) & af.KaguyaTCImagery(bbox=(-25.0, -15.0, -15.0, 5.0), patch_size=16, length=6, seed=1)
assert moondata.bbox == (-25.0, -10.0, -20.0, 0.0)
assert len(moondata) == 4
for sample in moondata:
assert set(sample) == SAMPLE_KEYS
assert sample["image"].shape == (2, 16, 16)
assert sample["mask"].shape == (2, 16, 16)
- assert sample["layers"] == ["kaguya_tc_dtm", "lroc_wac"]
+ assert sample["layers"] == ["kaguya_tc_dtm", "kaguya_tc_image"]
def test_intersection_nests() -> None:
moondata = (
af.KaguyaTC(products=["dtm"], patch_size=16, length=2, seed=0)
& af.KaguyaTC(products=["ortho"], patch_size=16, length=2, seed=0)
- & af.LROCWAC(patch_size=16, length=2, seed=0)
+ & af.KaguyaTCImagery(patch_size=16, length=2, seed=0)
)
sample = next(iter(moondata))
assert sample["image"].shape == (3, 16, 16)
- assert sample["layers"] == ["kaguya_tc_dtm", "kaguya_tc_ortho", "lroc_wac"]
+ assert sample["layers"] == ["kaguya_tc_dtm", "kaguya_tc_ortho", "kaguya_tc_image"]
def test_works_with_default_dataloader_collation() -> None:
- moondata = af.LROCWAC(patch_size=16, length=4, seed=0)
+ moondata = af.KaguyaTCImagery(patch_size=16, length=4, seed=0)
loader = DataLoader(moondata, batch_size=2)
batches = list(loader)
assert len(batches) == 2
@@ -86,26 +163,60 @@ def test_rejects_unknown_product() -> None:
def test_rejects_invalid_bbox() -> None:
with pytest.raises(ValueError):
- af.LROCWAC(bbox=(10.0, 0.0, -10.0, 5.0))
+ af.KaguyaTCImagery(bbox=(10.0, 0.0, -10.0, 5.0))
def test_rejects_disjoint_intersection() -> None:
with pytest.raises(ValueError):
- af.KaguyaTC(bbox=(-60.0, 5.0, -55.0, 10.0)) & af.LROCWAC(bbox=(30.0, -10.0, 40.0, 0.0))
+ af.KaguyaTC(bbox=(-60.0, 5.0, -55.0, 10.0)) & af.KaguyaTCImagery(
+ bbox=(30.0, -10.0, 40.0, 0.0)
+ )
def test_rejects_mismatched_grids() -> None:
with pytest.raises(ValueError):
- af.KaguyaTC(patch_size=16) & af.LROCWAC(patch_size=32)
+ af.KaguyaTC(patch_size=16) & af.KaguyaTCImagery(patch_size=32)
def test_catalog_points_at_dataset_classes() -> None:
- assert MOON.probes["lro"].instruments["lroc_wac"].dataset is af.LROCWAC
- assert MOON.probes["kaguya"].instruments["tc"].dataset is af.KaguyaTC
+ assert MOON.probes["kaguya"].instruments["tc_dtm"].dataset is af.KaguyaTC
+ assert MOON.probes["kaguya"].instruments["tc_imagery"].dataset is af.KaguyaTCImagery
def test_catalog_and_registry_agree() -> None:
- spec = MOON.probes["lro"].instruments["lroc_wac"].products["mosaic"]
- assert spec is LAYERS["lroc_wac"]
- assert spec.probe == af.LROCWAC.probe
- assert spec.instrument == af.LROCWAC.instrument
+ spec = MOON.probes["kaguya"].instruments["tc_dtm"].products["dtm"]
+ assert spec is LAYERS["kaguya_tc_dtm"]
+ assert spec.collection == af.KaguyaTC.collection
+ assert spec.asset == "dtm"
+
+
+def test_mosaic_prefers_earlier_items_and_fills_gaps(monkeypatch: pytest.MonkeyPatch) -> None:
+ from astrofetch.data.grid import TargetGrid
+
+ def _coverage(href, grid, band=1, resampling=None):
+ image = np.full((grid.height, grid.width), float(href[-1]), dtype=np.float32)
+ mask = np.zeros((grid.height, grid.width), dtype=bool)
+ if href.endswith("1"): # first item covers only the left half
+ mask[:, : grid.width // 2] = True
+ else: # second item covers everything
+ mask[:] = True
+ image[~mask] = 0.0
+ return image, mask
+
+ monkeypatch.setattr(ds.raster, "read_window", _coverage)
+ grid = TargetGrid(bbox=(0.0, 0.0, 1.0, 1.0), width=8, height=8)
+
+ image, mask = ds._mosaic(["item1", "item2"], grid)
+
+ assert mask.all()
+ assert (image[:, :4] == 1.0).all() # earlier item wins where it has data
+ assert (image[:, 4:] == 2.0).all() # later item fills the gap
+
+
+def test_mosaic_with_no_items_is_all_invalid() -> None:
+ from astrofetch.data.grid import TargetGrid
+
+ grid = TargetGrid(bbox=(0.0, 0.0, 1.0, 1.0), width=8, height=8)
+ image, mask = ds._mosaic([], grid)
+ assert not mask.any()
+ assert (image == 0.0).all()
diff --git a/tests/unit/test_grid.py b/tests/unit/test_grid.py
new file mode 100644
index 0000000..89ecc5d
--- /dev/null
+++ b/tests/unit/test_grid.py
@@ -0,0 +1,49 @@
+import math
+
+import pytest
+
+from astrofetch.data.grid import (
+ GEOGRAPHIC_CRS,
+ MOON_RADIUS_M,
+ TargetGrid,
+ meters_to_degrees,
+)
+
+
+def test_transform_spans_bbox() -> None:
+ grid = TargetGrid(bbox=(-10.0, -5.0, 10.0, 5.0), width=200, height=100)
+ transform = grid.transform
+ # Top-left pixel corner maps to (west, north); bottom-right to (east, south).
+ assert transform * (0, 0) == pytest.approx((-10.0, 5.0))
+ assert transform * (200, 100) == pytest.approx((10.0, -5.0))
+
+
+def test_default_crs_is_geographic() -> None:
+ assert TargetGrid(bbox=(0.0, 0.0, 1.0, 1.0), width=8, height=8).crs == GEOGRAPHIC_CRS
+
+
+def test_rejects_inverted_bbox() -> None:
+ with pytest.raises(ValueError):
+ TargetGrid(bbox=(10.0, 0.0, -10.0, 5.0), width=8, height=8)
+
+
+def test_rejects_nonpositive_size() -> None:
+ with pytest.raises(ValueError):
+ TargetGrid(bbox=(0.0, 0.0, 1.0, 1.0), width=0, height=8)
+
+
+def test_meters_to_degrees_at_equator_is_isotropic() -> None:
+ dlon, dlat = meters_to_degrees(MOON_RADIUS_M * math.radians(1.0), latitude=0.0)
+ assert dlon == pytest.approx(1.0)
+ assert dlat == pytest.approx(1.0)
+
+
+def test_longitude_span_grows_toward_the_pole() -> None:
+ dlon, dlat = meters_to_degrees(1000.0, latitude=60.0)
+ # cos(60) = 0.5, so a fixed ground distance spans twice the longitude.
+ assert dlon == pytest.approx(2.0 * dlat)
+
+
+def test_pole_longitude_span_is_clamped() -> None:
+ dlon, _ = meters_to_degrees(1000.0, latitude=90.0)
+ assert dlon == 360.0
diff --git a/tests/unit/test_raster.py b/tests/unit/test_raster.py
new file mode 100644
index 0000000..baf2c94
--- /dev/null
+++ b/tests/unit/test_raster.py
@@ -0,0 +1,102 @@
+"""Unit tests for windowed COG reads — local GeoTIFFs only, never the network."""
+
+from __future__ import annotations
+
+from pathlib import Path
+
+import numpy as np
+import pytest
+import rasterio
+from rasterio.enums import Resampling
+from rasterio.transform import from_bounds
+
+from astrofetch.data.grid import GEOGRAPHIC_CRS, TargetGrid
+from astrofetch.data.raster import read_window
+from astrofetch.errors import EndpointError
+
+
+def _write_geotiff(
+ path: Path,
+ data: np.ndarray,
+ bbox: tuple[float, float, float, float],
+ *,
+ crs: str = GEOGRAPHIC_CRS,
+ nodata: float | None = None,
+ scale: float = 1.0,
+ offset: float = 0.0,
+) -> str:
+ height, width = data.shape
+ west, south, east, north = bbox
+ with rasterio.open(
+ path,
+ "w",
+ driver="GTiff",
+ height=height,
+ width=width,
+ count=1,
+ dtype=data.dtype,
+ crs=crs,
+ transform=from_bounds(west, south, east, north, width, height),
+ nodata=nodata,
+ ) as dst:
+ dst.write(data, 1)
+ dst.scales = (scale,)
+ dst.offsets = (offset,)
+ return str(path)
+
+
+def test_reads_grid_shape_and_dtype(tmp_path: Path) -> None:
+ src = np.arange(64, dtype=np.float32).reshape(8, 8)
+ href = _write_geotiff(tmp_path / "src.tif", src, (0.0, 0.0, 8.0, 8.0))
+ grid = TargetGrid(bbox=(0.0, 0.0, 8.0, 8.0), width=8, height=8)
+
+ image, mask = read_window(href, grid)
+
+ assert image.shape == (8, 8)
+ assert image.dtype == np.float32
+ assert mask.dtype == np.bool_
+ assert mask.all()
+
+
+def test_applies_scale_and_offset(tmp_path: Path) -> None:
+ src = np.full((8, 8), 100, dtype=np.int16)
+ href = _write_geotiff(
+ tmp_path / "radiance.tif", src, (0.0, 0.0, 8.0, 8.0), scale=0.013, offset=2.0
+ )
+ grid = TargetGrid(bbox=(0.0, 0.0, 8.0, 8.0), width=8, height=8)
+
+ image, _ = read_window(href, grid, resampling=Resampling.nearest)
+
+ assert image == pytest.approx(100 * 0.013 + 2.0)
+
+
+def test_nodata_becomes_invalid_mask(tmp_path: Path) -> None:
+ src = np.ones((8, 8), dtype=np.float32)
+ src[:, :4] = -9999.0
+ href = _write_geotiff(tmp_path / "gappy.tif", src, (0.0, 0.0, 8.0, 8.0), nodata=-9999.0)
+ grid = TargetGrid(bbox=(0.0, 0.0, 8.0, 8.0), width=8, height=8)
+
+ image, mask = read_window(href, grid, resampling=Resampling.nearest)
+
+ assert not mask[:, :4].any()
+ assert mask[:, 4:].all()
+ # Invalid pixels are zero-filled, never left as the nodata sentinel.
+ assert (image[~mask] == 0.0).all()
+
+
+def test_area_outside_source_is_invalid(tmp_path: Path) -> None:
+ src = np.ones((8, 8), dtype=np.float32)
+ href = _write_geotiff(tmp_path / "small.tif", src, (0.0, 0.0, 4.0, 4.0), nodata=0.0)
+ # Grid extends east of the source coverage.
+ grid = TargetGrid(bbox=(0.0, 0.0, 8.0, 4.0), width=16, height=8)
+
+ _, mask = read_window(href, grid, resampling=Resampling.nearest)
+
+ assert mask[:, :8].all()
+ assert not mask[:, 8:].any()
+
+
+def test_missing_file_raises_endpoint_error(tmp_path: Path) -> None:
+ grid = TargetGrid(bbox=(0.0, 0.0, 1.0, 1.0), width=8, height=8)
+ with pytest.raises(EndpointError):
+ read_window(str(tmp_path / "does_not_exist.tif"), grid)
diff --git a/tests/unit/test_stac.py b/tests/unit/test_stac.py
new file mode 100644
index 0000000..9718df8
--- /dev/null
+++ b/tests/unit/test_stac.py
@@ -0,0 +1,78 @@
+"""Unit tests for the STAC wrapper — the client is stubbed, never the network."""
+
+from __future__ import annotations
+
+import pytest
+from pystac_client.exceptions import APIError
+
+from astrofetch.data import stac
+from astrofetch.errors import EndpointError
+
+
+class _Asset:
+ def __init__(self, href: str) -> None:
+ self.href = href
+
+
+class _Item:
+ def __init__(self, item_id: str, assets: dict[str, _Asset]) -> None:
+ self.id = item_id
+ self.assets = assets
+
+
+class _Search:
+ def __init__(self, items: list[_Item]) -> None:
+ self._items = items
+
+ def items(self) -> list[_Item]:
+ return self._items
+
+
+class _Client:
+ def __init__(self, items: list[_Item]) -> None:
+ self._items = items
+ self.calls: list[dict] = []
+
+ def search(self, **kwargs: object) -> _Search:
+ self.calls.append(kwargs)
+ return _Search(self._items)
+
+
+def _patch_catalog(monkeypatch: pytest.MonkeyPatch, client: _Client) -> None:
+ monkeypatch.setattr(stac, "catalog", lambda root=stac.STAC_API_ROOT: client)
+
+
+def test_returns_hrefs_in_item_order(monkeypatch: pytest.MonkeyPatch) -> None:
+ client = _Client([
+ _Item("a", {"dtm": _Asset("http://x/a.tif")}),
+ _Item("b", {"dtm": _Asset("http://x/b.tif")}),
+ ])
+ _patch_catalog(monkeypatch, client)
+
+ hrefs = stac.find_asset_hrefs("coll", "dtm", (-1.0, -1.0, 1.0, 1.0), max_items=5)
+
+ assert hrefs == ["http://x/a.tif", "http://x/b.tif"]
+ assert client.calls == [
+ {"collections": ["coll"], "bbox": [-1.0, -1.0, 1.0, 1.0], "max_items": 5}
+ ]
+
+
+def test_no_overlap_returns_empty(monkeypatch: pytest.MonkeyPatch) -> None:
+ _patch_catalog(monkeypatch, _Client([]))
+ assert stac.find_asset_hrefs("coll", "dtm", (0.0, 0.0, 1.0, 1.0)) == []
+
+
+def test_missing_asset_raises_endpoint_error(monkeypatch: pytest.MonkeyPatch) -> None:
+ _patch_catalog(monkeypatch, _Client([_Item("a", {"thumbnail": _Asset("http://x/a.jpg")})]))
+ with pytest.raises(EndpointError, match="no asset 'dtm'"):
+ stac.find_asset_hrefs("coll", "dtm", (0.0, 0.0, 1.0, 1.0))
+
+
+def test_api_error_becomes_endpoint_error(monkeypatch: pytest.MonkeyPatch) -> None:
+ class _Boom(_Client):
+ def search(self, **kwargs: object) -> _Search:
+ raise APIError("boom")
+
+ _patch_catalog(monkeypatch, _Boom([]))
+ with pytest.raises(EndpointError, match="STAC search failed"):
+ stac.find_asset_hrefs("coll", "dtm", (0.0, 0.0, 1.0, 1.0))
diff --git a/uv.lock b/uv.lock
index 09fe12a..6ec6907 100644
--- a/uv.lock
+++ b/uv.lock
@@ -8,6 +8,15 @@ resolution-markers = [
"python_full_version < '3.11'",
]
+[[package]]
+name = "affine"
+version = "2.4.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/69/98/d2f0bb06385069e799fc7d2870d9e078cfa0fa396dc8a2b81227d0da08b9/affine-2.4.0.tar.gz", hash = "sha256:a24d818d6a836c131976d22f8c27b8d3ca32d0af64c1d8d29deb7bafa4da1eea", size = 17132, upload-time = "2023-01-19T23:44:30.696Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/0b/f7/85273299ab57117850cc0a936c64151171fac4da49bc6fba0dad984a7c5f/affine-2.4.0-py3-none-any.whl", hash = "sha256:8a3df80e2b2378aef598a83c1392efd47967afec4242021a0b06b4c7cbc61a92", size = 15662, upload-time = "2023-01-19T23:44:28.833Z" },
+]
+
[[package]]
name = "astrofetch"
version = "0.1.0"
@@ -16,6 +25,9 @@ dependencies = [
{ name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" },
{ name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" },
{ name = "numpy", version = "2.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" },
+ { name = "pystac-client" },
+ { name = "rasterio", version = "1.4.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" },
+ { name = "rasterio", version = "1.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" },
{ name = "torch" },
]
@@ -36,6 +48,8 @@ test = [
[package.metadata]
requires-dist = [
{ name = "numpy", specifier = ">=1.24" },
+ { name = "pystac-client", specifier = ">=0.7" },
+ { name = "rasterio", specifier = ">=1.3" },
{ name = "torch", specifier = ">=2.0" },
]
@@ -51,6 +65,15 @@ test = [
{ name = "pytest-cov", specifier = ">=4.1" },
]
+[[package]]
+name = "attrs"
+version = "26.1.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/9a/8e/82a0fe20a541c03148528be8cac2408564a6c9a0cc7e9171802bc1d26985/attrs-26.1.0.tar.gz", hash = "sha256:d03ceb89cb322a8fd706d4fb91940737b6642aa36998fe130a9bc96c985eff32", size = 952055, upload-time = "2026-03-19T14:22:25.026Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/64/b4/17d4b0b2a2dc85a6df63d1157e028ed19f90d4cd97c36717afef2bc2f395/attrs-26.1.0-py3-none-any.whl", hash = "sha256:c647aa4a12dfbad9333ca4e71fe62ddc36f4e63b2d260a37a8b83d2f043ac309", size = 67548, upload-time = "2026-03-19T14:22:23.645Z" },
+]
+
[[package]]
name = "babel"
version = "2.18.0"
@@ -181,6 +204,30 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/fb/e2/79c688af8b210d232694e31e59da9f6ec747bae31c3f5946e4e9b98860d5/click-8.4.2-py3-none-any.whl", hash = "sha256:e6f9f66136c816745b9d65817da91d61d957fb16e02e4dcd0552553c5a197b76", size = 119243, upload-time = "2026-06-24T17:45:13.73Z" },
]
+[[package]]
+name = "click-plugins"
+version = "1.1.1.2"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "click", marker = "python_full_version < '3.12'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/c3/a4/34847b59150da33690a36da3681d6bbc2ec14ee9a846bc30a6746e5984e4/click_plugins-1.1.1.2.tar.gz", hash = "sha256:d7af3984a99d243c131aa1a828331e7630f4a88a9741fd05c927b204bcf92261", size = 8343, upload-time = "2025-06-25T00:47:37.555Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/3d/9a/2abecb28ae875e39c8cad711eb1186d8d14eab564705325e77e4e6ab9ae5/click_plugins-1.1.1.2-py2.py3-none-any.whl", hash = "sha256:008d65743833ffc1f5417bf0e78e8d2c23aab04d9745ba817bd3e71b0feb6aa6", size = 11051, upload-time = "2025-06-25T00:47:36.731Z" },
+]
+
+[[package]]
+name = "cligj"
+version = "0.7.2"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "click" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/ea/0d/837dbd5d8430fd0f01ed72c4cfb2f548180f4c68c635df84ce87956cff32/cligj-0.7.2.tar.gz", hash = "sha256:a4bc13d623356b373c2c27c53dbd9c68cae5d526270bfa71f6c6fa69669c6b27", size = 9803, upload-time = "2021-05-28T21:23:27.935Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/73/86/43fa9f15c5b9fb6e82620428827cd3c284aa933431405d1bcf5231ae3d3e/cligj-0.7.2-py3-none-any.whl", hash = "sha256:c1ca117dbce1fe20a5809dc96f01e1c2840f6dcc939b3ddbb1111bf330ba82df", size = 7069, upload-time = "2021-05-28T21:23:26.877Z" },
+]
+
[[package]]
name = "colorama"
version = "0.4.6"
@@ -333,34 +380,34 @@ wheels = [
[package.optional-dependencies]
cudart = [
- { name = "nvidia-cuda-runtime" },
+ { name = "nvidia-cuda-runtime", marker = "sys_platform == 'linux' or sys_platform == 'win32'" },
]
cufft = [
- { name = "nvidia-cufft" },
+ { name = "nvidia-cufft", marker = "sys_platform == 'linux' or sys_platform == 'win32'" },
]
cufile = [
- { name = "nvidia-cufile" },
+ { name = "nvidia-cufile", marker = "sys_platform == 'linux'" },
]
cupti = [
- { name = "nvidia-cuda-cupti" },
+ { name = "nvidia-cuda-cupti", marker = "sys_platform == 'linux' or sys_platform == 'win32'" },
]
curand = [
- { name = "nvidia-curand" },
+ { name = "nvidia-curand", marker = "sys_platform == 'linux' or sys_platform == 'win32'" },
]
cusolver = [
- { name = "nvidia-cusolver" },
+ { name = "nvidia-cusolver", marker = "sys_platform == 'linux' or sys_platform == 'win32'" },
]
cusparse = [
- { name = "nvidia-cusparse" },
+ { name = "nvidia-cusparse", marker = "sys_platform == 'linux' or sys_platform == 'win32'" },
]
nvjitlink = [
- { name = "nvidia-nvjitlink" },
+ { name = "nvidia-nvjitlink", marker = "sys_platform == 'linux' or sys_platform == 'win32'" },
]
nvrtc = [
- { name = "nvidia-cuda-nvrtc" },
+ { name = "nvidia-cuda-nvrtc", marker = "sys_platform == 'linux' or sys_platform == 'win32'" },
]
nvtx = [
- { name = "nvidia-nvtx" },
+ { name = "nvidia-nvtx", marker = "sys_platform == 'linux' or sys_platform == 'win32'" },
]
[[package]]
@@ -368,7 +415,7 @@ name = "exceptiongroup"
version = "1.3.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "typing-extensions" },
+ { name = "typing-extensions", marker = "python_full_version < '3.11'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" }
wheels = [
@@ -435,6 +482,34 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" },
]
+[[package]]
+name = "jsonschema"
+version = "4.26.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "attrs" },
+ { name = "jsonschema-specifications" },
+ { name = "referencing" },
+ { name = "rpds-py", version = "0.30.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" },
+ { name = "rpds-py", version = "2026.6.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/b3/fc/e067678238fa451312d4c62bf6e6cf5ec56375422aee02f9cb5f909b3047/jsonschema-4.26.0.tar.gz", hash = "sha256:0c26707e2efad8aa1bfc5b7ce170f3fccc2e4918ff85989ba9ffa9facb2be326", size = 366583, upload-time = "2026-01-07T13:41:07.246Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/69/90/f63fb5873511e014207a475e2bb4e8b2e570d655b00ac19a9a0ca0a385ee/jsonschema-4.26.0-py3-none-any.whl", hash = "sha256:d489f15263b8d200f8387e64b4c3a75f06629559fb73deb8fdfb525f2dab50ce", size = 90630, upload-time = "2026-01-07T13:41:05.306Z" },
+]
+
+[[package]]
+name = "jsonschema-specifications"
+version = "2025.9.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "referencing" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/19/74/a633ee74eb36c44aa6d1095e7cc5569bebf04342ee146178e2d36600708b/jsonschema_specifications-2025.9.1.tar.gz", hash = "sha256:b540987f239e745613c7a9176f3edb72b832a4ac465cf02712288397832b5e8d", size = 32855, upload-time = "2025-09-08T01:34:59.186Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe", size = 18437, upload-time = "2025-09-08T01:34:57.871Z" },
+]
+
[[package]]
name = "markdown"
version = "3.10.2"
@@ -1072,6 +1147,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/d6/54/da572c98c0b77626a91b5d3b89f0231d8bff5125c225420908632f8b342d/pymdown_extensions-11.0.1-py3-none-any.whl", hash = "sha256:db3943a62bab7e03af1364f0c4083e64b91fb097675a4b6cceccfbe9a77e5eb2", size = 269455, upload-time = "2026-07-02T17:59:21.271Z" },
]
+[[package]]
+name = "pyparsing"
+version = "3.3.2"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/f3/91/9c6ee907786a473bf81c5f53cf703ba0957b23ab84c264080fb5a450416f/pyparsing-3.3.2.tar.gz", hash = "sha256:c777f4d763f140633dcb6d8a3eda953bf7a214dc4eff598413c070bcdc117cbc", size = 6851574, upload-time = "2026-01-21T03:57:59.36Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl", hash = "sha256:850ba148bd908d7e2411587e247a1e4f0327839c40e2e5e6d05a007ecc69911d", size = 122781, upload-time = "2026-01-21T03:57:55.912Z" },
+]
+
[[package]]
name = "pyright"
version = "1.1.411"
@@ -1085,6 +1169,335 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/0a/49/385be530a6a5b78d1cbcd5c2e38debc8959a2fc6bdb716f4e581002979fc/pyright-1.1.411-py3-none-any.whl", hash = "sha256:dc7c72a8e2700c55baa127554040e067041ea53ccfd50bf96308cc4291c7d5d9", size = 6181526, upload-time = "2026-06-25T02:14:04.691Z" },
]
+[[package]]
+name = "pystac"
+version = "1.15.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "pystac-core" },
+ { name = "pystac-ext-classification" },
+ { name = "pystac-ext-datacube" },
+ { name = "pystac-ext-eo" },
+ { name = "pystac-ext-file" },
+ { name = "pystac-ext-grid" },
+ { name = "pystac-ext-item-assets" },
+ { name = "pystac-ext-label" },
+ { name = "pystac-ext-mgrs" },
+ { name = "pystac-ext-mlm" },
+ { name = "pystac-ext-pointcloud" },
+ { name = "pystac-ext-projection" },
+ { name = "pystac-ext-raster" },
+ { name = "pystac-ext-render" },
+ { name = "pystac-ext-sar" },
+ { name = "pystac-ext-sat" },
+ { name = "pystac-ext-scientific" },
+ { name = "pystac-ext-storage" },
+ { name = "pystac-ext-table" },
+ { name = "pystac-ext-timestamps" },
+ { name = "pystac-ext-version" },
+ { name = "pystac-ext-view" },
+ { name = "pystac-ext-xarray-assets" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/e8/f8/d792facb89e85d1e7d7d94e819477296486e5d227c5b755fef827bc441c2/pystac-1.15.1.tar.gz", hash = "sha256:0975f78ac648b89f180378089fdd358006c0dc5d974db57c6f8fa81c4162f5d5", size = 5549, upload-time = "2026-06-30T21:48:54.603Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/f0/7d/b3a4dd7f5c7873c4e884c26bd6fd7b7791036b31ef82bb5eca7f12c3acaa/pystac-1.15.1-py3-none-any.whl", hash = "sha256:3a1be7a1d819a3dadfa6612be0035fec62ceb38d64acdc9cef66649d03eb84da", size = 5079, upload-time = "2026-06-30T21:48:53.3Z" },
+]
+
+[package.optional-dependencies]
+validation = [
+ { name = "jsonschema" },
+]
+
+[[package]]
+name = "pystac-client"
+version = "0.9.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "pystac", extra = ["validation"] },
+ { name = "python-dateutil" },
+ { name = "requests" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/c5/8d/b98aeffd325fc208e1624cf586d0c4dfb927bc7a2bce20d3b58ee80d2483/pystac_client-0.9.0.tar.gz", hash = "sha256:3908951583bcc6a3aaaf2828024a8e03764e6ca9d9f9f1d8149df587e14dd744", size = 52339, upload-time = "2025-07-18T15:44:41.1Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/5d/d2/5f6367b14c9f250d1a6725d18bd1e9584f5ab1587e292f3a847e59189598/pystac_client-0.9.0-py3-none-any.whl", hash = "sha256:eed146b5980f93646aaa3a59080f11f1dcab6000b0bfbc28b1d0c6fd0a61eda1", size = 41826, upload-time = "2025-07-18T15:44:40.197Z" },
+]
+
+[[package]]
+name = "pystac-core"
+version = "1.15.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "python-dateutil" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/73/74/8f95e14d899748d4c340783f9c45089e2a011075a480d332561d605af054/pystac_core-1.15.1.tar.gz", hash = "sha256:c24e50176035cc49b99a40a13da12115cab0b05c97ebac64c5e3ca8330d47b41", size = 95327, upload-time = "2026-06-30T21:49:04.044Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/a1/4c/108088b03de051de59153ad52ff2ac586ff3b2b20a8f6546f19f91ba103c/pystac_core-1.15.1-py3-none-any.whl", hash = "sha256:69c504dd42da77e741f7f54f18a1deeb8122b8817a5aa2827767459a2f9cfae0", size = 119040, upload-time = "2026-06-30T21:49:02.763Z" },
+]
+
+[[package]]
+name = "pystac-ext-classification"
+version = "2.0.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "pystac-core" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/e6/ef/4ae97a54de23f84f8ca9637a73efc42dfc343881a9ce159ff40f7dc5c5ce/pystac_ext_classification-2.0.0.tar.gz", hash = "sha256:b744d42eb74fa5038cf00896f3cf4fbb85b79cf208454b7401bd8219e491eb94", size = 21154, upload-time = "2026-06-25T15:49:41.6Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/76/d3/f19b3e52938785e3540e5515ba0453291700729d7fa6863c6c07c3faf6bc/pystac_ext_classification-2.0.0-py3-none-any.whl", hash = "sha256:fe8c94fd83b2402cb314f608e835d45d70397614928d6cbb29b12d4728586abd", size = 7036, upload-time = "2026-06-25T15:49:40.585Z" },
+]
+
+[[package]]
+name = "pystac-ext-datacube"
+version = "2.2.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "pystac-core" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/da/bc/77eb864cc837ce95c36b67b94f9b102ab6ecc6889a6c131a382f6c5afd08/pystac_ext_datacube-2.2.0.tar.gz", hash = "sha256:a64643cb826c3f2f169c7497b5e5ba218f0a1ababbbd3bad04c57f99df3a67c7", size = 22016, upload-time = "2026-06-25T15:49:43.776Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/26/12/f988dea7251ba3bb089fa9012ebb459b1229b35da23eedf53a9a0e05faed/pystac_ext_datacube-2.2.0-py3-none-any.whl", hash = "sha256:823538db9751b6e7312a06ec9b6a74f98561830664f45ed43908bea73f624f16", size = 6824, upload-time = "2026-06-25T15:49:42.815Z" },
+]
+
+[[package]]
+name = "pystac-ext-eo"
+version = "1.1.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "pystac-core" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/9c/f3/b15b8919d6849b4c94ddef1f3dcf12a6c69d3674d9f67e62ced31483f43e/pystac_ext_eo-1.1.0.tar.gz", hash = "sha256:c599b1f4a470eceefb745b26f4dabea05661e843d26c8c59f0d7343b6ac9ab35", size = 21113, upload-time = "2026-06-25T15:49:47.578Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/59/39/6bc20044e0513bb290a871aba05608b945f261d294c2cdc78207e98a1938/pystac_ext_eo-1.1.0-py3-none-any.whl", hash = "sha256:ca367465d37bc65d9a4c0cd54c4a04a45deb9904d55ae2c1f37d11734cc38ca0", size = 7366, upload-time = "2026-06-25T15:49:46.629Z" },
+]
+
+[[package]]
+name = "pystac-ext-file"
+version = "2.1.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "pystac-core" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/74/37/e8a111a9ba9547bd36a3a2cab3b6797115b57ed518d21b51d7447463ea5c/pystac_ext_file-2.1.0.tar.gz", hash = "sha256:93964df13550d0b016770774a6608728928f3a2c8d92ae03dc2f11bd76dbc6f0", size = 12979, upload-time = "2026-06-25T15:49:53.524Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/d5/e1/2d5b893323fdc7ca64e58464f6eea91158d0dcfd76a1463c4d9390fcf34b/pystac_ext_file-2.1.0-py3-none-any.whl", hash = "sha256:90b91c82155f763143de2daf1aec37dd0e7f98da7f39f6636a4ede590be9f322", size = 5485, upload-time = "2026-06-25T15:49:52.11Z" },
+]
+
+[[package]]
+name = "pystac-ext-grid"
+version = "1.1.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "pystac-core" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/2f/0f/a59e46a04e36668e9ae52da4493786f0a0af3513020551e784947ab9efe1/pystac_ext_grid-1.1.0.tar.gz", hash = "sha256:0b8855ff3ddb278bf6f2caf01bb857da50cf23bc322d0722f93b206b7f3673f6", size = 9086, upload-time = "2026-06-25T15:49:48.002Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/b0/a4/43d317a422528090320166a908f9d110384d67ac2f30353a4a331e990151/pystac_ext_grid-1.1.0-py3-none-any.whl", hash = "sha256:6c8c804a07b4c1fc9dc4058a16d4ec18d7715883a183555c826d312830018db1", size = 3319, upload-time = "2026-06-25T15:49:46.927Z" },
+]
+
+[[package]]
+name = "pystac-ext-item-assets"
+version = "1.0.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "pystac-core" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/ab/93/fd0d0251ac227b654dabffc15bafad69600c91edf0472d1bba48096cd1c5/pystac_ext_item_assets-1.0.0.tar.gz", hash = "sha256:63b063ae3b0c9508ad84aa06c85513fb2ca09f505ee2aed1a8a1f0c3a906d070", size = 3908, upload-time = "2026-06-25T15:49:56.02Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/7e/63/eca6602e084b1bd764fe0c7e6e5efa351bb6ad5432aaab2a3d56cb1fdbc9/pystac_ext_item_assets-1.0.0-py3-none-any.whl", hash = "sha256:4dd61fcaeb861f1bd0c77299d9e3332865bfded44e061206fc0bb9fd8ce8d156", size = 3418, upload-time = "2026-06-25T15:49:54.905Z" },
+]
+
+[[package]]
+name = "pystac-ext-label"
+version = "1.0.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "pystac-core" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/27/e4/b3c010715ec522735f5c5393f685cadf55a687b47c9084fe4ae82bd9c41c/pystac_ext_label-1.0.1.tar.gz", hash = "sha256:34e52bbc79d46a5a0f76833ce0f656de65b425a41ca362e705be68430196abc7", size = 8339, upload-time = "2026-06-25T15:49:51.709Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/d9/f5/583b8f20f8c041fb3decb36ab4b5be703f64cc84883ef152373f9a075a99/pystac_ext_label-1.0.1-py3-none-any.whl", hash = "sha256:9b089b290ce953916cd087a86d108262cf74ca39d0f00e27af6eeaf1ab2be167", size = 7871, upload-time = "2026-06-25T15:49:50.341Z" },
+]
+
+[[package]]
+name = "pystac-ext-mgrs"
+version = "1.0.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "pystac-core" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/97/d6/5f54dccb399f0246e12d5b2a9c1ca5269f1adcf993a3812ecf897663e004/pystac_ext_mgrs-1.0.0.tar.gz", hash = "sha256:31d79b288f2523640138aff1b12acd2b67d7a40b1822909a04700fb048861d11", size = 6610, upload-time = "2026-06-25T15:49:54.474Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/62/c2/aa4c889ffbd5d95b9cbcb78163399feb51eae3968f387f87f26efef2bf59/pystac_ext_mgrs-1.0.0-py3-none-any.whl", hash = "sha256:4efe67b4dac4fb7c3057985a056e59275371c535d84c82d0a55f110f9144f9f0", size = 3849, upload-time = "2026-06-25T15:49:53.206Z" },
+]
+
+[[package]]
+name = "pystac-ext-mlm"
+version = "1.4.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "pystac-core" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/df/5c/7b540355fc4be970f076047a222216f2af513870ebfca53c48242de5e078/pystac_ext_mlm-1.4.0.tar.gz", hash = "sha256:fdaf464e729071348b3921e908dbe3c2e1cce5d65153ee19995e2e7315e9edf9", size = 35919, upload-time = "2026-06-25T15:49:53.895Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/85/65/a4f7b03201993bd647a3260d9b1663b1d0d28145d35bc69562334ef49a28/pystac_ext_mlm-1.4.0-py3-none-any.whl", hash = "sha256:69dac825477b7dd458ad5392aa72188377b267f267b6b28a60e883daed0ef252", size = 13599, upload-time = "2026-06-25T15:49:52.664Z" },
+]
+
+[[package]]
+name = "pystac-ext-pointcloud"
+version = "1.0.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "pystac-core" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/14/51/cf14464aad24d1547dd0e5c06bcfba9627a0b0510d8ec147e17a7c90c2de/pystac_ext_pointcloud-1.0.0.tar.gz", hash = "sha256:92a4bc88fe6eb15ad27c75358b1dbf5bf40ded26df4cd6b8bfc16a4c9a838764", size = 12581, upload-time = "2026-06-25T15:49:59.857Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/bb/e9/783c234f711661603c21c5c83c6cbb41efcb2cd0ca791cf27316498088b2/pystac_ext_pointcloud-1.0.0-py3-none-any.whl", hash = "sha256:3bc688b3da93dc0dbf91b357026eb7648ec3e49a3008003859648b80dfb55732", size = 6166, upload-time = "2026-06-25T15:49:58.81Z" },
+]
+
+[[package]]
+name = "pystac-ext-projection"
+version = "2.0.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "pystac-core" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/ba/55/2d2230a5bb55d9e954338e71c5fd469ff63097532f2b45fa219f607c075a/pystac_ext_projection-2.0.0.tar.gz", hash = "sha256:72517587636c3d0f325cf11dfa75e0431570c6a13305bf53d0f95b2548deec1f", size = 72074, upload-time = "2026-06-25T15:50:02.03Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/74/27/cede1d2f621f4c399ee2434072bf5a8bba0e3b0e3369547664f343e0ed84/pystac_ext_projection-2.0.0-py3-none-any.whl", hash = "sha256:892e9852a1a81cf1be1b79e3ad52e9b11f780143d3a75d67cde3e4fd31ec83e4", size = 7114, upload-time = "2026-06-25T15:50:00.785Z" },
+]
+
+[[package]]
+name = "pystac-ext-raster"
+version = "1.1.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "pystac-core" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/2d/04/2459cd1a4b954fc3320594ef86e6f4379f15d0cf4a63259852fb4b4a9b5b/pystac_ext_raster-1.1.0.tar.gz", hash = "sha256:19dd7c993defe450fa90b3fd690f30a6191b701fc704f1626748bdb57d9da1ed", size = 30679, upload-time = "2026-06-25T15:50:05.636Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/13/1e/9bced0383702b1ceb240a4aa4c32f0f4f76f6666325a7d35c3e499ef1104/pystac_ext_raster-1.1.0-py3-none-any.whl", hash = "sha256:07c312ae956d70ccdb1da7af67fc92793c2546ca8e26ca384013ebf5c674b410", size = 6567, upload-time = "2026-06-25T15:50:04.448Z" },
+]
+
+[[package]]
+name = "pystac-ext-render"
+version = "2.0.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "pystac-core" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/fd/ba/e8711c8b46719f4d8e74fd73c5ae66fa5cd0b79e0dc7770d00bc1d14adcf/pystac_ext_render-2.0.0.tar.gz", hash = "sha256:ed53926d0af556c98d5f6bbfd86b8d02d635df24b70607f37f386cea7713b4f7", size = 9611, upload-time = "2026-06-25T15:50:00.903Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/6e/63/3162c536d3f3cacf627b7a74915dc90abb10cf3baa5ed9b28b3b92b5914c/pystac_ext_render-2.0.0-py3-none-any.whl", hash = "sha256:69c3a8307d9528b8ae5baadde76dae9e546c19c1f4210a57daf235f8e9d9e427", size = 5020, upload-time = "2026-06-25T15:49:59.974Z" },
+]
+
+[[package]]
+name = "pystac-ext-sar"
+version = "1.0.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "pystac-core" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/c8/30/e229a96be9e03157c92eb7f3bfafbd84702372bd1a7ed23e734094c48ead/pystac_ext_sar-1.0.0.tar.gz", hash = "sha256:0d49130fc55152f039f22ac843df35e1c5b2a1a667a4ee91d4a9c5b3ecd379ca", size = 11122, upload-time = "2026-06-25T15:50:01.836Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/5c/75/8d62c8d697740c01c01d707446b53b332a466d6caf174187588000daa9a8/pystac_ext_sar-1.0.0-py3-none-any.whl", hash = "sha256:4b73d97e4c1cdba1c1d6cc1d69181fed31eefb0ec164500b636d6a8d8bca271c", size = 6262, upload-time = "2026-06-25T15:50:00.762Z" },
+]
+
+[[package]]
+name = "pystac-ext-sat"
+version = "1.0.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "pystac-core" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/07/d7/0492bae1d417dc4990748183a950fd1e6acbc202a2b11bbb73cc75a37f34/pystac_ext_sat-1.0.0.tar.gz", hash = "sha256:2163b31d3044f9c2965abd9cb4144482813068ff9f99107a240eed06cf78bace", size = 9875, upload-time = "2026-06-25T15:50:12.336Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/e0/ca/20d80d50b5108f649383568d3c103e9cc0cf12e53f283562f9dfc1774dd3/pystac_ext_sat-1.0.0-py3-none-any.whl", hash = "sha256:ee951eee24b47c576863bbf5890957b7094a6a2bb0ef8fc967a4d327dff571fd", size = 4576, upload-time = "2026-06-25T15:50:11.303Z" },
+]
+
+[[package]]
+name = "pystac-ext-scientific"
+version = "1.0.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "pystac-core" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/ca/f6/1bf1ec97be09266a258f53620020573d8321956e390361daa81d0905b9ae/pystac_ext_scientific-1.0.0.tar.gz", hash = "sha256:b55128b7b2281c28dcfc3c00c008fe4391c84b47a7572c4770766f1e96eec5b5", size = 12764, upload-time = "2026-06-25T15:50:06.101Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/e7/f7/cde7d45905f4f15449f142c7d6f81de488b3920bf3fb063b2382d1ffefba/pystac_ext_scientific-1.0.0-py3-none-any.whl", hash = "sha256:1f78efcd34a4b4344c70d80ddcde70785815e738455e3c1d35b5e843ca671207", size = 5174, upload-time = "2026-06-25T15:50:05.179Z" },
+]
+
+[[package]]
+name = "pystac-ext-storage"
+version = "2.0.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "pystac-core" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/e9/dc/57f0dfa715f6bdc0de1830359969ffb019424838c875f899c011fdbf2af0/pystac_ext_storage-2.0.0.tar.gz", hash = "sha256:58b28988e49a188cc8ee09e8bc88440aec9985f0d3ccd295b6209215896e4250", size = 13063, upload-time = "2026-06-25T15:50:08.61Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/3c/af/5aaa33cf912da18e178c7bc15633c3c9f1cc9a22efcad7f78964136e4e14/pystac_ext_storage-2.0.0-py3-none-any.whl", hash = "sha256:8f1d809a8f5efebe051450fcfce3ae13ef4c7ad9133b43b2a1f4df69e74645c2", size = 7353, upload-time = "2026-06-25T15:50:07.687Z" },
+]
+
+[[package]]
+name = "pystac-ext-table"
+version = "1.2.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "pystac-core" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/ee/06/d4ad9edaf4bae2922cf86a6ac216375d530fa70e4f13bff28b52c595a2d1/pystac_ext_table-1.2.0.tar.gz", hash = "sha256:c7035db662272a84f1149e7d694c8409f85093cc056cdc655c06e9b9f5cefe37", size = 8003, upload-time = "2026-06-25T15:50:09.908Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/9d/52/6209f65f1b4f966525486b7eb39f82f1cd0f8aae592e52c679b520e969ea/pystac_ext_table-1.2.0-py3-none-any.whl", hash = "sha256:461e8475eaee81190c36db818150e0b24d14d46b021f063a928ff6a0ee77a971", size = 4430, upload-time = "2026-06-25T15:50:08.808Z" },
+]
+
+[[package]]
+name = "pystac-ext-timestamps"
+version = "1.1.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "pystac-core" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/59/74/97061e036f74c850b63450ab425f7c1aa2681582ce53b98ed4f7235acc7b/pystac_ext_timestamps-1.1.0.tar.gz", hash = "sha256:d78250e3ddc61b825348444ad05c8cff8c2aa1cd3d18155b880783ee880520d3", size = 8544, upload-time = "2026-06-25T15:50:18.662Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/1f/22/d0e9f1e46da156c34e070ba04ebca1bba67b4b60724118cfb98c0b83b368/pystac_ext_timestamps-1.1.0-py3-none-any.whl", hash = "sha256:17990ad4827bfd0798010d247fa2f3d770f78d80180579cbe63d8267fb3e8d39", size = 4252, upload-time = "2026-06-25T15:50:17.677Z" },
+]
+
+[[package]]
+name = "pystac-ext-version"
+version = "1.2.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "pystac-core" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/85/48/0d67ffdc3c930cd64805e15603372f2a759d2ad453f31d979fefda637999/pystac_ext_version-1.2.0.tar.gz", hash = "sha256:aac23024e76cd64c9bec46a8b49f884f2e8c89cc3d0fe317f5f58dfaa56d1aa0", size = 12543, upload-time = "2026-06-25T15:50:19.674Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/e2/bb/c0a732f1827233e91200b4c14538e8cc4dafa9e843c46c048462212e2b72/pystac_ext_version-1.2.0-py3-none-any.whl", hash = "sha256:95ba628abe934098356654f2bd57423475d1cb59e2f49c44dd7e2ef5392efefd", size = 5366, upload-time = "2026-06-25T15:50:18.547Z" },
+]
+
+[[package]]
+name = "pystac-ext-view"
+version = "1.0.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "pystac-core" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/2e/1d/663184b0c94583ed2c3d2fbe1f1b3ab8da27ee7ff5695da2d0d42652e610/pystac_ext_view-1.0.0.tar.gz", hash = "sha256:cb677dcd49f9a62824087d150f01eea97fc24c577971edb60374c9e48e517311", size = 9698, upload-time = "2026-06-25T15:50:11.379Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/fc/1f/aab5f54ee091b47b6edf22dea97863c36996ca7886b32e8772374fcbb5e9/pystac_ext_view-1.0.0-py3-none-any.whl", hash = "sha256:3000615569d1826093a6b231778e7ddb1afb106dd516e8029a0083bfcd166d7d", size = 4479, upload-time = "2026-06-25T15:50:10.359Z" },
+]
+
+[[package]]
+name = "pystac-ext-xarray-assets"
+version = "1.0.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "pystac-core" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/6f/a0/1bcb9ba80eb0e0a6cc4605520d831d7118863559f13f797f271430bad125/pystac_ext_xarray_assets-1.0.0.tar.gz", hash = "sha256:da4f4877c018d531b6b5c45f20bc64842f14587ae86bac8d2acf110804c773d1", size = 6703, upload-time = "2026-06-25T15:50:18.944Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/48/cc/4f310e5acb0c59c8b3db417fc903a055dc01ac7e7f7e82c941304f30b4d9/pystac_ext_xarray_assets-1.0.0-py3-none-any.whl", hash = "sha256:a31e90e7e9bc5ae191e7449df28c39857c818e8c1cf2c09a2eceaa7c6e13241c", size = 3614, upload-time = "2026-06-25T15:50:17.871Z" },
+]
+
[[package]]
name = "pytest"
version = "9.1.1"
@@ -1205,6 +1618,136 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl", hash = "sha256:17109e1a528561e32f026364712fee1264bc2ea6715120891174ed1b980d2e04", size = 4722, upload-time = "2025-05-13T15:23:59.629Z" },
]
+[[package]]
+name = "rasterio"
+version = "1.4.4"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version == '3.11.*'",
+ "python_full_version < '3.11'",
+]
+dependencies = [
+ { name = "affine", marker = "python_full_version < '3.12'" },
+ { name = "attrs", marker = "python_full_version < '3.12'" },
+ { name = "certifi", marker = "python_full_version < '3.12'" },
+ { name = "click", marker = "python_full_version < '3.12'" },
+ { name = "click-plugins", marker = "python_full_version < '3.12'" },
+ { name = "cligj", marker = "python_full_version < '3.12'" },
+ { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" },
+ { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" },
+ { name = "pyparsing", marker = "python_full_version < '3.12'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/ec/fa/fce8dc9f09e5bc6520b6fc1b4ecfa510af9ca06eb42ad7bdff9c9b8989d0/rasterio-1.4.4.tar.gz", hash = "sha256:c95424e2c7f009b8f7df1095d645c52895cd332c0c2e1b4c2e073ea28b930320", size = 445004, upload-time = "2025-12-12T18:01:08.971Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/7e/24/eedb9dfed1706c696b4f43ba9b85e830ce332f4f57ffcb7b6a4c4e66ade9/rasterio-1.4.4-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:35401e84d4d0b239bd62b33d4ee68d7bb13b47c3b41078f4aad7ad7964e61c73", size = 21127567, upload-time = "2025-12-12T17:58:42.664Z" },
+ { url = "https://files.pythonhosted.org/packages/67/5f/482a24bf75bcd48236cd223d037f22abc6c08da6961e390e6a35249a9f58/rasterio-1.4.4-cp310-cp310-macosx_15_0_x86_64.whl", hash = "sha256:1f17fc9608b6b6666894a04e0118d3329e831a6347bc3650584d247a9d476fdd", size = 25735929, upload-time = "2025-12-12T17:58:46.503Z" },
+ { url = "https://files.pythonhosted.org/packages/b0/da/b988ffb1bb37cc4cb8a028447ab654a16dbac0b339d977fb9c8adc5bd995/rasterio-1.4.4-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:1f0edb8cb30ff8f5be341583f69c115b7c36ad52bbbe7582345d32af115bc6b3", size = 34040467, upload-time = "2025-12-12T17:58:50.109Z" },
+ { url = "https://files.pythonhosted.org/packages/e0/0a/2eace22e990203d47a8fed4b174b87be50281bf3f5b2509cf3700036cbcc/rasterio-1.4.4-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:5197da0e3dd09907bdb343717a49e8fb5229ffdbff0e583b874959ec41fa9558", size = 35339947, upload-time = "2025-12-12T17:58:53.269Z" },
+ { url = "https://files.pythonhosted.org/packages/40/e5/16acecbbaedd820c5d71f99f3bab73c00455078a414b511f6854364d3e1e/rasterio-1.4.4-cp310-cp310-win_amd64.whl", hash = "sha256:15109134c7b4770e6aeb8d45dc52c2603824805ba734323268a44f5a81756a7a", size = 25708679, upload-time = "2025-12-12T17:58:56.661Z" },
+ { url = "https://files.pythonhosted.org/packages/c6/0d/d3859e49ab94464de2623fec82c6798d8d7c8bea2473cd2696fc5e09f717/rasterio-1.4.4-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:b8eea428b5f0c78a963f6003a19b60777df83a0aba8c28231d65431e32ac160e", size = 21144125, upload-time = "2025-12-12T17:58:59.511Z" },
+ { url = "https://files.pythonhosted.org/packages/aa/3c/97ba4b146309cdc0e36f289b02ac69465b026a21afc828e4e4e1dc39466a/rasterio-1.4.4-cp311-cp311-macosx_15_0_x86_64.whl", hash = "sha256:1cc0ea5aa0d22f5f349aa221674481de689b7b3a99607ce6bb58a29e5be54d17", size = 25746406, upload-time = "2025-12-12T17:59:02.902Z" },
+ { url = "https://files.pythonhosted.org/packages/ce/33/75f81bd837ac2336b24456fdb249597a4b9af2a212b7151f64d09022be36/rasterio-1.4.4-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:7eb25b23666b29dadfc49a59206cead62c99190584b61771bba0e95f7da06801", size = 34587242, upload-time = "2025-12-12T17:59:05.848Z" },
+ { url = "https://files.pythonhosted.org/packages/f9/77/3869a426f6e752dde13f3868cdf16253ca0214f92107db79c1583c9aa07b/rasterio-1.4.4-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:e24b7b8c2df801dde2a1dffb44c58902bd76b5cab740dc11de4ff9963992a71a", size = 35881871, upload-time = "2025-12-12T17:59:09.779Z" },
+ { url = "https://files.pythonhosted.org/packages/66/d0/3818859ddbd3750d0ef5a6580a3272e81764286d943c689dd41e49b8b786/rasterio-1.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:0718630f607be2f5742d8e4b34b434746fd788a192d77eefc9bb924399fea802", size = 25716477, upload-time = "2025-12-12T17:59:13.519Z" },
+ { url = "https://files.pythonhosted.org/packages/4b/02/039eb4970c93aaef4c9eb1ee159abad18e6e7f932c2eed575c95f78d94f6/rasterio-1.4.4-cp311-cp311-win_arm64.whl", hash = "sha256:0308ff4762ae9eb40a991f12d758626b59af4376b13675480391dd7295d17bbf", size = 24075993, upload-time = "2025-12-12T17:59:16.407Z" },
+ { url = "https://files.pythonhosted.org/packages/4c/fc/63d89ddfcb4643730553683ee322566b9b15fe56d026e4c21c4f4f5d9d26/rasterio-1.4.4-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:f3c4f0cbd188f893011f2a0a6dc2852b3892799b3a0d79eddf92f2b115ec7ed7", size = 21120715, upload-time = "2025-12-12T17:59:19.35Z" },
+ { url = "https://files.pythonhosted.org/packages/43/70/2c003f76a23dbb078fdee35c8e2ec490d2ad8982f4dc956ba08b56027b87/rasterio-1.4.4-cp312-cp312-macosx_15_0_x86_64.whl", hash = "sha256:6fce26090b9f509eab337228420145947c491a13628965410f25bc3e6e05cf75", size = 25732944, upload-time = "2025-12-12T17:59:22.533Z" },
+ { url = "https://files.pythonhosted.org/packages/f6/cc/4a8e92362c0ff496dd1007c3dcba66e9ededf1a45eca8ad1db302b071c49/rasterio-1.4.4-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:c1c722da390dc264aeccdc0dc200ca37923875d910ca4cd5bec0fec351bb818e", size = 34295209, upload-time = "2025-12-12T17:59:26.035Z" },
+ { url = "https://files.pythonhosted.org/packages/e6/6d/717d2dec47fbefad33ca0d27bd5f0d543b1d1bc9fcab5ef82a13adaaf38d/rasterio-1.4.4-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:98b6dfb8282b2a54b9d75c3dc8d2520a69bbc66916c7d43de8e0bbf6e0240ca1", size = 35661866, upload-time = "2025-12-12T17:59:29.928Z" },
+ { url = "https://files.pythonhosted.org/packages/ed/60/ae3351fba2726ec0976974ce2eb030c159edd3363b8771e832b8db571c24/rasterio-1.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:9513f4c7a6d93b45098f8dff2421fa9516604e3bfbf35aa144484a88d36a321f", size = 25682853, upload-time = "2025-12-12T17:59:35.869Z" },
+ { url = "https://files.pythonhosted.org/packages/38/ee/35387296bbacfc5cbbb4273228b1b959793d3ce38b0402a07f11a248420b/rasterio-1.4.4-cp312-cp312-win_arm64.whl", hash = "sha256:60b49a482e0f12f12ce9d2cc3090add02f89f3d422e85f2cffaa9207adb83c04", size = 24043249, upload-time = "2025-12-12T17:59:39.915Z" },
+ { url = "https://files.pythonhosted.org/packages/c1/fe/e3e37041c49956f4f4cbe473c3fe290aaba96ed20e9c07da304e0cad2015/rasterio-1.4.4-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:df26c96aa81ffbd0b33189680859211eadf9950123c21579f84de73bb0f91d81", size = 21107336, upload-time = "2025-12-12T17:59:43.585Z" },
+ { url = "https://files.pythonhosted.org/packages/f3/02/c217fdcc8e80a4b7d1b1bc4529d78f98452816e9add53ff8742049a77ae7/rasterio-1.4.4-cp313-cp313-macosx_15_0_x86_64.whl", hash = "sha256:b3af0ecc922a80f3755516629f7948e37bade9077b5f5c12a3869a5e7f01619b", size = 25719929, upload-time = "2025-12-12T17:59:47.64Z" },
+ { url = "https://files.pythonhosted.org/packages/c0/d0/7f177f37bc9595d809dabb0073abd0c42358469f6b10875192b46331c652/rasterio-1.4.4-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:7ce3b0f9a22e95a27790087908753973644d7c3877d495ec9bd6e04a25233ca4", size = 34198845, upload-time = "2025-12-12T17:59:52.405Z" },
+ { url = "https://files.pythonhosted.org/packages/7b/84/66c0d9cca2a09074ec2ce6fffa87709ca51b0d197ae742d835e841bac660/rasterio-1.4.4-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:c072450caa96428b1218b030500bb908fd6f09bc013a88969ff81a124b6a112a", size = 35576074, upload-time = "2025-12-12T17:59:56.392Z" },
+ { url = "https://files.pythonhosted.org/packages/32/68/f7df5478458ace2fa50be43e9fab1a39957a0e71afaa3e6147ec289e0fc8/rasterio-1.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:16ee92ef10c0ba89f45f9c2b40fca9f971f357385f04ee9b716fb09cbd9ce20c", size = 25680573, upload-time = "2025-12-12T18:00:00.45Z" },
+ { url = "https://files.pythonhosted.org/packages/34/e5/1bdaccb658430dfd391ad4a63d206546f36639d7e4130bf31f125c6525b4/rasterio-1.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:65c10afe64b5e488185aaff0b659e08eda22c89285b54a3e433b80e6c6621770", size = 24040367, upload-time = "2025-12-12T18:00:04.443Z" },
+ { url = "https://files.pythonhosted.org/packages/32/76/54643a7d1d650fd7f1acea9093c298603e4c01bba6f90be2254310b48507/rasterio-1.4.4-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:18c2c1130e789dc2771d0aa5ec4b56d5b8a0097c648ccb94882d5ff3ab55c928", size = 21247203, upload-time = "2025-12-12T18:00:07.547Z" },
+ { url = "https://files.pythonhosted.org/packages/76/ef/434b4849ccd6a3e03a0b1ac37c963c1771564945745613d15c5d96ce768d/rasterio-1.4.4-cp313-cp313t-macosx_15_0_x86_64.whl", hash = "sha256:2d1654b7ffa6f3dde42c5fd27159ae45148c11e352de26f12fe7313a3236aeed", size = 25822050, upload-time = "2025-12-12T18:00:11.081Z" },
+ { url = "https://files.pythonhosted.org/packages/2d/fa/fe9a478aa0cde246da58baeb0df3248c7ca174e4d9c9b27e81b504e40a76/rasterio-1.4.4-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:c4022cbddb659856e120603b12233cec8913ae760fff220657ce888c3c6b9f9d", size = 34833783, upload-time = "2025-12-12T18:00:14.525Z" },
+ { url = "https://files.pythonhosted.org/packages/04/cd/ed4716590dbcd4b8ae633417d758564e510bee4d6aaac5050a0f6d5179c5/rasterio-1.4.4-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:96b88880551a07b7a3b50439483cefbd9af91a09e19ff2b736815994e5671314", size = 35738114, upload-time = "2025-12-12T18:00:17.96Z" },
+ { url = "https://files.pythonhosted.org/packages/7e/29/da7050d11ba1d041e0333ac14768e6e9ca1aa2b9fa8416f317d2650ed276/rasterio-1.4.4-cp313-cp313t-win_amd64.whl", hash = "sha256:def75d486d0ab8f306f918a913c425ed57159495518c54efe8e18d5164d37d90", size = 25896835, upload-time = "2025-12-12T18:00:21.411Z" },
+ { url = "https://files.pythonhosted.org/packages/88/80/304dbe5434c4aa8dfaf90480c16d770161796a6a61fa88e72e8a402153df/rasterio-1.4.4-cp313-cp313t-win_arm64.whl", hash = "sha256:770b7e86f6c565e6f9cf30f6fa4479a5a2bab4e10ff44fe7acfd518ca4a71d1b", size = 24128074, upload-time = "2025-12-12T18:00:24.653Z" },
+ { url = "https://files.pythonhosted.org/packages/03/01/d5a3dc51cd5fef62b76ecc77d33c1ca20de305fed7e16c71bcdf4858e466/rasterio-1.4.4-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:019693f14a83ae9225cb57c16e466901d0e6284962dcf13a9f4bb1175b979011", size = 21120237, upload-time = "2025-12-12T18:00:27.723Z" },
+ { url = "https://files.pythonhosted.org/packages/50/da/db18362602b17327c0e00c9e9c0847c1c4ac657c1a289169ca06a26faccb/rasterio-1.4.4-cp314-cp314-macosx_15_0_x86_64.whl", hash = "sha256:87d7c3e97e3b40c9041d1602e2dcb4fc2d88abe6c645fccb4939dec297a91cf8", size = 25720506, upload-time = "2025-12-12T18:00:30.592Z" },
+ { url = "https://files.pythonhosted.org/packages/5a/8f/a15d66c9c05bffb176c9707ef1f2bfcf9c0b835272937c80ac7207a20b5c/rasterio-1.4.4-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:a2401e4c43a31c7382154d4042b60a63b9bca5886802983c5c9362cdc5b09548", size = 34153931, upload-time = "2025-12-12T18:00:33.852Z" },
+ { url = "https://files.pythonhosted.org/packages/05/2d/cd778286b910db7a3f0bc1743ca362173f1fbb7365137e4982ca857b6d26/rasterio-1.4.4-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:6c4287d8934d953f7870b8e2a1df1096fbf47eba39ad0f777a31ea500f4e5010", size = 35421139, upload-time = "2025-12-12T18:00:37.482Z" },
+ { url = "https://files.pythonhosted.org/packages/70/97/13a2e33aede8d7a42178c696a6a93868d1f9560f73de05033a1675f0806a/rasterio-1.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:c3ba1871549221140661227dd4fa1f9a472ded4a6d2f2c2e367b0648bb15b99d", size = 26419132, upload-time = "2025-12-12T18:00:40.871Z" },
+ { url = "https://files.pythonhosted.org/packages/27/d8/2dcfcb362d6a2fd07c14cfb803a345a7926d4d9fb6243e196df105671e97/rasterio-1.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:7c9d7dc824cb8d222808be153643cd4e65ea3e1f66019ada1ccd630221edfe30", size = 24800998, upload-time = "2025-12-12T18:00:45.332Z" },
+ { url = "https://files.pythonhosted.org/packages/13/f8/16e9b648e7f16cadb41df7c0116dbab26b4a2ba02c85cbe3f744065bdf56/rasterio-1.4.4-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:98e17bded830a59992d9f8f8d9f227ce1c4be0694930afcc4360358f5cb1a5db", size = 21247046, upload-time = "2025-12-12T18:00:49.429Z" },
+ { url = "https://files.pythonhosted.org/packages/a8/ea/f3dc3a25d7591821d488f5c5eb89f6abcd1f5c8e2ef4bd2792f965cbc9c8/rasterio-1.4.4-cp314-cp314t-macosx_15_0_x86_64.whl", hash = "sha256:56134ca203f952855e60774b06672033cf65057eb9810fcc5c1a75f1921053a3", size = 25821677, upload-time = "2025-12-12T18:00:52.458Z" },
+ { url = "https://files.pythonhosted.org/packages/2e/d3/1e038350218e852f904c8dc4ab751aa023a2e82e68998767b7b42e33832c/rasterio-1.4.4-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:52edde65515b33fe4314c8a44a9ee2fc00b550deed6d56e1a8d085d42bbca3e6", size = 34829572, upload-time = "2025-12-12T18:00:56.294Z" },
+ { url = "https://files.pythonhosted.org/packages/0b/ce/28abf7a5f5d9cb014c2e14cc396bebe953b3deefbf604d49f4322e73fa35/rasterio-1.4.4-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:d61d3f2c171c64050bd75e54a5d964ff7f165b3f5d2b92c9ee09b9716aa1b8bf", size = 35735171, upload-time = "2025-12-12T18:00:59.531Z" },
+ { url = "https://files.pythonhosted.org/packages/54/91/1ce35cfda2d56dacd6395faf20a5290268bd9009c53393ac42b5f9bb2c4c/rasterio-1.4.4-cp314-cp314t-win_amd64.whl", hash = "sha256:40137fe512c0d6e96c0167a0ae4e56d82c488f244163c45494b7392e51c844de", size = 26700712, upload-time = "2025-12-12T18:01:03.023Z" },
+ { url = "https://files.pythonhosted.org/packages/3b/33/4d13f48a8f01d782ffc1eece20821586518f3f515dca7cf152bca9fd22d4/rasterio-1.4.4-cp314-cp314t-win_arm64.whl", hash = "sha256:29ec3a794454b5bb255c9c0374cc380030a8a1e295c81eee7feb036802d2a9e3", size = 24875933, upload-time = "2025-12-12T18:01:06.134Z" },
+]
+
+[[package]]
+name = "rasterio"
+version = "1.5.0"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version >= '3.15'",
+ "python_full_version >= '3.12' and python_full_version < '3.15'",
+]
+dependencies = [
+ { name = "affine", marker = "python_full_version >= '3.12'" },
+ { name = "attrs", marker = "python_full_version >= '3.12'" },
+ { name = "certifi", marker = "python_full_version >= '3.12'" },
+ { name = "click", marker = "python_full_version >= '3.12'" },
+ { name = "cligj", marker = "python_full_version >= '3.12'" },
+ { name = "numpy", version = "2.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" },
+ { name = "pyparsing", marker = "python_full_version >= '3.12'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/f6/88/edb4b66b6cb2c13f123af5a3896bf70c0cbe73ab3cd4243cb4eb0212a0f6/rasterio-1.5.0.tar.gz", hash = "sha256:1e0ea56b02eea4989b36edf8e58a5a3ef40e1b7edcb04def2603accd5ab3ee7b", size = 452184, upload-time = "2026-01-05T16:06:47.169Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/ac/de/ba1cd11d7d1182bfb26e758bf07016d04e5442f4f5fea35b0d7279b72399/rasterio-1.5.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:420656074897a460f5ef46f657b3061d2e004f9d99e613914b0671643e69d92c", size = 22787192, upload-time = "2026-01-05T16:05:19.779Z" },
+ { url = "https://files.pythonhosted.org/packages/e6/42/efaeb6dc531dbcd02fec01c791a853bb5a139a5126ecec579ac0f735eeb9/rasterio-1.5.0-cp312-cp312-macosx_15_0_x86_64.whl", hash = "sha256:c5c3597a783857e760550e8f26365d928b0377ac5ffc3e12ba447ac65ca5406d", size = 24412221, upload-time = "2026-01-05T16:05:22.526Z" },
+ { url = "https://files.pythonhosted.org/packages/a2/14/89645988424c40cbcb8334f94305ffe094dd28d85c643341d9690704c9f0/rasterio-1.5.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:e14d07a09833b6df6024ce7a57aee1e1977b3aec682e30b1e58ce773462f2382", size = 36128020, upload-time = "2026-01-05T16:05:25.556Z" },
+ { url = "https://files.pythonhosted.org/packages/85/23/5a52319a98451ff910f42e5f7f4804bfb39f9327933a89daab685d1ce2dd/rasterio-1.5.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:26dbcffcf0d01fc121cbb92186bc1cb78e16efe62b17be45ad7494446b325cf8", size = 37634010, upload-time = "2026-01-05T16:05:28.673Z" },
+ { url = "https://files.pythonhosted.org/packages/57/d6/fe8826f813c98b046d8d4c3bc83053c89c71f367f89257d211fe5dd0b0ba/rasterio-1.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:ac8d04eee66ca8060763ead607800e5611d857dd005905d920365e24a16ba20a", size = 30142328, upload-time = "2026-01-05T16:05:31.357Z" },
+ { url = "https://files.pythonhosted.org/packages/af/62/6397379271d5628ed65ef781bf2d3a8f56094a86e6d8479c6ca506a1b960/rasterio-1.5.0-cp312-cp312-win_arm64.whl", hash = "sha256:31f1edc45c781ebd087e60cc00a4fc37028dd3fe25cff4098e4139fc9d0565be", size = 28500710, upload-time = "2026-01-05T16:05:33.906Z" },
+ { url = "https://files.pythonhosted.org/packages/42/87/42865a77cebf2e524d27b6afc71db48984799ecd1dbe6a213d4713f42f5f/rasterio-1.5.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:e7b25b0a19975ccd511e507e6de45b0a2d8fb6802abe49bb726cf48588e34833", size = 22776107, upload-time = "2026-01-05T16:05:36.967Z" },
+ { url = "https://files.pythonhosted.org/packages/6a/53/e81683fbbfdf04e019e68b042d9cff8524b0571aa80e4f4d81c373c31a49/rasterio-1.5.0-cp313-cp313-macosx_15_0_x86_64.whl", hash = "sha256:1162c18eaece9f6d2aa1c2ff6b373b99651d93f113f24120a991eaebf28aa4f4", size = 24401477, upload-time = "2026-01-05T16:05:39.702Z" },
+ { url = "https://files.pythonhosted.org/packages/bc/3c/6aa6e0690b18eea02a61739cb362a47c5df66138f0a02cc69e1181b964e5/rasterio-1.5.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:8eb87fd6f843eea109f3df9bef83f741b053b716b0465932276e2c0577dfb929", size = 36018214, upload-time = "2026-01-05T16:05:42.741Z" },
+ { url = "https://files.pythonhosted.org/packages/48/4a/1af9aa9810fb30668568f2c4dd3eec2412c8e9762b69201d971c509b295e/rasterio-1.5.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:08a7580cbb9b3bd320bdf827e10c9b2424d0df066d8eef6f2feb37e154ce0c17", size = 37544972, upload-time = "2026-01-05T16:05:45.815Z" },
+ { url = "https://files.pythonhosted.org/packages/01/62/bfe3408743c9837919ff232474a09ece9eaa88d4ee8c040711fa3dff6dad/rasterio-1.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:d7d6729c0739b5ec48c33686668a30e27f5bdb361093f180ee7818ff19665547", size = 30140141, upload-time = "2026-01-05T16:05:48.751Z" },
+ { url = "https://files.pythonhosted.org/packages/63/ca/e90e19a6d065a718cc3d468a12b9f015289ad17017656dea8c76f7318d1f/rasterio-1.5.0-cp313-cp313-win_arm64.whl", hash = "sha256:8af7c368c22f0a99d1259ccc5a5cd96c432c2bde6f132c1ac78508cd7445a745", size = 28498556, upload-time = "2026-01-05T16:05:51.334Z" },
+ { url = "https://files.pythonhosted.org/packages/a0/ba/e37462d8c33bbbd6c152a0390ec6911a3d9614ded3d2bc6f6a48e147e833/rasterio-1.5.0-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:b4ccfcc8ed9400e4f14efdf2005533fcf72048748b727f85ff89b9291ecdf98a", size = 22920107, upload-time = "2026-01-05T16:05:53.773Z" },
+ { url = "https://files.pythonhosted.org/packages/66/dc/7bfa9cf96ac39b451b2f94dfc584c223ec584c52c148df2e4bab60c3341b/rasterio-1.5.0-cp313-cp313t-macosx_15_0_x86_64.whl", hash = "sha256:2f57c36ca4d3c896f7024226bd71eeb5cd10c8183c2a94508534d78cc05ff9e7", size = 24508993, upload-time = "2026-01-05T16:05:57.062Z" },
+ { url = "https://files.pythonhosted.org/packages/e5/55/7293743f3b69de4b726c67b8dc9da01fc194070b6becc51add4ca8a20a27/rasterio-1.5.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:cc1395475e4bb7032cd81dda4d5558061c4c7d5a50b1b5e146bdf9716d0b9353", size = 36565784, upload-time = "2026-01-05T16:06:00.019Z" },
+ { url = "https://files.pythonhosted.org/packages/cf/ef/5354c47de16c6e289728c3a3d6961ffcf7a9ad6313aef7e8db5d6a40c46e/rasterio-1.5.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:592a485e2057b1aaeab4f843c9897628e60e3ff45e2509325c3e1479116599cb", size = 37686456, upload-time = "2026-01-05T16:06:02.772Z" },
+ { url = "https://files.pythonhosted.org/packages/b7/fc/fe1f034b1acd1900d9fbd616826d001a3d5811f1d0c97c785f88f525853e/rasterio-1.5.0-cp313-cp313t-win_amd64.whl", hash = "sha256:0c739e70a72fb080f039ee1570c5d02b974dde32ded1a3216e1f13fe38ac4844", size = 30355842, upload-time = "2026-01-05T16:06:06.359Z" },
+ { url = "https://files.pythonhosted.org/packages/e0/cb/4dee9697891c9c6474b240d00e27688e03ecd882d3c83cc97eb25c2266ff/rasterio-1.5.0-cp313-cp313t-win_arm64.whl", hash = "sha256:a3539a2f401a7b4b2e94ff2db334878c0e15a2d1c9fe90bb0879c52f89367ae5", size = 28589538, upload-time = "2026-01-05T16:06:09.662Z" },
+ { url = "https://files.pythonhosted.org/packages/77/9f/f84dfa54110c1c82f9f4fd929465d12519569b6f5d015273aa0957013b2e/rasterio-1.5.0-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:597be8df418d5ba7b6a927b6b9febfcb42b192882448a8d5b2e2e75a1296631f", size = 22788832, upload-time = "2026-01-05T16:06:12.247Z" },
+ { url = "https://files.pythonhosted.org/packages/20/f1/de55255c918b17afd7292f793a3500c4aea7e9530b2b3f5b3a57836c7d49/rasterio-1.5.0-cp314-cp314-macosx_15_0_x86_64.whl", hash = "sha256:dd292030d39d685c0b35eddef233e7f1cb8b43052578a3ec97a2da57799693be", size = 24405917, upload-time = "2026-01-05T16:06:14.603Z" },
+ { url = "https://files.pythonhosted.org/packages/a9/57/054087a9d5011ad5dfa799277ba8814e41775e1967d37a59ab7b8e2f1876/rasterio-1.5.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:62c3f97a3c72643c74f2d0f310621a09c35c0c412229c327ae6bcc1ee4b9c3bc", size = 35987536, upload-time = "2026-01-05T16:06:17.707Z" },
+ { url = "https://files.pythonhosted.org/packages/c9/72/5fbe5f67ae75d7e89ffb718c500d5fecbaa84f6ba354db306de689faf961/rasterio-1.5.0-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:19577f0f0c5f1158af47b57f73356961cbd1782a5f6ae6f3adf6f2650f4eb369", size = 37408048, upload-time = "2026-01-05T16:06:20.82Z" },
+ { url = "https://files.pythonhosted.org/packages/c4/3e/0c4ef19980204bdcbc8f9e084056adebc97916ff4edcc718750ef34e5bf9/rasterio-1.5.0-cp314-cp314-win_amd64.whl", hash = "sha256:015c1ab6e5453312c5e29692752e7ad73568fe4d13567cbd448d7893128cbd2d", size = 30949590, upload-time = "2026-01-05T16:06:23.425Z" },
+ { url = "https://files.pythonhosted.org/packages/c2/d8/2e6b81505408926c00e629d7d3d73fd0454213201bd9907450e0fe82f3dd/rasterio-1.5.0-cp314-cp314-win_arm64.whl", hash = "sha256:ff677c0a9d3ba667c067227ef2b76872488b37ff29b061bc3e576fad9baa3286", size = 29337287, upload-time = "2026-01-05T16:06:26.599Z" },
+ { url = "https://files.pythonhosted.org/packages/19/49/7b6e6afb28d4e3f69f2229f990ed87dfdc21a3e15ca63b96b2fd9ba17d89/rasterio-1.5.0-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:508251b9c746d8d008771a30c2160ff321bfc3b41f6a1aa8e8ef1dd4a00d97ba", size = 22926149, upload-time = "2026-01-05T16:06:29.617Z" },
+ { url = "https://files.pythonhosted.org/packages/24/30/19345d8bc7d2b96c1172594026b9009702e9ab9f0baf07079d3612aaadae/rasterio-1.5.0-cp314-cp314t-macosx_15_0_x86_64.whl", hash = "sha256:742841ed48bc70f6ef517b8fa3521f231780bf408fde0aa6d73770337a36374e", size = 24516040, upload-time = "2026-01-05T16:06:32.964Z" },
+ { url = "https://files.pythonhosted.org/packages/9e/43/dc7a4518fa78904bc41952cbf346c3c2a88a20e61b479154058392914c0b/rasterio-1.5.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:c9a9eee49ce9410c2f352b34c370bb3a96bb518b6a7f97b3a72ee4c835fd4b5c", size = 36589519, upload-time = "2026-01-05T16:06:35.922Z" },
+ { url = "https://files.pythonhosted.org/packages/8f/f2/8f706083c6c163054d12c7ed6d5ac4e4ed02252b761288d74e6158871b34/rasterio-1.5.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:b9fd87a0b63ab5c6267dfb0bc96f54fdf49d000651b9ee85ed37798141cff046", size = 37714599, upload-time = "2026-01-05T16:06:38.818Z" },
+ { url = "https://files.pythonhosted.org/packages/a6/d5/bbca726d5fea5864f7e4bcf3ee893095369e93ad51120495e8c40e2aa1a0/rasterio-1.5.0-cp314-cp314t-win_amd64.whl", hash = "sha256:f459db8953ba30ca04fcef2b5e1260eeeff0eae8158bd9c3d6adbe56289765cc", size = 31233931, upload-time = "2026-01-05T16:06:42.208Z" },
+ { url = "https://files.pythonhosted.org/packages/6e/d1/8b017856e63ccaff3cbd0e82490dbb01363a42f3a462a41b1d8a391e1443/rasterio-1.5.0-cp314-cp314t-win_arm64.whl", hash = "sha256:f4b9c2c3b5f10469eb9588f105086e68f0279e62cc9095c4edd245e3f9b88c8a", size = 29418321, upload-time = "2026-01-05T16:06:44.758Z" },
+]
+
+[[package]]
+name = "referencing"
+version = "0.37.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "attrs" },
+ { name = "rpds-py", version = "0.30.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" },
+ { name = "rpds-py", version = "2026.6.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
+ { name = "typing-extensions", marker = "python_full_version < '3.13'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/22/f5/df4e9027acead3ecc63e50fe1e36aca1523e1719559c499951bb4b53188f/referencing-0.37.0.tar.gz", hash = "sha256:44aefc3142c5b842538163acb373e24cce6632bd54bdb01b21ad5863489f50d8", size = 78036, upload-time = "2025-10-13T15:30:48.871Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl", hash = "sha256:381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231", size = 26766, upload-time = "2025-10-13T15:30:47.625Z" },
+]
+
[[package]]
name = "requests"
version = "2.34.2"
@@ -1220,6 +1763,259 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/a0/f4/c67b0b3f1b9245e8d266f0f112c500d50e5b4e83cb6f3b71b6528104182a/requests-2.34.2-py3-none-any.whl", hash = "sha256:2a0d60c172f83ac6ab31e4554906c0f3b3588d37b5cb939b1c061f4907e278e0", size = 73075, upload-time = "2026-05-14T19:25:26.443Z" },
]
+[[package]]
+name = "rpds-py"
+version = "0.30.0"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version < '3.11'",
+]
+sdist = { url = "https://files.pythonhosted.org/packages/20/af/3f2f423103f1113b36230496629986e0ef7e199d2aa8392452b484b38ced/rpds_py-0.30.0.tar.gz", hash = "sha256:dd8ff7cf90014af0c0f787eea34794ebf6415242ee1d6fa91eaba725cc441e84", size = 69469, upload-time = "2025-11-30T20:24:38.837Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/06/0c/0c411a0ec64ccb6d104dcabe0e713e05e153a9a2c3c2bd2b32ce412166fe/rpds_py-0.30.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:679ae98e00c0e8d68a7fda324e16b90fd5260945b45d3b824c892cec9eea3288", size = 370490, upload-time = "2025-11-30T20:21:33.256Z" },
+ { url = "https://files.pythonhosted.org/packages/19/6a/4ba3d0fb7297ebae71171822554abe48d7cab29c28b8f9f2c04b79988c05/rpds_py-0.30.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4cc2206b76b4f576934f0ed374b10d7ca5f457858b157ca52064bdfc26b9fc00", size = 359751, upload-time = "2025-11-30T20:21:34.591Z" },
+ { url = "https://files.pythonhosted.org/packages/cd/7c/e4933565ef7f7a0818985d87c15d9d273f1a649afa6a52ea35ad011195ea/rpds_py-0.30.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:389a2d49eded1896c3d48b0136ead37c48e221b391c052fba3f4055c367f60a6", size = 389696, upload-time = "2025-11-30T20:21:36.122Z" },
+ { url = "https://files.pythonhosted.org/packages/5e/01/6271a2511ad0815f00f7ed4390cf2567bec1d4b1da39e2c27a41e6e3b4de/rpds_py-0.30.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:32c8528634e1bf7121f3de08fa85b138f4e0dc47657866630611b03967f041d7", size = 403136, upload-time = "2025-11-30T20:21:37.728Z" },
+ { url = "https://files.pythonhosted.org/packages/55/64/c857eb7cd7541e9b4eee9d49c196e833128a55b89a9850a9c9ac33ccf897/rpds_py-0.30.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f207f69853edd6f6700b86efb84999651baf3789e78a466431df1331608e5324", size = 524699, upload-time = "2025-11-30T20:21:38.92Z" },
+ { url = "https://files.pythonhosted.org/packages/9c/ed/94816543404078af9ab26159c44f9e98e20fe47e2126d5d32c9d9948d10a/rpds_py-0.30.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:67b02ec25ba7a9e8fa74c63b6ca44cf5707f2fbfadae3ee8e7494297d56aa9df", size = 412022, upload-time = "2025-11-30T20:21:40.407Z" },
+ { url = "https://files.pythonhosted.org/packages/61/b5/707f6cf0066a6412aacc11d17920ea2e19e5b2f04081c64526eb35b5c6e7/rpds_py-0.30.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c0e95f6819a19965ff420f65578bacb0b00f251fefe2c8b23347c37174271f3", size = 390522, upload-time = "2025-11-30T20:21:42.17Z" },
+ { url = "https://files.pythonhosted.org/packages/13/4e/57a85fda37a229ff4226f8cbcf09f2a455d1ed20e802ce5b2b4a7f5ed053/rpds_py-0.30.0-cp310-cp310-manylinux_2_31_riscv64.whl", hash = "sha256:a452763cc5198f2f98898eb98f7569649fe5da666c2dc6b5ddb10fde5a574221", size = 404579, upload-time = "2025-11-30T20:21:43.769Z" },
+ { url = "https://files.pythonhosted.org/packages/f9/da/c9339293513ec680a721e0e16bf2bac3db6e5d7e922488de471308349bba/rpds_py-0.30.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e0b65193a413ccc930671c55153a03ee57cecb49e6227204b04fae512eb657a7", size = 421305, upload-time = "2025-11-30T20:21:44.994Z" },
+ { url = "https://files.pythonhosted.org/packages/f9/be/522cb84751114f4ad9d822ff5a1aa3c98006341895d5f084779b99596e5c/rpds_py-0.30.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:858738e9c32147f78b3ac24dc0edb6610000e56dc0f700fd5f651d0a0f0eb9ff", size = 572503, upload-time = "2025-11-30T20:21:46.91Z" },
+ { url = "https://files.pythonhosted.org/packages/a2/9b/de879f7e7ceddc973ea6e4629e9b380213a6938a249e94b0cdbcc325bb66/rpds_py-0.30.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:da279aa314f00acbb803da1e76fa18666778e8a8f83484fba94526da5de2cba7", size = 598322, upload-time = "2025-11-30T20:21:48.709Z" },
+ { url = "https://files.pythonhosted.org/packages/48/ac/f01fc22efec3f37d8a914fc1b2fb9bcafd56a299edbe96406f3053edea5a/rpds_py-0.30.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7c64d38fb49b6cdeda16ab49e35fe0da2e1e9b34bc38bd78386530f218b37139", size = 560792, upload-time = "2025-11-30T20:21:50.024Z" },
+ { url = "https://files.pythonhosted.org/packages/e2/da/4e2b19d0f131f35b6146425f846563d0ce036763e38913d917187307a671/rpds_py-0.30.0-cp310-cp310-win32.whl", hash = "sha256:6de2a32a1665b93233cde140ff8b3467bdb9e2af2b91079f0333a0974d12d464", size = 221901, upload-time = "2025-11-30T20:21:51.32Z" },
+ { url = "https://files.pythonhosted.org/packages/96/cb/156d7a5cf4f78a7cc571465d8aec7a3c447c94f6749c5123f08438bcf7bc/rpds_py-0.30.0-cp310-cp310-win_amd64.whl", hash = "sha256:1726859cd0de969f88dc8673bdd954185b9104e05806be64bcd87badbe313169", size = 235823, upload-time = "2025-11-30T20:21:52.505Z" },
+ { url = "https://files.pythonhosted.org/packages/4d/6e/f964e88b3d2abee2a82c1ac8366da848fce1c6d834dc2132c3fda3970290/rpds_py-0.30.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a2bffea6a4ca9f01b3f8e548302470306689684e61602aa3d141e34da06cf425", size = 370157, upload-time = "2025-11-30T20:21:53.789Z" },
+ { url = "https://files.pythonhosted.org/packages/94/ba/24e5ebb7c1c82e74c4e4f33b2112a5573ddc703915b13a073737b59b86e0/rpds_py-0.30.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dc4f992dfe1e2bc3ebc7444f6c7051b4bc13cd8e33e43511e8ffd13bf407010d", size = 359676, upload-time = "2025-11-30T20:21:55.475Z" },
+ { url = "https://files.pythonhosted.org/packages/84/86/04dbba1b087227747d64d80c3b74df946b986c57af0a9f0c98726d4d7a3b/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:422c3cb9856d80b09d30d2eb255d0754b23e090034e1deb4083f8004bd0761e4", size = 389938, upload-time = "2025-11-30T20:21:57.079Z" },
+ { url = "https://files.pythonhosted.org/packages/42/bb/1463f0b1722b7f45431bdd468301991d1328b16cffe0b1c2918eba2c4eee/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:07ae8a593e1c3c6b82ca3292efbe73c30b61332fd612e05abee07c79359f292f", size = 402932, upload-time = "2025-11-30T20:21:58.47Z" },
+ { url = "https://files.pythonhosted.org/packages/99/ee/2520700a5c1f2d76631f948b0736cdf9b0acb25abd0ca8e889b5c62ac2e3/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:12f90dd7557b6bd57f40abe7747e81e0c0b119bef015ea7726e69fe550e394a4", size = 525830, upload-time = "2025-11-30T20:21:59.699Z" },
+ { url = "https://files.pythonhosted.org/packages/e0/ad/bd0331f740f5705cc555a5e17fdf334671262160270962e69a2bdef3bf76/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:99b47d6ad9a6da00bec6aabe5a6279ecd3c06a329d4aa4771034a21e335c3a97", size = 412033, upload-time = "2025-11-30T20:22:00.991Z" },
+ { url = "https://files.pythonhosted.org/packages/f8/1e/372195d326549bb51f0ba0f2ecb9874579906b97e08880e7a65c3bef1a99/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:33f559f3104504506a44bb666b93a33f5d33133765b0c216a5bf2f1e1503af89", size = 390828, upload-time = "2025-11-30T20:22:02.723Z" },
+ { url = "https://files.pythonhosted.org/packages/ab/2b/d88bb33294e3e0c76bc8f351a3721212713629ffca1700fa94979cb3eae8/rpds_py-0.30.0-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:946fe926af6e44f3697abbc305ea168c2c31d3e3ef1058cf68f379bf0335a78d", size = 404683, upload-time = "2025-11-30T20:22:04.367Z" },
+ { url = "https://files.pythonhosted.org/packages/50/32/c759a8d42bcb5289c1fac697cd92f6fe01a018dd937e62ae77e0e7f15702/rpds_py-0.30.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:495aeca4b93d465efde585977365187149e75383ad2684f81519f504f5c13038", size = 421583, upload-time = "2025-11-30T20:22:05.814Z" },
+ { url = "https://files.pythonhosted.org/packages/2b/81/e729761dbd55ddf5d84ec4ff1f47857f4374b0f19bdabfcf929164da3e24/rpds_py-0.30.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d9a0ca5da0386dee0655b4ccdf46119df60e0f10da268d04fe7cc87886872ba7", size = 572496, upload-time = "2025-11-30T20:22:07.713Z" },
+ { url = "https://files.pythonhosted.org/packages/14/f6/69066a924c3557c9c30baa6ec3a0aa07526305684c6f86c696b08860726c/rpds_py-0.30.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8d6d1cc13664ec13c1b84241204ff3b12f9bb82464b8ad6e7a5d3486975c2eed", size = 598669, upload-time = "2025-11-30T20:22:09.312Z" },
+ { url = "https://files.pythonhosted.org/packages/5f/48/905896b1eb8a05630d20333d1d8ffd162394127b74ce0b0784ae04498d32/rpds_py-0.30.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3896fa1be39912cf0757753826bc8bdc8ca331a28a7c4ae46b7a21280b06bb85", size = 561011, upload-time = "2025-11-30T20:22:11.309Z" },
+ { url = "https://files.pythonhosted.org/packages/22/16/cd3027c7e279d22e5eb431dd3c0fbc677bed58797fe7581e148f3f68818b/rpds_py-0.30.0-cp311-cp311-win32.whl", hash = "sha256:55f66022632205940f1827effeff17c4fa7ae1953d2b74a8581baaefb7d16f8c", size = 221406, upload-time = "2025-11-30T20:22:13.101Z" },
+ { url = "https://files.pythonhosted.org/packages/fa/5b/e7b7aa136f28462b344e652ee010d4de26ee9fd16f1bfd5811f5153ccf89/rpds_py-0.30.0-cp311-cp311-win_amd64.whl", hash = "sha256:a51033ff701fca756439d641c0ad09a41d9242fa69121c7d8769604a0a629825", size = 236024, upload-time = "2025-11-30T20:22:14.853Z" },
+ { url = "https://files.pythonhosted.org/packages/14/a6/364bba985e4c13658edb156640608f2c9e1d3ea3c81b27aa9d889fff0e31/rpds_py-0.30.0-cp311-cp311-win_arm64.whl", hash = "sha256:47b0ef6231c58f506ef0b74d44e330405caa8428e770fec25329ed2cb971a229", size = 229069, upload-time = "2025-11-30T20:22:16.577Z" },
+ { url = "https://files.pythonhosted.org/packages/03/e7/98a2f4ac921d82f33e03f3835f5bf3a4a40aa1bfdc57975e74a97b2b4bdd/rpds_py-0.30.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a161f20d9a43006833cd7068375a94d035714d73a172b681d8881820600abfad", size = 375086, upload-time = "2025-11-30T20:22:17.93Z" },
+ { url = "https://files.pythonhosted.org/packages/4d/a1/bca7fd3d452b272e13335db8d6b0b3ecde0f90ad6f16f3328c6fb150c889/rpds_py-0.30.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6abc8880d9d036ecaafe709079969f56e876fcf107f7a8e9920ba6d5a3878d05", size = 359053, upload-time = "2025-11-30T20:22:19.297Z" },
+ { url = "https://files.pythonhosted.org/packages/65/1c/ae157e83a6357eceff62ba7e52113e3ec4834a84cfe07fa4b0757a7d105f/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca28829ae5f5d569bb62a79512c842a03a12576375d5ece7d2cadf8abe96ec28", size = 390763, upload-time = "2025-11-30T20:22:21.661Z" },
+ { url = "https://files.pythonhosted.org/packages/d4/36/eb2eb8515e2ad24c0bd43c3ee9cd74c33f7ca6430755ccdb240fd3144c44/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a1010ed9524c73b94d15919ca4d41d8780980e1765babf85f9a2f90d247153dd", size = 408951, upload-time = "2025-11-30T20:22:23.408Z" },
+ { url = "https://files.pythonhosted.org/packages/d6/65/ad8dc1784a331fabbd740ef6f71ce2198c7ed0890dab595adb9ea2d775a1/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f8d1736cfb49381ba528cd5baa46f82fdc65c06e843dab24dd70b63d09121b3f", size = 514622, upload-time = "2025-11-30T20:22:25.16Z" },
+ { url = "https://files.pythonhosted.org/packages/63/8e/0cfa7ae158e15e143fe03993b5bcd743a59f541f5952e1546b1ac1b5fd45/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d948b135c4693daff7bc2dcfc4ec57237a29bd37e60c2fabf5aff2bbacf3e2f1", size = 414492, upload-time = "2025-11-30T20:22:26.505Z" },
+ { url = "https://files.pythonhosted.org/packages/60/1b/6f8f29f3f995c7ffdde46a626ddccd7c63aefc0efae881dc13b6e5d5bb16/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47f236970bccb2233267d89173d3ad2703cd36a0e2a6e92d0560d333871a3d23", size = 394080, upload-time = "2025-11-30T20:22:27.934Z" },
+ { url = "https://files.pythonhosted.org/packages/6d/d5/a266341051a7a3ca2f4b750a3aa4abc986378431fc2da508c5034d081b70/rpds_py-0.30.0-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:2e6ecb5a5bcacf59c3f912155044479af1d0b6681280048b338b28e364aca1f6", size = 408680, upload-time = "2025-11-30T20:22:29.341Z" },
+ { url = "https://files.pythonhosted.org/packages/10/3b/71b725851df9ab7a7a4e33cf36d241933da66040d195a84781f49c50490c/rpds_py-0.30.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a8fa71a2e078c527c3e9dc9fc5a98c9db40bcc8a92b4e8858e36d329f8684b51", size = 423589, upload-time = "2025-11-30T20:22:31.469Z" },
+ { url = "https://files.pythonhosted.org/packages/00/2b/e59e58c544dc9bd8bd8384ecdb8ea91f6727f0e37a7131baeff8d6f51661/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:73c67f2db7bc334e518d097c6d1e6fed021bbc9b7d678d6cc433478365d1d5f5", size = 573289, upload-time = "2025-11-30T20:22:32.997Z" },
+ { url = "https://files.pythonhosted.org/packages/da/3e/a18e6f5b460893172a7d6a680e86d3b6bc87a54c1f0b03446a3c8c7b588f/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5ba103fb455be00f3b1c2076c9d4264bfcb037c976167a6047ed82f23153f02e", size = 599737, upload-time = "2025-11-30T20:22:34.419Z" },
+ { url = "https://files.pythonhosted.org/packages/5c/e2/714694e4b87b85a18e2c243614974413c60aa107fd815b8cbc42b873d1d7/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7cee9c752c0364588353e627da8a7e808a66873672bcb5f52890c33fd965b394", size = 563120, upload-time = "2025-11-30T20:22:35.903Z" },
+ { url = "https://files.pythonhosted.org/packages/6f/ab/d5d5e3bcedb0a77f4f613706b750e50a5a3ba1c15ccd3665ecc636c968fd/rpds_py-0.30.0-cp312-cp312-win32.whl", hash = "sha256:1ab5b83dbcf55acc8b08fc62b796ef672c457b17dbd7820a11d6c52c06839bdf", size = 223782, upload-time = "2025-11-30T20:22:37.271Z" },
+ { url = "https://files.pythonhosted.org/packages/39/3b/f786af9957306fdc38a74cef405b7b93180f481fb48453a114bb6465744a/rpds_py-0.30.0-cp312-cp312-win_amd64.whl", hash = "sha256:a090322ca841abd453d43456ac34db46e8b05fd9b3b4ac0c78bcde8b089f959b", size = 240463, upload-time = "2025-11-30T20:22:39.021Z" },
+ { url = "https://files.pythonhosted.org/packages/f3/d2/b91dc748126c1559042cfe41990deb92c4ee3e2b415f6b5234969ffaf0cc/rpds_py-0.30.0-cp312-cp312-win_arm64.whl", hash = "sha256:669b1805bd639dd2989b281be2cfd951c6121b65e729d9b843e9639ef1fd555e", size = 230868, upload-time = "2025-11-30T20:22:40.493Z" },
+ { url = "https://files.pythonhosted.org/packages/ed/dc/d61221eb88ff410de3c49143407f6f3147acf2538c86f2ab7ce65ae7d5f9/rpds_py-0.30.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:f83424d738204d9770830d35290ff3273fbb02b41f919870479fab14b9d303b2", size = 374887, upload-time = "2025-11-30T20:22:41.812Z" },
+ { url = "https://files.pythonhosted.org/packages/fd/32/55fb50ae104061dbc564ef15cc43c013dc4a9f4527a1f4d99baddf56fe5f/rpds_py-0.30.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e7536cd91353c5273434b4e003cbda89034d67e7710eab8761fd918ec6c69cf8", size = 358904, upload-time = "2025-11-30T20:22:43.479Z" },
+ { url = "https://files.pythonhosted.org/packages/58/70/faed8186300e3b9bdd138d0273109784eea2396c68458ed580f885dfe7ad/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2771c6c15973347f50fece41fc447c054b7ac2ae0502388ce3b6738cd366e3d4", size = 389945, upload-time = "2025-11-30T20:22:44.819Z" },
+ { url = "https://files.pythonhosted.org/packages/bd/a8/073cac3ed2c6387df38f71296d002ab43496a96b92c823e76f46b8af0543/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0a59119fc6e3f460315fe9d08149f8102aa322299deaa5cab5b40092345c2136", size = 407783, upload-time = "2025-11-30T20:22:46.103Z" },
+ { url = "https://files.pythonhosted.org/packages/77/57/5999eb8c58671f1c11eba084115e77a8899d6e694d2a18f69f0ba471ec8b/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:76fec018282b4ead0364022e3c54b60bf368b9d926877957a8624b58419169b7", size = 515021, upload-time = "2025-11-30T20:22:47.458Z" },
+ { url = "https://files.pythonhosted.org/packages/e0/af/5ab4833eadc36c0a8ed2bc5c0de0493c04f6c06de223170bd0798ff98ced/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:692bef75a5525db97318e8cd061542b5a79812d711ea03dbc1f6f8dbb0c5f0d2", size = 414589, upload-time = "2025-11-30T20:22:48.872Z" },
+ { url = "https://files.pythonhosted.org/packages/b7/de/f7192e12b21b9e9a68a6d0f249b4af3fdcdff8418be0767a627564afa1f1/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9027da1ce107104c50c81383cae773ef5c24d296dd11c99e2629dbd7967a20c6", size = 394025, upload-time = "2025-11-30T20:22:50.196Z" },
+ { url = "https://files.pythonhosted.org/packages/91/c4/fc70cd0249496493500e7cc2de87504f5aa6509de1e88623431fec76d4b6/rpds_py-0.30.0-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:9cf69cdda1f5968a30a359aba2f7f9aa648a9ce4b580d6826437f2b291cfc86e", size = 408895, upload-time = "2025-11-30T20:22:51.87Z" },
+ { url = "https://files.pythonhosted.org/packages/58/95/d9275b05ab96556fefff73a385813eb66032e4c99f411d0795372d9abcea/rpds_py-0.30.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a4796a717bf12b9da9d3ad002519a86063dcac8988b030e405704ef7d74d2d9d", size = 422799, upload-time = "2025-11-30T20:22:53.341Z" },
+ { url = "https://files.pythonhosted.org/packages/06/c1/3088fc04b6624eb12a57eb814f0d4997a44b0d208d6cace713033ff1a6ba/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5d4c2aa7c50ad4728a094ebd5eb46c452e9cb7edbfdb18f9e1221f597a73e1e7", size = 572731, upload-time = "2025-11-30T20:22:54.778Z" },
+ { url = "https://files.pythonhosted.org/packages/d8/42/c612a833183b39774e8ac8fecae81263a68b9583ee343db33ab571a7ce55/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ba81a9203d07805435eb06f536d95a266c21e5b2dfbf6517748ca40c98d19e31", size = 599027, upload-time = "2025-11-30T20:22:56.212Z" },
+ { url = "https://files.pythonhosted.org/packages/5f/60/525a50f45b01d70005403ae0e25f43c0384369ad24ffe46e8d9068b50086/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:945dccface01af02675628334f7cf49c2af4c1c904748efc5cf7bbdf0b579f95", size = 563020, upload-time = "2025-11-30T20:22:58.2Z" },
+ { url = "https://files.pythonhosted.org/packages/0b/5d/47c4655e9bcd5ca907148535c10e7d489044243cc9941c16ed7cd53be91d/rpds_py-0.30.0-cp313-cp313-win32.whl", hash = "sha256:b40fb160a2db369a194cb27943582b38f79fc4887291417685f3ad693c5a1d5d", size = 223139, upload-time = "2025-11-30T20:23:00.209Z" },
+ { url = "https://files.pythonhosted.org/packages/f2/e1/485132437d20aa4d3e1d8b3fb5a5e65aa8139f1e097080c2a8443201742c/rpds_py-0.30.0-cp313-cp313-win_amd64.whl", hash = "sha256:806f36b1b605e2d6a72716f321f20036b9489d29c51c91f4dd29a3e3afb73b15", size = 240224, upload-time = "2025-11-30T20:23:02.008Z" },
+ { url = "https://files.pythonhosted.org/packages/24/95/ffd128ed1146a153d928617b0ef673960130be0009c77d8fbf0abe306713/rpds_py-0.30.0-cp313-cp313-win_arm64.whl", hash = "sha256:d96c2086587c7c30d44f31f42eae4eac89b60dabbac18c7669be3700f13c3ce1", size = 230645, upload-time = "2025-11-30T20:23:03.43Z" },
+ { url = "https://files.pythonhosted.org/packages/ff/1b/b10de890a0def2a319a2626334a7f0ae388215eb60914dbac8a3bae54435/rpds_py-0.30.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:eb0b93f2e5c2189ee831ee43f156ed34e2a89a78a66b98cadad955972548be5a", size = 364443, upload-time = "2025-11-30T20:23:04.878Z" },
+ { url = "https://files.pythonhosted.org/packages/0d/bf/27e39f5971dc4f305a4fb9c672ca06f290f7c4e261c568f3dea16a410d47/rpds_py-0.30.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:922e10f31f303c7c920da8981051ff6d8c1a56207dbdf330d9047f6d30b70e5e", size = 353375, upload-time = "2025-11-30T20:23:06.342Z" },
+ { url = "https://files.pythonhosted.org/packages/40/58/442ada3bba6e8e6615fc00483135c14a7538d2ffac30e2d933ccf6852232/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cdc62c8286ba9bf7f47befdcea13ea0e26bf294bda99758fd90535cbaf408000", size = 383850, upload-time = "2025-11-30T20:23:07.825Z" },
+ { url = "https://files.pythonhosted.org/packages/14/14/f59b0127409a33c6ef6f5c1ebd5ad8e32d7861c9c7adfa9a624fc3889f6c/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:47f9a91efc418b54fb8190a6b4aa7813a23fb79c51f4bb84e418f5476c38b8db", size = 392812, upload-time = "2025-11-30T20:23:09.228Z" },
+ { url = "https://files.pythonhosted.org/packages/b3/66/e0be3e162ac299b3a22527e8913767d869e6cc75c46bd844aa43fb81ab62/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1f3587eb9b17f3789ad50824084fa6f81921bbf9a795826570bda82cb3ed91f2", size = 517841, upload-time = "2025-11-30T20:23:11.186Z" },
+ { url = "https://files.pythonhosted.org/packages/3d/55/fa3b9cf31d0c963ecf1ba777f7cf4b2a2c976795ac430d24a1f43d25a6ba/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:39c02563fc592411c2c61d26b6c5fe1e51eaa44a75aa2c8735ca88b0d9599daa", size = 408149, upload-time = "2025-11-30T20:23:12.864Z" },
+ { url = "https://files.pythonhosted.org/packages/60/ca/780cf3b1a32b18c0f05c441958d3758f02544f1d613abf9488cd78876378/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51a1234d8febafdfd33a42d97da7a43f5dcb120c1060e352a3fbc0c6d36e2083", size = 383843, upload-time = "2025-11-30T20:23:14.638Z" },
+ { url = "https://files.pythonhosted.org/packages/82/86/d5f2e04f2aa6247c613da0c1dd87fcd08fa17107e858193566048a1e2f0a/rpds_py-0.30.0-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:eb2c4071ab598733724c08221091e8d80e89064cd472819285a9ab0f24bcedb9", size = 396507, upload-time = "2025-11-30T20:23:16.105Z" },
+ { url = "https://files.pythonhosted.org/packages/4b/9a/453255d2f769fe44e07ea9785c8347edaf867f7026872e76c1ad9f7bed92/rpds_py-0.30.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6bdfdb946967d816e6adf9a3d8201bfad269c67efe6cefd7093ef959683c8de0", size = 414949, upload-time = "2025-11-30T20:23:17.539Z" },
+ { url = "https://files.pythonhosted.org/packages/a3/31/622a86cdc0c45d6df0e9ccb6becdba5074735e7033c20e401a6d9d0e2ca0/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c77afbd5f5250bf27bf516c7c4a016813eb2d3e116139aed0096940c5982da94", size = 565790, upload-time = "2025-11-30T20:23:19.029Z" },
+ { url = "https://files.pythonhosted.org/packages/1c/5d/15bbf0fb4a3f58a3b1c67855ec1efcc4ceaef4e86644665fff03e1b66d8d/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:61046904275472a76c8c90c9ccee9013d70a6d0f73eecefd38c1ae7c39045a08", size = 590217, upload-time = "2025-11-30T20:23:20.885Z" },
+ { url = "https://files.pythonhosted.org/packages/6d/61/21b8c41f68e60c8cc3b2e25644f0e3681926020f11d06ab0b78e3c6bbff1/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4c5f36a861bc4b7da6516dbdf302c55313afa09b81931e8280361a4f6c9a2d27", size = 555806, upload-time = "2025-11-30T20:23:22.488Z" },
+ { url = "https://files.pythonhosted.org/packages/f9/39/7e067bb06c31de48de3eb200f9fc7c58982a4d3db44b07e73963e10d3be9/rpds_py-0.30.0-cp313-cp313t-win32.whl", hash = "sha256:3d4a69de7a3e50ffc214ae16d79d8fbb0922972da0356dcf4d0fdca2878559c6", size = 211341, upload-time = "2025-11-30T20:23:24.449Z" },
+ { url = "https://files.pythonhosted.org/packages/0a/4d/222ef0b46443cf4cf46764d9c630f3fe4abaa7245be9417e56e9f52b8f65/rpds_py-0.30.0-cp313-cp313t-win_amd64.whl", hash = "sha256:f14fc5df50a716f7ece6a80b6c78bb35ea2ca47c499e422aa4463455dd96d56d", size = 225768, upload-time = "2025-11-30T20:23:25.908Z" },
+ { url = "https://files.pythonhosted.org/packages/86/81/dad16382ebbd3d0e0328776d8fd7ca94220e4fa0798d1dc5e7da48cb3201/rpds_py-0.30.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:68f19c879420aa08f61203801423f6cd5ac5f0ac4ac82a2368a9fcd6a9a075e0", size = 362099, upload-time = "2025-11-30T20:23:27.316Z" },
+ { url = "https://files.pythonhosted.org/packages/2b/60/19f7884db5d5603edf3c6bce35408f45ad3e97e10007df0e17dd57af18f8/rpds_py-0.30.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ec7c4490c672c1a0389d319b3a9cfcd098dcdc4783991553c332a15acf7249be", size = 353192, upload-time = "2025-11-30T20:23:29.151Z" },
+ { url = "https://files.pythonhosted.org/packages/bf/c4/76eb0e1e72d1a9c4703c69607cec123c29028bff28ce41588792417098ac/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f251c812357a3fed308d684a5079ddfb9d933860fc6de89f2b7ab00da481e65f", size = 384080, upload-time = "2025-11-30T20:23:30.785Z" },
+ { url = "https://files.pythonhosted.org/packages/72/87/87ea665e92f3298d1b26d78814721dc39ed8d2c74b86e83348d6b48a6f31/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ac98b175585ecf4c0348fd7b29c3864bda53b805c773cbf7bfdaffc8070c976f", size = 394841, upload-time = "2025-11-30T20:23:32.209Z" },
+ { url = "https://files.pythonhosted.org/packages/77/ad/7783a89ca0587c15dcbf139b4a8364a872a25f861bdb88ed99f9b0dec985/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3e62880792319dbeb7eb866547f2e35973289e7d5696c6e295476448f5b63c87", size = 516670, upload-time = "2025-11-30T20:23:33.742Z" },
+ { url = "https://files.pythonhosted.org/packages/5b/3c/2882bdac942bd2172f3da574eab16f309ae10a3925644e969536553cb4ee/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4e7fc54e0900ab35d041b0601431b0a0eb495f0851a0639b6ef90f7741b39a18", size = 408005, upload-time = "2025-11-30T20:23:35.253Z" },
+ { url = "https://files.pythonhosted.org/packages/ce/81/9a91c0111ce1758c92516a3e44776920b579d9a7c09b2b06b642d4de3f0f/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47e77dc9822d3ad616c3d5759ea5631a75e5809d5a28707744ef79d7a1bcfcad", size = 382112, upload-time = "2025-11-30T20:23:36.842Z" },
+ { url = "https://files.pythonhosted.org/packages/cf/8e/1da49d4a107027e5fbc64daeab96a0706361a2918da10cb41769244b805d/rpds_py-0.30.0-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:b4dc1a6ff022ff85ecafef7979a2c6eb423430e05f1165d6688234e62ba99a07", size = 399049, upload-time = "2025-11-30T20:23:38.343Z" },
+ { url = "https://files.pythonhosted.org/packages/df/5a/7ee239b1aa48a127570ec03becbb29c9d5a9eb092febbd1699d567cae859/rpds_py-0.30.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4559c972db3a360808309e06a74628b95eaccbf961c335c8fe0d590cf587456f", size = 415661, upload-time = "2025-11-30T20:23:40.263Z" },
+ { url = "https://files.pythonhosted.org/packages/70/ea/caa143cf6b772f823bc7929a45da1fa83569ee49b11d18d0ada7f5ee6fd6/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:0ed177ed9bded28f8deb6ab40c183cd1192aa0de40c12f38be4d59cd33cb5c65", size = 565606, upload-time = "2025-11-30T20:23:42.186Z" },
+ { url = "https://files.pythonhosted.org/packages/64/91/ac20ba2d69303f961ad8cf55bf7dbdb4763f627291ba3d0d7d67333cced9/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:ad1fa8db769b76ea911cb4e10f049d80bf518c104f15b3edb2371cc65375c46f", size = 591126, upload-time = "2025-11-30T20:23:44.086Z" },
+ { url = "https://files.pythonhosted.org/packages/21/20/7ff5f3c8b00c8a95f75985128c26ba44503fb35b8e0259d812766ea966c7/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:46e83c697b1f1c72b50e5ee5adb4353eef7406fb3f2043d64c33f20ad1c2fc53", size = 553371, upload-time = "2025-11-30T20:23:46.004Z" },
+ { url = "https://files.pythonhosted.org/packages/72/c7/81dadd7b27c8ee391c132a6b192111ca58d866577ce2d9b0ca157552cce0/rpds_py-0.30.0-cp314-cp314-win32.whl", hash = "sha256:ee454b2a007d57363c2dfd5b6ca4a5d7e2c518938f8ed3b706e37e5d470801ed", size = 215298, upload-time = "2025-11-30T20:23:47.696Z" },
+ { url = "https://files.pythonhosted.org/packages/3e/d2/1aaac33287e8cfb07aab2e6b8ac1deca62f6f65411344f1433c55e6f3eb8/rpds_py-0.30.0-cp314-cp314-win_amd64.whl", hash = "sha256:95f0802447ac2d10bcc69f6dc28fe95fdf17940367b21d34e34c737870758950", size = 228604, upload-time = "2025-11-30T20:23:49.501Z" },
+ { url = "https://files.pythonhosted.org/packages/e8/95/ab005315818cc519ad074cb7784dae60d939163108bd2b394e60dc7b5461/rpds_py-0.30.0-cp314-cp314-win_arm64.whl", hash = "sha256:613aa4771c99f03346e54c3f038e4cc574ac09a3ddfb0e8878487335e96dead6", size = 222391, upload-time = "2025-11-30T20:23:50.96Z" },
+ { url = "https://files.pythonhosted.org/packages/9e/68/154fe0194d83b973cdedcdcc88947a2752411165930182ae41d983dcefa6/rpds_py-0.30.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:7e6ecfcb62edfd632e56983964e6884851786443739dbfe3582947e87274f7cb", size = 364868, upload-time = "2025-11-30T20:23:52.494Z" },
+ { url = "https://files.pythonhosted.org/packages/83/69/8bbc8b07ec854d92a8b75668c24d2abcb1719ebf890f5604c61c9369a16f/rpds_py-0.30.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a1d0bc22a7cdc173fedebb73ef81e07faef93692b8c1ad3733b67e31e1b6e1b8", size = 353747, upload-time = "2025-11-30T20:23:54.036Z" },
+ { url = "https://files.pythonhosted.org/packages/ab/00/ba2e50183dbd9abcce9497fa5149c62b4ff3e22d338a30d690f9af970561/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d08f00679177226c4cb8c5265012eea897c8ca3b93f429e546600c971bcbae7", size = 383795, upload-time = "2025-11-30T20:23:55.556Z" },
+ { url = "https://files.pythonhosted.org/packages/05/6f/86f0272b84926bcb0e4c972262f54223e8ecc556b3224d281e6598fc9268/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5965af57d5848192c13534f90f9dd16464f3c37aaf166cc1da1cae1fd5a34898", size = 393330, upload-time = "2025-11-30T20:23:57.033Z" },
+ { url = "https://files.pythonhosted.org/packages/cb/e9/0e02bb2e6dc63d212641da45df2b0bf29699d01715913e0d0f017ee29438/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9a4e86e34e9ab6b667c27f3211ca48f73dba7cd3d90f8d5b11be56e5dbc3fb4e", size = 518194, upload-time = "2025-11-30T20:23:58.637Z" },
+ { url = "https://files.pythonhosted.org/packages/ee/ca/be7bca14cf21513bdf9c0606aba17d1f389ea2b6987035eb4f62bd923f25/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e5d3e6b26f2c785d65cc25ef1e5267ccbe1b069c5c21b8cc724efee290554419", size = 408340, upload-time = "2025-11-30T20:24:00.2Z" },
+ { url = "https://files.pythonhosted.org/packages/c2/c7/736e00ebf39ed81d75544c0da6ef7b0998f8201b369acf842f9a90dc8fce/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:626a7433c34566535b6e56a1b39a7b17ba961e97ce3b80ec62e6f1312c025551", size = 383765, upload-time = "2025-11-30T20:24:01.759Z" },
+ { url = "https://files.pythonhosted.org/packages/4a/3f/da50dfde9956aaf365c4adc9533b100008ed31aea635f2b8d7b627e25b49/rpds_py-0.30.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:acd7eb3f4471577b9b5a41baf02a978e8bdeb08b4b355273994f8b87032000a8", size = 396834, upload-time = "2025-11-30T20:24:03.687Z" },
+ { url = "https://files.pythonhosted.org/packages/4e/00/34bcc2565b6020eab2623349efbdec810676ad571995911f1abdae62a3a0/rpds_py-0.30.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fe5fa731a1fa8a0a56b0977413f8cacac1768dad38d16b3a296712709476fbd5", size = 415470, upload-time = "2025-11-30T20:24:05.232Z" },
+ { url = "https://files.pythonhosted.org/packages/8c/28/882e72b5b3e6f718d5453bd4d0d9cf8df36fddeb4ddbbab17869d5868616/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:74a3243a411126362712ee1524dfc90c650a503502f135d54d1b352bd01f2404", size = 565630, upload-time = "2025-11-30T20:24:06.878Z" },
+ { url = "https://files.pythonhosted.org/packages/3b/97/04a65539c17692de5b85c6e293520fd01317fd878ea1995f0367d4532fb1/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:3e8eeb0544f2eb0d2581774be4c3410356eba189529a6b3e36bbbf9696175856", size = 591148, upload-time = "2025-11-30T20:24:08.445Z" },
+ { url = "https://files.pythonhosted.org/packages/85/70/92482ccffb96f5441aab93e26c4d66489eb599efdcf96fad90c14bbfb976/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:dbd936cde57abfee19ab3213cf9c26be06d60750e60a8e4dd85d1ab12c8b1f40", size = 556030, upload-time = "2025-11-30T20:24:10.956Z" },
+ { url = "https://files.pythonhosted.org/packages/20/53/7c7e784abfa500a2b6b583b147ee4bb5a2b3747a9166bab52fec4b5b5e7d/rpds_py-0.30.0-cp314-cp314t-win32.whl", hash = "sha256:dc824125c72246d924f7f796b4f63c1e9dc810c7d9e2355864b3c3a73d59ade0", size = 211570, upload-time = "2025-11-30T20:24:12.735Z" },
+ { url = "https://files.pythonhosted.org/packages/d0/02/fa464cdfbe6b26e0600b62c528b72d8608f5cc49f96b8d6e38c95d60c676/rpds_py-0.30.0-cp314-cp314t-win_amd64.whl", hash = "sha256:27f4b0e92de5bfbc6f86e43959e6edd1425c33b5e69aab0984a72047f2bcf1e3", size = 226532, upload-time = "2025-11-30T20:24:14.634Z" },
+ { url = "https://files.pythonhosted.org/packages/69/71/3f34339ee70521864411f8b6992e7ab13ac30d8e4e3309e07c7361767d91/rpds_py-0.30.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:c2262bdba0ad4fc6fb5545660673925c2d2a5d9e2e0fb603aad545427be0fc58", size = 372292, upload-time = "2025-11-30T20:24:16.537Z" },
+ { url = "https://files.pythonhosted.org/packages/57/09/f183df9b8f2d66720d2ef71075c59f7e1b336bec7ee4c48f0a2b06857653/rpds_py-0.30.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:ee6af14263f25eedc3bb918a3c04245106a42dfd4f5c2285ea6f997b1fc3f89a", size = 362128, upload-time = "2025-11-30T20:24:18.086Z" },
+ { url = "https://files.pythonhosted.org/packages/7a/68/5c2594e937253457342e078f0cc1ded3dd7b2ad59afdbf2d354869110a02/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3adbb8179ce342d235c31ab8ec511e66c73faa27a47e076ccc92421add53e2bb", size = 391542, upload-time = "2025-11-30T20:24:20.092Z" },
+ { url = "https://files.pythonhosted.org/packages/49/5c/31ef1afd70b4b4fbdb2800249f34c57c64beb687495b10aec0365f53dfc4/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:250fa00e9543ac9b97ac258bd37367ff5256666122c2d0f2bc97577c60a1818c", size = 404004, upload-time = "2025-11-30T20:24:22.231Z" },
+ { url = "https://files.pythonhosted.org/packages/e3/63/0cfbea38d05756f3440ce6534d51a491d26176ac045e2707adc99bb6e60a/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9854cf4f488b3d57b9aaeb105f06d78e5529d3145b1e4a41750167e8c213c6d3", size = 527063, upload-time = "2025-11-30T20:24:24.302Z" },
+ { url = "https://files.pythonhosted.org/packages/42/e6/01e1f72a2456678b0f618fc9a1a13f882061690893c192fcad9f2926553a/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:993914b8e560023bc0a8bf742c5f303551992dcb85e247b1e5c7f4a7d145bda5", size = 413099, upload-time = "2025-11-30T20:24:25.916Z" },
+ { url = "https://files.pythonhosted.org/packages/b8/25/8df56677f209003dcbb180765520c544525e3ef21ea72279c98b9aa7c7fb/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58edca431fb9b29950807e301826586e5bbf24163677732429770a697ffe6738", size = 392177, upload-time = "2025-11-30T20:24:27.834Z" },
+ { url = "https://files.pythonhosted.org/packages/4a/b4/0a771378c5f16f8115f796d1f437950158679bcd2a7c68cf251cfb00ed5b/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_31_riscv64.whl", hash = "sha256:dea5b552272a944763b34394d04577cf0f9bd013207bc32323b5a89a53cf9c2f", size = 406015, upload-time = "2025-11-30T20:24:29.457Z" },
+ { url = "https://files.pythonhosted.org/packages/36/d8/456dbba0af75049dc6f63ff295a2f92766b9d521fa00de67a2bd6427d57a/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ba3af48635eb83d03f6c9735dfb21785303e73d22ad03d489e88adae6eab8877", size = 423736, upload-time = "2025-11-30T20:24:31.22Z" },
+ { url = "https://files.pythonhosted.org/packages/13/64/b4d76f227d5c45a7e0b796c674fd81b0a6c4fbd48dc29271857d8219571c/rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:dff13836529b921e22f15cb099751209a60009731a68519630a24d61f0b1b30a", size = 573981, upload-time = "2025-11-30T20:24:32.934Z" },
+ { url = "https://files.pythonhosted.org/packages/20/91/092bacadeda3edf92bf743cc96a7be133e13a39cdbfd7b5082e7ab638406/rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:1b151685b23929ab7beec71080a8889d4d6d9fa9a983d213f07121205d48e2c4", size = 599782, upload-time = "2025-11-30T20:24:35.169Z" },
+ { url = "https://files.pythonhosted.org/packages/d1/b7/b95708304cd49b7b6f82fdd039f1748b66ec2b21d6a45180910802f1abf1/rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:ac37f9f516c51e5753f27dfdef11a88330f04de2d564be3991384b2f3535d02e", size = 562191, upload-time = "2025-11-30T20:24:36.853Z" },
+]
+
+[[package]]
+name = "rpds-py"
+version = "2026.6.3"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version >= '3.15'",
+ "python_full_version >= '3.12' and python_full_version < '3.15'",
+ "python_full_version == '3.11.*'",
+]
+sdist = { url = "https://files.pythonhosted.org/packages/aa/2a/9618a122aeb2a169a28b03889a2995fe297588964333d4a7d67bdf46e147/rpds_py-2026.6.3.tar.gz", hash = "sha256:1cebd1337c242e4ec2293e541f712b2da849b29f48f0c293684b71c0632625d4", size = 64051, upload-time = "2026-06-30T07:17:53.009Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/94/1f/a2dca5ffdbf1d475ffc4e80e4d5d720ff3a00f691795910116960ee12511/rpds_py-2026.6.3-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:7b689145a1485c335569bd056464f3243a29af7ed3871c7be31ad624ba239bc7", size = 342174, upload-time = "2026-06-30T07:14:54.821Z" },
+ { url = "https://files.pythonhosted.org/packages/4d/dc/323d08583c0832911768663d1944f0107fcd4088704858d84b5e06d105a0/rpds_py-2026.6.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:db08f45aecde626498fb3df07bcf6d2ec040af42e859a4f5040d79c200342911", size = 345513, upload-time = "2026-06-30T07:14:56.515Z" },
+ { url = "https://files.pythonhosted.org/packages/0b/2a/e31989834d18d2f26ec1d2774c5b1eb3331df4ea8ada525175294c94b48a/rpds_py-2026.6.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:acc992ab27b15f852c76755eb2ab7dce86585ddadba6fa5946e58556088845b4", size = 373783, upload-time = "2026-06-30T07:14:57.736Z" },
+ { url = "https://files.pythonhosted.org/packages/87/fe/e80107ee3639585c9941c17d6a42cd65325022f656c023191fce78c324c8/rpds_py-2026.6.3-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7f88d653e7b3b779d71ae7454e20dcc9b6bae903f33c269db9f2be41bda3f261", size = 378316, upload-time = "2026-06-30T07:14:59.077Z" },
+ { url = "https://files.pythonhosted.org/packages/22/6f/81e3adf81acfb6fa694de2a6e4e7d8863121e3e0799e0a7725e6cf5679c4/rpds_py-2026.6.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e52655eaf81e32593abedaa4bfe33170c8cfedf3365ed9be6e11e07f148f0278", size = 499423, upload-time = "2026-06-30T07:15:00.488Z" },
+ { url = "https://files.pythonhosted.org/packages/2d/9a/41263969df0ce3d9af2a96d5005a288200af1989aed3354bfceb5fc0b21f/rpds_py-2026.6.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dfcc8b909769d19db55c7cc9541eb64b9b774b1057ffffb4f1048070475bb9f9", size = 386077, upload-time = "2026-06-30T07:15:01.911Z" },
+ { url = "https://files.pythonhosted.org/packages/5e/19/7e98f468bd50346faff5b10e5297374b443bfdddacc8e9fbc65984539597/rpds_py-2026.6.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c1255b302953c86a486b81d330d5ee1d5bd937691ce271b6be0ef0e299eaab7", size = 371315, upload-time = "2026-06-30T07:15:03.317Z" },
+ { url = "https://files.pythonhosted.org/packages/99/3c/2b973b4d371906a134b03decfea7f5d9835a2c6d263454392e15b64b5b18/rpds_py-2026.6.3-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:8d2294a31386bfa251d8c8a39472beee17db67d4f1a6eabea665d35c9a4461c3", size = 383502, upload-time = "2026-06-30T07:15:04.627Z" },
+ { url = "https://files.pythonhosted.org/packages/98/2a/12e2799500af0a307bca76b63361c51f9fe479223561489c29eea1f2ee41/rpds_py-2026.6.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f8f23ead891a3b762f35ab3b04623da7056545b48aa60d59957e6789914545da", size = 402673, upload-time = "2026-06-30T07:15:05.856Z" },
+ { url = "https://files.pythonhosted.org/packages/2d/e3/21e5872d165fe08be4f229e3d5ee9d90019c0bf0e5538de60dbd54009450/rpds_py-2026.6.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:421aba32367055614287a4292b6a17f1939c9452299f7a0209c117e990b646d4", size = 549964, upload-time = "2026-06-30T07:15:07.159Z" },
+ { url = "https://files.pythonhosted.org/packages/1a/d0/5ee0fe36844297de8123bee27bc12078c1a7416ad9f1b8a8ca18d6b0c0ac/rpds_py-2026.6.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:1e5822dfc2f0d4ab7e745eaa6d85945069329beeccef965af3f3bb26058fcab6", size = 615446, upload-time = "2026-06-30T07:15:08.531Z" },
+ { url = "https://files.pythonhosted.org/packages/b1/80/1ea5873cb683f2fbe5f21b23ea1f6d179ead19f3c5b249b7eb5dca568ef2/rpds_py-2026.6.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:83e35b57523816c8613fd0776b40cd8bb9f596b37ddd2692eb4a6bb5ab2f8c93", size = 576975, upload-time = "2026-06-30T07:15:09.97Z" },
+ { url = "https://files.pythonhosted.org/packages/c9/e1/90ef639217a5ddb15b7f4f61b1c33911fd044ad03c311bafdd2bcab85582/rpds_py-2026.6.3-cp311-cp311-win32.whl", hash = "sha256:de3eceba0b683bcbb1ab93da016d0270df1f9ae7be716b40214c5dafac6ea45a", size = 204453, upload-time = "2026-06-30T07:15:11.324Z" },
+ { url = "https://files.pythonhosted.org/packages/f2/b7/b7a1695d7af36f521fb11e80d6d3adbd744f73b921859bd3c2a2c0dc706f/rpds_py-2026.6.3-cp311-cp311-win_amd64.whl", hash = "sha256:2c54a076ca4d370980ab57bc0e31df57bbe8d41340436a90ef8b1219a3cbb127", size = 223219, upload-time = "2026-06-30T07:15:12.476Z" },
+ { url = "https://files.pythonhosted.org/packages/d7/a2/145afacf796e4506062825941176ad9445c2dcf2b3b6a1f13d3030a15e19/rpds_py-2026.6.3-cp311-cp311-win_arm64.whl", hash = "sha256:168c733a7112e071bb7a66460e667edfcff06c017a3c523f7a8a8e08d0140804", size = 219137, upload-time = "2026-06-30T07:15:13.631Z" },
+ { url = "https://files.pythonhosted.org/packages/5c/be/2e8974163072e7bab7df1a5acd54c4498e75e35d6d18b864d3a9d5dadc92/rpds_py-2026.6.3-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a0811d33247c3d6128a3001d763f2aa056bb3425204335400ac54f89eec3a0d0", size = 343691, upload-time = "2026-06-30T07:15:14.96Z" },
+ { url = "https://files.pythonhosted.org/packages/a4/73/319dfa745dd668efe89309141ded489126461fcecd2b8f3a3cda185129b6/rpds_py-2026.6.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:538949e262e46caa31ac01bdb3c1e8f642622922cacbabbae6a8445d9dc33eaf", size = 338542, upload-time = "2026-06-30T07:15:16.267Z" },
+ { url = "https://files.pythonhosted.org/packages/21/63/4239893be1c4d09b709b1a8f6be4188f0870084ff547f46606b8a75f1b03/rpds_py-2026.6.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55927d532399c2c646100ff7feb48eaa940ad70f42cd68e1328f3ded9f81ca24", size = 368180, upload-time = "2026-06-30T07:15:17.62Z" },
+ { url = "https://files.pythonhosted.org/packages/1c/ca/9c5de382225234ceb37b1844ebdb140db12b2a278bb9efe2fcd19f6c82ce/rpds_py-2026.6.3-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f56f1695bc5c0871cbc33dc0130fcf503aab0c57dcc5a6700a4f49eba4f2652e", size = 375067, upload-time = "2026-06-30T07:15:18.952Z" },
+ { url = "https://files.pythonhosted.org/packages/87/dc/863f69d1bf04ade34b7fe0d59b9fdf6f0135fe2d7cbca74f1d665589559d/rpds_py-2026.6.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:270b293dae9058fc9fcedab50f13cebf46fb8ed1d1d54e0521a9da5d6b211975", size = 490509, upload-time = "2026-06-30T07:15:20.434Z" },
+ { url = "https://files.pythonhosted.org/packages/ce/ef/eac16a12048b45ec7c7fa94f2be3438a5f26bf9cc8580b18a1cfd609b7f6/rpds_py-2026.6.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:127565fead0a10943b282957bd5447804ff3160ad79f2ad2635e6d249e380680", size = 382754, upload-time = "2026-06-30T07:15:21.831Z" },
+ { url = "https://files.pythonhosted.org/packages/04/8f/d2f3f532616be4d06c316ef119683e832bd3d41e112bf3a88f4151c95b17/rpds_py-2026.6.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ecabd69db66de867690f9797f2f8fa27ba501bbc24540cbdbdc649cd15888ba6", size = 366189, upload-time = "2026-06-30T07:15:23.371Z" },
+ { url = "https://files.pythonhosted.org/packages/e3/29/41a7b0e98a4b44cd676ab7598419623373eb43b20be68c084935c1a8cf88/rpds_py-2026.6.3-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:58eadac9cd119677b60e1cf8ac4052f35949d71b8a9e5556efccbe82533cf22a", size = 377750, upload-time = "2026-06-30T07:15:24.659Z" },
+ { url = "https://files.pythonhosted.org/packages/2e/05/ecda0bec46f9a1565090bcdc941d023f6a25aff85fda28f89f8d19878152/rpds_py-2026.6.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7491ee23305ac3eb59e492b6945881f5cd77a6f731061a3f25b77fd40f9e99a4", size = 395576, upload-time = "2026-06-30T07:15:25.987Z" },
+ { url = "https://files.pythonhosted.org/packages/68/a8/6ed52f03ee6cb854ce78785cc9a9a672eb880e83fd7224d471f667d151f1/rpds_py-2026.6.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2c99f7e8ccb3dd6e3e4bfeac657a7b208c9bac8075f4b078c02d7404c34107fa", size = 543807, upload-time = "2026-06-30T07:15:27.356Z" },
+ { url = "https://files.pythonhosted.org/packages/8f/d6/156c0d3eea27ba09b92562ba2364ba124c0a061b199e17eac637cd25a5e2/rpds_py-2026.6.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:62698275682bf121181861295c9181e789030a2d516071f5b8f3c23c170cd0fc", size = 611187, upload-time = "2026-06-30T07:15:28.931Z" },
+ { url = "https://files.pythonhosted.org/packages/f1/31/774212ed989c62f7f310220089f9b0a3fb8f40f5443d1727abd5d9f52bc9/rpds_py-2026.6.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a214c993455f99a89aaeadc9b21241900037adc9d97203e374d75513c5911822", size = 573030, upload-time = "2026-06-30T07:15:30.553Z" },
+ { url = "https://files.pythonhosted.org/packages/c9/50/22f73127a41f1ce4f87fe39aadfb9a126345801c274aa93ae88456249327/rpds_py-2026.6.3-cp312-cp312-win32.whl", hash = "sha256:501f9f04a588d6a09179368c57071301445191767c64e4b52a6aa9871f1ef5ed", size = 202185, upload-time = "2026-06-30T07:15:32.027Z" },
+ { url = "https://files.pythonhosted.org/packages/04/3a/f0ee4d4dde9d3b69dedf1b5f74e7a40017046d55052d173e418c6a94f960/rpds_py-2026.6.3-cp312-cp312-win_amd64.whl", hash = "sha256:2c958bf94822e9290a40aaf2a822d4bc5c88099093e3948ad6c571eca9272e5f", size = 220394, upload-time = "2026-06-30T07:15:33.359Z" },
+ { url = "https://files.pythonhosted.org/packages/f3/83/3382fe37f809b59f02aac04dbc4e765b480b46ee0227ed516e3bdc4d3dfc/rpds_py-2026.6.3-cp312-cp312-win_arm64.whl", hash = "sha256:22bffe6042b9bcb0822bcd1955ec00e245daf17b4344e4ed8e9551b976b63e96", size = 215753, upload-time = "2026-06-30T07:15:34.778Z" },
+ { url = "https://files.pythonhosted.org/packages/a4/9e/b818ee580026ec578138e961027a68820c40afeb1ec8f6819b54fb99e196/rpds_py-2026.6.3-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:3cfe765c1da0072636ca06628261e0ea05688e160d5c8a03e0217c3854037223", size = 343012, upload-time = "2026-06-30T07:15:36.005Z" },
+ { url = "https://files.pythonhosted.org/packages/f3/6b/686d9dc4359a8f163cfbbf89ee0b4e586431de22fe8248edb63a8cf50d49/rpds_py-2026.6.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f4d78253f6996be4901669ad25319f842f740eccf4d58e3c7f3dd39e6dde1d8f", size = 338203, upload-time = "2026-06-30T07:15:37.462Z" },
+ { url = "https://files.pythonhosted.org/packages/9e/9b/069aa329940f8207615e091f5eedbbd40e1e15eac68a0790fd05ccdf796c/rpds_py-2026.6.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54f45a148e28767bf343d33a684693c70e451c6f4c0e9904709a723fafbdfc1f", size = 367984, upload-time = "2026-06-30T07:15:39.008Z" },
+ { url = "https://files.pythonhosted.org/packages/14/db/34c203e4becff3703e4d3bc121842c00b8689197f398161203a880052f4e/rpds_py-2026.6.3-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:842e7b070435622248c7a2c44ae53fa1440e073cc3023bc919fed570884097a7", size = 374815, upload-time = "2026-06-30T07:15:40.253Z" },
+ { url = "https://files.pythonhosted.org/packages/ee/7d/8071067d2cc453d916ad836e828c943f575e8a44612537759002a1e07381/rpds_py-2026.6.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8020133a74bd81b4572dd8e4be028a6b1ebcd70e6726edc3918008c08bee6ee6", size = 490545, upload-time = "2026-06-30T07:15:41.729Z" },
+ { url = "https://files.pythonhosted.org/packages/a3/42/da06c5aa8f0484ff07f270787434204d9f4535e2f8c3b51ed402267e63c3/rpds_py-2026.6.3-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cdc7e35386f3847df728fbcb5e887e2d79c19e2fa1eba9e51b6621d23e3243af", size = 382828, upload-time = "2026-06-30T07:15:43.327Z" },
+ { url = "https://files.pythonhosted.org/packages/57/d7/fe978efc2ae50abe48eb7464668ea99f53c010c60aeebb7b35ad27f23661/rpds_py-2026.6.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:acac386b453c2516111b50985d60ce46e7fadb5ea71ae7b25f4c946935bf27cf", size = 365678, upload-time = "2026-06-30T07:15:44.992Z" },
+ { url = "https://files.pythonhosted.org/packages/69/9d/1d8922e1990b2a6eb532b6ff53d3e73d2b3bbffc84116c75826bee73dfc6/rpds_py-2026.6.3-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:425560c6fa0415f27261727bb20bd097568485e5eb0c121f1949417d1c516885", size = 377811, upload-time = "2026-06-30T07:15:46.523Z" },
+ { url = "https://files.pythonhosted.org/packages/b1/3d/198dceafb4fb034a6a47347e1b0735d34e0bd4a50be4e898d408ee66cb14/rpds_py-2026.6.3-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a550fb4950a06dde3beb4721f5ad4b25bf4513784665b0a8522c792e2bd822a4", size = 395382, upload-time = "2026-06-30T07:15:47.955Z" },
+ { url = "https://files.pythonhosted.org/packages/1f/f1/13968e49655d40b6b19d8b9140296bbc6f1d86b3f0f6c346cf9f1adddf4b/rpds_py-2026.6.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4f4bca01b63096f606e095734dd56e74e175f94cfbf24ff3d63281cec61f7bb7", size = 543832, upload-time = "2026-06-30T07:15:49.33Z" },
+ { url = "https://files.pythonhosted.org/packages/ac/ab/289bcb1b90bd3e40a2900c561fa0e2087345ecbb094f0b870f2345142b7c/rpds_py-2026.6.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ccffae9a092a00deb7efd545fe5e2c33c33b88e7c054337e9a74c179347d0b7d", size = 611011, upload-time = "2026-06-30T07:15:50.847Z" },
+ { url = "https://files.pythonhosted.org/packages/1e/16/5043105e679436ccfbc8e5e0dd2d663ed18a8b8113515fd06a5e5d77c83e/rpds_py-2026.6.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1cf01971c4f2c5553b772a542e4aaf191789cd331bc2cd4ff0e6e65ba49e1e97", size = 572431, upload-time = "2026-06-30T07:15:52.394Z" },
+ { url = "https://files.pythonhosted.org/packages/85/ed/adab103321c0a6565d5ae1c2998349bc3ee175b82ccc5ae8fc04cc413075/rpds_py-2026.6.3-cp313-cp313-win32.whl", hash = "sha256:8c3d1e9c15b9d51ca0391e13da1a25a0a4df3c58a37c9dc368e0736cf7f69df0", size = 201710, upload-time = "2026-06-30T07:15:53.894Z" },
+ { url = "https://files.pythonhosted.org/packages/7b/ed/a03b09668e74e5dabbf2e211f6468e1820c0552f7b0500082da31841bf7b/rpds_py-2026.6.3-cp313-cp313-win_amd64.whl", hash = "sha256:9250a9a0a6fd4648b3f868da8d91a4c52b5811a62df58e753d50ae4454a36f80", size = 219454, upload-time = "2026-06-30T07:15:55.25Z" },
+ { url = "https://files.pythonhosted.org/packages/27/17/b8642c12930b71bc2b25831f6708ccf0f75abcd11883932ec9ce54ba3a78/rpds_py-2026.6.3-cp313-cp313-win_arm64.whl", hash = "sha256:900a67df3fd1660b035a4761c4ce73c382ea6b35f90f9863c36c6fd8bf8b09bb", size = 215063, upload-time = "2026-06-30T07:15:56.573Z" },
+ { url = "https://files.pythonhosted.org/packages/b6/36/7fbe9dcdaf857fb3f63c2a2284b62492d95f5e8334e947e5fb6e7f68c9be/rpds_py-2026.6.3-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:931908d9fc855d8f74783377822be318edb6dcb19e47169dc038f9a1bf60b06e", size = 344510, upload-time = "2026-06-30T07:15:57.921Z" },
+ { url = "https://files.pythonhosted.org/packages/ba/54/f785cc3d3f60839ca57a5af4927a9f347b07b2799c373fc20f7949f87c7e/rpds_py-2026.6.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:d7469697dce35be237db177d42e2a2ee26e6dcc5fc052078a6fefabd288c6edd", size = 339495, upload-time = "2026-06-30T07:15:59.238Z" },
+ { url = "https://files.pythonhosted.org/packages/63/ef/d4cdaf309e6b095b43597103cf8c0b951d6cca2acce68c474f75ec12e0c7/rpds_py-2026.6.3-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bcfbcf66006befb9fd2aeaa9e01feaf881b4dc330a02ba07d2322b1c11be7b5d", size = 369454, upload-time = "2026-06-30T07:16:01.021Z" },
+ { url = "https://files.pythonhosted.org/packages/96/4a/9559a68b7ee15db09d7981212e8c2e219d2a1d6d4faa0391d813c3496a36/rpds_py-2026.6.3-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:847927daf4cffbd4e90e42bc890069897101edd015f956cb8721b3473372edda", size = 374583, upload-time = "2026-06-30T07:16:02.287Z" },
+ { url = "https://files.pythonhosted.org/packages/ef/75/8964aa7d2c6e8ac43eba8eb6e6b0fdda1f46d39f2fc3e6aa9f2cb17f485d/rpds_py-2026.6.3-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aca6c1ef08a82bfe327cc156da694660f599923e2e6665b6d81c9c2d0ac9ffc8", size = 492919, upload-time = "2026-06-30T07:16:03.723Z" },
+ { url = "https://files.pythonhosted.org/packages/8f/97/6908094ac804115e65aedfd90f1b5fee4eebebd3f6c4cfc5419939267565/rpds_py-2026.6.3-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ae50181a047c871561212bb97f7932a2d45fb53e947bd9b57ebad85b529cbc53", size = 383725, upload-time = "2026-06-30T07:16:05.305Z" },
+ { url = "https://files.pythonhosted.org/packages/d1/9c/0d1fdc2e7aba23e290d603bc494e97bd205bae262ce33c6b32a69768ed5e/rpds_py-2026.6.3-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc319e5a1de4b6913aac94bf6a2f9e847371e0a140a43dd4991db1a09bc2d504", size = 367255, upload-time = "2026-06-30T07:16:07.086Z" },
+ { url = "https://files.pythonhosted.org/packages/c4/fe/f0209ca4a9ed074bc8acb44dfd0e81c3122e94c9689f5645b7973a866719/rpds_py-2026.6.3-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:e4316bf32babbed84e691e352faf967ce2f0f024174a8643c37c94a1080374fc", size = 379060, upload-time = "2026-06-30T07:16:08.525Z" },
+ { url = "https://files.pythonhosted.org/packages/c6/8d/f1cc54c616b9d8897de8738aac148d20afca93f68187475fe194d09a71b9/rpds_py-2026.6.3-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8c6e5a2f750cc71c3e3b11d71661f21d6f9bc6cebc6564b1466417a1ec03ec77", size = 395960, upload-time = "2026-06-30T07:16:09.989Z" },
+ { url = "https://files.pythonhosted.org/packages/fb/04/aafff00f73aeca2945f734f1d483c64ab8f472d0864ab02377fd8e89c3b2/rpds_py-2026.6.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:4470ce197d4090875cf6affbf1f853338387428df97c4fb7b7106317b8214698", size = 545356, upload-time = "2026-06-30T07:16:11.816Z" },
+ { url = "https://files.pythonhosted.org/packages/fd/cc/e229663b9e4ddac5a4acbe9085dd80a71af2a5d356b8b39d6bff233f24b0/rpds_py-2026.6.3-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:ea964164cc9afa72d4d9b23cc28dafae93693c0a53e0b42acbff15b22c3f9ddd", size = 612319, upload-time = "2026-06-30T07:16:13.586Z" },
+ { url = "https://files.pythonhosted.org/packages/e3/7a/8a0e6d3e6cd066af108b71b43122c3fe158dd9eb86acac626593a2582eb1/rpds_py-2026.6.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:639c8929aa0afe81be836b04de888460d6bed38b9c54cfc18da8f6bfabf5af5d", size = 573508, upload-time = "2026-06-30T07:16:15.23Z" },
+ { url = "https://files.pythonhosted.org/packages/87/03/2a69ab618a789cf6cf85c86bb844c62d090e700ab1a2aa676b3741b6c516/rpds_py-2026.6.3-cp314-cp314-win32.whl", hash = "sha256:882076c00c0a608b131187055ddc5ae29f2e7eaf870d6168980420d58528a5c8", size = 202504, upload-time = "2026-06-30T07:16:16.893Z" },
+ { url = "https://files.pythonhosted.org/packages/85/62/a3892ba945f4e24c78f352e5de3c7620d8479f73f211406a97263d13c7d2/rpds_py-2026.6.3-cp314-cp314-win_amd64.whl", hash = "sha256:0be972be84cfcaf46c8c6edf690ca0f154ac17babf1f6a955a51579b34ad2dc5", size = 220380, upload-time = "2026-06-30T07:16:18.108Z" },
+ { url = "https://files.pythonhosted.org/packages/3d/e7/c2bd44dc831931815ad11ebb5f430b5a0a4d3caa9de837107876c30c3432/rpds_py-2026.6.3-cp314-cp314-win_arm64.whl", hash = "sha256:2a9c6f195058cb45335e8cc3802745c603d716eb96bc9625950c1aac71c0c703", size = 215976, upload-time = "2026-06-30T07:16:19.654Z" },
+ { url = "https://files.pythonhosted.org/packages/79/9c/fff7b74bce9a091ec9a012a03f9ff5f69364eaf9451060dfc4486da2ffdd/rpds_py-2026.6.3-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:f90938e92afda60266da758ee7d363447f7f0138c9559f9e1811629580582d90", size = 346840, upload-time = "2026-06-30T07:16:21.268Z" },
+ { url = "https://files.pythonhosted.org/packages/e9/44/77bcb1168b33704908295533d27f10eb811e9e3e193e8993dc99572211d3/rpds_py-2026.6.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ec829541c45bca16e61c7ae50c20501f213605beb75d1aba91a6ee37fbbb56a4", size = 340282, upload-time = "2026-06-30T07:16:22.875Z" },
+ { url = "https://files.pythonhosted.org/packages/87/3c/7a9081c7c9e645b39efe19e4ffbeccd80add246327cd9b888aecffd72317/rpds_py-2026.6.3-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afd70d95892096cdb26f15a00c45907b17817577aa8d1c76b2dcc2788391f9e9", size = 370403, upload-time = "2026-06-30T07:16:24.415Z" },
+ { url = "https://files.pythonhosted.org/packages/f7/69/af47021eb7dad6ff3396cb001c08f0f3c4d06c20253f75be6421a59fe6b7/rpds_py-2026.6.3-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:29dfa0533a5d4c94d4dfa1b694fcb56c9c63aad8330ffdd816fd225d0a7a162f", size = 376055, upload-time = "2026-06-30T07:16:26.111Z" },
+ { url = "https://files.pythonhosted.org/packages/81/fc/a3bcf517084396a6dd258c592567a3c011ba4557f2fde23dceaf26e74f2e/rpds_py-2026.6.3-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:af05d726809bff6b141be124d4c7ce998f9c9c7f30edb1f46c07aa103d540b41", size = 494419, upload-time = "2026-06-30T07:16:27.596Z" },
+ { url = "https://files.pythonhosted.org/packages/c9/eb/13d529d1788135425c7bf207f8463458ca5d92e43f3f701365b83e9dffc1/rpds_py-2026.6.3-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9826217f048f620d9a712672818bf231442c1b35d96b227a07eabd11b4bb6945", size = 384848, upload-time = "2026-06-30T07:16:29.183Z" },
+ { url = "https://files.pythonhosted.org/packages/8e/f4/b7ac49f30013aba8f7b9566b1dd07e81de95e708c1374b7bacc5b9bc5c9c/rpds_py-2026.6.3-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:536bceea4fa4acf7e1c61da2b5786304367c816c8895be71b8f537c480b0ea1f", size = 371369, upload-time = "2026-06-30T07:16:30.912Z" },
+ { url = "https://files.pythonhosted.org/packages/31/86/6260bafa622f788b07ddec0e52d810305c8b9b0b8c27f58a2ab04bf62b4f/rpds_py-2026.6.3-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:bc0011654b91cc4fb2ae701bec0a0ba1e552c0714247fa7af6c59e0ccfa3a4e1", size = 379673, upload-time = "2026-06-30T07:16:32.486Z" },
+ { url = "https://files.pythonhosted.org/packages/19/c3/03f1ee79a047b48daeca157c89a18509cde22b6b951d642b9b0af1be660a/rpds_py-2026.6.3-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:539d75de9e0d536c84ff18dfeb805398e58227001ce09231a26a08b9aed1ee0e", size = 397500, upload-time = "2026-06-30T07:16:34.471Z" },
+ { url = "https://files.pythonhosted.org/packages/f0/95/8ed0cd8c377dca12aea498f119fe639fc474d1461545c39d2b5872eb1c0f/rpds_py-2026.6.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:166cf54d9f44fc6ceb53c7860258dde44a81406646de79f8ed3234fca3b6e538", size = 545978, upload-time = "2026-06-30T07:16:36.45Z" },
+ { url = "https://files.pythonhosted.org/packages/d3/f2/0eb57f0eaa83f8fc152a7e03de968ab77e1f00732bebc892b190c6eebde7/rpds_py-2026.6.3-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:d34c20167764fbcf927194d532dd7e0c56772f0a5f943fa5ef9e9afbba8fb9db", size = 613350, upload-time = "2026-06-30T07:16:38.213Z" },
+ { url = "https://files.pythonhosted.org/packages/5b/de/e0674bdbc3ef7634989b3f854c3f34bc1f587d36e5bfdc5c378d57034619/rpds_py-2026.6.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ea7bb13b7c9a29791f87a0387ba7d3ad3a6d783d827e4d3f27b40a0ff44495e2", size = 576486, upload-time = "2026-06-30T07:16:39.797Z" },
+ { url = "https://files.pythonhosted.org/packages/f2/f6/21101359743cd136ada781e8210a85769578422ba460672eea0e29739200/rpds_py-2026.6.3-cp314-cp314t-win32.whl", hash = "sha256:6de4744d05bd1aa1be4ed7ea1189e3979196808008113bbbf899a460966b925e", size = 201068, upload-time = "2026-06-30T07:16:41.316Z" },
+ { url = "https://files.pythonhosted.org/packages/a6/b2/9574d4d44f7760c2aa32d92a0a4f41698e33f5b204a0bf5c9758f52c79d5/rpds_py-2026.6.3-cp314-cp314t-win_amd64.whl", hash = "sha256:c7b9a2f8f4d8e90af72571d3d495deebdd7e3c75451f5b41719aee166e940fc2", size = 220600, upload-time = "2026-06-30T07:16:43.091Z" },
+ { url = "https://files.pythonhosted.org/packages/08/ae/f23a2697e6ee6340a578b0f136be6483657bef0c6f9497b752bb5c0964bb/rpds_py-2026.6.3-cp315-cp315-macosx_10_12_x86_64.whl", hash = "sha256:e059c5dde6452b44424bd1834557556c226b57781dee1227af23518459722b13", size = 344726, upload-time = "2026-06-30T07:16:44.5Z" },
+ { url = "https://files.pythonhosted.org/packages/c3/63/e7b3a1a5358dd32c930a1062d8e15b67fd6e8922e81df9e91706d66ee5c8/rpds_py-2026.6.3-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:2f7c26fbc5acd2522b95d4177fe4710ffd8e9b20529e703ffbf8db4d93903f05", size = 339587, upload-time = "2026-06-30T07:16:46.255Z" },
+ { url = "https://files.pythonhosted.org/packages/ec/64/10a85681916ca55fffb91b0a211f84e34297c109243484dd6394660a8a7c/rpds_py-2026.6.3-cp315-cp315-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a3086b538543802f84c843911242db20447de00d8752dd0efc936dbcf02218ba", size = 369585, upload-time = "2026-06-30T07:16:48.101Z" },
+ { url = "https://files.pythonhosted.org/packages/76/c2/baf95c7c38823e12ba34407c5f5767a89e5cf2233895e56f608167ae9493/rpds_py-2026.6.3-cp315-cp315-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8f2e5c5ee828d42cb11760761c0af6507927bec42d0ad5458f97c9203b054617", size = 375479, upload-time = "2026-06-30T07:16:49.93Z" },
+ { url = "https://files.pythonhosted.org/packages/6a/94/0aad06c72d65101e11d33528d438cda99a39ce0da99466e156158f2541d3/rpds_py-2026.6.3-cp315-cp315-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ed0c1e5d10cdc7135537988c74a0188da68e2f3c30813ba3744ab1e42e0480f9", size = 492418, upload-time = "2026-06-30T07:16:51.641Z" },
+ { url = "https://files.pythonhosted.org/packages/b5/17/de3f5a479a1f056535d7489819639d8cd591ea6281d700390b43b1abd745/rpds_py-2026.6.3-cp315-cp315-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8c2642a7603ec0b16ed77da4555db3b4b472341904873788327c0b0d7b95f1bb", size = 384123, upload-time = "2026-06-30T07:16:53.622Z" },
+ { url = "https://files.pythonhosted.org/packages/46/7d/bf09bd1b145bb2671c03e1e6d1ab8651858d90d8c7dfeadd85a37a934fd8/rpds_py-2026.6.3-cp315-cp315-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8e4320744c1ffdd95a603def63344bfab2d33edeab301c5007e7de9f9f5b3885", size = 367351, upload-time = "2026-06-30T07:16:55.241Z" },
+ { url = "https://files.pythonhosted.org/packages/a3/ea/1bb734f314b8be319149ddee80b18bd41372bdcfbdf88d28131c0cd37719/rpds_py-2026.6.3-cp315-cp315-manylinux_2_31_riscv64.whl", hash = "sha256:a9f4645593036b81bbdb36b9c8e0ea0d1c3fee968c4d59db0344c14087ef143a", size = 378827, upload-time = "2026-06-30T07:16:56.841Z" },
+ { url = "https://files.pythonhosted.org/packages/4b/93/d9611e5b25e26df9a3649813ed66193ace9347a7c7fc4ab7cf70e94851c0/rpds_py-2026.6.3-cp315-cp315-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e55d236be29255554da47abe5c577637db7c24a02b8b46f0ca9524c855801868", size = 395966, upload-time = "2026-06-30T07:16:58.557Z" },
+ { url = "https://files.pythonhosted.org/packages/c3/cb/99d77e16e5534ae1d90629bbe419ba6ee170833a6a85e3aa1cc41726fbbc/rpds_py-2026.6.3-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:24e9c5386e16669b674a69c156c8eeefcb578f3b3397b713b08e6d60f3c7b187", size = 545680, upload-time = "2026-06-30T07:17:00.164Z" },
+ { url = "https://files.pythonhosted.org/packages/59/15/11a29755f790cef7a2f755e8e14f4f0c33f39489e1893a632a2eee59672b/rpds_py-2026.6.3-cp315-cp315-musllinux_1_2_i686.whl", hash = "sha256:c60924535c75f1566b6eb75b5c31a48a43fef04fa2d0d201acbad8a9969c6107", size = 611853, upload-time = "2026-06-30T07:17:01.962Z" },
+ { url = "https://files.pythonhosted.org/packages/68/86/0c27547e21644da938fb530f7e1a8148dd24d02db07e7a5f2567a17ce710/rpds_py-2026.6.3-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:38a2fea2787428f811719ceb9114cb78964a3138838320c29ac39526c79c16ba", size = 573715, upload-time = "2026-06-30T07:17:03.693Z" },
+ { url = "https://files.pythonhosted.org/packages/29/71/4d8fcf700931815594bce892255bbd973b94efaf0fc1932b0590df18d886/rpds_py-2026.6.3-cp315-cp315-win32.whl", hash = "sha256:d483fe17f01ad64b7bf7cc38fcefff1ca9fb83f8c2b2542b68f97ffe0611b369", size = 202864, upload-time = "2026-06-30T07:17:05.746Z" },
+ { url = "https://files.pythonhosted.org/packages/eb/62/b577562de0edbb55b2be85ce5fd09c33e386b9b13eee09833af4240fd5c4/rpds_py-2026.6.3-cp315-cp315-win_amd64.whl", hash = "sha256:67e3a721ffc5d8d2210d3671872298c4a84e4b8035cfe42ffd7cde35d772b146", size = 220430, upload-time = "2026-06-30T07:17:07.471Z" },
+ { url = "https://files.pythonhosted.org/packages/c8/95/d6d0b2509825141eef60669a5739eec88dbc6a48053d6c92993a5704defe/rpds_py-2026.6.3-cp315-cp315-win_arm64.whl", hash = "sha256:6e84adbcf4bf841aed8116a8264b9f50b4cb3e7bd89b516122e616ac56ca269e", size = 215877, upload-time = "2026-06-30T07:17:09.008Z" },
+ { url = "https://files.pythonhosted.org/packages/b7/bf/f3ea278f0afd615c1d0f19cb69043a41526e2bb600c2b536eb192218eb27/rpds_py-2026.6.3-cp315-cp315t-macosx_10_12_x86_64.whl", hash = "sha256:ae6dd8f10bd17aad820876d24caec9efdafd80a318d16c0a48edb5e136902c6b", size = 346933, upload-time = "2026-06-30T07:17:10.762Z" },
+ { url = "https://files.pythonhosted.org/packages/9d/29/9907bdf1c5346763cf10b7f6852aad86652168c259def904cbe0082c5864/rpds_py-2026.6.3-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:bdbd97738551fca3917c1bd7188bec1920bb520104f28e7e1007f9ceb17b7690", size = 340274, upload-time = "2026-06-30T07:17:12.266Z" },
+ { url = "https://files.pythonhosted.org/packages/6f/2c/8e03767b5778ef25cebf74a7a91a2c3806f8eced4c92cb7406bbe060756d/rpds_py-2026.6.3-cp315-cp315t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b95977e7211527ab0ba576e286d023389fbeeb32a6b7b771665d333c60e5342", size = 370763, upload-time = "2026-06-30T07:17:14.107Z" },
+ { url = "https://files.pythonhosted.org/packages/2e/e1/df2a7e1ba2efd796af26194250b8d42c821b46592311595162af9ef0528d/rpds_py-2026.6.3-cp315-cp315t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d15fde0e6fb0d88a60d221204873743e5d9f0b7d29165e62cd86d0413ad74ba6", size = 376467, upload-time = "2026-06-30T07:17:15.76Z" },
+ { url = "https://files.pythonhosted.org/packages/6b/de/8a0814d1946af29cb068fb259aa8622f856df1d0bab58429448726b537f5/rpds_py-2026.6.3-cp315-cp315t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a136d453475ac0fcbda502ef1e6504bd28d6d904700915d278deeab0d00fe140", size = 496689, upload-time = "2026-06-30T07:17:17.308Z" },
+ { url = "https://files.pythonhosted.org/packages/df/f3/f19e0c852ba13694f5a79f3b719331051573cb5693feacf8a88ffffc3a71/rpds_py-2026.6.3-cp315-cp315t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f826877d462181e5eb1c26a0026b8d0cab05d99844ecb6d8bf3627a2ca0c0442", size = 385340, upload-time = "2026-06-30T07:17:18.928Z" },
+ { url = "https://files.pythonhosted.org/packages/e2/ae/7ec3a9d2d4351f99e37bcb06b6b6f954512646bfdbf9742e1de727865daf/rpds_py-2026.6.3-cp315-cp315t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:79486287de1730dbaff3dbd124d0ca4d2ef7f9d29bf2544f1f93c09b5bcbbd12", size = 372179, upload-time = "2026-06-30T07:17:20.539Z" },
+ { url = "https://files.pythonhosted.org/packages/d3/ac/9cee911dff2aaa9a5a8354f6610bf2e6a616de9197c5fff4f54f82585f1e/rpds_py-2026.6.3-cp315-cp315t-manylinux_2_31_riscv64.whl", hash = "sha256:808345f53cb952433ca2816f1604ff3515608a81784954f38d4452acfe8e61d5", size = 379993, upload-time = "2026-06-30T07:17:22.212Z" },
+ { url = "https://files.pythonhosted.org/packages/83/6b/7c2a07ba88d1e9a936612f7a5d067467ed03d971d5a06f7d309dff044a7e/rpds_py-2026.6.3-cp315-cp315t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1967debc37f64f2c4dc90a7f563aec558b471966e12adcac4e1c4240496b6ebf", size = 398909, upload-time = "2026-06-30T07:17:23.66Z" },
+ { url = "https://files.pythonhosted.org/packages/97/0b/776ffcb66783637b0031f6d58d6fb55913c8b5abf00aeecd46bf933fb477/rpds_py-2026.6.3-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:f0840b5b17057f7fd918b76183a4b5a0635f43e14eb2ce60dce1d4ee4707ea00", size = 546584, upload-time = "2026-06-30T07:17:25.264Z" },
+ { url = "https://files.pythonhosted.org/packages/55/33/ba3bc04d7092bd553c9b2b195624992d2cc4f3de1f380b7b93cbee67bd79/rpds_py-2026.6.3-cp315-cp315t-musllinux_1_2_i686.whl", hash = "sha256:faa679d19a6696fd54259ad321251ad77a13e70e03dd834daa762a44fb6196ef", size = 614357, upload-time = "2026-06-30T07:17:26.888Z" },
+ { url = "https://files.pythonhosted.org/packages/8b/71/14edf065f04630b1a8472f7653cad03f6c478bcf95ea0e6aed55451e33ea/rpds_py-2026.6.3-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:23a439f31ccbeff1574e24889128821d1f7917470e830cf6544dced1c662262a", size = 576533, upload-time = "2026-06-30T07:17:28.546Z" },
+ { url = "https://files.pythonhosted.org/packages/ba/76/65002b08596c389105720a8c0d22298b8dc25a4baf89b2ce431343c8b1de/rpds_py-2026.6.3-cp315-cp315t-win32.whl", hash = "sha256:913ca42ccad3f8cc6e292b587ae8ae49c8c823e5dce51a736252fc7c7cdfa577", size = 201204, upload-time = "2026-06-30T07:17:30.193Z" },
+ { url = "https://files.pythonhosted.org/packages/8c/97/d855d6b3c322d1f27e26f5241c42016b56cf01377ea8ed348285f54652f0/rpds_py-2026.6.3-cp315-cp315t-win_amd64.whl", hash = "sha256:ae3d4fe8c0b9213624fdce7279d70e3b148b682ca20719ebd193a23ebfa47324", size = 220719, upload-time = "2026-06-30T07:17:31.788Z" },
+ { url = "https://files.pythonhosted.org/packages/b4/9c/f0d19ac587fd0e4ab6b72cda355e9c5a6166b01ef7e064e437aef8eb9fef/rpds_py-2026.6.3-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:4cf2d36a2357e4d07bb5a4f98801265327b48256867816cfd2ceb001e9754a8f", size = 349791, upload-time = "2026-06-30T07:17:33.315Z" },
+ { url = "https://files.pythonhosted.org/packages/38/c7/1d49d204c9fd2ee6c537601dc4c1ba921e03363ca576bfab94a00254ac9a/rpds_py-2026.6.3-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:30c6dc199b24a5e3e81d50da0f00858c5bbdb2617a750395687f4339c5818171", size = 352842, upload-time = "2026-06-30T07:17:34.897Z" },
+ { url = "https://files.pythonhosted.org/packages/ac/e5/c0b5dc93cd0d4c06ce1f438907649514e2ea077bcd911e3154a51e96c38e/rpds_py-2026.6.3-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9891e594296ab9dada6551c8e7b387b2721f27a67eecd528412e8906247a7b90", size = 382094, upload-time = "2026-06-30T07:17:36.514Z" },
+ { url = "https://files.pythonhosted.org/packages/0d/54/ec0e907b4ca8d541112db352409bd15f871c9b243e0c92c9b5a46ae96f01/rpds_py-2026.6.3-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b5c2dc92304aa48a4a60443b548bb12f12e119d4b72f314015e67b9e1be97fca", size = 388662, upload-time = "2026-06-30T07:17:38.235Z" },
+ { url = "https://files.pythonhosted.org/packages/d3/f4/921c22a4fd0f1c1ac13a3996ffbf0aa67951e2c8ad0d1d9574938a2932e8/rpds_py-2026.6.3-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:127e08c0642d880cf32ca47ec2a4a77b901f7e2dd1ad9762adb13955d72ffcc9", size = 504896, upload-time = "2026-06-30T07:17:39.689Z" },
+ { url = "https://files.pythonhosted.org/packages/0b/1b/a114b972cefa1ab1cdb3c7bb177cd3844a12826c507c722d3a73516dbbaf/rpds_py-2026.6.3-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8bb68f03f395eb793220b45c097bd4d8c32944393da0fad8b999efac0868fc8c", size = 391545, upload-time = "2026-06-30T07:17:41.336Z" },
+ { url = "https://files.pythonhosted.org/packages/4e/98/af9b3db77d47fcbe6c8c1f36e2c2147ec70292819e99c325f871584a1c11/rpds_py-2026.6.3-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a3450b693fde92133e9f51060568a4c31fcca76d5e53bbd611e689ca446517e9", size = 380059, upload-time = "2026-06-30T07:17:42.857Z" },
+ { url = "https://files.pythonhosted.org/packages/c9/ba/0efd8668b97c1d26a61566386c636a7a7a09829e474fdf807caa15a2c844/rpds_py-2026.6.3-pp311-pypy311_pp73-manylinux_2_31_riscv64.whl", hash = "sha256:5e8d07bddee435a2ff6f1920e18feff28d0bc4533e42f4bf6927fbd073312c41", size = 393235, upload-time = "2026-06-30T07:17:44.637Z" },
+ { url = "https://files.pythonhosted.org/packages/62/90/8c139ee9690f73b0829f32647de6f40d826f8f443af6fa72644f96351aac/rpds_py-2026.6.3-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3a83ae6c67b7676b9878378547ca8e93ed77a580037bcbcd1d32f739e1e6089c", size = 413008, upload-time = "2026-06-30T07:17:46.225Z" },
+ { url = "https://files.pythonhosted.org/packages/9c/97/0043896fdd7828ce09a1d9a8b06433714d0960fc4ff3fc4aa72b666b764e/rpds_py-2026.6.3-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:2bfd04c19ddbd6640de0b51894d764bd2758854d5b75bd102d2ef10cb9c293a9", size = 558118, upload-time = "2026-06-30T07:17:47.759Z" },
+ { url = "https://files.pythonhosted.org/packages/f6/40/02355f0e134f783a8f9814c4680a1bd311d37671577a5964ea838573ff37/rpds_py-2026.6.3-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:ca6546b66be9dc4738b1b043d5ebd5488c66c578c5ff0fd0e8065313fe3afb76", size = 623138, upload-time = "2026-06-30T07:17:49.355Z" },
+ { url = "https://files.pythonhosted.org/packages/10/85/48f0abdcef5cce4e034c7a5b0ceeceba0b01bf0d942824f4bb720afe2dec/rpds_py-2026.6.3-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:8e65860d238379ed982fd9ba690579b5e95af2f4840f99c772816dbe573cb826", size = 586486, upload-time = "2026-06-30T07:17:51.141Z" },
+]
+
[[package]]
name = "ruff"
version = "0.15.20"