From 970ff8669e3f653b8b83bd1c75ccd9cfb3b011fc Mon Sep 17 00:00:00 2001 From: Deepak Cherian Date: Fri, 13 Feb 2026 12:23:45 -0700 Subject: [PATCH 01/21] Add uv-based just recipes for running benchmarks Makes it easy to build, setup, and run benchmarks with three commands: `just bench-build`, `just bench-setup`, `just bench`. Co-Authored-By: Claude Opus 4.6 --- Justfile | 11 +++++++++ icechunk-python/benchmarks/README.md | 37 +++++++++++++++++++++++----- 2 files changed, 42 insertions(+), 6 deletions(-) diff --git a/Justfile b/Justfile index 84e33c2d0..f3ffe74da 100644 --- a/Justfile +++ b/Justfile @@ -100,6 +100,17 @@ samply *args: chrome-trace *args: ICECHUNK_TRACE=chrome cargo bench --features logs --bench main -- {{args}} --test +# install benchmark deps and build icechunk in release mode +bench-build: + cd icechunk-python && uv sync --group benchmark && env -u CONDA_PREFIX uv run maturin develop --uv --release + +# create/refresh benchmark datasets (run once, or after format changes) +bench-setup *args='': + cd icechunk-python && uv run pytest -nauto -m --benchmark-disable setup_benchmarks benchmarks/ {{args}} + +# run benchmarks (pass extra pytest args, e.g.: just bench "-k getsize") +bench *args='': + cd icechunk-python && uv run pytest --benchmark-autosave benchmarks/ {{args}} [doc("Compare pytest-benchmark results")] bench-compare *args: pytest-benchmark compare --group=group,func,param --sort=fullname --columns=median --name=short "$@" diff --git a/icechunk-python/benchmarks/README.md b/icechunk-python/benchmarks/README.md index 1c116c5f2..a771a85f9 100644 --- a/icechunk-python/benchmarks/README.md +++ b/icechunk-python/benchmarks/README.md @@ -3,6 +3,28 @@ This is a benchmark suite based on `pytest-benchmark`. It is best to think of these benchmarks as benchmarking "integration" workflows that exercise the ecosystem from the Xarray/Zarr level down to Icechunk. +## Quick Start (uv) + +From the repo root: + +``` sh +# Install benchmark deps and build icechunk (release mode) +just bench-build + +# Create benchmark datasets (once, ~3 min) +just bench-setup + +# Run all benchmarks +just bench + +# Run specific benchmarks +just bench "-k getsize" +just bench "-k write" + +# Compare saved runs +just bench-compare 0020 0021 +``` + ## Setup Install the necessary dependencies with the `[benchmarks]` extra. @@ -128,14 +150,17 @@ This will 4. Runs the benchmarks. 5. Compares the benchmarks. -### Just aliases - -> [!WARNING] -> This doesn't work yet +### Just recipes -Some useful `just` aliases: +Some useful `just` recipes: -| Compare these benchmark runs | `just bench-compare 0020 0021 0022` | +| Task | Command | +|---------------------------|-------------------------------------| +| Install deps + build | `just bench-build` | +| Create benchmark datasets | `just bench-setup` | +| Run benchmarks | `just bench` | +| Run specific benchmarks | `just bench "-k getsize"` | +| Compare benchmark runs | `just bench-compare 0020 0021 0022` | ### Run the read benchmarks: ``` sh From 1103a5cf6a531339213469249a419887fe0774f1 Mon Sep 17 00:00:00 2001 From: Deepak Cherian Date: Fri, 13 Feb 2026 12:48:57 -0700 Subject: [PATCH 02/21] Merge --force-setup and --skip-setup into --setup=[force|skip] in runner.py Co-Authored-By: Claude Opus 4.6 --- icechunk-python/benchmarks/README.md | 6 +++--- icechunk-python/benchmarks/runner.py | 15 ++++++--------- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/icechunk-python/benchmarks/README.md b/icechunk-python/benchmarks/README.md index a771a85f9..dc81a2644 100644 --- a/icechunk-python/benchmarks/README.md +++ b/icechunk-python/benchmarks/README.md @@ -40,7 +40,7 @@ pytest -nauto -m setup_benchmarks benchmarks/ ``` As of Jan 20, 2025 this command takes about 3 minutes to run. -Use the `--force-setup` flag to avoid re-creating datasets if possible. +Use `--force-setup=False` to avoid re-creating datasets if possible. ``` sh pytest -nauto -m setup_benchmarks --force-setup=False benchmarks/ @@ -146,7 +146,7 @@ python benchmarks/runner.py icechunk-v0.1.0-alpha.12 main This will 1. setup a virtual env with the icechunk version 2. compile it, -3. run `setup_benchmarks` with `force-setup=False`. This will recreate datasets if the version in the bucket cannot be opened by this icechunk version. +3. run `setup_benchmarks`. This will recreate datasets if the version in the bucket cannot be opened by this icechunk version. Pass `--setup=force` to always recreate, or `--setup=skip` to skip setup entirely. 4. Runs the benchmarks. 5. Compares the benchmarks. @@ -225,7 +225,7 @@ To easily run benchmarks for some named refs use `benchmarks/run_refs.py` ### Comparing across multiple stores ```sh -python benchmarks/runner.py --skip-setup --pytest '-k test_write_simple' --where 's3|s3_ob|gcs' main +python benchmarks/runner.py --setup=skip --pytest '-k test_write_simple' --where 's3|s3_ob|gcs' main ``` ``` sh diff --git a/icechunk-python/benchmarks/runner.py b/icechunk-python/benchmarks/runner.py index 6b04ee3ed..cab2b1043 100644 --- a/icechunk-python/benchmarks/runner.py +++ b/icechunk-python/benchmarks/runner.py @@ -265,9 +265,9 @@ def run_there(where: str, *, args, save_prefix) -> None: # we can only initialize in parallel since the two refs may have the same spec version. process_map(serial=args.serial, func=partial(init_for_ref), iterable=runners) - if not args.skip_setup: + if args.setup != "skip": for runner in runners: - runner.setup(force=args.force_setup) + runner.setup(force=args.setup == "force") # TODO: this could be parallelized for coiled runners for runner in tqdm.tqdm(runners): @@ -285,13 +285,10 @@ def run_there(where: str, *, args, save_prefix) -> None: default="local", ) parser.add_argument( - "--skip-setup", - help="skip setup step, useful for benchmarks that don't need data", - action="store_true", - default=False, - ) - parser.add_argument( - "--force-setup", help="forced recreation of datasets?", type=bool, default=False + "--setup", + help="control setup step: 'force' to force recreation, 'skip' to skip entirely. Default: run setup without forcing.", + choices=["force", "skip"], + default=None, ) args = parser.parse_args() From bcb7e35d804c48a0f82daeffba2f788ace00f560 Mon Sep 17 00:00:00 2001 From: Deepak Cherian Date: Fri, 13 Feb 2026 12:53:03 -0700 Subject: [PATCH 03/21] Update bucket locations in benchmarks README to list all current stores Co-Authored-By: Claude Opus 4.6 --- icechunk-python/benchmarks/README.md | 8 +++++++- icechunk-python/benchmarks/datasets.py | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/icechunk-python/benchmarks/README.md b/icechunk-python/benchmarks/README.md index dc81a2644..8f9cc3d39 100644 --- a/icechunk-python/benchmarks/README.md +++ b/icechunk-python/benchmarks/README.md @@ -137,7 +137,13 @@ Downsides: ### `runner.py` `runner.py` abstracts the painful task of setting up envs with different versions (with potential format changes), and recreating datasets where needed. -Datasets are written to `s3://icechunk-test/benchmarks/REFNAME_SHORTCOMMIT`. +Datasets are written to `{bucket}/benchmarks/{REF}_{SHORTCOMMIT}/` where the bucket depends on `--where`: +| Store | Bucket | +|---------|-----------------------| +| s3 | `icechunk-ci` | +| gcs | `icechunk-test-gcp` | +| tigris | `icechunk-test` | +| r2 | `icechunk-test-r2` | Usage: ``` sh diff --git a/icechunk-python/benchmarks/datasets.py b/icechunk-python/benchmarks/datasets.py index ea82ef013..0d1a0fa05 100644 --- a/icechunk-python/benchmarks/datasets.py +++ b/icechunk-python/benchmarks/datasets.py @@ -39,7 +39,7 @@ "r2": ic.s3_storage, } TEST_BUCKETS = { - "s3": dict(store="s3", bucket="icechunk-test", region="us-east-1"), + "s3": dict(store="s3", bucket="icechunk-ci", region="us-east-1"), "gcs": dict(store="gcs", bucket="icechunk-test-gcp", region="us-east1"), # "gcs": dict(store="gcs", bucket="arraylake-scratch", region="us-east1"), # not using region="auto", because for now we pass this directly to coiled. From 5b3a166c228e9fecf491325e7163ca2b944ff46a Mon Sep 17 00:00:00 2001 From: Deepak Cherian Date: Fri, 13 Feb 2026 13:18:52 -0700 Subject: [PATCH 04/21] Switch to uv for local runner --- icechunk-python/benchmarks/runner.py | 44 ++++++++++++++++++++-------- 1 file changed, 32 insertions(+), 12 deletions(-) diff --git a/icechunk-python/benchmarks/runner.py b/icechunk-python/benchmarks/runner.py index cab2b1043..83dcecfe0 100644 --- a/icechunk-python/benchmarks/runner.py +++ b/icechunk-python/benchmarks/runner.py @@ -25,7 +25,6 @@ logger = setup_logger() -PIP_OPTIONS = "--disable-pip-version-check -q" PYTEST_OPTIONS = "-q --durations 10 --rootdir=benchmarks --tb=line" TMP = tempfile.gettempdir() CURRENTDIR = os.getcwd() @@ -132,31 +131,52 @@ def run(self, *, pytest_extra: str = "") -> None: class LocalRunner(Runner): - activate: str = "source .venv/bin/activate" bench_store_dir = CURRENTDIR def __init__(self, *, ref: str, where: str, save_prefix: str): super().__init__(ref=ref, where=where, save_prefix="") suffix = self.commit self.base = f"{TMP}/icechunk-bench-{suffix}" - self.cwd = f"{TMP}/icechunk-bench-{suffix}/icechunk" - self.pycwd = f"{TMP}/icechunk-bench-{suffix}/icechunk/icechunk-python" + self.pycwd = f"{self.base}/icechunk-python" + self.repo_root = subprocess.run( + ["git", "rev-parse", "--show-toplevel"], + capture_output=True, + text=True, + check=True, + ).stdout.strip() def sync_benchmarks_folder(self): - subprocess.run(["cp", "-r", "benchmarks", f"{self.pycwd}"], check=True) + subprocess.run(["rm", "-rf", f"{self.pycwd}/benchmarks"], check=False) + subprocess.run(["cp", "-r", "benchmarks", f"{self.pycwd}/"], check=True) def execute(self, cmd: str, **kwargs) -> None: - # don't stop if benchmarks fail - subprocess.run(f"{self.activate} && {cmd}", cwd=self.pycwd, shell=True, **kwargs) + subprocess.run(f"uv run {cmd}", cwd=self.pycwd, shell=True, **kwargs) def initialize(self) -> None: logger.info(f"Running initialize for {self.ref} in {self.base}") - deps = get_benchmark_deps(f"{CURRENTDIR}/pyproject.toml") - subprocess.run(["mkdir", "-p", self.pycwd], check=False) - subprocess.run(["python3", "-m", "venv", ".venv"], cwd=self.pycwd, check=True) - cmd = f"pip install {PIP_OPTIONS} {self.pip_github_url} {deps}" - self.execute(cmd, check=True) + if not os.path.exists(self.base): + subprocess.run( + ["git", "clone", "--local", "--no-checkout", self.repo_root, self.base], + check=True, + ) + subprocess.run( + ["git", "checkout", self.full_commit], + cwd=self.base, + check=True, + ) + subprocess.run( + ["uv", "sync", "--group", "benchmark"], + cwd=self.pycwd, + check=True, + ) + build_env = {k: v for k, v in os.environ.items() if k != "CONDA_PREFIX"} + subprocess.run( + ["uv", "run", "maturin", "develop", "--uv", "--release"], + cwd=self.pycwd, + check=True, + env=build_env, + ) super().initialize() def run(self, *, pytest_extra: str = "") -> None: From f16ad61c5ae1afa67b9a0efcb889556a153bd21b Mon Sep 17 00:00:00 2001 From: Deepak Cherian Date: Fri, 13 Feb 2026 13:51:10 -0700 Subject: [PATCH 05/21] Fix benchmarks --- icechunk-python/benchmarks/conftest.py | 14 ++++++++++++-- icechunk-python/benchmarks/helpers.py | 9 ++++++++- icechunk-python/benchmarks/runner.py | 4 +++- icechunk-python/benchmarks/tasks.py | 3 +-- .../benchmarks/test_benchmark_writes.py | 16 ++++++++++++++-- icechunk-python/pyproject.toml | 2 +- 6 files changed, 39 insertions(+), 9 deletions(-) diff --git a/icechunk-python/benchmarks/conftest.py b/icechunk-python/benchmarks/conftest.py index d45e2b300..7d468434e 100644 --- a/icechunk-python/benchmarks/conftest.py +++ b/icechunk-python/benchmarks/conftest.py @@ -15,7 +15,13 @@ BenchmarkWriteDataset, Dataset, ) -from icechunk import Repository, local_filesystem_storage +from icechunk import ( + Repository, + RepositoryConfig, + VirtualChunkContainer, + local_filesystem_storage, + s3_store, +) try: from icechunk import ManifestSplittingConfig # noqa: F401 @@ -41,7 +47,11 @@ def request_to_dataset(request, moar_prefix: str = "") -> Dataset: @pytest.fixture(scope="function") def repo(tmpdir: str) -> Repository: - return Repository.create(storage=local_filesystem_storage(tmpdir)) + config = RepositoryConfig.default() + config.set_virtual_chunk_container( + VirtualChunkContainer("s3://foo/", s3_store(region="us-east-1")) + ) + return Repository.create(storage=local_filesystem_storage(tmpdir), config=config) @pytest.fixture(params=[pytest.param(PANCAKE_WRITES, id="pancake-writes")]) diff --git a/icechunk-python/benchmarks/helpers.py b/icechunk-python/benchmarks/helpers.py index e42c17d0c..8d27abd8d 100644 --- a/icechunk-python/benchmarks/helpers.py +++ b/icechunk-python/benchmarks/helpers.py @@ -73,13 +73,20 @@ def rdms() -> str: def repo_config_with( - *, inline_chunk_threshold_bytes: int | None = None, preload=None, splitting=None + *, + inline_chunk_threshold_bytes: int | None = None, + preload=None, + splitting=None, + virtual_chunk_containers: list | None = None, ) -> ic.RepositoryConfig: config = ic.RepositoryConfig.default() if inline_chunk_threshold_bytes is not None: config.inline_chunk_threshold_bytes = inline_chunk_threshold_bytes if splitting is not None: config.manifest = ic.ManifestConfig(preload=preload, splitting=splitting) + if virtual_chunk_containers is not None: + for cont in virtual_chunk_containers: + config.set_virtual_chunk_container(cont) return config diff --git a/icechunk-python/benchmarks/runner.py b/icechunk-python/benchmarks/runner.py index 83dcecfe0..18fc2d2a3 100644 --- a/icechunk-python/benchmarks/runner.py +++ b/icechunk-python/benchmarks/runner.py @@ -150,7 +150,9 @@ def sync_benchmarks_folder(self): subprocess.run(["cp", "-r", "benchmarks", f"{self.pycwd}/"], check=True) def execute(self, cmd: str, **kwargs) -> None: - subprocess.run(f"uv run {cmd}", cwd=self.pycwd, shell=True, **kwargs) + subprocess.run( + f"uv run --group benchmark {cmd}", cwd=self.pycwd, shell=True, **kwargs + ) def initialize(self) -> None: logger.info(f"Running initialize for {self.ref} in {self.base}") diff --git a/icechunk-python/benchmarks/tasks.py b/icechunk-python/benchmarks/tasks.py index 56c7ac1d5..b6bc32841 100644 --- a/icechunk-python/benchmarks/tasks.py +++ b/icechunk-python/benchmarks/tasks.py @@ -212,8 +212,7 @@ def write( # TODO: record time & byte size? with timer.time(op="merge_changeset", **timer_context): - if not isinstance(executor, futures.ThreadPoolExecutor): - session.merge(*(res.session for res in results)) + session.merge(*(res.session for res in results)) assert session.has_uncommitted_changes with timer.time(op="commit", **timer_context): diff --git a/icechunk-python/benchmarks/test_benchmark_writes.py b/icechunk-python/benchmarks/test_benchmark_writes.py index efe77e8cc..b065edc7f 100644 --- a/icechunk-python/benchmarks/test_benchmark_writes.py +++ b/icechunk-python/benchmarks/test_benchmark_writes.py @@ -11,8 +11,10 @@ Repository, RepositoryConfig, Session, + VirtualChunkContainer, VirtualChunkSpec, local_filesystem_storage, + s3_store, ) NUM_CHUNK_REFS = 10_000 @@ -185,7 +187,12 @@ def test_write_split_manifest_refs_full_rewrite( benchmark, splitting, large_write_dataset ) -> None: dataset = large_write_dataset - config = repo_config_with(splitting=splitting) + config = repo_config_with( + splitting=splitting, + virtual_chunk_containers=[ + VirtualChunkContainer("s3://foo/", s3_store(region="us-east-1")) + ], + ) assert config is not None if hasattr(config.manifest, "splitting"): assert config.manifest.splitting == splitting @@ -228,7 +235,12 @@ def test_write_split_manifest_refs_append( benchmark, splitting, large_write_dataset ) -> None: dataset = large_write_dataset - config = repo_config_with(splitting=splitting) + config = repo_config_with( + splitting=splitting, + virtual_chunk_containers=[ + VirtualChunkContainer("s3://foo/", s3_store(region="us-east-1")) + ], + ) assert config is not None if hasattr(config.manifest, "splitting"): assert config.manifest.splitting == splitting diff --git a/icechunk-python/pyproject.toml b/icechunk-python/pyproject.toml index 9c3970e6c..c777dfa31 100644 --- a/icechunk-python/pyproject.toml +++ b/icechunk-python/pyproject.toml @@ -117,7 +117,7 @@ minversion = "7" testpaths = ["tests", "integration_tests"] log_cli_level = "INFO" xfail_strict = true -addopts = ["-ra", "--strict-config", "--strict-markers", "-n", "auto"] +addopts = ["-ra", "--strict-config", "--strict-markers", "-n", "auto", "--benchmark-disable"] markers = [ "gpu", # need this to run the zarr tests "hypothesis", # auto-applied by hypothesis to @given tests From eb43fa108666385cb76a5d42ae6e1cda616cb5c4 Mon Sep 17 00:00:00 2001 From: Deepak Cherian Date: Fri, 13 Feb 2026 14:19:09 -0700 Subject: [PATCH 06/21] Add CLAUDE.md --- icechunk-python/benchmarks/.claude/CLAUDE.md | 143 ++++++++++++++++++ .../benchmarks/.claude/settings.local.json | 7 + 2 files changed, 150 insertions(+) create mode 100644 icechunk-python/benchmarks/.claude/CLAUDE.md create mode 100644 icechunk-python/benchmarks/.claude/settings.local.json diff --git a/icechunk-python/benchmarks/.claude/CLAUDE.md b/icechunk-python/benchmarks/.claude/CLAUDE.md new file mode 100644 index 000000000..c305eaa24 --- /dev/null +++ b/icechunk-python/benchmarks/.claude/CLAUDE.md @@ -0,0 +1,143 @@ +# Benchmarks CLAUDE.md + +## Overview + +Integration benchmarks exercising the Xarray/Zarr/Icechunk stack end-to-end, built on `pytest-benchmark`. + +## Quick Reference + +```bash +just bench-build # uv sync + maturin develop --release +just bench-setup # create datasets (once, ~3 min) +just bench # run all benchmarks +just bench "-k getsize" # run specific benchmarks +just bench-compare 0020 0021 # compare saved runs +``` + +All commands run from the repo root. Under the hood they `cd icechunk-python` and use `uv run`. + +## File Map + +| File | Purpose | +|---|---| +| `test_benchmark_reads.py` | Read benchmarks (store open, getsize, zarr/xarray open, chunk reads, first-byte) | +| `test_benchmark_writes.py` | Write benchmarks (task-based writes, 1D writes, chunk refs, virtual refs, split manifests) | +| `datasets.py` | Dataset definitions (`BenchmarkReadDataset`, `BenchmarkWriteDataset`, `IngestDataset`) and setup functions | +| `conftest.py` | Pytest fixtures, custom options (`--where`, `--icechunk-prefix`, `--force-setup`), markers | +| `runner.py` | Multi-version orchestration: clones repo, builds each ref, runs setup + benchmarks, compares | +| `tasks.py` | Low-level concurrent write tasks using `ForkSession` with thread/process pool executors | +| `helpers.py` | Utilities: logger, coiled kwargs, git commit resolution, `repo_config_with()`, splitting config | +| `lib.py` | Math/timing: `stats()`, `slices_from_chunks()`, `normalize_chunks()`, `Timer` context manager | +| `create_era5.py` | ERA5 dataset creation using Coiled + Dask (separate from `setup_benchmarks` due to cost) | + +## Architecture + +### Datasets (`datasets.py`) + +`StorageConfig` wraps bucket/prefix/region and constructs `ic.Storage` objects. `Dataset` wraps a `StorageConfig` and provides `create()` (with optional `clear`) and a `.store` property. + +Two specialized subclasses: +- **`BenchmarkReadDataset`** — adds `load_variables`, `chunk_selector`, `full_load_selector`, `first_byte_variable`, `setupfn` +- **`BenchmarkWriteDataset`** — adds `num_arrays`, `shape`, `chunks` + +Predefined datasets: + +| Name | Type | Description | +|---|---|---| +| `ERA5_SINGLE` | Read | Single NCAR ERA5 netCDF (~17k chunks) | +| `ERA5_ARCO` | Read | ARCO-ERA5 from GCP (metadata only, no data arrays written) | +| `GB_8MB_CHUNKS` | Read | 512^3 int64 array, 4x512x512 chunks | +| `GB_128MB_CHUNKS` | Read | 512^3 int64 array, 64x512x512 chunks | +| `LARGE_MANIFEST_UNSHARDED` | Read | 500M x 1000 array, no manifest splitting | +| `LARGE_MANIFEST_SHARDED` | Read | 500M x 1000 array, split_size=100k | +| `PANCAKE_WRITES` | Write | 320x720x1441, chunks=(1,-1,-1) | +| `SIMPLE_1D` | Write | 2M elements, chunks=1000 | +| `LARGE_1D` | Write | 500M elements, chunks=1000 | + +### Storage Targets + +Controlled by `--where` flag. Buckets defined in `TEST_BUCKETS` dict: + +| Store | Bucket | Region | +|---|---|---| +| `local` | platformdirs cache | - | +| `s3` | `icechunk-ci` | us-east-1 | +| `s3_ob` | (same as s3, uses `s3_object_store_storage`) | us-east-1 | +| `gcs` | `icechunk-test-gcp` | us-east1 | +| `tigris` | `icechunk-test` | iad | +| `r2` | `icechunk-test-r2` | us-east-1 | + +### Pytest Markers + +- `@pytest.mark.setup_benchmarks` — dataset creation (run with `-m setup_benchmarks`) +- `@pytest.mark.read_benchmark` — all read tests +- `@pytest.mark.write_benchmark` — all write tests + +### Fixtures (`conftest.py`) + +- `synth_dataset` — parameterized over read datasets (currently large-manifest-no-split, large-manifest-split) +- `synth_write_dataset` — PANCAKE_WRITES +- `simple_write_dataset` — SIMPLE_1D +- `large_write_dataset` — LARGE_1D +- `repo` — local tmpdir repo with virtual chunk container configured + +`request_to_dataset()` applies `--where` and `--icechunk-prefix` to any dataset fixture. + +### Runner (`runner.py`) + +Multi-version benchmarking orchestrator. Two runner classes: + +- **`LocalRunner`** — clones repo to `/tmp/icechunk-bench-{commit}`, runs `uv sync --group benchmark`, `maturin develop --release`, copies benchmarks/ from CWD, executes via `uv run` +- **`CoiledRunner`** — creates Coiled software environments, runs on cloud VMs (m5.4xlarge / n2-standard-16), syncs results via S3 + +Usage: `python benchmarks/runner.py [--where local|s3|gcs] [--setup force|skip] [--pytest "-k pattern"] ref1 ref2 ...` + +Datasets written to `{bucket}/benchmarks/{ref}_{shortcommit}/`. + +### Task-Based Writes (`tasks.py`) + +Uses `ForkSession` for concurrent writes: +1. Create tasks with `ForkSession` + region slices +2. Submit to thread/process pool +3. Each worker writes a chunk region via zarr +4. Merge all `ForkSession`s back into parent session +5. Commit + +## Read Benchmarks (`test_benchmark_reads.py`) + +| Test | What It Measures | +|---|---| +| `test_time_create_store` | Repository.open + readonly_session + store creation | +| `test_time_getsize_key` | `store.getsize(key)` for zarr.json metadata keys | +| `test_time_getsize_prefix` | `array.nbytes_stored()` (prefix-based size aggregation) | +| `test_time_zarr_open` | Cold `zarr.open_group` (re-downloads snapshot each round) | +| `test_time_zarr_members` | `group.members()` enumeration | +| `test_time_xarray_open` | `xr.open_zarr` with `chunks=None, consolidated=False` | +| `test_time_xarray_read_chunks_cold_cache` | Full open + isel + compute (parameterized: single-chunk vs full-read, with preload fixture) | +| `test_time_xarray_read_chunks_hot_cache` | Repeated compute on pre-opened dataset (measures chunk fetch, not metadata) | +| `test_time_first_bytes` | Open group + read coordinate array (sensitive to manifest splitting) | + +The `preload` fixture parameterizes `ManifestPreloadConfig` (default vs off). + +## Write Benchmarks (`test_benchmark_writes.py`) + +| Test | What It Measures | +|---|---| +| `test_write_chunks_with_tasks` | Concurrent task-based writes (ThreadPool/ProcessPool), captures per-task timings | +| `test_write_simple_1d` | Simple array write + commit cycle (good for comparing S3 vs GCS latency) | +| `test_write_many_chunk_refs` | Writing 10k chunk refs, parameterized: inlined vs not, committed vs not | +| `test_set_many_virtual_chunk_refs` | Setting 100k virtual chunk refs via `store.set_virtual_ref()` | +| `test_write_split_manifest_refs_full_rewrite` | Commit time for 500k virtual refs (full rewrite), parameterized by splitting | +| `test_write_split_manifest_refs_append` | Commit time for incremental appends of virtual refs, 10 rounds | + +The `splitting` fixture parameterizes `ManifestSplittingConfig` (None vs split_size=10000). + +## Key Patterns + +- **Benchmark results** saved to `.benchmarks/` as JSON via `--benchmark-autosave` or `--benchmark-save=NAME` +- **Comparing runs**: `pytest-benchmark compare 0020 0021 --group=func,param --columns=median --name=short` +- **Cold vs hot cache**: cold benchmarks re-create store/repo inside the benchmarked function; hot benchmarks create once outside +- **`pedantic` mode**: used in task writes and split-manifest tests for finer control (`setup=` callback, explicit `rounds`/`iterations`) +- **`benchmark.extra_info`**: task-based writes record per-task timing statistics in the JSON output +- **zarr async concurrency**: set to 64 globally in read benchmarks, configurable in writes +- **Version compatibility**: `conftest.py` uses `pytest_configure` hook instead of `pyproject.toml` markers to support older icechunk versions diff --git a/icechunk-python/benchmarks/.claude/settings.local.json b/icechunk-python/benchmarks/.claude/settings.local.json new file mode 100644 index 000000000..ae6b9ba44 --- /dev/null +++ b/icechunk-python/benchmarks/.claude/settings.local.json @@ -0,0 +1,7 @@ +{ + "permissions": { + "allow": [ + "Bash(just bench-build:*)" + ] + } +} From cb8f99c72b78aac288cce952ce52df2c703607b6 Mon Sep 17 00:00:00 2001 From: Deepak Cherian Date: Fri, 13 Feb 2026 14:30:21 -0700 Subject: [PATCH 07/21] Fix agan --- icechunk-python/benchmarks/runner.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/icechunk-python/benchmarks/runner.py b/icechunk-python/benchmarks/runner.py index 18fc2d2a3..e240065ee 100644 --- a/icechunk-python/benchmarks/runner.py +++ b/icechunk-python/benchmarks/runner.py @@ -49,12 +49,17 @@ def get_benchmark_deps(filepath: str) -> str: """ with open(filepath, mode="rb") as f: data = tomllib.load(f) - return ( - " ".join(data["project"]["optional-dependencies"].get("benchmark", "")) - + " " - + " ".join(data["project"]["optional-dependencies"].get("test", "")) + + # Support both [dependency-groups] (uv) and [project.optional-dependencies] (legacy) + groups = data.get("dependency-groups") or data.get("project", {}).get( + "optional-dependencies", {} ) + deps = [] + for group_name in ("benchmark", "test"): + deps.extend(dep for dep in groups.get(group_name, []) if isinstance(dep, str)) + return " ".join(deps) + class Runner: bench_store_dir = None From ca2bf859760a1d956b58b483a1f14e362ec6d8c7 Mon Sep 17 00:00:00 2001 From: Deepak Cherian Date: Fri, 13 Feb 2026 14:56:25 -0700 Subject: [PATCH 08/21] Fix on coiled. --- icechunk-python/benchmarks/runner.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/icechunk-python/benchmarks/runner.py b/icechunk-python/benchmarks/runner.py index e240065ee..a2a429d17 100644 --- a/icechunk-python/benchmarks/runner.py +++ b/icechunk-python/benchmarks/runner.py @@ -229,15 +229,17 @@ def initialize(self) -> None: ckwargs = self.get_coiled_kwargs() # repeated calls are a no-op! - coiled.create_software_environment( - name=ckwargs["software"], - workspace=ckwargs["workspace"], - conda={ - "channels": ["conda-forge"], - "dependencies": ["rust", "python=3.12", "pip"], - }, - pip=[self.pip_github_url, "coiled", *deps], - ) + envs = coiled.list_software_environments(workspace=ckwargs["workspace"]) + if ckwargs["software"] not in envs: + coiled.create_software_environment( + name=ckwargs["software"], + workspace=ckwargs["workspace"], + conda={ + "channels": ["conda-forge"], + "dependencies": ["rust", "python=3.12", "pip"], + }, + pip=[self.pip_github_url, "coiled", *deps], + ) super().initialize() def execute(self, cmd, **kwargs): From 5bffe9fd4d230c393db4d11f1fc8c29186b13e53 Mon Sep 17 00:00:00 2001 From: Deepak Cherian Date: Fri, 20 Mar 2026 10:26:11 -0600 Subject: [PATCH 09/21] Remove old alpha stuff --- icechunk-python/benchmarks/README.md | 8 ++-- icechunk-python/benchmarks/coiled_runner.py | 38 ------------------- icechunk-python/benchmarks/conftest.py | 2 - icechunk-python/benchmarks/create_era5.py | 5 +-- .../benchmarks/envs/icechunk-alpha-12.txt | 9 ----- .../envs/icechunk-alpha-release.txt | 9 ----- icechunk-python/benchmarks/runner.py | 12 +----- 7 files changed, 7 insertions(+), 76 deletions(-) delete mode 100644 icechunk-python/benchmarks/coiled_runner.py delete mode 100644 icechunk-python/benchmarks/envs/icechunk-alpha-12.txt delete mode 100644 icechunk-python/benchmarks/envs/icechunk-alpha-release.txt diff --git a/icechunk-python/benchmarks/README.md b/icechunk-python/benchmarks/README.md index 8f9cc3d39..fef27944a 100644 --- a/icechunk-python/benchmarks/README.md +++ b/icechunk-python/benchmarks/README.md @@ -147,7 +147,7 @@ Datasets are written to `{bucket}/benchmarks/{REF}_{SHORTCOMMIT}/` where the buc Usage: ``` sh -python benchmarks/runner.py icechunk-v0.1.0-alpha.12 main +python benchmarks/runner.py v0.1.2 main ``` This will 1. setup a virtual env with the icechunk version @@ -214,9 +214,9 @@ pytest-benchmark list which for me prints ``` ... -/Users/deepak/repos/icechunk/icechunk-python/.benchmarks/Darwin-CPython-3.12-64bit/0019_icechunk-v0.1.0-alpha.12.json -/Users/deepak/repos/icechunk/icechunk-python/.benchmarks/Darwin-CPython-3.12-64bit/0020_icechunk-v0.1.0-alpha.8.json -/Users/deepak/repos/icechunk/icechunk-python/.benchmarks/Darwin-CPython-3.12-64bit/0021_icechunk-v0.1.0-alpha.10.json +/Users/deepak/repos/icechunk/icechunk-python/.benchmarks/Darwin-CPython-3.12-64bit/0019_v0.1.2.json +/Users/deepak/repos/icechunk/icechunk-python/.benchmarks/Darwin-CPython-3.12-64bit/0020_v0.1.3.json +/Users/deepak/repos/icechunk/icechunk-python/.benchmarks/Darwin-CPython-3.12-64bit/0021_main.json ``` Note the 4 digit ID of the runs you want. Then diff --git a/icechunk-python/benchmarks/coiled_runner.py b/icechunk-python/benchmarks/coiled_runner.py deleted file mode 100644 index f5f4af682..000000000 --- a/icechunk-python/benchmarks/coiled_runner.py +++ /dev/null @@ -1,38 +0,0 @@ -#!/usr/bin/env python3 -# -# This is just a scratch script for testing purposes -# coiled notebook start --sync --software icechunk-alpha-12 --vm-type m5.4xlarge -import subprocess - -# software = "icechunk-alpha-12" -vm_type = { - "s3": "m5.4xlarge", - "gcs": None, - "tigris": None, -} -ref = "icechunk-v0.1.0-alpha.12" - -COILED_SOFTWARE = { - "icechunk-v0.1.0-alpha.1": "icechunk-alpha-release", - "icechunk-v0.1.0-alpha.12": "icechunk-alpha-12", -} -software = COILED_SOFTWARE[ref] - -cmd = f'python benchmarks/runner.py --where coiled --pytest "-k zarr_open" {ref}' -subprocess.run( - [ - "coiled", - "run", - "--name", - "icebench-712f1eb2", - "--sync", - "--sync-ignore='python/ reports/ profiling/'", - "--keepalive", - "5m", - "--workspace=earthmover-devs", - "--vm-type=m5.4xlarge", - "--software=icechunk-bench-712f1eb2", - "--region=us-east-1", - "pytest -v benchmarks/", - ] -) diff --git a/icechunk-python/benchmarks/conftest.py b/icechunk-python/benchmarks/conftest.py index 7d468434e..7d1299af5 100644 --- a/icechunk-python/benchmarks/conftest.py +++ b/icechunk-python/benchmarks/conftest.py @@ -101,8 +101,6 @@ def synth_dataset(request) -> BenchmarkReadDataset: return cast(BenchmarkReadDataset, ds) -# This hook is used instead of `pyproject.toml` so that we can run the benchmark infra -# on versions older than alpha-13 # TODO: Migrate to pyproject.toml after 1.0 has been released. def pytest_configure(config): config.addinivalue_line( diff --git a/icechunk-python/benchmarks/create_era5.py b/icechunk-python/benchmarks/create_era5.py index e2b2c5a7f..0d5290636 100644 --- a/icechunk-python/benchmarks/create_era5.py +++ b/icechunk-python/benchmarks/create_era5.py @@ -199,10 +199,7 @@ def setup_dataset( def get_version() -> str: version = Version(ic.__version__) - if version.pre is not None and "a" in version.pre: - return f"icechunk-v{version.base_version}-alpha.{version.pre[1]}" - else: - return f"icechunk-v{version.base_version}" + return f"icechunk-v{version.base_version}" if __name__ == "__main__": diff --git a/icechunk-python/benchmarks/envs/icechunk-alpha-12.txt b/icechunk-python/benchmarks/envs/icechunk-alpha-12.txt deleted file mode 100644 index 740ea23a9..000000000 --- a/icechunk-python/benchmarks/envs/icechunk-alpha-12.txt +++ /dev/null @@ -1,9 +0,0 @@ -icechunk==0.1.0-alpha.12 -zarr==3.0.1 -dask==2025.01.0 -distributed==2025.01.0 -xarray==2025.01.0 -coiled -jupyterlab -jupyter-server-proxy -h5netcdf diff --git a/icechunk-python/benchmarks/envs/icechunk-alpha-release.txt b/icechunk-python/benchmarks/envs/icechunk-alpha-release.txt deleted file mode 100644 index e6264b50e..000000000 --- a/icechunk-python/benchmarks/envs/icechunk-alpha-release.txt +++ /dev/null @@ -1,9 +0,0 @@ -icechunk==0.1.0-alpha.1 -zarr==3.0.0b0 -dask==2024.10.0 -distributed==2024.10.0 -xarray==2024.10.0 -coiled -jupyterlab -jupyter-server-proxy -h5netcdf diff --git a/icechunk-python/benchmarks/runner.py b/icechunk-python/benchmarks/runner.py index a2a429d17..8d72ce793 100644 --- a/icechunk-python/benchmarks/runner.py +++ b/icechunk-python/benchmarks/runner.py @@ -71,7 +71,7 @@ def __init__(self, *, ref: str, where: str, save_prefix: str) -> None: self.where = where self.save_prefix = save_prefix # shorten the name so `pytest-benchmark compare` is readable - self.clean_ref = self.ref.removeprefix("icechunk-v0.1.0-alph") + self.clean_ref = self.ref.removeprefix("icechunk-v") @property def pip_github_url(self) -> str: @@ -208,16 +208,9 @@ def get_coiled_run_args(self) -> tuple[str]: ) def get_coiled_kwargs(self): - COILED_SOFTWARE = { - "icechunk-v0.1.0-alpha.1": "icechunk-alpha-release", - "icechunk-v0.1.0-alpha.12": "icechunk-alpha-12", - } - # using the default region here kwargs = get_coiled_kwargs(store=self.where) - kwargs["software"] = COILED_SOFTWARE.get( - self.ref, f"icechunk-bench-{self.commit}" - ) + kwargs["software"] = f"icechunk-bench-{self.commit}" kwargs["name"] = f"icebench-{self.commit}-{self.where}" kwargs["keepalive"] = "10m" return kwargs @@ -228,7 +221,6 @@ def initialize(self) -> None: deps = get_benchmark_deps(f"{CURRENTDIR}/pyproject.toml").split(" ") ckwargs = self.get_coiled_kwargs() - # repeated calls are a no-op! envs = coiled.list_software_environments(workspace=ckwargs["workspace"]) if ckwargs["software"] not in envs: coiled.create_software_environment( From 6f6ee7bf5c65d55a4730dbb01e15fd426691a76c Mon Sep 17 00:00:00 2001 From: Deepak Cherian Date: Fri, 27 Feb 2026 16:13:43 -0700 Subject: [PATCH 10/21] WIP --- icechunk-python/benchmarks/datasets.py | 2 +- icechunk-python/benchmarks/helpers.py | 2 +- icechunk-python/benchmarks/test_benchmark_writes.py | 7 ++++++- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/icechunk-python/benchmarks/datasets.py b/icechunk-python/benchmarks/datasets.py index 0d1a0fa05..cc1442fa5 100644 --- a/icechunk-python/benchmarks/datasets.py +++ b/icechunk-python/benchmarks/datasets.py @@ -538,6 +538,6 @@ def setup_split_manifest_refs(dataset: Dataset, *, split_size: int | None): LARGE_1D = BenchmarkWriteDataset( storage_config=StorageConfig(prefix="large_1d_writes"), num_arrays=1, - shape=(500_000 * 1000,), + shape=(1_000_000 * 1000,), chunks=(1000,), ) diff --git a/icechunk-python/benchmarks/helpers.py b/icechunk-python/benchmarks/helpers.py index 8d27abd8d..63b0895cc 100644 --- a/icechunk-python/benchmarks/helpers.py +++ b/icechunk-python/benchmarks/helpers.py @@ -82,7 +82,7 @@ def repo_config_with( config = ic.RepositoryConfig.default() if inline_chunk_threshold_bytes is not None: config.inline_chunk_threshold_bytes = inline_chunk_threshold_bytes - if splitting is not None: + if splitting is not None or preload is not None: config.manifest = ic.ManifestConfig(preload=preload, splitting=splitting) if virtual_chunk_containers is not None: for cont in virtual_chunk_containers: diff --git a/icechunk-python/benchmarks/test_benchmark_writes.py b/icechunk-python/benchmarks/test_benchmark_writes.py index b065edc7f..7020dd8a6 100644 --- a/icechunk-python/benchmarks/test_benchmark_writes.py +++ b/icechunk-python/benchmarks/test_benchmark_writes.py @@ -8,6 +8,7 @@ from benchmarks.helpers import get_splitting_config, repo_config_with from benchmarks.tasks import Executor, write from icechunk import ( + ManifestPreloadConfig, Repository, RepositoryConfig, Session, @@ -18,7 +19,7 @@ ) NUM_CHUNK_REFS = 10_000 -NUM_VIRTUAL_CHUNK_REFS = 100_000 +NUM_VIRTUAL_CHUNK_REFS = 1_000_000 pytestmark = pytest.mark.write_benchmark @@ -236,6 +237,7 @@ def test_write_split_manifest_refs_append( ) -> None: dataset = large_write_dataset config = repo_config_with( + preload=ManifestPreloadConfig(max_total_refs=0), splitting=splitting, virtual_chunk_containers=[ VirtualChunkContainer("s3://foo/", s3_store(region="us-east-1")) @@ -265,6 +267,8 @@ def test_write_split_manifest_refs_append( num_chunks = dataset.shape[0] // dataset.chunks[0] batch_size = num_chunks // rounds + print(batch_size, num_chunks, splitting) + def write_refs() -> Session: global counter session = repo.writable_session(branch="main") @@ -276,6 +280,7 @@ def write_refs() -> Session: ] counter += 1 session.store.set_virtual_refs("array", chunks) + del chunks # (args, kwargs) return ((session,), {}) From 5dbe60ac82cff727eb81fdbeb881c4f2e4d064c4 Mon Sep 17 00:00:00 2001 From: Deepak Cherian Date: Fri, 20 Mar 2026 10:28:21 -0600 Subject: [PATCH 11/21] python 3.14 --- icechunk-python/benchmarks/runner.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/icechunk-python/benchmarks/runner.py b/icechunk-python/benchmarks/runner.py index 8d72ce793..c6d5a1b46 100644 --- a/icechunk-python/benchmarks/runner.py +++ b/icechunk-python/benchmarks/runner.py @@ -228,7 +228,7 @@ def initialize(self) -> None: workspace=ckwargs["workspace"], conda={ "channels": ["conda-forge"], - "dependencies": ["rust", "python=3.12", "pip"], + "dependencies": ["rust", "python=3.14", "pip"], }, pip=[self.pip_github_url, "coiled", *deps], ) From a6c73724dff727e51c9af95e28fdcd2cb9116c39 Mon Sep 17 00:00:00 2001 From: Deepak Cherian Date: Fri, 20 Mar 2026 14:21:45 -0600 Subject: [PATCH 12/21] Cleanup --- icechunk-python/benchmarks/README.md | 2 +- icechunk-python/benchmarks/datasets.py | 7 +- icechunk-python/benchmarks/helpers.py | 2 +- icechunk-python/benchmarks/most_recent.sh | 7 -- icechunk-python/benchmarks/runner.py | 132 +++++++++++++++++----- icechunk-python/pyproject.toml | 5 +- 6 files changed, 110 insertions(+), 45 deletions(-) delete mode 100644 icechunk-python/benchmarks/most_recent.sh diff --git a/icechunk-python/benchmarks/README.md b/icechunk-python/benchmarks/README.md index fef27944a..d4236c0b7 100644 --- a/icechunk-python/benchmarks/README.md +++ b/icechunk-python/benchmarks/README.md @@ -142,7 +142,7 @@ Datasets are written to `{bucket}/benchmarks/{REF}_{SHORTCOMMIT}/` where the buc |---------|-----------------------| | s3 | `icechunk-ci` | | gcs | `icechunk-test-gcp` | -| tigris | `icechunk-test` | +| tigris | `icechunk-test-github-actions` | | r2 | `icechunk-test-r2` | Usage: diff --git a/icechunk-python/benchmarks/datasets.py b/icechunk-python/benchmarks/datasets.py index cc1442fa5..e988b8184 100644 --- a/icechunk-python/benchmarks/datasets.py +++ b/icechunk-python/benchmarks/datasets.py @@ -41,13 +41,8 @@ TEST_BUCKETS = { "s3": dict(store="s3", bucket="icechunk-ci", region="us-east-1"), "gcs": dict(store="gcs", bucket="icechunk-test-gcp", region="us-east1"), - # "gcs": dict(store="gcs", bucket="arraylake-scratch", region="us-east1"), - # not using region="auto", because for now we pass this directly to coiled. "r2": dict(store="r2", bucket="icechunk-test-r2", region="us-east-1"), - # "tigris": dict( - # store="tigris", bucket="deepak-private-bucket" + "-test", region="iad" - # ), - "tigris": dict(store="tigris", bucket="icechunk-test", region="iad"), + "tigris": dict(store="tigris", bucket="test-icechunk-github-actions", region="iad"), "local": dict(store="local", bucket=platformdirs.site_cache_dir()), } TEST_BUCKETS["s3_ob"] = TEST_BUCKETS["s3"] diff --git a/icechunk-python/benchmarks/helpers.py b/icechunk-python/benchmarks/helpers.py index 63b0895cc..ba03d7438 100644 --- a/icechunk-python/benchmarks/helpers.py +++ b/icechunk-python/benchmarks/helpers.py @@ -14,7 +14,7 @@ def setup_logger(): return logger -def get_coiled_kwargs(*, store: str, region: str | None = None) -> str: +def get_coiled_kwargs(*, store: str, region: str | None = None) -> dict[str, str]: if store == "s3_ob": store = "s3" COILED_VM_TYPES = { diff --git a/icechunk-python/benchmarks/most_recent.sh b/icechunk-python/benchmarks/most_recent.sh deleted file mode 100644 index 5c9d9348e..000000000 --- a/icechunk-python/benchmarks/most_recent.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/usr/bin/env sh - -LATEST_BENCHMARK=$(ls -t ./.benchmarks/**/* | head -n 1) - -echo "$LATEST_BENCHMARK" -pytest-benchmark compare --group=group,func,param --sort=fullname --columns=median --name=normal "$LATEST_BENCHMARK" -aws s3 cp "$LATEST_BENCHMARK" s3://earthmover-scratch/benchmarks/$1 diff --git a/icechunk-python/benchmarks/runner.py b/icechunk-python/benchmarks/runner.py index c6d5a1b46..90022e1bb 100644 --- a/icechunk-python/benchmarks/runner.py +++ b/icechunk-python/benchmarks/runner.py @@ -22,16 +22,36 @@ rdms, setup_logger, ) +from object_store import ObjectStore logger = setup_logger() PYTEST_OPTIONS = "-q --durations 10 --rootdir=benchmarks --tb=line" TMP = tempfile.gettempdir() CURRENTDIR = os.getcwd() +NIGHTLY_INDEX_URL = "https://pypi.anaconda.org/scientific-python-nightly-wheels/simple" assert_cwd_is_icechunk_python() +# Resolve AWS credentials eagerly at import time, before process_map forks +# child processes. boto3.Session() reads AWS_PROFILE / env vars / instance roles, +# but child processes spawned by ProcessPoolExecutor don't inherit the parent's +# env reliably, so we freeze the credentials here. +_AWS_CREDENTIALS: dict[str, str] = {} +try: + import boto3 + + _creds = boto3.Session().get_credentials() + if _creds: + _frozen = _creds.get_frozen_credentials() + _AWS_CREDENTIALS["AWS_ACCESS_KEY_ID"] = _frozen.access_key + _AWS_CREDENTIALS["AWS_SECRET_ACCESS_KEY"] = _frozen.secret_key + if _frozen.token: + _AWS_CREDENTIALS["AWS_SESSION_TOKEN"] = _frozen.token +except Exception: + pass + def process_map(*, serial: bool, func: Callable, iterable): if serial: @@ -64,10 +84,20 @@ def get_benchmark_deps(filepath: str) -> str: class Runner: bench_store_dir = None + # Refs that install icechunk from PyPI instead of building from source. + PYPI_REFS = { + "pypi-nightly": "--pre icechunk", + "pypi-v1": "icechunk<2", + } + def __init__(self, *, ref: str, where: str, save_prefix: str) -> None: self.ref = ref - self.full_commit = get_full_commit(ref) - self.commit = self.full_commit[:8] + if ref in self.PYPI_REFS: + self.full_commit = ref + self.commit = ref + else: + self.full_commit = get_full_commit(ref) + self.commit = self.full_commit[:8] self.where = where self.save_prefix = save_prefix # shorten the name so `pytest-benchmark compare` is readable @@ -94,7 +124,7 @@ def sync_benchmarks_folder(self) -> None: """Sync the benchmarks folder over to the cwd.""" raise NotImplementedError - def execute(cmd: str) -> None: + def execute(self, cmd: str, **kwargs) -> None: """Execute a command""" raise NotImplementedError @@ -193,9 +223,9 @@ def run(self, *, pytest_extra: str = "") -> None: class CoiledRunner(Runner): bench_store_dir = "." - def get_coiled_run_args(self) -> tuple[str]: + def get_coiled_run_args(self) -> tuple[str, ...]: ckwargs = self.get_coiled_kwargs() - return ( + args = [ "coiled", "run", "--interactive", @@ -205,7 +235,13 @@ def get_coiled_run_args(self) -> tuple[str]: f"--vm-type={ckwargs['vm_type']}", f"--software={ckwargs['software']}", f"--region={ckwargs['region']}", - ) + ] + # Forward AWS credentials to the VM so it can access S3/R2/etc + # regardless of which cloud the VM is on. Uses pre-resolved + # _AWS_CREDENTIALS (frozen at import time, before process forks). + for key, val in _AWS_CREDENTIALS.items(): + args.extend(["--env", f"{key}={val}"]) + return tuple(args) def get_coiled_kwargs(self): # using the default region here @@ -222,16 +258,38 @@ def initialize(self) -> None: ckwargs = self.get_coiled_kwargs() envs = coiled.list_software_environments(workspace=ckwargs["workspace"]) + pypi_spec = self.PYPI_REFS.get(self.ref) if ckwargs["software"] not in envs: + if pypi_spec is not None: + pip_deps = ["coiled", *deps] + else: + # TODO: support building wheels for arbitrary git refs. + # The pip_github_url approach below doesn't work because Coiled + # ignores git+ URLs with "Local path requirement ... is not supported". + # pip_deps = [self.pip_github_url, "coiled", *deps] + raise NotImplementedError( + f"Coiled runner only supports PYPI_REFS ({list(self.PYPI_REFS)}). " + f"Got ref={self.ref!r}. Use LocalRunner (--where local) for git refs." + ) coiled.create_software_environment( name=ckwargs["software"], workspace=ckwargs["workspace"], conda={ "channels": ["conda-forge"], - "dependencies": ["rust", "python=3.14", "pip"], + "dependencies": ["python=3.14"], }, - pip=[self.pip_github_url, "coiled", *deps], + pip=pip_deps, ) + + if pypi_spec is not None: + # Install icechunk on the VM. Coiled's pip= doesn't support flags + # like --pre or --extra-index-url, so we run pip install separately. + pip_cmd = f"pip install {pypi_spec}" + if self.ref == "pypi-nightly": + pip_cmd += f" --extra-index-url {NIGHTLY_INDEX_URL}" + logger.info(f"Installing icechunk on VM: {pip_cmd}") + self.execute(pip_cmd, check=True) + super().initialize() def execute(self, cmd, **kwargs): @@ -250,12 +308,45 @@ def sync_benchmarks_folder(self) -> None: def run(self, *, pytest_extra: str = "") -> None: super().run(pytest_extra=pytest_extra) - filename = f"{self.save_prefix}/{self.where}_{self.clean_ref}_{self.commit}.json" - # This is crappy; but for some reason coiled.function doesn't see the right files :/ - # So we need to use 'coiled run'; upload to a bucket; and then download from bucket - # pytest-benchmark cannot write directly to a bucket yet, sadly. - # TODO: explore a mounting a bucket as a volume. - self.execute(f"sh benchmarks/most_recent.sh {filename}") + self._fetch_benchmark_json() + + def _fetch_benchmark_json(self) -> None: + """Fetch benchmark JSON: upload from VM to object store, download locally. + + Uses object_store_python which supports S3/GCS/Tigris/R2 with a + single API. The VM already has credentials for the target store. + """ + from datasets import TEST_BUCKETS + + SCHEMES = {"s3": "s3", "gcs": "gs", "tigris": "s3", "r2": "s3"} + bucket_info = TEST_BUCKETS[self.where] + store_url = f"{SCHEMES[bucket_info['store']]}://{bucket_info['bucket']}" + + local_name = f"{self.where}_{self.clean_ref}_{self.commit}.json" + obj_key = f"_bench_results/{self.save_prefix}/{local_name}" + + # Upload from VM using object_store + upload_cmd = ( + 'python -c "' + "from object_store import ObjectStore; " + "import glob,os; " + "f=sorted(glob.glob('./.benchmarks/**/*',recursive=True),key=os.path.getmtime,reverse=True)[0]; " + f"store=ObjectStore('{store_url}'); " + f"store.put('{obj_key}',open(f,'rb').read())" + '"' + ) + self.execute(upload_cmd, check=True) + + # Download locally + store = ObjectStore(store_url) + data = store.get(obj_key) + + local_dir = f"/tmp/benchmarks/{self.save_prefix}" + os.makedirs(local_dir, exist_ok=True) + local_path = f"{local_dir}/{local_name}" + with open(local_path, "wb") as f: + f.write(data) + logger.info(f"Saved benchmark results to {local_path}") def read_latest_benchmark_json() -> str: @@ -326,21 +417,8 @@ def run_there(where: str, *, args, save_prefix) -> None: iterable=where, ) - if any(w != "local" for w in where): - subprocess.run( - [ - "aws", - "s3", - "cp", - f"s3://earthmover-scratch/benchmarks/{save_prefix}/", - f"/tmp/benchmarks/{save_prefix}/", - "--recursive", - ], - check=True, - ) refs = args.refs - # TODO: clean this up if where == ("local",) and len(refs) > 1: files = sorted( glob.glob("./.benchmarks/**/*.json", recursive=True), diff --git a/icechunk-python/pyproject.toml b/icechunk-python/pyproject.toml index c777dfa31..b90d0cf7e 100644 --- a/icechunk-python/pyproject.toml +++ b/icechunk-python/pyproject.toml @@ -53,10 +53,9 @@ test = [ "rich", ] benchmark = [ + "boto3", + "object-store-python", "pytest-benchmark[histogram]", - "pytest-xdist", - "s3fs", - "gcsfs", "h5netcdf", "pooch", "tqdm", From a9dfc89fb36642af4d67114cd974fa505295f8de Mon Sep 17 00:00:00 2001 From: Deepak Cherian Date: Fri, 20 Mar 2026 14:52:38 -0600 Subject: [PATCH 13/21] more cleanup --- icechunk-python/benchmarks/README.md | 26 ++++++---- icechunk-python/benchmarks/buckets.py | 41 +++++++++++++++ icechunk-python/benchmarks/datasets.py | 39 ++++----------- icechunk-python/benchmarks/helpers.py | 2 +- icechunk-python/benchmarks/runner.py | 69 +++++++++++++++++--------- 5 files changed, 114 insertions(+), 63 deletions(-) create mode 100644 icechunk-python/benchmarks/buckets.py diff --git a/icechunk-python/benchmarks/README.md b/icechunk-python/benchmarks/README.md index d4236c0b7..ccfeefe27 100644 --- a/icechunk-python/benchmarks/README.md +++ b/icechunk-python/benchmarks/README.md @@ -112,12 +112,12 @@ test_time_getsize_prefix[era5-single] (NOW) 2.2133 (1.0) ### Notes ### Where to run the benchmarks? -- Pass the `--where [local|s3|s3_ob|gcs|tigris]` flag to control where benchmarks are run. +- Pass the `--where` flag to control where benchmarks are run: `local`, `s3`, `s3_ob`, `gcs`, `tigris`, `r2`. - `s3_ob` uses the `s3_object_store_storage` constructor. -- Pass multiple stores with `--where 's3|gcs'` +- Comma-separate for multiple stores: `--where s3,gcs` ```sh -python benchmarks/runner.py --where gcs v0.1.2 +python benchmarks/runner.py --where gcs pypi-nightly ``` By default all benchmarks are run locally: @@ -127,12 +127,20 @@ By default all benchmarks are run locally: It is possible to run the benchmarks in the cloud using Coiled. You will need to be a member of the Coiled workspaces: `earthmover-devs` (AWS), `earthmover-devs-gcp` (GCS) and `earthmover-devs-azure` (Azure). 1. We create a new "coiled software environment" with a specific name. 2. We use `coiled run` targeting a specific machine type, with a specific software env. +3. Two special refs install icechunk from PyPI instead of building from source: + - `pypi-nightly` — installs the latest pre-release from the [scientific-python-nightly-wheels](https://pypi.anaconda.org/scientific-python-nightly-wheels/simple) index + - `pypi-v1` — installs the latest stable v1 release (`icechunk<2`) 4. The VM stays alive for 10 minutes to allow for quick iteration. -5. Coiled does not sync stdout until the pytest command is done, for some reason. See the logs on the Coiled platform for quick feedback. -6. We use the `--sync` flag, so you will need [`mutagen`](https://mutagen.io/documentation/synchronization/) installed on your system. This will sync the benchmark JSON outputs between the VM and your machine. -Downsides: -1. At the moment, we can only benchmark released versions of icechunk. We may need a more complicated Docker container strategy in the future to support dev branch benchmarks. -2. When a new env is created, the first run always fails :/. The second run works though, so just re-run. +5. Benchmark JSON results are uploaded from the VM to the target bucket via `object_store_python`, then downloaded locally for comparison. +6. AWS credentials are forwarded to the VM via `--env` flags (resolved from your local boto3 session at startup). + +```sh +# Run nightly benchmarks on S3 and GCS +python benchmarks/runner.py --where s3,gcs pypi-nightly + +# Compare nightly vs stable v1 +python benchmarks/runner.py --where s3 pypi-nightly pypi-v1 +``` ### `runner.py` @@ -231,7 +239,7 @@ To easily run benchmarks for some named refs use `benchmarks/run_refs.py` ### Comparing across multiple stores ```sh -python benchmarks/runner.py --setup=skip --pytest '-k test_write_simple' --where 's3|s3_ob|gcs' main +python benchmarks/runner.py --setup=skip --pytest '-k test_write_simple' --where s3,s3_ob,gcs pypi-nightly ``` ``` sh diff --git a/icechunk-python/benchmarks/buckets.py b/icechunk-python/benchmarks/buckets.py new file mode 100644 index 000000000..71c773b4f --- /dev/null +++ b/icechunk-python/benchmarks/buckets.py @@ -0,0 +1,41 @@ +import platformdirs + +PUBLIC_DATA_BUCKET = "icechunk-public-data" + +TEST_BUCKETS: dict[str, dict] = { + "s3": dict(store="s3", bucket="icechunk-ci", region="us-east-1"), + "gcs": dict(store="gcs", bucket="icechunk-test-gcp", region="us-east1"), + "r2": dict( + store="r2", + bucket="icechunk-test-r2", + region="us-east-1", + endpoint_url="https://caa3022c13c9823de0d22b3b6c249494.r2.cloudflarestorage.com", + ), + "tigris": dict( + store="tigris", + bucket="test-icechunk-github-actions", + region="us-east-2", + endpoint_url="https://t3.storage.dev", + ), + "local": dict(store="local", bucket=platformdirs.site_cache_dir()), +} +TEST_BUCKETS["s3_ob"] = TEST_BUCKETS["s3"] + +BUCKETS = { + "s3": dict(store="s3", bucket=PUBLIC_DATA_BUCKET, region="us-east-1"), + "gcs": dict(store="gcs", bucket=PUBLIC_DATA_BUCKET + "-gcs", region="us-east1"), + "tigris": dict(store="tigris", bucket=PUBLIC_DATA_BUCKET + "-tigris", region="iad"), + "r2": dict(store="r2", bucket=PUBLIC_DATA_BUCKET + "-r2", region="us-east-1"), +} + +# Object store URLs and regions for benchmark result upload/download. +_SCHEMES = {"s3": "s3", "gcs": "gs", "tigris": "s3", "r2": "s3"} +RESULT_STORE_URLS: dict[str, dict[str, str]] = { + name: { + "url": f"{_SCHEMES[info['store']]}://{info['bucket']}", + "region": info.get("region", ""), + "endpoint_url": info.get("endpoint_url", ""), + } + for name, info in TEST_BUCKETS.items() + if info.get("store") in _SCHEMES +} diff --git a/icechunk-python/benchmarks/datasets.py b/icechunk-python/benchmarks/datasets.py index e988b8184..99407ca60 100644 --- a/icechunk-python/benchmarks/datasets.py +++ b/icechunk-python/benchmarks/datasets.py @@ -9,13 +9,12 @@ import fsspec import numpy as np -import platformdirs -import pytest import icechunk as ic import icechunk.xarray import xarray as xr import zarr +from benchmarks.buckets import BUCKETS, TEST_BUCKETS from benchmarks.helpers import ( get_coiled_kwargs, get_splitting_config, @@ -25,9 +24,7 @@ ) rng = np.random.default_rng(seed=123) - Store: TypeAlias = Literal["s3", "gcs", "az", "tigris"] -PUBLIC_DATA_BUCKET = "icechunk-public-data" ZARR_KWARGS = dict(zarr_format=3, consolidated=False) CONSTRUCTORS = { @@ -38,25 +35,11 @@ "local": ic.local_filesystem_storage, "r2": ic.s3_storage, } -TEST_BUCKETS = { - "s3": dict(store="s3", bucket="icechunk-ci", region="us-east-1"), - "gcs": dict(store="gcs", bucket="icechunk-test-gcp", region="us-east1"), - "r2": dict(store="r2", bucket="icechunk-test-r2", region="us-east-1"), - "tigris": dict(store="tigris", bucket="test-icechunk-github-actions", region="iad"), - "local": dict(store="local", bucket=platformdirs.site_cache_dir()), -} -TEST_BUCKETS["s3_ob"] = TEST_BUCKETS["s3"] -BUCKETS = { - "s3": dict(store="s3", bucket=PUBLIC_DATA_BUCKET, region="us-east-1"), - "gcs": dict(store="gcs", bucket=PUBLIC_DATA_BUCKET + "-gcs", region="us-east1"), - "tigris": dict(store="tigris", bucket=PUBLIC_DATA_BUCKET + "-tigris", region="iad"), - "r2": dict(store="r2", bucket=PUBLIC_DATA_BUCKET + "-r2", region="us-east-1"), -} logger = setup_logger() -def set_env_credentials() -> tuple[str, str]: +def set_env_credentials() -> dict[str, str]: import boto3 session = boto3.Session() @@ -125,7 +108,7 @@ def with_extra( force_idempotent: bool = False, ) -> Self: if self.prefix is not None: - if force_idempotent and self.prefix.startswith(prefix): + if force_idempotent and prefix is not None and self.prefix.startswith(prefix): return self new_prefix = (prefix or "") + self.prefix else: @@ -163,7 +146,10 @@ def clear_uri(self) -> str: else: return f"{self.protocol}://{self.bucket}/{self.prefix}" - def get_coiled_kwargs(self) -> str: + def get_coiled_kwargs(self) -> dict[str, str]: + assert self.store is not None, ( + "store must be set before calling get_coiled_kwargs" + ) return get_coiled_kwargs(store=self.store, region=self.region) @@ -381,7 +367,7 @@ def setup_ingest_for_benchmarks(dataset: Dataset, *, ingest: IngestDataset) -> N def setup_era5(*args, **kwargs): - from benchmarks.create_era5 import setup_for_benchmarks + from benchmarks.create_era5 import setup_for_benchmarks # type: ignore[attr-defined] return setup_for_benchmarks(*args, **kwargs, arrays_to_write=[]) @@ -390,14 +376,9 @@ def setup_split_manifest_refs(dataset: Dataset, *, split_size: int | None): shape = (500_000 * 1000,) chunks = (1000,) + splitting = None if split_size is not None: - try: - splitting = get_splitting_config(split_size=split_size) - except ImportError: - logger.info("splitting not supported") - pytest.skip("splitting not supported on this version") - else: - splitting = None + splitting = get_splitting_config(split_size=split_size) config = repo_config_with(splitting=splitting) assert config is not None if hasattr(config.manifest, "splitting"): diff --git a/icechunk-python/benchmarks/helpers.py b/icechunk-python/benchmarks/helpers.py index ba03d7438..651ad5f9a 100644 --- a/icechunk-python/benchmarks/helpers.py +++ b/icechunk-python/benchmarks/helpers.py @@ -38,7 +38,7 @@ def get_coiled_kwargs(*, store: str, region: str | None = None) -> dict[str, str "gcs": "earthmover-devs-gcp", "az": "earthmover-devs-azure", } - TIGRIS_REGIONS = {"iad": "us-east-1"} + TIGRIS_REGIONS = {"us-east-2": "us-east-1", "iad": "us-east-1"} if region is None: region = DEFAULT_REGIONS[store] diff --git a/icechunk-python/benchmarks/runner.py b/icechunk-python/benchmarks/runner.py index 90022e1bb..c29f3aaaa 100644 --- a/icechunk-python/benchmarks/runner.py +++ b/icechunk-python/benchmarks/runner.py @@ -11,10 +11,12 @@ import tempfile import tomllib from collections.abc import Callable +from datetime import UTC, datetime from functools import partial import tqdm import tqdm.contrib.concurrent +from buckets import RESULT_STORE_URLS from helpers import ( assert_cwd_is_icechunk_python, get_coiled_kwargs, @@ -35,20 +37,24 @@ assert_cwd_is_icechunk_python() # Resolve AWS credentials eagerly at import time, before process_map forks -# child processes. boto3.Session() reads AWS_PROFILE / env vars / instance roles, -# but child processes spawned by ProcessPoolExecutor don't inherit the parent's -# env reliably, so we freeze the credentials here. +# child processes. ProcessPoolExecutor spawns fresh interpreters that may not +# have AWS_PROFILE available, so we freeze everything here. _AWS_CREDENTIALS: dict[str, str] = {} try: import boto3 - _creds = boto3.Session().get_credentials() + _session = boto3.Session() + _creds = _session.get_credentials() if _creds: _frozen = _creds.get_frozen_credentials() _AWS_CREDENTIALS["AWS_ACCESS_KEY_ID"] = _frozen.access_key _AWS_CREDENTIALS["AWS_SECRET_ACCESS_KEY"] = _frozen.secret_key if _frozen.token: _AWS_CREDENTIALS["AWS_SESSION_TOKEN"] = _frozen.token + _client = boto3.client("s3") + _endpoint = _client.meta.endpoint_url + if _endpoint and _endpoint != "https://s3.amazonaws.com": + _AWS_CREDENTIALS["AWS_ENDPOINT_URL"] = _endpoint except Exception: pass @@ -246,8 +252,8 @@ def get_coiled_run_args(self) -> tuple[str, ...]: def get_coiled_kwargs(self): # using the default region here kwargs = get_coiled_kwargs(store=self.where) - kwargs["software"] = f"icechunk-bench-{self.commit}" - kwargs["name"] = f"icebench-{self.commit}-{self.where}" + kwargs["software"] = f"icechunk-bench-{self.ref}" + kwargs["name"] = f"icebench-{self.ref}-{self.where}" kwargs["keepalive"] = "10m" return kwargs @@ -314,31 +320,49 @@ def _fetch_benchmark_json(self) -> None: """Fetch benchmark JSON: upload from VM to object store, download locally. Uses object_store_python which supports S3/GCS/Tigris/R2 with a - single API. The VM already has credentials for the target store. + single API. Region and endpoint_url are read from RESULT_STORE_URLS + and passed as ObjectStore options (no env var hacks needed). """ - from datasets import TEST_BUCKETS - - SCHEMES = {"s3": "s3", "gcs": "gs", "tigris": "s3", "r2": "s3"} - bucket_info = TEST_BUCKETS[self.where] - store_url = f"{SCHEMES[bucket_info['store']]}://{bucket_info['bucket']}" - - local_name = f"{self.where}_{self.clean_ref}_{self.commit}.json" + result_info = RESULT_STORE_URLS[self.where] + store_url = result_info["url"] + region = result_info["region"] + endpoint = result_info["endpoint_url"] + ts = datetime.now(UTC).strftime("%Y%m%d-%H%M") + local_name = f"{self.where}_{self.clean_ref}_{self.commit}_{ts}.json" obj_key = f"_bench_results/{self.save_prefix}/{local_name}" - # Upload from VM using object_store + # Upload from VM using object_store. + # The VM has forwarded AWS creds as env vars. We set region/endpoint + # as env vars too so ObjectStore picks up everything from the environment. + env_setup = f"os.environ['AWS_DEFAULT_REGION']='{region}'; " if region else "" + if endpoint: + env_setup += f"os.environ['AWS_ENDPOINT_URL']='{endpoint}'; " upload_cmd = ( 'python -c "' + "import os,glob; " + f"{env_setup}" "from object_store import ObjectStore; " - "import glob,os; " - "f=sorted(glob.glob('./.benchmarks/**/*',recursive=True),key=os.path.getmtime,reverse=True)[0]; " + "files=sorted(glob.glob('./.benchmarks/**/*',recursive=True),key=os.path.getmtime,reverse=True); " + "assert files, 'No benchmark JSON found in .benchmarks/'; " + "f=files[0]; " f"store=ObjectStore('{store_url}'); " f"store.put('{obj_key}',open(f,'rb').read())" '"' ) self.execute(upload_cmd, check=True) - # Download locally - store = ObjectStore(store_url) + # Download locally using options= so we don't pollute the process env. + # For S3-compatible stores, pass pre-resolved AWS credentials (ObjectStore + # doesn't support AWS_PROFILE). GCS uses application default credentials. + store_options: dict[str, str] = {} + if store_url.startswith("s3://"): + if region: + store_options["aws_default_region"] = region + if endpoint: + store_options["aws_endpoint"] = endpoint + for key, val in _AWS_CREDENTIALS.items(): + store_options[key.lower()] = val + store = ObjectStore(store_url, options=store_options) data = store.get(obj_key) local_dir = f"/tmp/benchmarks/{self.save_prefix}" @@ -393,7 +417,7 @@ def run_there(where: str, *, args, save_prefix) -> None: parser.add_argument("--serial", action="store_true", default=False) parser.add_argument( "--where", - help="where to run? [local|s3|s3_ob|gcs], combinations are allowed: [s3|gcs]", + help="where to run: local,s3,s3_ob,gcs,tigris,r2. Comma-separated for multiple.", default="local", ) parser.add_argument( @@ -404,10 +428,7 @@ def run_there(where: str, *, args, save_prefix) -> None: ) args = parser.parse_args() - if "|" in args.where: - where = args.where.split("|") - else: - where = (args.where,) + where = tuple(w.strip() for w in args.where.split(",")) save_prefix = rdms() From 8369b75e0d66305035327678c34e713ba356d594 Mon Sep 17 00:00:00 2001 From: Deepak Cherian Date: Fri, 20 Mar 2026 16:38:13 -0600 Subject: [PATCH 14/21] Fixes --- icechunk-python/benchmarks/runner.py | 21 +++++++-------------- icechunk-python/pyproject.toml | 2 ++ 2 files changed, 9 insertions(+), 14 deletions(-) diff --git a/icechunk-python/benchmarks/runner.py b/icechunk-python/benchmarks/runner.py index c29f3aaaa..35476e73d 100644 --- a/icechunk-python/benchmarks/runner.py +++ b/icechunk-python/benchmarks/runner.py @@ -93,7 +93,7 @@ class Runner: # Refs that install icechunk from PyPI instead of building from source. PYPI_REFS = { "pypi-nightly": "--pre icechunk", - "pypi-v1": "icechunk<2", + "pypi-v1": "icechunk==1.*", } def __init__(self, *, ref: str, where: str, save_prefix: str) -> None: @@ -365,7 +365,7 @@ def _fetch_benchmark_json(self) -> None: store = ObjectStore(store_url, options=store_options) data = store.get(obj_key) - local_dir = f"/tmp/benchmarks/{self.save_prefix}" + local_dir = f"./.benchmarks/{self.save_prefix}" os.makedirs(local_dir, exist_ok=True) local_path = f"{local_dir}/{local_name}" with open(local_path, "wb") as f: @@ -440,18 +440,11 @@ def run_there(where: str, *, args, save_prefix) -> None: refs = args.refs - if where == ("local",) and len(refs) > 1: - files = sorted( - glob.glob("./.benchmarks/**/*.json", recursive=True), - key=os.path.getmtime, - reverse=True, - )[: len(refs)] - else: - files = sorted( - glob.glob(f"/tmp/benchmarks/{save_prefix}/*.json", recursive=True), - key=os.path.getmtime, - reverse=True, - ) + files = sorted( + glob.glob("./.benchmarks/**/*.json", recursive=True), + key=os.path.getmtime, + reverse=True, + )[: len(refs) * len(where)] # TODO: Use `just` here when we figure that out. subprocess.run( [ diff --git a/icechunk-python/pyproject.toml b/icechunk-python/pyproject.toml index b90d0cf7e..dfdcd45cd 100644 --- a/icechunk-python/pyproject.toml +++ b/icechunk-python/pyproject.toml @@ -55,6 +55,8 @@ test = [ benchmark = [ "boto3", "object-store-python", + "s3fs", + "gcsfs", "pytest-benchmark[histogram]", "h5netcdf", "pooch", From 0cf6d8fed251d55a6162e48077189b71a8c10aad Mon Sep 17 00:00:00 2001 From: Deepak Cherian Date: Fri, 20 Mar 2026 16:50:55 -0600 Subject: [PATCH 15/21] Switch to object_store --- icechunk-python/benchmarks/datasets.py | 55 +++++++++++--------------- icechunk-python/pyproject.toml | 2 - 2 files changed, 24 insertions(+), 33 deletions(-) diff --git a/icechunk-python/benchmarks/datasets.py b/icechunk-python/benchmarks/datasets.py index 99407ca60..b5a6ddcfa 100644 --- a/icechunk-python/benchmarks/datasets.py +++ b/icechunk-python/benchmarks/datasets.py @@ -7,8 +7,8 @@ from functools import partial from typing import Any, Literal, Self, TypeAlias -import fsspec import numpy as np +from object_store import ObjectStore import icechunk as ic import icechunk.xarray @@ -129,22 +129,30 @@ def env_vars(self) -> dict[str, str]: # return {"AWS_ENDPOINT_URL_IAM": "https://fly.iam.storage.tigris.dev"} return {} - @property - def protocol(self) -> str: - if self.store in ("s3", "tigris"): - protocol = "s3" - elif self.store == "gcs": - protocol = "gcs" - else: - protocol = "file" - return protocol + _SCHEMES = {"s3": "s3", "s3_ob": "s3", "gcs": "gs", "tigris": "s3", "r2": "s3"} + + def clear_prefix(self) -> None: + """Delete all objects under this prefix using object_store.""" + import shutil - def clear_uri(self) -> str: - """URI to clear when re-creating data from scratch.""" if self.store == "local": - return f"{self.protocol}://{self.path}" - else: - return f"{self.protocol}://{self.bucket}/{self.prefix}" + shutil.rmtree(self.path, ignore_errors=True) + return + + scheme = self._SCHEMES.get(self.store) + if scheme is None: + warnings.warn( + f"Clearing not supported for store {self.store!r}", + RuntimeWarning, + stacklevel=2, + ) + return + + store_url = f"{scheme}://{self.bucket}" + store = ObjectStore(store_url) + objects = store.list(prefix=self.prefix) + for obj in objects: + store.delete(obj["path"]) def get_coiled_kwargs(self) -> dict[str, str]: assert self.store is not None, ( @@ -174,22 +182,7 @@ def create( self, clear: bool = False, config: ic.RepositoryConfig | None = None ) -> ic.Repository: if clear: - clear_uri = self.storage_config.clear_uri() - if clear_uri is None: - raise NotImplementedError - if self.storage_config.protocol not in ["file", "s3", "gcs"]: - warnings.warn( - f"Only clearing of GCS, S3-compatible URIs supported at the moment. Received {clear_uri!r}", - RuntimeWarning, - stacklevel=2, - ) - else: - fs = fsspec.filesystem(self.storage_config.protocol) - try: - logger.info(f"Clearing prefix: {clear_uri!r}") - fs.rm(clear_uri, recursive=True) - except FileNotFoundError: - pass + self.storage_config.clear_prefix() logger.info(repr(self.storage)) return ic.Repository.create(self.storage, config=config) diff --git a/icechunk-python/pyproject.toml b/icechunk-python/pyproject.toml index dfdcd45cd..b90d0cf7e 100644 --- a/icechunk-python/pyproject.toml +++ b/icechunk-python/pyproject.toml @@ -55,8 +55,6 @@ test = [ benchmark = [ "boto3", "object-store-python", - "s3fs", - "gcsfs", "pytest-benchmark[histogram]", "h5netcdf", "pooch", From 39d45b4624b8cd5c9c92aaa7c2a2b96dbb5b32d6 Mon Sep 17 00:00:00 2001 From: Deepak Cherian Date: Mon, 23 Mar 2026 08:50:00 -0600 Subject: [PATCH 16/21] small edits --- icechunk-python/benchmarks/conftest.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/icechunk-python/benchmarks/conftest.py b/icechunk-python/benchmarks/conftest.py index 7d1299af5..7fd2e594f 100644 --- a/icechunk-python/benchmarks/conftest.py +++ b/icechunk-python/benchmarks/conftest.py @@ -5,6 +5,8 @@ from benchmarks import helpers from benchmarks.datasets import ( + GB_8MB_CHUNKS, + GB_128MB_CHUNKS, LARGE_1D, LARGE_MANIFEST_SHARDED, LARGE_MANIFEST_UNSHARDED, @@ -75,8 +77,8 @@ def large_write_dataset(request) -> BenchmarkWriteDataset: @pytest.fixture( params=[ - # pytest.param(GB_8MB_CHUNKS, id="gb-8mb"), - # pytest.param(GB_128MB_CHUNKS, id="gb-128mb"), + pytest.param(GB_8MB_CHUNKS, id="gb-8mb"), + pytest.param(GB_128MB_CHUNKS, id="gb-128mb"), # pytest.param(ERA5_SINGLE, id="era5-single"), # pytest.param(ERA5, id="era5-weatherbench"), # pytest.param(ERA5_ARCO, id="era5-arco"), From 5f2236a002b2322d5e2670c72abd44fce6b54c35 Mon Sep 17 00:00:00 2001 From: Deepak Cherian Date: Mon, 23 Mar 2026 21:38:37 -0600 Subject: [PATCH 17/21] Add bench-report skill --- .claude/skills/bench-report.md | 67 ++++++++++++++++++++++++++++ icechunk-python/benchmarks/README.md | 12 +++++ icechunk-python/benchmarks/runner.py | 3 ++ 3 files changed, 82 insertions(+) create mode 100644 .claude/skills/bench-report.md diff --git a/.claude/skills/bench-report.md b/.claude/skills/bench-report.md new file mode 100644 index 000000000..c046644f6 --- /dev/null +++ b/.claude/skills/bench-report.md @@ -0,0 +1,67 @@ +--- +name: bench-report +description: Generate an HTML benchmark comparison report from pytest-benchmark JSON files +user_invocable: true +argument_prompt: "Directory containing benchmark JSON files (default: .benchmarks/)" +--- + +Generate a self-contained HTML benchmark report with two views (Tables and Box Plots) from pytest-benchmark JSON files. + +## Steps + +1. Glob `{directory}/*.json` to find all benchmark result files. If the user provided no directory, use `.benchmarks/`. + +2. Parse each filename to extract the store and version. Filenames follow the pattern `{store}_{version}_{version}_{timestamp}.json` where `{version}` appears twice. The store portion is everything before the first `pypi-` token. For example `s3_pypi-nightly_pypi-nightly_20260320-2224.json` has store=`s3` and version=`nightly`; `s3_ob_pypi-v1_pypi-v1_20260323-1959.json` has store=`s3-object-store` and version=`v1`. Rename `s3_ob` → `s3-object-store` for display. Also handle filenames from pytest-benchmark's `--benchmark-save` which look like `Linux-CPython-3.14-64bit/0001_s3_pypi-nightly_pypi-nightly.json`. + +3. For each unique `{store}_{version}` combination, extract benchmark data — need full box-plot stats: + ``` + jq -s '[.[] | .benchmarks[] | {name: .name, group: .group, min: .stats.min, q1: .stats.q1, median: .stats.median, q3: .stats.q3, max: .stats.max, rounds: .stats.rounds}]' + ``` + +4. Combine all extracted data into a single JSON object keyed by `{store}_{version}` (e.g. `s3_nightly`, `gcs_v1`, `s3-object-store_nightly`). + +5. Read the HTML template from `.benchmarks/report.html`. Find the existing `const DATA = ...;` block and replace it with the new combined JSON. If the template doesn't exist, inform the user it needs to be created first. + +6. Write the updated HTML back to `.benchmarks/report.html`. + +7. Open the report in the browser with `open .benchmarks/report.html`. + +## Report layout + +The report is a single-page self-contained HTML file (no external dependencies). It has two top-level view modes toggled by prominent buttons: **Tables** and **Box Plots**. + +### Shared controls (always visible) + +- **Reads / Writes toggle** — splits benchmarks into reads (`test_time_*`) and writes (`test_write_*`, `test_set_*`). Everything re-renders based on selection. +- **Text search** filter +- **Group sections** — benchmarks grouped by their `group` field. For reads: Zarr (`zarr-read`), Xarray (`xarray-read`), Other (null group). For writes: Refs (`refs-write`), Other (null group). Each group gets a colored section/header row. + +### Tables view + +Contains sub-tabs: + +- **Summary cards** with geometric mean ratios across stores and versions (scoped to current read/write selection) +- **v1 vs nightly tab** — compares versions per store with ratio bars. Group header rows. +- **S3 vs S3-object-store vs GCS tab** — compares stores with S3 as baseline (ob/s3 and gcs/s3 ratios). Group header rows. +- **All Results tab** — flat table with best values highlighted. Group header rows. +- **"Hide results within 15%" checkbox** +- **Ratio toggle**: flip between nightly/v1 and v1/nightly direction + +### Box Plots view + +- **Color-coded legend** at top — one color per `{store}_{version}` key. Fixed color map: + - `s3_v1`: blue, `s3_nightly`: green + - `s3-object-store_v1`: orange, `s3-object-store_nightly`: pink + - `gcs_v1`: purple, `gcs_nightly`: yellow +- **One card per benchmark** containing: + - Benchmark function name + labeled pills + - Horizontal box & whisker canvas plot with all store/version combos stacked vertically + - Shared x-axis with auto-scaled units (μs, ms, s) and grid lines + - Each box: whisker min→Q1, box Q1→Q3, thick median line, whisker Q3→max, caps + - Labels on left show `{store}_{version}` key + +### Labeled colored pills (both views) + +Each fixture parameter shown as `fixture: value` pill (e.g. `dataset: gb-8mb`, `preload: default`, `commit: True`). Distinct color per fixture type. Compound tokens kept as single pills using greedy longest-match against known tokens. + +Token-to-fixture mapping: datasets (`gb-8mb`, `gb-128mb`, `large-manifest-*`, `simple-1d`, `large-1d`, `pancake-writes`), preload (`default`, `off`), selector (`single-chunk`, `full-read`), splitting (`no-splitting`, `split-size-10_000`), config (`default-inlined`, `not-inlined`), commit (`True`, `False`), executor (`threads`). diff --git a/icechunk-python/benchmarks/README.md b/icechunk-python/benchmarks/README.md index ccfeefe27..cd353a6bc 100644 --- a/icechunk-python/benchmarks/README.md +++ b/icechunk-python/benchmarks/README.md @@ -252,6 +252,18 @@ test_write_simple_1d[simple-1d] (g/s3_ob_main_9) 1.6909 (1.02) --------------------------------------------------------------------- ``` +### Generating an HTML comparison report + +Collect the benchmark JSON files you want to compare into a subdirectory under `.benchmarks/`, then use the `/bench-report` Claude Code skill: + +```sh +# Example: compare nightly vs v1 across stores +mkdir -p .benchmarks/march-comparison +cp .benchmarks/s3_pypi-nightly*.json .benchmarks/gcs_pypi-v1*.json .benchmarks/march-comparison/ +``` + +Then in Claude Code run `/bench-report .benchmarks/march-comparison`. This generates a self-contained `report.html` with interactive tables, geometric mean summaries, and filterable comparisons. + ## Design decisions / future choices 1. We chose `pytest-benchmark` instead of `asv` because it seemed easier to learn --- all our pytest knowledge and idioms carry over (e.g. fixtures, `-k` to subselect benchmarks to run, `-s` to print stdout/sterr etc.). For example `pytest -nauto -m setup_benchmarks benchmarks` gives easy selection and parallelization of setup steps! diff --git a/icechunk-python/benchmarks/runner.py b/icechunk-python/benchmarks/runner.py index 35476e73d..dfd047b9e 100644 --- a/icechunk-python/benchmarks/runner.py +++ b/icechunk-python/benchmarks/runner.py @@ -55,6 +55,9 @@ _endpoint = _client.meta.endpoint_url if _endpoint and _endpoint != "https://s3.amazonaws.com": _AWS_CREDENTIALS["AWS_ENDPOINT_URL"] = _endpoint + _region = _session.region_name + if _region: + _AWS_CREDENTIALS["AWS_REGION"] = _region except Exception: pass From 8850efcb024b3c446693ca8167920623dd7b5fb8 Mon Sep 17 00:00:00 2001 From: Deepak Cherian Date: Mon, 23 Mar 2026 22:18:10 -0600 Subject: [PATCH 18/21] update readme --- icechunk-python/benchmarks/README.md | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/icechunk-python/benchmarks/README.md b/icechunk-python/benchmarks/README.md index cd353a6bc..aed81670c 100644 --- a/icechunk-python/benchmarks/README.md +++ b/icechunk-python/benchmarks/README.md @@ -112,8 +112,9 @@ test_time_getsize_prefix[era5-single] (NOW) 2.2133 (1.0) ### Notes ### Where to run the benchmarks? -- Pass the `--where` flag to control where benchmarks are run: `local`, `s3`, `s3_ob`, `gcs`, `tigris`, `r2`. +- Pass the `--where` flag to control where benchmarks are run: `local`, `s3`, `s3_ob`, `gcs`. - `s3_ob` uses the `s3_object_store_storage` constructor. +- `tigris` and `r2` are defined but **non-functional** at the moment. - Comma-separate for multiple stores: `--where s3,gcs` ```sh @@ -127,7 +128,7 @@ By default all benchmarks are run locally: It is possible to run the benchmarks in the cloud using Coiled. You will need to be a member of the Coiled workspaces: `earthmover-devs` (AWS), `earthmover-devs-gcp` (GCS) and `earthmover-devs-azure` (Azure). 1. We create a new "coiled software environment" with a specific name. 2. We use `coiled run` targeting a specific machine type, with a specific software env. -3. Two special refs install icechunk from PyPI instead of building from source: +3. Only benchmarking against nightly wheels is currently supported. Building from source (arbitrary git refs) is not functional at the moment. The two supported refs are: - `pypi-nightly` — installs the latest pre-release from the [scientific-python-nightly-wheels](https://pypi.anaconda.org/scientific-python-nightly-wheels/simple) index - `pypi-v1` — installs the latest stable v1 release (`icechunk<2`) 4. The VM stays alive for 10 minutes to allow for quick iteration. @@ -155,14 +156,15 @@ Datasets are written to `{bucket}/benchmarks/{REF}_{SHORTCOMMIT}/` where the buc Usage: ``` sh -python benchmarks/runner.py v0.1.2 main +python benchmarks/runner.py --where s3,gcs pypi-nightly pypi-v1 ``` This will -1. setup a virtual env with the icechunk version -2. compile it, -3. run `setup_benchmarks`. This will recreate datasets if the version in the bucket cannot be opened by this icechunk version. Pass `--setup=force` to always recreate, or `--setup=skip` to skip setup entirely. -4. Runs the benchmarks. -5. Compares the benchmarks. +1. setup a virtual env with the icechunk version (from PyPI wheels) +2. run `setup_benchmarks`. This will recreate datasets if the version in the bucket cannot be opened by this icechunk version. Pass `--setup=force` to always recreate, or `--setup=skip` to skip setup entirely. +3. Runs the benchmarks. +4. Compares the benchmarks. + +> **Note:** Only `pypi-nightly` and `pypi-v1` are supported as refs. Building from arbitrary git refs/tags is not functional at the moment. ### Just recipes From 7acd863e3b0098682056aa184a0f2f7d57df5af5 Mon Sep 17 00:00:00 2001 From: Deepak Cherian Date: Mon, 23 Mar 2026 22:23:25 -0600 Subject: [PATCH 19/21] update lock file --- icechunk-python/pixi.lock | 417 +------------------------------------- 1 file changed, 4 insertions(+), 413 deletions(-) diff --git a/icechunk-python/pixi.lock b/icechunk-python/pixi.lock index 5ceaf806d..f07f25e5b 100644 --- a/icechunk-python/pixi.lock +++ b/icechunk-python/pixi.lock @@ -41,10 +41,8 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_h366c992_103.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb78ec9c_6.conda - - pypi: https://files.pythonhosted.org/packages/02/78/79aa8169408996f5a71150abdea2e5e0f364df250c9e54ac385f115c7436/aiobotocore-3.2.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7c/24/75d274228acf35ceeb2850b8ce04de9dd7355ff7a0b49d607ee60c29c518/aiohttp-3.13.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/10/a1/510b0a7fadc6f43a6ce50152e69dbd86415240835868bb0bd9b5b88b1e06/aioitertools-0.13.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/38/0e/27be9fdef66e72d64c0cdc3cc2823101b80585f8119b5c112c2e8f5f7dab/anyio-4.12.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d2/39/e7eaf1799466a4aef85b6a4fe7bd175ad2b1c6345066aa33f1f58d4b18d0/asttokens-3.0.1-py3-none-any.whl @@ -65,26 +63,13 @@ environments: - pypi: https://files.pythonhosted.org/packages/84/d0/205d54408c08b13550c733c4b85429e7ead111c7f0014309637425520a9a/deprecated-1.3.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ad/14/0fe5889a83991ac29c93e6b2e121ad2afc3bff5f9327f34447d3068d8142/distributed-2026.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0c/d5/c5db1ea3394c6e1732fb3286b3bd878b59507a8f77d32a2cebda7d7b7cd4/donfig-0.8.1.post1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ab/84/02fc1827e8cdded4aa65baef11296a9bbe595c474f0d6d758af082d849fd/execnet-2.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d6/1f/e99e23ee01847147fa194e8d41cfcf2535a2dbfcb51414c541cadb15c5d7/fabric-3.2.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f9/0b/de6f54d4a8bedfe8645c41497f3c18d749f0bd3218170c667bf4b81d0cdd/filelock-3.25.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d5/4e/e4691508f9477ce67da2015d8c00acd751e6287739123113a9fca6f1604e/frozenlist-1.8.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl - pypi: https://files.pythonhosted.org/packages/e6/ab/fb21f4c939bb440104cc2b396d3be1d9b7a9fd3c6c2a53d98c45b3d7c954/fsspec-2026.2.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/9c/6b/c2f68ac51229fc94f094c7f802648fc1de3d19af36434def5e64c0caa32b/gcsfs-2026.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/55/91/2f7285d8802aa40e0b37673783209d5cd7cfc8d7229bfff82c795329599e/gilknocker-0.4.2-cp313-cp313-manylinux_2_24_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/45/27/09c33d67f7e0dcf06d7ac17d196594e66989299374bfb0d4331d1038e76b/google_api_core-2.30.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/83/1d/d6466de3a5249d35e832a52834115ca9d1d0de6abc22065f049707516d47/google_auth-2.48.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/2f/56/909fd5632226d3fba31d7aeffd4754410735d49362f5809956fe3e9af344/google_auth_oauthlib-1.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/89/20/bfa472e327c8edee00f04beecc80baeddd2ab33ee0e86fd7654da49d45e9/google_cloud_core-2.5.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/46/0b/816a6ae3c9fd096937d2e5f9670558908811d57d59ddf69dd4b83b326fd1/google_cloud_storage-3.9.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/8e/04/96a674d4ee90eed4e99c0f4faec21c9bbe1a470d37a4757508e90e31f5b9/google_cloud_storage_control-1.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ce/42/b468aec74a0354b34c8cbf748db20d6e350a68a2b0912e128cabee49806c/google_crc32c-1.8.0-cp313-cp313-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/1f/0b/93afde9cfe012260e9fe1522f35c9b72d6ee222f316586b1f23ecf44d518/google_resumable_media-2.8.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c4/ab/09169d5a4612a5f92490806649ac8d41e3ec9129c636754575b3553f4ea4/googleapis_common_protos-1.72.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/4a/bd/330a1bbdb1afe0b96311249e699b6dc9cfc17916394fd4503ac5aca2514b/grpc_google_iam_v1-0.14.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/a6/62/cc03fffb07bfba982a9ec097b164e8835546980aec25ecfa5f9c1a47e022/grpcio-1.78.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/83/8a/1241ec22c41028bddd4a052ae9369267b4475265ad0ce7140974548dc3fa/grpcio_status-1.78.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/69/b2/119f6e6dcbd96f9069ce9a2665e0146588dc9f88f29549711853645e736a/h2-4.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b1/8b/88f16936a8e8070a83d36239555227ecd91728f9ef222c5382cda07e0fd6/h5netcdf-1.8.1-py3-none-any.whl @@ -113,7 +98,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/5d/ba/459f18c16f2b3fc1a1ca871f72f07d70c07bf768ad0a507a698b8052ac58/msgpack-1.1.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/b0/73/6e1b01cbeb458807aa0831742232dbdd1fa92bfa33f52a3f176b4ff3dc11/multidict-6.7.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/c4/e6/d359fdd37498e74d26a167f7a51e54542e642ea47181eb4e643a69a066c3/numcodecs-0.16.5-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/be/9c/92789c596b8df838baa98fa71844d84283302f7604ed565dafe5a6b5041a/oauthlib-3.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/57/bc/d44f9f9e651fbf4a048a345ab20bd860b946f47c5d0cca30cb8af622f3bc/object_store_python-0.1.10-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/a9/90/a744336f5af32c433bd09af7854599682a383b37cfd78f7de263de6ad6cb/paramiko-4.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b6/61/fae042894f4296ec49e3f193aff5d7c18440da9e48102c3315e1bc4519a7/parso-0.8.6-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/71/e7/40fb618334dcdf7c5a316c0e7343c5cd82d3d866edc100d98e29bc945ecd/partd-1.4.2-py3-none-any.whl @@ -126,14 +111,10 @@ environments: - pypi: https://files.pythonhosted.org/packages/74/c3/24a2f845e3917201628ecaba4f18bab4d18a337834c1df2a159ee9d22a42/prometheus_client-0.24.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f1/8b/544bc867e24e1bd48f3118cecd3b05c694e160a168478fa28770f22fd094/propcache-0.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/5d/79/ac273cbbf744691821a9cca88957257f41afe271637794975ca090b9588b/proto_plus-1.27.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/9b/53/a9443aa3ca9ba8724fdfa02dd1887c1bcd8e89556b715cfbacca6b63dbec/protobuf-6.33.5-cp39-abi3-manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/b5/70/5d8df3b09e25bce090399cf48e452d25c935ab72dad19406c77f4e828045/psutil-7.2.2-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/22/a6/858897256d0deac81a172289110f31629fc4cee19b6f01283303e18c8db3/ptyprocess-0.7.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e0/a9/023730ba63db1e494a271cb018dcd361bd2c917ba7004c3e49d5daf795a2/py_cpuinfo-9.0.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/44/b5/a96872e5184f354da9c84ae119971a0a4c221fe9b27a4d94bd43f2596727/pyasn1-0.6.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/47/8d/d529b5d697919ba8c11ad626e835d4039be708a35b0d22de83a269a6682c/pyasn1_modules-0.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0b/4c/2862dd25352fe4b22ec7760d4fa12cb692587cd7ec3e378cbf644fc0d2a8/pygal-3.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/49/6f/07dab31ca496feda35cf3455b9e9380c43b5c685bb54ad890831c790da38/pygaljs-1.0.2-py2.py3-none-any.whl @@ -142,14 +123,10 @@ environments: - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/33/29/e756e715a48959f1c0045342088d7ca9762a2f509b945f362a316e9412b7/pytest_benchmark-5.2.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ca/31/d4e37e9e550c2b92a9cbc2e4d0b7420a27224968580b5a447f420847c975/pytest_xdist-3.8.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3b/5d/63d4ae3b9daea098d5d6f5da83984853c1bbacd5dc826764b249fe119d24/requests_oauthlib-2.0.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/14/25/b208c5683343959b670dc001595f2f3737e051da617f66c31f7c4fa93abc/rich-14.3.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/64/8d/0133e4eb4beed9e425d9a98ed6e081a55d195481b7632472be1af08d2f6b/rsa-4.9.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/57/e1/64c264db50b68de8a438b60ceeb921b2f22da3ebb7ad6255150225d0beac/s3fs-2026.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fc/51/727abb13f44c1fcf6d145979e1535a35794db0f6e450a0cb46aa24732fe2/s3transfer-0.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e1/c6/76dc613121b793286a3f91621d7b75a2b493e0390ddca50f11993eadf192/setuptools-82.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl @@ -204,10 +181,8 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/tk-8.6.13-noxft_h0dc03b3_103.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/zstd-1.5.7-h85ac4a6_6.conda - - pypi: https://files.pythonhosted.org/packages/02/78/79aa8169408996f5a71150abdea2e5e0f364df250c9e54ac385f115c7436/aiobotocore-3.2.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/6d/40/a46b03ca03936f832bc7eaa47cfbb1ad012ba1be4790122ee4f4f8cba074/aiohttp-3.13.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl - - pypi: https://files.pythonhosted.org/packages/10/a1/510b0a7fadc6f43a6ce50152e69dbd86415240835868bb0bd9b5b88b1e06/aioitertools-0.13.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/38/0e/27be9fdef66e72d64c0cdc3cc2823101b80585f8119b5c112c2e8f5f7dab/anyio-4.12.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d2/39/e7eaf1799466a4aef85b6a4fe7bd175ad2b1c6345066aa33f1f58d4b18d0/asttokens-3.0.1-py3-none-any.whl @@ -228,26 +203,13 @@ environments: - pypi: https://files.pythonhosted.org/packages/84/d0/205d54408c08b13550c733c4b85429e7ead111c7f0014309637425520a9a/deprecated-1.3.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ad/14/0fe5889a83991ac29c93e6b2e121ad2afc3bff5f9327f34447d3068d8142/distributed-2026.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0c/d5/c5db1ea3394c6e1732fb3286b3bd878b59507a8f77d32a2cebda7d7b7cd4/donfig-0.8.1.post1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ab/84/02fc1827e8cdded4aa65baef11296a9bbe595c474f0d6d758af082d849fd/execnet-2.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d6/1f/e99e23ee01847147fa194e8d41cfcf2535a2dbfcb51414c541cadb15c5d7/fabric-3.2.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f9/0b/de6f54d4a8bedfe8645c41497f3c18d749f0bd3218170c667bf4b81d0cdd/filelock-3.25.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/40/76/c202df58e3acdf12969a7895fd6f3bc016c642e6726aa63bd3025e0fc71c/frozenlist-1.8.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl - pypi: https://files.pythonhosted.org/packages/e6/ab/fb21f4c939bb440104cc2b396d3be1d9b7a9fd3c6c2a53d98c45b3d7c954/fsspec-2026.2.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/9c/6b/c2f68ac51229fc94f094c7f802648fc1de3d19af36434def5e64c0caa32b/gcsfs-2026.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8f/02/32030edd0d5cf517e7f057407c024051dedceb99c02dc9581e0a31241752/gilknocker-0.4.2-cp313-cp313-manylinux_2_24_aarch64.whl - - pypi: https://files.pythonhosted.org/packages/45/27/09c33d67f7e0dcf06d7ac17d196594e66989299374bfb0d4331d1038e76b/google_api_core-2.30.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/83/1d/d6466de3a5249d35e832a52834115ca9d1d0de6abc22065f049707516d47/google_auth-2.48.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/2f/56/909fd5632226d3fba31d7aeffd4754410735d49362f5809956fe3e9af344/google_auth_oauthlib-1.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/89/20/bfa472e327c8edee00f04beecc80baeddd2ab33ee0e86fd7654da49d45e9/google_cloud_core-2.5.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/46/0b/816a6ae3c9fd096937d2e5f9670558908811d57d59ddf69dd4b83b326fd1/google_cloud_storage-3.9.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/8e/04/96a674d4ee90eed4e99c0f4faec21c9bbe1a470d37a4757508e90e31f5b9/google_cloud_storage_control-1.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1c/e8/b33784d6fc77fb5062a8a7854e43e1e618b87d5ddf610a88025e4de6226e/google_crc32c-1.8.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl - - pypi: https://files.pythonhosted.org/packages/1f/0b/93afde9cfe012260e9fe1522f35c9b72d6ee222f316586b1f23ecf44d518/google_resumable_media-2.8.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c4/ab/09169d5a4612a5f92490806649ac8d41e3ec9129c636754575b3553f4ea4/googleapis_common_protos-1.72.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/4a/bd/330a1bbdb1afe0b96311249e699b6dc9cfc17916394fd4503ac5aca2514b/grpc_google_iam_v1-0.14.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/88/93/b59fe7832ff6ae3c78b813ea43dac60e295fa03606d14d89d2e0ec29f4f3/grpcio-1.78.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl - - pypi: https://files.pythonhosted.org/packages/83/8a/1241ec22c41028bddd4a052ae9369267b4475265ad0ce7140974548dc3fa/grpcio_status-1.78.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/69/b2/119f6e6dcbd96f9069ce9a2665e0146588dc9f88f29549711853645e736a/h2-4.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b1/8b/88f16936a8e8070a83d36239555227ecd91728f9ef222c5382cda07e0fd6/h5netcdf-1.8.1-py3-none-any.whl @@ -276,7 +238,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/d3/68/93180dce57f684a61a88a45ed13047558ded2be46f03acb8dec6d7c513af/msgpack-1.1.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl - pypi: https://files.pythonhosted.org/packages/f6/32/befed7f74c458b4a525e60519fe8d87eef72bb1e99924fa2b0f9d97a221e/multidict-6.7.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl - pypi: https://files.pythonhosted.org/packages/0b/00/787ea5f237b8ea7bc67140c99155f9c00b5baf11c49afc5f3bfefa298f95/numcodecs-0.16.5-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl - - pypi: https://files.pythonhosted.org/packages/be/9c/92789c596b8df838baa98fa71844d84283302f7604ed565dafe5a6b5041a/oauthlib-3.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/77/cd/4da4d5d16bc1c99d21e0f94a21400c8ba1f1fbf4cfba10b086b070e5c1ee/object_store_python-0.1.10-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl - pypi: https://files.pythonhosted.org/packages/a9/90/a744336f5af32c433bd09af7854599682a383b37cfd78f7de263de6ad6cb/paramiko-4.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b6/61/fae042894f4296ec49e3f193aff5d7c18440da9e48102c3315e1bc4519a7/parso-0.8.6-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/71/e7/40fb618334dcdf7c5a316c0e7343c5cd82d3d866edc100d98e29bc945ecd/partd-1.4.2-py3-none-any.whl @@ -289,14 +251,10 @@ environments: - pypi: https://files.pythonhosted.org/packages/74/c3/24a2f845e3917201628ecaba4f18bab4d18a337834c1df2a159ee9d22a42/prometheus_client-0.24.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2d/48/c5ac64dee5262044348d1d78a5f85dd1a57464a60d30daee946699963eb3/propcache-0.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl - - pypi: https://files.pythonhosted.org/packages/5d/79/ac273cbbf744691821a9cca88957257f41afe271637794975ca090b9588b/proto_plus-1.27.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/4e/b1/c79468184310de09d75095ed1314b839eb2f72df71097db9d1404a1b2717/protobuf-6.33.5-cp39-abi3-manylinux2014_aarch64.whl - pypi: https://files.pythonhosted.org/packages/63/65/37648c0c158dc222aba51c089eb3bdfa238e621674dc42d48706e639204f/psutil-7.2.2-cp36-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl - pypi: https://files.pythonhosted.org/packages/22/a6/858897256d0deac81a172289110f31629fc4cee19b6f01283303e18c8db3/ptyprocess-0.7.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e0/a9/023730ba63db1e494a271cb018dcd361bd2c917ba7004c3e49d5daf795a2/py_cpuinfo-9.0.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/44/b5/a96872e5184f354da9c84ae119971a0a4c221fe9b27a4d94bd43f2596727/pyasn1-0.6.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/47/8d/d529b5d697919ba8c11ad626e835d4039be708a35b0d22de83a269a6682c/pyasn1_modules-0.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0b/4c/2862dd25352fe4b22ec7760d4fa12cb692587cd7ec3e378cbf644fc0d2a8/pygal-3.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/49/6f/07dab31ca496feda35cf3455b9e9380c43b5c685bb54ad890831c790da38/pygaljs-1.0.2-py2.py3-none-any.whl @@ -305,14 +263,10 @@ environments: - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/33/29/e756e715a48959f1c0045342088d7ca9762a2f509b945f362a316e9412b7/pytest_benchmark-5.2.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ca/31/d4e37e9e550c2b92a9cbc2e4d0b7420a27224968580b5a447f420847c975/pytest_xdist-3.8.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl - pypi: https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3b/5d/63d4ae3b9daea098d5d6f5da83984853c1bbacd5dc826764b249fe119d24/requests_oauthlib-2.0.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/14/25/b208c5683343959b670dc001595f2f3737e051da617f66c31f7c4fa93abc/rich-14.3.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/64/8d/0133e4eb4beed9e425d9a98ed6e081a55d195481b7632472be1af08d2f6b/rsa-4.9.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/57/e1/64c264db50b68de8a438b60ceeb921b2f22da3ebb7ad6255150225d0beac/s3fs-2026.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fc/51/727abb13f44c1fcf6d145979e1535a35794db0f6e450a0cb46aa24732fe2/s3transfer-0.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e1/c6/76dc613121b793286a3f91621d7b75a2b493e0390ddca50f11993eadf192/setuptools-82.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl @@ -364,10 +318,8 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/readline-8.3-h46df422_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tk-8.6.13-h010d191_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda - - pypi: https://files.pythonhosted.org/packages/02/78/79aa8169408996f5a71150abdea2e5e0f364df250c9e54ac385f115c7436/aiobotocore-3.2.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/17/f8/8dd2cf6112a5a76f81f81a5130c57ca829d101ad583ce57f889179accdda/aiohttp-3.13.3-cp313-cp313-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/10/a1/510b0a7fadc6f43a6ce50152e69dbd86415240835868bb0bd9b5b88b1e06/aioitertools-0.13.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/38/0e/27be9fdef66e72d64c0cdc3cc2823101b80585f8119b5c112c2e8f5f7dab/anyio-4.12.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d2/39/e7eaf1799466a4aef85b6a4fe7bd175ad2b1c6345066aa33f1f58d4b18d0/asttokens-3.0.1-py3-none-any.whl @@ -388,26 +340,13 @@ environments: - pypi: https://files.pythonhosted.org/packages/84/d0/205d54408c08b13550c733c4b85429e7ead111c7f0014309637425520a9a/deprecated-1.3.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ad/14/0fe5889a83991ac29c93e6b2e121ad2afc3bff5f9327f34447d3068d8142/distributed-2026.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0c/d5/c5db1ea3394c6e1732fb3286b3bd878b59507a8f77d32a2cebda7d7b7cd4/donfig-0.8.1.post1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ab/84/02fc1827e8cdded4aa65baef11296a9bbe595c474f0d6d758af082d849fd/execnet-2.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d6/1f/e99e23ee01847147fa194e8d41cfcf2535a2dbfcb51414c541cadb15c5d7/fabric-3.2.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f9/0b/de6f54d4a8bedfe8645c41497f3c18d749f0bd3218170c667bf4b81d0cdd/filelock-3.25.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0c/ab/6e5080ee374f875296c4243c381bbdef97a9ac39c6e3ce1d5f7d42cb78d6/frozenlist-1.8.0-cp313-cp313-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/e6/ab/fb21f4c939bb440104cc2b396d3be1d9b7a9fd3c6c2a53d98c45b3d7c954/fsspec-2026.2.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/9c/6b/c2f68ac51229fc94f094c7f802648fc1de3d19af36434def5e64c0caa32b/gcsfs-2026.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/66/14/ae607709ddc328f9325da59105668def6e525a42e79ef6471b1e055d0946/gilknocker-0.4.2-cp313-cp313-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl - - pypi: https://files.pythonhosted.org/packages/45/27/09c33d67f7e0dcf06d7ac17d196594e66989299374bfb0d4331d1038e76b/google_api_core-2.30.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/83/1d/d6466de3a5249d35e832a52834115ca9d1d0de6abc22065f049707516d47/google_auth-2.48.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/2f/56/909fd5632226d3fba31d7aeffd4754410735d49362f5809956fe3e9af344/google_auth_oauthlib-1.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/89/20/bfa472e327c8edee00f04beecc80baeddd2ab33ee0e86fd7654da49d45e9/google_cloud_core-2.5.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/46/0b/816a6ae3c9fd096937d2e5f9670558908811d57d59ddf69dd4b83b326fd1/google_cloud_storage-3.9.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/8e/04/96a674d4ee90eed4e99c0f4faec21c9bbe1a470d37a4757508e90e31f5b9/google_cloud_storage_control-1.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d1/db/000f15b41724589b0e7bc24bc7a8967898d8d3bc8caf64c513d91ef1f6c0/google_crc32c-1.8.0-cp313-cp313-macosx_12_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/1f/0b/93afde9cfe012260e9fe1522f35c9b72d6ee222f316586b1f23ecf44d518/google_resumable_media-2.8.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c4/ab/09169d5a4612a5f92490806649ac8d41e3ec9129c636754575b3553f4ea4/googleapis_common_protos-1.72.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/4a/bd/330a1bbdb1afe0b96311249e699b6dc9cfc17916394fd4503ac5aca2514b/grpc_google_iam_v1-0.14.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/55/06/0b78408e938ac424100100fd081189451b472236e8a3a1f6500390dc4954/grpcio-1.78.0-cp313-cp313-macosx_11_0_universal2.whl - - pypi: https://files.pythonhosted.org/packages/83/8a/1241ec22c41028bddd4a052ae9369267b4475265ad0ce7140974548dc3fa/grpcio_status-1.78.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/69/b2/119f6e6dcbd96f9069ce9a2665e0146588dc9f88f29549711853645e736a/h2-4.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b1/8b/88f16936a8e8070a83d36239555227ecd91728f9ef222c5382cda07e0fd6/h5netcdf-1.8.1-py3-none-any.whl @@ -436,7 +375,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/92/dc/c385f38f2c2433333345a82926c6bfa5ecfff3ef787201614317b58dd8be/msgpack-1.1.2-cp313-cp313-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/79/76/55cd7186f498ed080a18440c9013011eb548f77ae1b297206d030eb1180a/multidict-6.7.1-cp313-cp313-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/d1/c0/5f84ba7525577c1b9909fc2d06ef11314825fc4ad4378f61d0e4c9883b4a/numcodecs-0.16.5-cp313-cp313-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/be/9c/92789c596b8df838baa98fa71844d84283302f7604ed565dafe5a6b5041a/oauthlib-3.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/05/7a/2b4d984f794bfd98b3be0e252ef22682e85f00e96be292d6c6bfb6fc600d/object_store_python-0.1.10-cp38-abi3-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl - pypi: https://files.pythonhosted.org/packages/a9/90/a744336f5af32c433bd09af7854599682a383b37cfd78f7de263de6ad6cb/paramiko-4.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b6/61/fae042894f4296ec49e3f193aff5d7c18440da9e48102c3315e1bc4519a7/parso-0.8.6-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/71/e7/40fb618334dcdf7c5a316c0e7343c5cd82d3d866edc100d98e29bc945ecd/partd-1.4.2-py3-none-any.whl @@ -449,14 +388,10 @@ environments: - pypi: https://files.pythonhosted.org/packages/74/c3/24a2f845e3917201628ecaba4f18bab4d18a337834c1df2a159ee9d22a42/prometheus_client-0.24.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/89/a4/92380f7ca60f99ebae761936bc48a72a639e8a47b29050615eef757cb2a7/propcache-0.4.1-cp313-cp313-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/5d/79/ac273cbbf744691821a9cca88957257f41afe271637794975ca090b9588b/proto_plus-1.27.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/a2/6b/e48dfc1191bc5b52950246275bf4089773e91cb5ba3592621723cdddca62/protobuf-6.33.5-cp39-abi3-macosx_10_9_universal2.whl - pypi: https://files.pythonhosted.org/packages/80/c4/f5af4c1ca8c1eeb2e92ccca14ce8effdeec651d5ab6053c589b074eda6e1/psutil-7.2.2-cp36-abi3-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/22/a6/858897256d0deac81a172289110f31629fc4cee19b6f01283303e18c8db3/ptyprocess-0.7.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e0/a9/023730ba63db1e494a271cb018dcd361bd2c917ba7004c3e49d5daf795a2/py_cpuinfo-9.0.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/44/b5/a96872e5184f354da9c84ae119971a0a4c221fe9b27a4d94bd43f2596727/pyasn1-0.6.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/47/8d/d529b5d697919ba8c11ad626e835d4039be708a35b0d22de83a269a6682c/pyasn1_modules-0.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0b/4c/2862dd25352fe4b22ec7760d4fa12cb692587cd7ec3e378cbf644fc0d2a8/pygal-3.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/49/6f/07dab31ca496feda35cf3455b9e9380c43b5c685bb54ad890831c790da38/pygaljs-1.0.2-py2.py3-none-any.whl @@ -465,14 +400,10 @@ environments: - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/33/29/e756e715a48959f1c0045342088d7ca9762a2f509b945f362a316e9412b7/pytest_benchmark-5.2.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ca/31/d4e37e9e550c2b92a9cbc2e4d0b7420a27224968580b5a447f420847c975/pytest_xdist-3.8.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3b/5d/63d4ae3b9daea098d5d6f5da83984853c1bbacd5dc826764b249fe119d24/requests_oauthlib-2.0.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/14/25/b208c5683343959b670dc001595f2f3737e051da617f66c31f7c4fa93abc/rich-14.3.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/64/8d/0133e4eb4beed9e425d9a98ed6e081a55d195481b7632472be1af08d2f6b/rsa-4.9.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/57/e1/64c264db50b68de8a438b60ceeb921b2f22da3ebb7ad6255150225d0beac/s3fs-2026.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fc/51/727abb13f44c1fcf6d145979e1535a35794db0f6e450a0cb46aa24732fe2/s3transfer-0.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e1/c6/76dc613121b793286a3f91621d7b75a2b493e0390ddca50f11993eadf192/setuptools-82.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl @@ -527,10 +458,8 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/vc-14.3-h41ae7f8_34.conda - conda: https://conda.anaconda.org/conda-forge/win-64/vc14_runtime-14.44.35208-h818238b_34.conda - conda: https://conda.anaconda.org/conda-forge/win-64/vcomp14-14.44.35208-h818238b_34.conda - - pypi: https://files.pythonhosted.org/packages/02/78/79aa8169408996f5a71150abdea2e5e0f364df250c9e54ac385f115c7436/aiobotocore-3.2.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4e/f1/ab0395f8a79933577cdd996dd2f9aa6014af9535f65dddcf88204682fe62/aiohttp-3.13.3-cp313-cp313-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/10/a1/510b0a7fadc6f43a6ce50152e69dbd86415240835868bb0bd9b5b88b1e06/aioitertools-0.13.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/38/0e/27be9fdef66e72d64c0cdc3cc2823101b80585f8119b5c112c2e8f5f7dab/anyio-4.12.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d2/39/e7eaf1799466a4aef85b6a4fe7bd175ad2b1c6345066aa33f1f58d4b18d0/asttokens-3.0.1-py3-none-any.whl @@ -552,26 +481,13 @@ environments: - pypi: https://files.pythonhosted.org/packages/84/d0/205d54408c08b13550c733c4b85429e7ead111c7f0014309637425520a9a/deprecated-1.3.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ad/14/0fe5889a83991ac29c93e6b2e121ad2afc3bff5f9327f34447d3068d8142/distributed-2026.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0c/d5/c5db1ea3394c6e1732fb3286b3bd878b59507a8f77d32a2cebda7d7b7cd4/donfig-0.8.1.post1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ab/84/02fc1827e8cdded4aa65baef11296a9bbe595c474f0d6d758af082d849fd/execnet-2.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d6/1f/e99e23ee01847147fa194e8d41cfcf2535a2dbfcb51414c541cadb15c5d7/fabric-3.2.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f9/0b/de6f54d4a8bedfe8645c41497f3c18d749f0bd3218170c667bf4b81d0cdd/filelock-3.25.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d8/cf/174c91dbc9cc49bc7b7aab74d8b734e974d1faa8f191c74af9b7e80848e6/frozenlist-1.8.0-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/e6/ab/fb21f4c939bb440104cc2b396d3be1d9b7a9fd3c6c2a53d98c45b3d7c954/fsspec-2026.2.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/9c/6b/c2f68ac51229fc94f094c7f802648fc1de3d19af36434def5e64c0caa32b/gcsfs-2026.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8c/f2/2acb023b257ccc5da7a2235deea2294e94ea8380d7dc52894065c1be4917/gilknocker-0.4.2-cp313-cp313-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/45/27/09c33d67f7e0dcf06d7ac17d196594e66989299374bfb0d4331d1038e76b/google_api_core-2.30.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/83/1d/d6466de3a5249d35e832a52834115ca9d1d0de6abc22065f049707516d47/google_auth-2.48.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/2f/56/909fd5632226d3fba31d7aeffd4754410735d49362f5809956fe3e9af344/google_auth_oauthlib-1.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/89/20/bfa472e327c8edee00f04beecc80baeddd2ab33ee0e86fd7654da49d45e9/google_cloud_core-2.5.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/46/0b/816a6ae3c9fd096937d2e5f9670558908811d57d59ddf69dd4b83b326fd1/google_cloud_storage-3.9.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/8e/04/96a674d4ee90eed4e99c0f4faec21c9bbe1a470d37a4757508e90e31f5b9/google_cloud_storage_control-1.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/92/b1/d3cbd4d988afb3d8e4db94ca953df429ed6db7282ed0e700d25e6c7bfc8d/google_crc32c-1.8.0-cp313-cp313-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/1f/0b/93afde9cfe012260e9fe1522f35c9b72d6ee222f316586b1f23ecf44d518/google_resumable_media-2.8.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c4/ab/09169d5a4612a5f92490806649ac8d41e3ec9129c636754575b3553f4ea4/googleapis_common_protos-1.72.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/4a/bd/330a1bbdb1afe0b96311249e699b6dc9cfc17916394fd4503ac5aca2514b/grpc_google_iam_v1-0.14.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/4d/27/d86b89e36de8a951501fb06a0f38df19853210f341d0b28f83f4aa0ffa08/grpcio-1.78.0-cp313-cp313-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/83/8a/1241ec22c41028bddd4a052ae9369267b4475265ad0ce7140974548dc3fa/grpcio_status-1.78.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/69/b2/119f6e6dcbd96f9069ce9a2665e0146588dc9f88f29549711853645e736a/h2-4.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b1/8b/88f16936a8e8070a83d36239555227ecd91728f9ef222c5382cda07e0fd6/h5netcdf-1.8.1-py3-none-any.whl @@ -600,7 +516,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/74/07/1ed8277f8653c40ebc65985180b007879f6a836c525b3885dcc6448ae6cb/msgpack-1.1.2-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/b2/35/e994121b0e90e46134673422dd564623f93304614f5d11886b1b3e06f503/multidict-6.7.1-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/27/72/6663cc0382ddbb866136c255c837bcb96cc7ce5e83562efec55e1b995941/numcodecs-0.16.5-cp313-cp313-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/be/9c/92789c596b8df838baa98fa71844d84283302f7604ed565dafe5a6b5041a/oauthlib-3.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/37/3e/f710554442df82eccce5c6e11c1c9a073b80cf5b25bd79ed72063a503da3/object_store_python-0.1.10-cp38-abi3-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/a9/90/a744336f5af32c433bd09af7854599682a383b37cfd78f7de263de6ad6cb/paramiko-4.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b6/61/fae042894f4296ec49e3f193aff5d7c18440da9e48102c3315e1bc4519a7/parso-0.8.6-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/71/e7/40fb618334dcdf7c5a316c0e7343c5cd82d3d866edc100d98e29bc945ecd/partd-1.4.2-py3-none-any.whl @@ -612,13 +528,9 @@ environments: - pypi: https://files.pythonhosted.org/packages/74/c3/24a2f845e3917201628ecaba4f18bab4d18a337834c1df2a159ee9d22a42/prometheus_client-0.24.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f5/ab/f76ec3c3627c883215b5c8080debb4394ef5a7a29be811f786415fc1e6fd/propcache-0.4.1-cp313-cp313-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/5d/79/ac273cbbf744691821a9cca88957257f41afe271637794975ca090b9588b/proto_plus-1.27.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/55/75/bb9bc917d10e9ee13dee8607eb9ab963b7cf8be607c46e7862c748aa2af7/protobuf-6.33.5-cp310-abi3-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/b4/90/e2159492b5426be0c1fef7acba807a03511f97c5f86b3caeda6ad92351a7/psutil-7.2.2-cp37-abi3-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e0/a9/023730ba63db1e494a271cb018dcd361bd2c917ba7004c3e49d5daf795a2/py_cpuinfo-9.0.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/44/b5/a96872e5184f354da9c84ae119971a0a4c221fe9b27a4d94bd43f2596727/pyasn1-0.6.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/47/8d/d529b5d697919ba8c11ad626e835d4039be708a35b0d22de83a269a6682c/pyasn1_modules-0.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0b/4c/2862dd25352fe4b22ec7760d4fa12cb692587cd7ec3e378cbf644fc0d2a8/pygal-3.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/49/6f/07dab31ca496feda35cf3455b9e9380c43b5c685bb54ad890831c790da38/pygaljs-1.0.2-py2.py3-none-any.whl @@ -627,14 +539,10 @@ environments: - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/33/29/e756e715a48959f1c0045342088d7ca9762a2f509b945f362a316e9412b7/pytest_benchmark-5.2.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ca/31/d4e37e9e550c2b92a9cbc2e4d0b7420a27224968580b5a447f420847c975/pytest_xdist-3.8.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3b/5d/63d4ae3b9daea098d5d6f5da83984853c1bbacd5dc826764b249fe119d24/requests_oauthlib-2.0.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/14/25/b208c5683343959b670dc001595f2f3737e051da617f66c31f7c4fa93abc/rich-14.3.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/64/8d/0133e4eb4beed9e425d9a98ed6e081a55d195481b7632472be1af08d2f6b/rsa-4.9.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/57/e1/64c264db50b68de8a438b60ceeb921b2f22da3ebb7ad6255150225d0beac/s3fs-2026.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fc/51/727abb13f44c1fcf6d145979e1535a35794db0f6e450a0cb46aa24732fe2/s3transfer-0.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e1/c6/76dc613121b793286a3f91621d7b75a2b493e0390ddca50f11993eadf192/setuptools-82.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl @@ -1915,21 +1823,6 @@ packages: - hypothesis ; extra == 'tests' - pytest ; extra == 'tests' requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/02/78/79aa8169408996f5a71150abdea2e5e0f364df250c9e54ac385f115c7436/aiobotocore-3.2.1-py3-none-any.whl - name: aiobotocore - version: 3.2.1 - sha256: 68b7474af3e7124666b8e191805db5a7255d14e6187e0472481c845b6062e42e - requires_dist: - - aiohttp>=3.12.0,<4.0.0 - - aioitertools>=0.5.1,<1.0.0 - - botocore>=1.42.53,<1.42.62 - - python-dateutil>=2.1,<3.0.0 - - jmespath>=0.7.1,<2.0.0 - - multidict>=6.0.0,<7.0.0 - - typing-extensions>=4.14.0,<5.0.0 ; python_full_version < '3.11' - - wrapt>=1.10.10,<3.0.0 - - httpx>=0.25.1,<0.29 ; extra == 'httpx' - requires_python: '>=3.9' - pypi: https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl name: aiohappyeyeballs version: 2.6.1 @@ -2007,13 +1900,6 @@ packages: - brotlicffi>=1.2 ; platform_python_implementation != 'CPython' and extra == 'speedups' - backports-zstd ; python_full_version < '3.14' and platform_python_implementation == 'CPython' and extra == 'speedups' requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/10/a1/510b0a7fadc6f43a6ce50152e69dbd86415240835868bb0bd9b5b88b1e06/aioitertools-0.13.0-py3-none-any.whl - name: aioitertools - version: 0.13.0 - sha256: 0be0292b856f08dfac90e31f4739432f4cb6d7520ab9eb73e143f4f2fa5259be - requires_dist: - - typing-extensions>=4.0 ; python_full_version < '3.10' - requires_python: '>=3.9' - pypi: https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl name: aiosignal version: 1.4.0 @@ -5033,22 +4919,6 @@ packages: purls: [] size: 28666 timestamp: 1770908257439 -- pypi: https://files.pythonhosted.org/packages/9c/6b/c2f68ac51229fc94f094c7f802648fc1de3d19af36434def5e64c0caa32b/gcsfs-2026.2.0-py3-none-any.whl - name: gcsfs - version: 2026.2.0 - sha256: 407feaa2af0de81ebce44ea7e6f68598a3753e5e42257b61d6a9f8c0d6d4754e - requires_dist: - - aiohttp!=4.0.0a0,!=4.0.0a1 - - decorator>4.1.2 - - fsspec==2026.2.0 - - google-auth>=1.2 - - google-auth-oauthlib - - google-cloud-storage>=3.9.0 - - google-cloud-storage-control - - requests - - fusepy ; extra == 'gcsfuse' - - crcmod ; extra == 'crc' - requires_python: '>=3.10' - conda: https://conda.anaconda.org/conda-forge/linux-64/gfortran-14.3.0-h76987e4_18.conda sha256: c216b97f00973fc6c37af16d1d974635e81cfc93462123b1d0ffc93fe509ea01 md5: 958a6ecb4188cce9edbd9bbd2831a61d @@ -5247,142 +5117,6 @@ packages: purls: [] size: 365188 timestamp: 1718981343258 -- pypi: https://files.pythonhosted.org/packages/45/27/09c33d67f7e0dcf06d7ac17d196594e66989299374bfb0d4331d1038e76b/google_api_core-2.30.0-py3-none-any.whl - name: google-api-core - version: 2.30.0 - sha256: 80be49ee937ff9aba0fd79a6eddfde35fe658b9953ab9b79c57dd7061afa8df5 - requires_dist: - - googleapis-common-protos>=1.56.3,<2.0.0 - - protobuf>=4.25.8,<7.0.0 - - proto-plus>=1.22.3,<2.0.0 - - proto-plus>=1.25.0,<2.0.0 ; python_full_version >= '3.13' - - google-auth>=2.14.1,<3.0.0 - - requests>=2.20.0,<3.0.0 - - google-auth[aiohttp]>=2.35.0,<3.0.0 ; extra == 'async-rest' - - grpcio>=1.33.2,<2.0.0 ; extra == 'grpc' - - grpcio>=1.49.1,<2.0.0 ; python_full_version >= '3.11' and extra == 'grpc' - - grpcio>=1.75.1,<2.0.0 ; python_full_version >= '3.14' and extra == 'grpc' - - grpcio-status>=1.33.2,<2.0.0 ; extra == 'grpc' - - grpcio-status>=1.49.1,<2.0.0 ; python_full_version >= '3.11' and extra == 'grpc' - - grpcio-status>=1.75.1,<2.0.0 ; python_full_version >= '3.14' and extra == 'grpc' - requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/83/1d/d6466de3a5249d35e832a52834115ca9d1d0de6abc22065f049707516d47/google_auth-2.48.0-py3-none-any.whl - name: google-auth - version: 2.48.0 - sha256: 2e2a537873d449434252a9632c28bfc268b0adb1e53f9fb62afc5333a975903f - requires_dist: - - pyasn1-modules>=0.2.1 - - cryptography>=38.0.3 - - rsa>=3.1.4,<5 - - cryptography>=38.0.3 ; extra == 'cryptography' - - aiohttp>=3.6.2,<4.0.0 ; extra == 'aiohttp' - - requests>=2.20.0,<3.0.0 ; extra == 'aiohttp' - - pyopenssl ; extra == 'enterprise-cert' - - pyopenssl>=20.0.0 ; extra == 'pyopenssl' - - pyjwt>=2.0 ; extra == 'pyjwt' - - pyu2f>=0.1.5 ; extra == 'reauth' - - requests>=2.20.0,<3.0.0 ; extra == 'requests' - - grpcio ; extra == 'testing' - - flask ; extra == 'testing' - - freezegun ; extra == 'testing' - - oauth2client ; extra == 'testing' - - pyjwt>=2.0 ; extra == 'testing' - - pytest ; extra == 'testing' - - pytest-cov ; extra == 'testing' - - pytest-localserver ; extra == 'testing' - - pyopenssl>=20.0.0 ; extra == 'testing' - - pyu2f>=0.1.5 ; extra == 'testing' - - responses ; extra == 'testing' - - urllib3 ; extra == 'testing' - - packaging ; extra == 'testing' - - aiohttp>=3.6.2,<4.0.0 ; extra == 'testing' - - requests>=2.20.0,<3.0.0 ; extra == 'testing' - - aioresponses ; extra == 'testing' - - pytest-asyncio ; extra == 'testing' - - pyopenssl<24.3.0 ; extra == 'testing' - - aiohttp<3.10.0 ; extra == 'testing' - - urllib3 ; extra == 'urllib3' - - packaging ; extra == 'urllib3' - requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/2f/56/909fd5632226d3fba31d7aeffd4754410735d49362f5809956fe3e9af344/google_auth_oauthlib-1.3.0-py3-none-any.whl - name: google-auth-oauthlib - version: 1.3.0 - sha256: 386b3fb85cf4a5b819c6ad23e3128d975216b4cac76324de1d90b128aaf38f29 - requires_dist: - - google-auth>=2.15.0,!=2.43.0,!=2.44.0,!=2.45.0,<3.0.0 - - requests-oauthlib>=0.7.0 - - click>=6.0.0 ; extra == 'tool' - requires_python: '>=3.7' -- pypi: https://files.pythonhosted.org/packages/89/20/bfa472e327c8edee00f04beecc80baeddd2ab33ee0e86fd7654da49d45e9/google_cloud_core-2.5.0-py3-none-any.whl - name: google-cloud-core - version: 2.5.0 - sha256: 67d977b41ae6c7211ee830c7912e41003ea8194bff15ae7d72fd6f51e57acabc - requires_dist: - - google-api-core>=1.31.6,!=2.0.*,!=2.1.*,!=2.2.*,!=2.3.0,<3.0.0 - - google-auth>=1.25.0,<3.0.0 - - importlib-metadata>1.0.0 ; python_full_version < '3.8' - - grpcio>=1.38.0,<2.0.0 ; python_full_version < '3.14' and extra == 'grpc' - - grpcio>=1.75.1,<2.0.0 ; python_full_version >= '3.14' and extra == 'grpc' - - grpcio-status>=1.38.0,<2.0.0 ; extra == 'grpc' - requires_python: '>=3.7' -- pypi: https://files.pythonhosted.org/packages/46/0b/816a6ae3c9fd096937d2e5f9670558908811d57d59ddf69dd4b83b326fd1/google_cloud_storage-3.9.0-py3-none-any.whl - name: google-cloud-storage - version: 3.9.0 - sha256: 2dce75a9e8b3387078cbbdad44757d410ecdb916101f8ba308abf202b6968066 - requires_dist: - - google-auth>=2.26.1,<3.0.0 - - google-api-core>=2.27.0,<3.0.0 - - google-cloud-core>=2.4.2,<3.0.0 - - google-resumable-media>=2.7.2,<3.0.0 - - requests>=2.22.0,<3.0.0 - - google-crc32c>=1.1.3,<2.0.0 - - google-api-core[grpc]>=2.27.0,<3.0.0 ; extra == 'grpc' - - grpcio>=1.33.2,<2.0.0 ; python_full_version < '3.14' and extra == 'grpc' - - grpcio>=1.75.1,<2.0.0 ; python_full_version >= '3.14' and extra == 'grpc' - - grpcio-status>=1.76.0,<2.0.0 ; extra == 'grpc' - - proto-plus>=1.22.3,<2.0.0 ; python_full_version < '3.13' and extra == 'grpc' - - proto-plus>=1.25.0,<2.0.0 ; python_full_version >= '3.13' and extra == 'grpc' - - protobuf>=3.20.2,!=4.21.0,!=4.21.1,!=4.21.2,!=4.21.3,!=4.21.4,!=4.21.5,<7.0.0 ; extra == 'grpc' - - grpc-google-iam-v1>=0.14.0,<1.0.0 ; extra == 'grpc' - - protobuf>=3.20.2,<7.0.0 ; extra == 'protobuf' - - opentelemetry-api>=1.1.0,<2.0.0 ; extra == 'tracing' - - google-cloud-testutils ; extra == 'testing' - - numpy ; extra == 'testing' - - psutil ; extra == 'testing' - - py-cpuinfo ; extra == 'testing' - - pytest-benchmark ; extra == 'testing' - - pyyaml ; extra == 'testing' - - mock ; extra == 'testing' - - pytest ; extra == 'testing' - - pytest-cov ; extra == 'testing' - - pytest-asyncio ; extra == 'testing' - - pytest-rerunfailures ; extra == 'testing' - - pytest-xdist ; extra == 'testing' - - google-cloud-testutils ; extra == 'testing' - - google-cloud-iam ; extra == 'testing' - - google-cloud-pubsub ; extra == 'testing' - - google-cloud-kms ; extra == 'testing' - - brotli ; extra == 'testing' - - coverage ; extra == 'testing' - - pyopenssl ; extra == 'testing' - - opentelemetry-sdk ; extra == 'testing' - - flake8 ; extra == 'testing' - - black ; extra == 'testing' - requires_python: '>=3.7' -- pypi: https://files.pythonhosted.org/packages/8e/04/96a674d4ee90eed4e99c0f4faec21c9bbe1a470d37a4757508e90e31f5b9/google_cloud_storage_control-1.10.0-py3-none-any.whl - name: google-cloud-storage-control - version: 1.10.0 - sha256: 81d9dc6b50106836733adca868501f879f0d7a1c41503d887a1a1b9b9ddbf508 - requires_dist: - - google-api-core[grpc]>=1.34.1,!=2.0.*,!=2.1.*,!=2.2.*,!=2.3.*,!=2.4.*,!=2.5.*,!=2.6.*,!=2.7.*,!=2.8.*,!=2.9.*,!=2.10.*,<3.0.0 - - google-auth>=2.14.1,!=2.24.0,!=2.25.0,<3.0.0 - - grpcio>=1.33.2,<2.0.0 - - grpcio>=1.75.1,<2.0.0 ; python_full_version >= '3.14' - - proto-plus>=1.22.3,<2.0.0 - - proto-plus>=1.25.0,<2.0.0 ; python_full_version >= '3.13' - - protobuf>=3.20.2,!=4.21.0,!=4.21.1,!=4.21.2,!=4.21.3,!=4.21.4,!=4.21.5,<7.0.0 - - grpc-google-iam-v1>=0.14.0,<1.0.0 - requires_python: '>=3.7' - pypi: https://files.pythonhosted.org/packages/1c/e8/b33784d6fc77fb5062a8a7854e43e1e618b87d5ddf610a88025e4de6226e/google_crc32c-1.8.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl name: google-crc32c version: 1.8.0 @@ -5403,24 +5137,6 @@ packages: version: 1.8.0 sha256: 3ebb04528e83b2634857f43f9bb8ef5b2bbe7f10f140daeb01b58f972d04736b requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/1f/0b/93afde9cfe012260e9fe1522f35c9b72d6ee222f316586b1f23ecf44d518/google_resumable_media-2.8.0-py3-none-any.whl - name: google-resumable-media - version: 2.8.0 - sha256: dd14a116af303845a8d932ddae161a26e86cc229645bc98b39f026f9b1717582 - requires_dist: - - google-crc32c>=1.0.0,<2.0.0 - - requests>=2.18.0,<3.0.0 ; extra == 'requests' - - aiohttp>=3.6.2,<4.0.0 ; extra == 'aiohttp' - - google-auth>=1.22.0,<2.0.0 ; extra == 'aiohttp' - requires_python: '>=3.7' -- pypi: https://files.pythonhosted.org/packages/c4/ab/09169d5a4612a5f92490806649ac8d41e3ec9129c636754575b3553f4ea4/googleapis_common_protos-1.72.0-py3-none-any.whl - name: googleapis-common-protos - version: 1.72.0 - sha256: 4299c5a82d5ae1a9702ada957347726b167f9f8d1fc352477702a1e851ff4038 - requires_dist: - - protobuf>=3.20.2,!=4.21.1,!=4.21.2,!=4.21.3,!=4.21.4,!=4.21.5,<7.0.0 - - grpcio>=1.44.0,<2.0.0 ; extra == 'grpc' - requires_python: '>=3.7' - pypi: https://files.pythonhosted.org/packages/49/8d/9771d03e7a8b1ee456511961e1b97a6d77ae1dea4a34a5b98eee706689d3/greenlet-3.3.2-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl name: greenlet version: 3.3.2 @@ -5463,56 +5179,6 @@ packages: - platformdirs>=4.2 ; extra == 'pypi' - wheel>=0.42 ; extra == 'pypi' requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/4a/bd/330a1bbdb1afe0b96311249e699b6dc9cfc17916394fd4503ac5aca2514b/grpc_google_iam_v1-0.14.3-py3-none-any.whl - name: grpc-google-iam-v1 - version: 0.14.3 - sha256: 7a7f697e017a067206a3dfef44e4c634a34d3dee135fe7d7a4613fe3e59217e6 - requires_dist: - - grpcio>=1.44.0,<2.0.0 - - googleapis-common-protos[grpc]>=1.56.0,<2.0.0 - - protobuf>=3.20.2,!=4.21.1,!=4.21.2,!=4.21.3,!=4.21.4,!=4.21.5,<7.0.0 - requires_python: '>=3.7' -- pypi: https://files.pythonhosted.org/packages/4d/27/d86b89e36de8a951501fb06a0f38df19853210f341d0b28f83f4aa0ffa08/grpcio-1.78.0-cp313-cp313-win_amd64.whl - name: grpcio - version: 1.78.0 - sha256: f2d4e43ee362adfc05994ed479334d5a451ab7bc3f3fee1b796b8ca66895acb4 - requires_dist: - - typing-extensions~=4.12 - - grpcio-tools>=1.78.0 ; extra == 'protobuf' - requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/55/06/0b78408e938ac424100100fd081189451b472236e8a3a1f6500390dc4954/grpcio-1.78.0-cp313-cp313-macosx_11_0_universal2.whl - name: grpcio - version: 1.78.0 - sha256: 2bf5e2e163b356978b23652c4818ce4759d40f4712ee9ec5a83c4be6f8c23a3a - requires_dist: - - typing-extensions~=4.12 - - grpcio-tools>=1.78.0 ; extra == 'protobuf' - requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/88/93/b59fe7832ff6ae3c78b813ea43dac60e295fa03606d14d89d2e0ec29f4f3/grpcio-1.78.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl - name: grpcio - version: 1.78.0 - sha256: 8f2ac84905d12918e4e55a16da17939eb63e433dc11b677267c35568aa63fc84 - requires_dist: - - typing-extensions~=4.12 - - grpcio-tools>=1.78.0 ; extra == 'protobuf' - requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/a6/62/cc03fffb07bfba982a9ec097b164e8835546980aec25ecfa5f9c1a47e022/grpcio-1.78.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl - name: grpcio - version: 1.78.0 - sha256: 735e38e176a88ce41840c21bb49098ab66177c64c82426e24e0082500cc68af5 - requires_dist: - - typing-extensions~=4.12 - - grpcio-tools>=1.78.0 ; extra == 'protobuf' - requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/83/8a/1241ec22c41028bddd4a052ae9369267b4475265ad0ce7140974548dc3fa/grpcio_status-1.78.0-py3-none-any.whl - name: grpcio-status - version: 1.78.0 - sha256: b492b693d4bf27b47a6c32590701724f1d3b9444b36491878fb71f6208857f34 - requires_dist: - - protobuf>=6.31.1,<7.0.0 - - grpcio>=1.78.0 - - googleapis-common-protos>=1.5.5 - requires_python: '>=3.9' - conda: https://conda.anaconda.org/conda-forge/linux-64/gxx-14.3.0-h76987e4_18.conda sha256: 1b490c9be9669f9c559db7b2a1f7d8b973c58ca0c6f21a5d2ba3f0ab2da63362 md5: 19189121d644d4ef75fed05383bc75f5 @@ -9684,16 +9350,6 @@ packages: - pkg:pypi/numpy?source=hash-mapping size: 7251046 timestamp: 1770098409520 -- pypi: https://files.pythonhosted.org/packages/be/9c/92789c596b8df838baa98fa71844d84283302f7604ed565dafe5a6b5041a/oauthlib-3.3.1-py3-none-any.whl - name: oauthlib - version: 3.3.1 - sha256: 88119c938d2b8fb88561af5f6ee0eec8cc8d552b7bb1f712743136eb7523b7a1 - requires_dist: - - cryptography>=3.0.0 ; extra == 'rsa' - - cryptography>=3.0.0 ; extra == 'signedtoken' - - pyjwt>=2.0.0,<3 ; extra == 'signedtoken' - - blinker>=1.4.0 ; extra == 'signals' - requires_python: '>=3.8' - pypi: https://files.pythonhosted.org/packages/05/7a/2b4d984f794bfd98b3be0e252ef22682e85f00e96be292d6c6bfb6fc600d/object_store_python-0.1.10-cp38-abi3-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl name: object-store-python version: 0.1.10 @@ -10638,34 +10294,6 @@ packages: version: 0.4.1 sha256: 381914df18634f5494334d201e98245c0596067504b9372d8cf93f4bb23e025e requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/5d/79/ac273cbbf744691821a9cca88957257f41afe271637794975ca090b9588b/proto_plus-1.27.1-py3-none-any.whl - name: proto-plus - version: 1.27.1 - sha256: e4643061f3a4d0de092d62aa4ad09fa4756b2cbb89d4627f3985018216f9fefc - requires_dist: - - protobuf>=3.19.0,<7.0.0 - - google-api-core>=1.31.5 ; extra == 'testing' - requires_python: '>=3.7' -- pypi: https://files.pythonhosted.org/packages/4e/b1/c79468184310de09d75095ed1314b839eb2f72df71097db9d1404a1b2717/protobuf-6.33.5-cp39-abi3-manylinux2014_aarch64.whl - name: protobuf - version: 6.33.5 - sha256: 9b71e0281f36f179d00cbcb119cb19dec4d14a81393e5ea220f64b286173e190 - requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/55/75/bb9bc917d10e9ee13dee8607eb9ab963b7cf8be607c46e7862c748aa2af7/protobuf-6.33.5-cp310-abi3-win_amd64.whl - name: protobuf - version: 6.33.5 - sha256: 3093804752167bcab3998bec9f1048baae6e29505adaf1afd14a37bddede533c - requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/9b/53/a9443aa3ca9ba8724fdfa02dd1887c1bcd8e89556b715cfbacca6b63dbec/protobuf-6.33.5-cp39-abi3-manylinux2014_x86_64.whl - name: protobuf - version: 6.33.5 - sha256: cbf16ba3350fb7b889fca858fb215967792dc125b35c7976ca4818bee3521cf0 - requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/a2/6b/e48dfc1191bc5b52950246275bf4089773e91cb5ba3592621723cdddca62/protobuf-6.33.5-cp39-abi3-macosx_10_9_universal2.whl - name: protobuf - version: 6.33.5 - sha256: a5cb85982d95d906df1e2210e58f8e4f1e3cdc088e52c921a041f9c9a0386de5 - requires_python: '>=3.9' - pypi: https://files.pythonhosted.org/packages/63/65/37648c0c158dc222aba51c089eb3bdfa238e621674dc42d48706e639204f/psutil-7.2.2-cp36-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl name: psutil version: 7.2.2 @@ -10877,18 +10505,6 @@ packages: name: py-cpuinfo version: 9.0.0 sha256: 859625bc251f64e21f077d099d4162689c762b5d6a4c3c97553d56241c9674d5 -- pypi: https://files.pythonhosted.org/packages/44/b5/a96872e5184f354da9c84ae119971a0a4c221fe9b27a4d94bd43f2596727/pyasn1-0.6.2-py3-none-any.whl - name: pyasn1 - version: 0.6.2 - sha256: 1eb26d860996a18e9b6ed05e7aae0e9fc21619fcee6af91cca9bad4fbea224bf - requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/47/8d/d529b5d697919ba8c11ad626e835d4039be708a35b0d22de83a269a6682c/pyasn1_modules-0.4.2-py3-none-any.whl - name: pyasn1-modules - version: 0.4.2 - sha256: 29253a9207ce32b64c3ac6600edc75368f98473906e8fd1043bd6b5b1de2c14a - requires_dist: - - pyasn1>=0.6.1,<0.7.0 - requires_python: '>=3.8' - pypi: https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl name: pycparser version: '3.0' @@ -11360,15 +10976,6 @@ packages: - pysocks>=1.5.6,!=1.5.7 ; extra == 'socks' - chardet>=3.0.2,<6 ; extra == 'use-chardet-on-py3' requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/3b/5d/63d4ae3b9daea098d5d6f5da83984853c1bbacd5dc826764b249fe119d24/requests_oauthlib-2.0.0-py2.py3-none-any.whl - name: requests-oauthlib - version: 2.0.0 - sha256: 7dd8a5c40426b779b0868c404bdef9768deccf22749cde15852df527e6269b36 - requires_dist: - - oauthlib>=3.0.0 - - requests>=2.0.0 - - oauthlib[signedtoken]>=3.0.0 ; extra == 'rsa' - requires_python: '>=3.4' - pypi: https://files.pythonhosted.org/packages/14/25/b208c5683343959b670dc001595f2f3737e051da617f66c31f7c4fa93abc/rich-14.3.3-py3-none-any.whl name: rich version: 14.3.3 @@ -11403,13 +11010,6 @@ packages: version: 0.30.0 sha256: e7536cd91353c5273434b4e003cbda89034d67e7710eab8761fd918ec6c69cf8 requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/64/8d/0133e4eb4beed9e425d9a98ed6e081a55d195481b7632472be1af08d2f6b/rsa-4.9.1-py3-none-any.whl - name: rsa - version: 4.9.1 - sha256: 68635866661c6836b8d39430f97a996acbd61bfa49406748ea243539fe239762 - requires_dist: - - pyasn1>=0.1.3 - requires_python: '>=3.6,<4' - pypi: https://files.pythonhosted.org/packages/06/87/e2f80a39164476fac4d45752a0d4721d6645f40b7f851e48add12af9947e/ruff-0.15.3-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl name: ruff version: 0.15.3 @@ -11527,15 +11127,6 @@ packages: purls: [] size: 36890656 timestamp: 1773066787379 -- pypi: https://files.pythonhosted.org/packages/57/e1/64c264db50b68de8a438b60ceeb921b2f22da3ebb7ad6255150225d0beac/s3fs-2026.2.0-py3-none-any.whl - name: s3fs - version: 2026.2.0 - sha256: 65198835b86b1d5771112b0085d1da52a6ede36508b1aaa6cae2aedc765dfe10 - requires_dist: - - aiobotocore>=2.19.0,<4.0.0 - - fsspec==2026.2.0 - - aiohttp!=4.0.0a0,!=4.0.0a1 - requires_python: '>=3.10' - pypi: https://files.pythonhosted.org/packages/fc/51/727abb13f44c1fcf6d145979e1535a35794db0f6e450a0cb46aa24732fe2/s3transfer-0.16.0-py3-none-any.whl name: s3transfer version: 0.16.0 From afcd8a619ebbee775298b5c91cb0f296388c7b1b Mon Sep 17 00:00:00 2001 From: Deepak Cherian Date: Mon, 23 Mar 2026 22:33:02 -0600 Subject: [PATCH 20/21] Fix --- icechunk-python/pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/icechunk-python/pyproject.toml b/icechunk-python/pyproject.toml index b90d0cf7e..b4726131a 100644 --- a/icechunk-python/pyproject.toml +++ b/icechunk-python/pyproject.toml @@ -116,7 +116,7 @@ minversion = "7" testpaths = ["tests", "integration_tests"] log_cli_level = "INFO" xfail_strict = true -addopts = ["-ra", "--strict-config", "--strict-markers", "-n", "auto", "--benchmark-disable"] +addopts = ["-ra", "--strict-config", "--strict-markers", "-n", "auto"] markers = [ "gpu", # need this to run the zarr tests "hypothesis", # auto-applied by hypothesis to @given tests From 539df7f7dfdb28cf7d0bbc70664f68f0f1bbe5e2 Mon Sep 17 00:00:00 2001 From: Deepak Cherian Date: Fri, 27 Mar 2026 09:46:34 -0600 Subject: [PATCH 21/21] Update comment --- icechunk-python/benchmarks/runner.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/icechunk-python/benchmarks/runner.py b/icechunk-python/benchmarks/runner.py index dfd047b9e..e4599d4ea 100644 --- a/icechunk-python/benchmarks/runner.py +++ b/icechunk-python/benchmarks/runner.py @@ -276,6 +276,9 @@ def initialize(self) -> None: # The pip_github_url approach below doesn't work because Coiled # ignores git+ URLs with "Local path requirement ... is not supported". # pip_deps = [self.pip_github_url, "coiled", *deps] + # One approach is to use a conda env with all deps under pip. + # See this code in VirtualiZarr for example. + # https://github.com/jbusecke/virtualizarr-benchmark/blob/1bd0ffd59a17cff728fbf2b3ba5cb57e7cd3e2fe/run_matrix.py#L115-L121 raise NotImplementedError( f"Coiled runner only supports PYPI_REFS ({list(self.PYPI_REFS)}). " f"Got ref={self.ref!r}. Use LocalRunner (--where local) for git refs."