From 82d5f654c35a84cebc26885a3ece0269cbc1bb77 Mon Sep 17 00:00:00 2001 From: Faolain Date: Mon, 14 Apr 2025 19:48:54 -0400 Subject: [PATCH 1/6] test: improve coverage on test retrieval --- tests/test_ipfs_retrieval.py | 360 +++++++++++++++++++++++++++++++---- 1 file changed, 324 insertions(+), 36 deletions(-) diff --git a/tests/test_ipfs_retrieval.py b/tests/test_ipfs_retrieval.py index 357ee39..8812234 100644 --- a/tests/test_ipfs_retrieval.py +++ b/tests/test_ipfs_retrieval.py @@ -1,9 +1,11 @@ import os import json +from typing import Dict, Any # Import Dict and Any +from unittest.mock import patch, mock_open, MagicMock import pytest import requests -from unittest.mock import patch, mock_open +from multiformats import CID # Import CID import dclimate_zarr_client.ipfs_retrieval as ipfs_retrieval from dclimate_zarr_client.ipfs_retrieval import ( @@ -13,7 +15,12 @@ DatasetNotFoundError, IpfsConnectionError, StacCatalogError, + # Import internal functions for testing + _get_ipfs_store, + fetch_json_from_cid, # noqa: E402 Testing private member + _get_host, # noqa: E402 Testing private member ) +from py_hamt import IPFSStore # Import IPFSStore # import xarray as xr @@ -26,6 +33,218 @@ pytestmark = pytest.mark.usefixtures("check_ipfs_connection") +# --- Type Hinting --- # Define type alias if needed, or use directly +MonkeyPatch = pytest.MonkeyPatch # Common practice for pytest +MockIPFSStore = MagicMock # Alias for clarity + + +# --- Tests for _get_ipfs_store --- + + +def test_get_ipfs_store_defaults(monkeypatch: MonkeyPatch): + """Test store creation uses defaults when no args/env vars are set.""" + monkeypatch.delenv("IPFS_GATEWAY_URI_STEM", raising=False) + monkeypatch.delenv("IPFS_RPC_URI_STEM", raising=False) + with patch("dclimate_zarr_client.ipfs_retrieval.IPFSStore") as mock_store_class: + store = _get_ipfs_store() + mock_store_class.assert_called_once_with() # Called with no args, uses internal defaults + assert isinstance( + store, MagicMock + ) # We mocked the class, so instance is MagicMock + + +def test_get_ipfs_store_args_override_env(monkeypatch: MonkeyPatch): + """Test that function arguments override environment variables.""" + monkeypatch.setenv("IPFS_GATEWAY_URI_STEM", "http://env-gateway:8080") + monkeypatch.setenv("IPFS_RPC_URI_STEM", "http://env-rpc:5001") + with patch("dclimate_zarr_client.ipfs_retrieval.IPFSStore") as mock_store_class: + store = _get_ipfs_store( + gateway_uri_stem="http://arg-gateway:8080", + rpc_uri_stem="http://arg-rpc:5001", + ) + mock_store_class.assert_called_once_with( + gateway_uri_stem="http://arg-gateway:8080", + rpc_uri_stem="http://arg-rpc:5001", + ) + assert isinstance(store, MagicMock) + + +def test_get_ipfs_store_env_vars(monkeypatch: MonkeyPatch): + """Test that environment variables are used when no args are provided.""" + monkeypatch.setenv("IPFS_GATEWAY_URI_STEM", "http://env-gateway:8080") + monkeypatch.setenv("IPFS_RPC_URI_STEM", "http://env-rpc:5001") + with patch("dclimate_zarr_client.ipfs_retrieval.IPFSStore") as mock_store_class: + store = _get_ipfs_store() + mock_store_class.assert_called_once_with( + gateway_uri_stem="http://env-gateway:8080", + rpc_uri_stem="http://env-rpc:5001", + ) + assert isinstance(store, MagicMock) + + +def test_get_ipfs_store_mixed_args_env(monkeypatch: MonkeyPatch): + """Test using a mix of args and env vars (args should take precedence).""" + monkeypatch.setenv("IPFS_GATEWAY_URI_STEM", "http://env-gateway:8080") + monkeypatch.delenv("IPFS_RPC_URI_STEM", raising=False) # RPC not set in env + with patch("dclimate_zarr_client.ipfs_retrieval.IPFSStore") as mock_store_class: + store = _get_ipfs_store( + rpc_uri_stem="http://arg-rpc:5001" + ) # Provide RPC as arg + mock_store_class.assert_called_once_with( + gateway_uri_stem="http://env-gateway:8080", # Gateway comes from env + rpc_uri_stem="http://arg-rpc:5001", # RPC comes from arg + ) + assert isinstance(store, MagicMock) + + +# --- Tests for fetch_json_from_cid --- + + +@pytest.fixture +def mock_ipfs_store() -> MockIPFSStore: + """Fixture to create a mock IPFSStore instance.""" + store = MagicMock(spec=IPFSStore) + # Set default URIs for error messages if needed + store.gateway_uri_stem = "http://mock-gateway:8080" + store.rpc_uri_stem = "http://mock-rpc:5001" + return store + + +def test_fetch_json_from_cid_success(mock_ipfs_store: MockIPFSStore): + """Test successful fetching and decoding of JSON from CID.""" + valid_cid_str = ( + "bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi" # Example CID + ) + json_data: Dict[str, Any] = {"key": "value"} + mock_ipfs_store.load.return_value = json.dumps(json_data).encode("utf-8") + + result: Dict[str, Any] = fetch_json_from_cid(valid_cid_str, mock_ipfs_store) + + assert result == json_data + mock_ipfs_store.load.assert_called_once() + # Check that CID.decode was implicitly called by store.load mock (or explicitly if store expects CID obj) + # For MagicMock, we check the arg type if needed, assuming it passes the string + call_args = mock_ipfs_store.load.call_args[0] + assert isinstance(call_args[0], CID) + assert str(call_args[0]) == valid_cid_str + + +def test_fetch_json_from_cid_success_with_prefix(mock_ipfs_store: MockIPFSStore): + """Test successful fetching when CID string has /ipfs/ prefix.""" + cid_str_no_prefix = "bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi" + cid_str_with_prefix = f"/ipfs/{cid_str_no_prefix}" + json_data: Dict[str, Any] = {"key": "value"} + mock_ipfs_store.load.return_value = json.dumps(json_data).encode("utf-8") + + result: Dict[str, Any] = fetch_json_from_cid(cid_str_with_prefix, mock_ipfs_store) + + assert result == json_data + mock_ipfs_store.load.assert_called_once() + call_args = mock_ipfs_store.load.call_args[0] + assert isinstance(call_args[0], CID) + assert str(call_args[0]) == cid_str_no_prefix # Prefix should be stripped + + +def test_fetch_json_from_cid_invalid_cid_string(mock_ipfs_store: MockIPFSStore): + """Test StacCatalogError when CID string is invalid.""" + invalid_cid_str = "this-is-not-a-cid" + with pytest.raises( + StacCatalogError, match=f"Failed to decode CID string '{invalid_cid_str}'" + ): + fetch_json_from_cid(invalid_cid_str, mock_ipfs_store) + mock_ipfs_store.load.assert_not_called() # Should fail before calling load + + +def test_fetch_json_from_cid_load_returns_none(mock_ipfs_store: MockIPFSStore): + """Test StacCatalogError when ipfs_store.load returns None or empty bytes.""" + valid_cid_str = "bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi" + mock_ipfs_store.load.return_value = b"" # Empty bytes + + with pytest.raises( + StacCatalogError, match=f"No data returned for CID: {valid_cid_str}" + ): + fetch_json_from_cid(valid_cid_str, mock_ipfs_store) + mock_ipfs_store.load.assert_called_once() + + +def test_fetch_json_from_cid_json_decode_error(mock_ipfs_store: MockIPFSStore): + """Test StacCatalogError when fetched data is not valid JSON.""" + valid_cid_str = "bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi" + mock_ipfs_store.load.return_value = b"this is not json" + + with pytest.raises( + StacCatalogError, match=f"Failed to decode JSON from CID {valid_cid_str}" + ): + fetch_json_from_cid(valid_cid_str, mock_ipfs_store) + mock_ipfs_store.load.assert_called_once() + + +def test_fetch_json_from_cid_timeout_error(mock_ipfs_store: MockIPFSStore): + """Test IpfsConnectionError when ipfs_store.load raises Timeout.""" + valid_cid_str = "bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi" + mock_ipfs_store.load.side_effect = requests.exceptions.Timeout("Request timed out") + + with pytest.raises( + IpfsConnectionError, match=f"Timeout fetching CID {valid_cid_str}" + ): + fetch_json_from_cid(valid_cid_str, mock_ipfs_store) + mock_ipfs_store.load.assert_called_once() + + +def test_fetch_json_from_cid_connection_error(mock_ipfs_store: MockIPFSStore): + """Test IpfsConnectionError when ipfs_store.load raises connection-related error.""" + valid_cid_str = "bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi" + # Simulate different connection error messages + errors_to_test = [ + requests.exceptions.ConnectionError("Connection refused"), + requests.exceptions.RequestException("Max retries exceeded"), + requests.exceptions.RequestException("Failed to establish a new connection"), + ] + for error in errors_to_test: + mock_ipfs_store.load.side_effect = error + with pytest.raises( + IpfsConnectionError, + match=f"Failed to connect via IPFSStore.*to fetch CID {valid_cid_str}", + ): + fetch_json_from_cid(valid_cid_str, mock_ipfs_store) + mock_ipfs_store.load.assert_called_once() + mock_ipfs_store.load.reset_mock() # Reset mock for next error in loop + + +def test_fetch_json_from_cid_generic_load_error(mock_ipfs_store: MockIPFSStore): + """Test StacCatalogError for other exceptions during ipfs_store.load.""" + valid_cid_str = "bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi" + mock_ipfs_store.load.side_effect = RuntimeError("Some other IPFSStore error") + + with pytest.raises( + StacCatalogError, + match=f"Error fetching data for CID {valid_cid_str} via IPFSStore", + ): + fetch_json_from_cid(valid_cid_str, mock_ipfs_store) + mock_ipfs_store.load.assert_called_once() + + +# --- Tests for _get_host --- + + +def test_get_host_default(): + """Test _get_host uses the default localhost when env var is not set.""" + # Import DEFAULT_HOST to use in assertion + from dclimate_zarr_client.ipfs_retrieval import DEFAULT_HOST + + with patch.dict(os.environ, {}, clear=True): # Ensure env var is not set + assert _get_host() == DEFAULT_HOST # Check default URI + # If IPFS_HOST is not set, _get_host IGNORES the uri argument and returns DEFAULT_HOST + assert _get_host("/custom/uri") == DEFAULT_HOST + + +def test_get_host_from_env(monkeypatch: MonkeyPatch): + """Test _get_host uses IPFS_HOST environment variable.""" + monkeypatch.setenv("IPFS_HOST", "http://my-ipfs-node:5002") + assert _get_host() == "http://my-ipfs-node:5002/api/v0" + assert _get_host("/other/uri") == "http://my-ipfs-node:5002/other/uri" + + # --- Tests for get_ipns_name_hash (Legacy/Utility Function) --- # These tests remain as UNIT tests, mocking requests and file system, # as they test the specific fallback logic of this function, not STAC traversal. @@ -245,21 +464,6 @@ def test_list_datasets_functional(): # assert "Failed to retrieve dataset list" in str(exc.value) -# def test_list_datasets_local_cache_empty(): -# """ -# Test that if the endpoint fails AND local cache file is empty, -# list_datasets() raises RuntimeError (no data to parse). -# """ -# with patch("requests.get") as mock_requests_get: -# mock_requests_get.side_effect = requests.RequestException("Simulated error") - -# with patch("os.path.exists", return_value=True): -# with patch("builtins.open", mock_open(read_data="")): -# with pytest.raises(RuntimeError) as exc: -# list_datasets() -# assert "Failed to retrieve dataset list" in str(exc.value) - - # def test_geo_temporal_query(): # ds_bytes = geo_temporal_query( # "cpc-precip-conus", @@ -312,14 +516,28 @@ def get_cache_path(): return os.path.join(package_dir, "cids.json") +# --- Helper Functions for Mocking --- # + + +def mock_exists_true(*args, **kwargs) -> bool: + return True + + +def mock_exists_false(*args, **kwargs) -> bool: + return False + + # Use monkeypatch fixture for modifying builtins like open -def test_update_cache_no_update(monkeypatch): +def test_update_cache_no_update(monkeypatch: MonkeyPatch): cached_data = {"dataset": "hash1"} new_data = {"dataset": "hash1"} file_path = get_cache_path() + # Mock os.path.exists needed if called before open + monkeypatch.setattr("os.path.exists", mock_exists_true) + m = mock_open(read_data=json.dumps(cached_data)) - monkeypatch.setattr("builtins.open", m) + monkeypatch.setattr("builtins.open", m) # Correct: Patch built-in open update_cache_if_changed(new_data) @@ -327,13 +545,17 @@ def test_update_cache_no_update(monkeypatch): m.assert_called_once_with(file_path, "r") -def test_update_cache_update(monkeypatch): +def test_update_cache_update(monkeypatch: MonkeyPatch): cached_data = {"dataset": "hash1"} new_data = {"dataset": "hash2"} file_path = get_cache_path() + # Mock os.path.exists needed if called before open + monkeypatch.setattr("os.path.exists", mock_exists_true) + m = mock_open(read_data=json.dumps(cached_data)) - monkeypatch.setattr("builtins.open", m) + # Patch open in the correct namespace where it's called + monkeypatch.setattr("builtins.open", m) # Correct: Patch built-in open update_cache_if_changed(new_data) @@ -342,21 +564,28 @@ def test_update_cache_update(monkeypatch): calls = m.call_args_list assert calls[0].args == (file_path, "r") assert calls[1].args == (file_path, "w") - # Remove the assertion checking the specific write content - # handle = m() # Remove this - # handle.write.assert_called_once_with(json.dumps(new_data)) # Remove this + # Check the content written (optional, but good practice) + # Ensure the mock handle captures the write + # Note: mock_open's write checking can be tricky. + # A simpler check is often sufficient unless exact content is critical. + # handle = m() + # handle.write.assert_called_once_with(json.dumps(new_data)) -def test_update_cache_file_not_found(monkeypatch): +def test_update_cache_file_not_found(monkeypatch: MonkeyPatch): new_data = {"dataset": "hash2"} file_path = get_cache_path() + # No need to mock os.path.exists as the function doesn't use it + # Mock 'open' to raise FileNotFoundError on first call (read), succeed on second (write) - # Create a mock handle instance for the successful write call return value mock_write_handle = mock_open().return_value m = mock_open() - m.side_effect = [FileNotFoundError, mock_write_handle] # Read fails, Write succeeds - monkeypatch.setattr("builtins.open", m) + # First call (open "r") raises FileNotFoundError + # Second call (open "w") returns the mock handle + m.side_effect = [FileNotFoundError, mock_write_handle] + # Patch open in the correct namespace + monkeypatch.setattr("builtins.open", m) # Correct: Patch built-in open update_cache_if_changed(new_data) @@ -365,16 +594,19 @@ def test_update_cache_file_not_found(monkeypatch): calls = m.call_args_list assert calls[0].args == (file_path, "r") assert calls[1].args == (file_path, "w") - # Remove the lines trying to get handle and assert write - # handle = m() # Remove this - # handle.write.assert_called_once_with(json.dumps(new_data)) # Remove this + # Optionally check write content if needed: + # handle = calls[1]._extract_mock_return_value() # Get the handle returned by the 2nd call + # handle.write.assert_called_once_with(json.dumps(new_data)) -def test_update_cache_decode_error(monkeypatch): +def test_update_cache_decode_error(monkeypatch: MonkeyPatch): """Test when the existing cache file has invalid JSON.""" new_data = {"dataset": "hash2"} file_path = get_cache_path() + # Mock os.path.exists needed if called before open + monkeypatch.setattr("os.path.exists", mock_exists_true) + # Create separate mock handles for read and write attempts # The read handle will simulate having invalid data read_handle = mock_open(read_data="invalid json").return_value @@ -383,16 +615,18 @@ def test_update_cache_decode_error(monkeypatch): m = mock_open() # Define side effect: return read_handle on first call, write_handle on second m.side_effect = [read_handle, write_handle] + # Patch open in the correct namespace + monkeypatch.setattr("builtins.open", m) # Correct: Patch built-in open # Mock json.load to raise error when the read_handle is passed to it # Patch it in the correct namespace where it's used mock_json_load = patch( - "dclimate_zarr_client.ipfs_retrieval.json.load", + "json.load", # Patching built-in json directly side_effect=json.JSONDecodeError("err", "doc", 0), - ).start() - - monkeypatch.setattr("builtins.open", m) + ).start() # No need for .start()/.stop() if using 'with patch(...)' + # Use 'with patch' for cleaner setup/teardown + # with patch("json.load", side_effect=json.JSONDecodeError("err", "doc", 0)): update_cache_if_changed(new_data) # Assert open was called twice: read attempt (failed decode), write attempt @@ -401,5 +635,59 @@ def test_update_cache_decode_error(monkeypatch): assert calls[0].args == (file_path, "r") # Read attempt assert calls[1].args == (file_path, "w") # Write attempt - # Clean up the patch for json.load + # Clean up the patch for json.load if using start/stop mock_json_load.stop() + + +# --- Test Legacy get_ipns_name_hash Errors --- +# (Ensure these cover JSONDecodeError during fallback read) + + +def test_get_ipns_name_hash_local_cache_malformed_json_during_fallback(): + """Test DatasetNotFoundError when local cache read fails during fallback""" + with patch( + "requests.get", side_effect=requests.RequestException("Simulated error") + ): + with patch("os.path.exists", return_value=True): + # Mock update_cache_if_changed to avoid file writes during test setup if called early + with patch("dclimate_zarr_client.ipfs_retrieval.update_cache_if_changed"): + # Mock open specifically within the fallback block + with patch( + "dclimate_zarr_client.ipfs_retrieval.open", + mock_open(read_data="INVALID JSON!!"), + create=True, # Allow create=True if needed by mock_open internals + ) as mock_file_open: + # Mock json.load raising the error when called by get_ipns_name_hash + with patch( + "json.load", side_effect=json.JSONDecodeError("err", "doc", 0) + ): + with pytest.raises( + DatasetNotFoundError, match="Invalid dataset name" + ): + get_ipns_name_hash("cpc-precip-conus") + # Assert the mocked open was called for reading the cache + mock_file_open.assert_called_once_with(get_cache_path(), "r") + + +def test_get_ipns_name_hash_local_cache_empty_during_fallback(): + """Test DatasetNotFoundError when local cache is empty during fallback""" + with patch( + "requests.get", side_effect=requests.RequestException("Simulated error") + ): + with patch("os.path.exists", return_value=True): + with patch("dclimate_zarr_client.ipfs_retrieval.update_cache_if_changed"): + with patch( + "dclimate_zarr_client.ipfs_retrieval.open", + mock_open(read_data=""), # Empty file + create=True, + ) as mock_file_open: + # json.load will raise JSONDecodeError on empty string + with patch( + "json.load", + side_effect=json.JSONDecodeError("Expecting value", "", 0), + ): + with pytest.raises( + DatasetNotFoundError, match="Invalid dataset name" + ): + get_ipns_name_hash("cpc-precip-conus") + mock_file_open.assert_called_once_with(get_cache_path(), "r") From e9583ec92aa83611ed60bdb8a00553fd5ba45a53 Mon Sep 17 00:00:00 2001 From: Faolain Date: Tue, 15 Apr 2025 01:28:57 -0400 Subject: [PATCH 2/6] tests: more tests using mocks for fetching json from ipns --- dclimate_zarr_client/ipfs_retrieval.py | 29 +-- tests/test_ipfs_retrieval.py | 263 +++++++++++++++++++++---- 2 files changed, 242 insertions(+), 50 deletions(-) diff --git a/dclimate_zarr_client/ipfs_retrieval.py b/dclimate_zarr_client/ipfs_retrieval.py index 7f186e5..ff116a7 100644 --- a/dclimate_zarr_client/ipfs_retrieval.py +++ b/dclimate_zarr_client/ipfs_retrieval.py @@ -185,7 +185,6 @@ def fetch_json_from_ipns( last_error = StacCatalogError( f"Unexpected error (nocache=true): {type(e).__name__}: {e}" ) - # --- Attempt 2: GET without nocache (if Attempt 1 failed) --- logger.info( f"Retrying fetch JSON via Gateway GET without nocache for: {ipns_name_for_url}" @@ -204,20 +203,34 @@ def fetch_json_from_ipns( allow_redirects=True, ) # Retry response.raise_for_status() - json_content = response.json() + json_content = response.json() # This call may raise JSONDecodeError logger.info( f"Successfully fetched JSON from IPNS '{ipns_name}' (nocache=false)" ) return json_content + except json.JSONDecodeError as e: + # Handle JSON decode errors explicitly on the retry attempt. + response_text = response.text[:500] if response else "[No Response]" + status_code = response.status_code if response else "[No Status]" + err_msg = ( + f"Invalid JSON fetching IPNS '{ipns_name}' (retry) via Gateway {gateway_base}: {e}. " + f"Response text: {response_text[:100]}" + ) + if last_error: + err_msg += f" | Initial error (nocache=true): {type(last_error).__name__}: {last_error}" + raise StacCatalogError(err_msg) from e + except requests.exceptions.ConnectionError as e: raise IpfsConnectionError( f"Connection error during IPNS fetch retry for '{ipns_name}' via Gateway {gateway_base}. Details: {e}" ) from e + except requests.exceptions.Timeout as e: raise IpfsConnectionError( f"Timeout ({timeout}s) during IPNS fetch retry for '{ipns_name}' via Gateway {gateway_base}." ) from e + except requests.exceptions.RequestException as e: # Includes HTTP errors on retry err_msg = ( f"Error fetching IPNS '{ipns_name}' (retry) via Gateway {gateway_base}: {e}" @@ -231,18 +244,12 @@ def fetch_json_from_ipns( except Exception: response_text = "[Could not read response text]" err_msg += f" Status Code: {status_code}, Response: {response_text}" - if last_error: - err_msg += f" | Initial error (nocache=true): {type(last_error).__name__}: {last_error}" - raise StacCatalogError( - err_msg - ) from e # Raise as StacCatalogError as it prevents catalog reading - except json.JSONDecodeError as e: - err_msg = f"Invalid JSON fetching IPNS '{ipns_name}' (retry) via Gateway {gateway_base}: {e}. Response text: {response.text[:500] if response else '[No Response]'}" if last_error: err_msg += f" | Initial error (nocache=true): {type(last_error).__name__}: {last_error}" raise StacCatalogError(err_msg) from e - except Exception as e: # Catch any other unexpected error during retry - err_msg = f"Unexpected error during IPNS fetch retry for '{ipns_name}' via Gateway: {e}" + + except Exception as e: # Catch any other unexpected exceptions + err_msg = f"Unexpected error during IPNS fetch retry for '{ipns_name}' via Gateway {gateway_base}: {e}" if last_error: err_msg += f" | Initial error (nocache=true): {type(last_error).__name__}: {last_error}" raise StacCatalogError(err_msg) from e diff --git a/tests/test_ipfs_retrieval.py b/tests/test_ipfs_retrieval.py index 8812234..ab1f339 100644 --- a/tests/test_ipfs_retrieval.py +++ b/tests/test_ipfs_retrieval.py @@ -9,7 +9,7 @@ import dclimate_zarr_client.ipfs_retrieval as ipfs_retrieval from dclimate_zarr_client.ipfs_retrieval import ( - # Keep imports for functions still tested here + fetch_json_from_ipns, get_ipns_name_hash, update_cache_if_changed, DatasetNotFoundError, @@ -22,20 +22,42 @@ ) from py_hamt import IPFSStore # Import IPFSStore - -# import xarray as xr - # Import constants/configs from dclimate_zarr_client.client import DCLIMATE_STAC_CATALOG_IPNS from .conftest import KNOWN_STAC_DATASET_ID, KNOWN_STAC_DATASET_ID_2 -# Apply IPFS check fixture to relevant tests/module -pytestmark = pytest.mark.usefixtures("check_ipfs_connection") +# Apply IPFS check fixture ONLY to tests that actually need functional IPFS +# Most tests in this file should be mocked unit tests. +# pytestmark = pytest.mark.usefixtures("check_ipfs_connection") # Remove module-level mark +# --- Type Hinting --- +MonkeyPatch = pytest.MonkeyPatch +MockIPFSStore = MagicMock +MockResponse = MagicMock # Alias for requests.Response mock -# --- Type Hinting --- # Define type alias if needed, or use directly -MonkeyPatch = pytest.MonkeyPatch # Common practice for pytest -MockIPFSStore = MagicMock # Alias for clarity + +# --- Fixtures --- +@pytest.fixture +def mock_ipfs_store() -> MockIPFSStore: + """Fixture to create a mock IPFSStore instance.""" + store = MagicMock(spec=IPFSStore) + store.gateway_uri_stem = "http://mock-gateway:8080" + store.rpc_uri_stem = "http://mock-rpc:5001" + return store + + +@pytest.fixture +def mock_requests_get(mocker) -> MagicMock: + """Fixture to mock requests.get.""" + return mocker.patch("requests.get") + + +# --- Helper to get cache path ---test_get_ipfs_store_defaults +def get_cache_path(): + # Helper to get the expected cache file path within the package + # Need to ensure this reflects the actual location used by the code + package_dir = os.path.dirname(ipfs_retrieval.__file__) + return os.path.join(package_dir, "cids.json") # --- Tests for _get_ipfs_store --- @@ -97,29 +119,15 @@ def test_get_ipfs_store_mixed_args_env(monkeypatch: MonkeyPatch): assert isinstance(store, MagicMock) -# --- Tests for fetch_json_from_cid --- - - -@pytest.fixture -def mock_ipfs_store() -> MockIPFSStore: - """Fixture to create a mock IPFSStore instance.""" - store = MagicMock(spec=IPFSStore) - # Set default URIs for error messages if needed - store.gateway_uri_stem = "http://mock-gateway:8080" - store.rpc_uri_stem = "http://mock-rpc:5001" - return store +# --- Tests for fetch_json_from_cid (Unit/Mocked) --- def test_fetch_json_from_cid_success(mock_ipfs_store: MockIPFSStore): """Test successful fetching and decoding of JSON from CID.""" - valid_cid_str = ( - "bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi" # Example CID - ) + valid_cid_str = "bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi" json_data: Dict[str, Any] = {"key": "value"} mock_ipfs_store.load.return_value = json.dumps(json_data).encode("utf-8") - result: Dict[str, Any] = fetch_json_from_cid(valid_cid_str, mock_ipfs_store) - assert result == json_data mock_ipfs_store.load.assert_called_once() # Check that CID.decode was implicitly called by store.load mock (or explicitly if store expects CID obj) @@ -135,9 +143,7 @@ def test_fetch_json_from_cid_success_with_prefix(mock_ipfs_store: MockIPFSStore) cid_str_with_prefix = f"/ipfs/{cid_str_no_prefix}" json_data: Dict[str, Any] = {"key": "value"} mock_ipfs_store.load.return_value = json.dumps(json_data).encode("utf-8") - result: Dict[str, Any] = fetch_json_from_cid(cid_str_with_prefix, mock_ipfs_store) - assert result == json_data mock_ipfs_store.load.assert_called_once() call_args = mock_ipfs_store.load.call_args[0] @@ -159,7 +165,6 @@ def test_fetch_json_from_cid_load_returns_none(mock_ipfs_store: MockIPFSStore): """Test StacCatalogError when ipfs_store.load returns None or empty bytes.""" valid_cid_str = "bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi" mock_ipfs_store.load.return_value = b"" # Empty bytes - with pytest.raises( StacCatalogError, match=f"No data returned for CID: {valid_cid_str}" ): @@ -171,7 +176,6 @@ def test_fetch_json_from_cid_json_decode_error(mock_ipfs_store: MockIPFSStore): """Test StacCatalogError when fetched data is not valid JSON.""" valid_cid_str = "bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi" mock_ipfs_store.load.return_value = b"this is not json" - with pytest.raises( StacCatalogError, match=f"Failed to decode JSON from CID {valid_cid_str}" ): @@ -183,7 +187,6 @@ def test_fetch_json_from_cid_timeout_error(mock_ipfs_store: MockIPFSStore): """Test IpfsConnectionError when ipfs_store.load raises Timeout.""" valid_cid_str = "bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi" mock_ipfs_store.load.side_effect = requests.exceptions.Timeout("Request timed out") - with pytest.raises( IpfsConnectionError, match=f"Timeout fetching CID {valid_cid_str}" ): @@ -225,8 +228,6 @@ def test_fetch_json_from_cid_generic_load_error(mock_ipfs_store: MockIPFSStore): # --- Tests for _get_host --- - - def test_get_host_default(): """Test _get_host uses the default localhost when env var is not set.""" # Import DEFAULT_HOST to use in assertion @@ -245,6 +246,197 @@ def test_get_host_from_env(monkeypatch: MonkeyPatch): assert _get_host("/other/uri") == "http://my-ipfs-node:5002/other/uri" +# --- NEW: Tests for fetch_json_from_ipns (Mocked Error Paths) --- + + +class TestFetchJsonFromIpnsErrors: + MOCK_IPNS = "k51qzi5uqu5dk89atnl883sr0g1cb2py631ckz9ng45qhk6dg0pj141jtxtx6l" + MOCK_GATEWAY = "http://mock-gateway:8080" + EXPECTED_URL = f"{MOCK_GATEWAY}/ipns/{MOCK_IPNS}" + + @pytest.fixture(autouse=True) + def setup_mocks(self, monkeypatch, mock_requests_get): + # Mock _get_ipfs_store to return a predictable gateway + mock_store = MagicMock(spec=IPFSStore) + mock_store.gateway_uri_stem = self.MOCK_GATEWAY + monkeypatch.setattr( + ipfs_retrieval, "_get_ipfs_store", lambda *args, **kwargs: mock_store + ) + self.mock_requests_get = mock_requests_get + + def mock_response( + self, status_code=200, json_data=None, text=None, raise_for_status_error=None + ) -> MockResponse: + mock_resp = MagicMock(spec=requests.Response) + mock_resp.status_code = status_code + mock_resp.raise_for_status.side_effect = raise_for_status_error + if json_data is not None: + mock_resp.json.return_value = json_data + # If json_data is provided, requests usually sets text as well + mock_resp.text = json.dumps(json_data) if text is None else text + else: + mock_resp.json.side_effect = requests.exceptions.JSONDecodeError( + "Expecting value", "doc", 0 + ) + mock_resp.text = text if text is not None else "Invalid JSON" + return mock_resp + + def test_fetch_json_from_ipns_empty_name(self): + with pytest.raises(ValueError, match="IPNS name cannot be empty"): + fetch_json_from_ipns("") + + def test_fetch_json_from_ipns_initial_timeout_then_success(self): + """Simulate timeout on first try, success on retry.""" + mock_resp_success = self.mock_response( + status_code=200, json_data={"type": "Catalog"} + ) + self.mock_requests_get.side_effect = [ + requests.exceptions.Timeout("Initial timeout"), + mock_resp_success, + ] + + result = fetch_json_from_ipns(self.MOCK_IPNS) + + assert result == {"type": "Catalog"} + assert self.mock_requests_get.call_count == 2 + # Check params: first call with nocache=true, second without + assert self.mock_requests_get.call_args_list[0].kwargs["params"] == { + "nocache": "true" + } + assert self.mock_requests_get.call_args_list[1].kwargs["params"] == {} + + def test_fetch_json_from_ipns_initial_connection_error(self): + """Simulate ConnectionError on first try (should raise immediately).""" + self.mock_requests_get.side_effect = requests.exceptions.ConnectionError( + "Gateway down" + ) + + with pytest.raises(IpfsConnectionError, match="Connection error fetching IPNS"): + fetch_json_from_ipns(self.MOCK_IPNS) + assert self.mock_requests_get.call_count == 1 + + def test_fetch_json_from_ipns_initial_json_decode_then_success(self): + """Simulate JSON decode error on first try, success on retry.""" + mock_resp_bad_json = self.mock_response( + status_code=200, text="error page" + ) + mock_resp_success = self.mock_response( + status_code=200, json_data={"type": "Catalog"} + ) + self.mock_requests_get.side_effect = [mock_resp_bad_json, mock_resp_success] + + result = fetch_json_from_ipns(self.MOCK_IPNS) + + assert result == {"type": "Catalog"} + assert self.mock_requests_get.call_count == 2 + assert self.mock_requests_get.call_args_list[0].kwargs["params"] == { + "nocache": "true" + } + assert self.mock_requests_get.call_args_list[1].kwargs["params"] == {} + + def test_fetch_json_from_ipns_initial_500_error_then_success(self): + """Simulate 500 HTTP error on first try, success on retry.""" + mock_resp_500 = self.mock_response( + status_code=500, + text="Server Error", + raise_for_status_error=requests.exceptions.HTTPError("500 Error"), + ) + mock_resp_success = self.mock_response( + status_code=200, json_data={"type": "Catalog"} + ) + self.mock_requests_get.side_effect = [mock_resp_500, mock_resp_success] + + result = fetch_json_from_ipns(self.MOCK_IPNS) + + assert result == {"type": "Catalog"} + assert self.mock_requests_get.call_count == 2 + + def test_fetch_json_from_ipns_retry_timeout(self): + """Simulate failure on first try (e.g., timeout) AND timeout on retry.""" + self.mock_requests_get.side_effect = [ + requests.exceptions.Timeout("Initial timeout"), + requests.exceptions.Timeout("Retry timeout"), + ] + + with pytest.raises( + IpfsConnectionError, match="Timeout .* during IPNS fetch retry" + ): + fetch_json_from_ipns(self.MOCK_IPNS) + assert self.mock_requests_get.call_count == 2 + + def test_fetch_json_from_ipns_retry_connection_error(self): + """Simulate failure on first try AND ConnectionError on retry.""" + self.mock_requests_get.side_effect = [ + requests.exceptions.Timeout("Initial timeout"), + requests.exceptions.ConnectionError("Retry connection failed"), + ] + + with pytest.raises( + IpfsConnectionError, match="Connection error during IPNS fetch retry" + ): + fetch_json_from_ipns(self.MOCK_IPNS) + assert self.mock_requests_get.call_count == 2 + + def test_fetch_json_from_ipns_retry_json_decode_error(self): + """Simulate failure on first try AND JSON decode error on retry.""" + mock_resp_bad_json_retry = self.mock_response( + status_code=200, text="Retry also bad json" + ) + self.mock_requests_get.side_effect = [ + requests.exceptions.Timeout("Initial timeout"), + mock_resp_bad_json_retry, + ] + + with pytest.raises( + StacCatalogError, match="Invalid JSON fetching IPNS .* \\(retry\\)" + ): + fetch_json_from_ipns(self.MOCK_IPNS) + assert self.mock_requests_get.call_count == 2 + + def test_fetch_json_from_ipns_retry_http_error(self): + """Simulate failure on first try AND HTTP error on retry.""" + mock_resp_503_retry = self.mock_response( + status_code=503, + text="Service Unavailable", + raise_for_status_error=requests.exceptions.HTTPError("503 Error"), + ) + self.mock_requests_get.side_effect = [ + requests.exceptions.Timeout("Initial timeout"), + mock_resp_503_retry, + ] + + with pytest.raises( + StacCatalogError, match="Error fetching IPNS .* \\(retry\\) via Gateway" + ): + fetch_json_from_ipns(self.MOCK_IPNS) + assert self.mock_requests_get.call_count == 2 + + def test_fetch_json_from_ipns_initial_other_exception_then_success(self): + """Simulate generic exception on first try, success on retry.""" + mock_resp_success = self.mock_response( + status_code=200, json_data={"type": "Catalog"} + ) + self.mock_requests_get.side_effect = [ + RuntimeError("Unexpected issue"), + mock_resp_success, + ] + result = fetch_json_from_ipns(self.MOCK_IPNS) + assert result == {"type": "Catalog"} + assert self.mock_requests_get.call_count == 2 + + def test_fetch_json_from_ipns_retry_other_exception(self): + """Simulate failure on first try AND generic exception on retry.""" + self.mock_requests_get.side_effect = [ + requests.exceptions.Timeout("Initial timeout"), + RuntimeError("Unexpected issue on retry"), + ] + with pytest.raises( + StacCatalogError, match="Unexpected error during IPNS fetch retry" + ): + fetch_json_from_ipns(self.MOCK_IPNS) + assert self.mock_requests_get.call_count == 2 + + # --- Tests for get_ipns_name_hash (Legacy/Utility Function) --- # These tests remain as UNIT tests, mocking requests and file system, # as they test the specific fallback logic of this function, not STAC traversal. @@ -509,13 +701,6 @@ def test_list_datasets_functional(): # These remain unit tests using mocks for file I/O. -def get_cache_path(): - # Helper to get the expected cache file path within the package - # Need to ensure this reflects the actual location used by the code - package_dir = os.path.dirname(ipfs_retrieval.__file__) - return os.path.join(package_dir, "cids.json") - - # --- Helper Functions for Mocking --- # From 55df8227da0afd23f91c48c1082c11833c5639a4 Mon Sep 17 00:00:00 2001 From: Faolain Date: Tue, 15 Apr 2025 01:54:53 -0400 Subject: [PATCH 3/6] fix: missing legacy support and update tests --- dclimate_zarr_client/ipfs_retrieval.py | 2 +- tests/test_ipfs_retrieval.py | 242 ++++++++++++++++++++++++- 2 files changed, 242 insertions(+), 2 deletions(-) diff --git a/dclimate_zarr_client/ipfs_retrieval.py b/dclimate_zarr_client/ipfs_retrieval.py index ff116a7..ce21ab2 100644 --- a/dclimate_zarr_client/ipfs_retrieval.py +++ b/dclimate_zarr_client/ipfs_retrieval.py @@ -314,7 +314,7 @@ def get_dataset_hamt_cid_from_stac( logger.warning( f"Skipping child link with unexpected string href format (expected dict): {link}" ) - # If needed: collections_to_visit.append(href_obj[6:]) + collections_to_visit.append(href_obj[6:]) else: logger.warning( f"Skipping invalid child link format in root catalog: {link}" diff --git a/tests/test_ipfs_retrieval.py b/tests/test_ipfs_retrieval.py index ab1f339..8cde0f5 100644 --- a/tests/test_ipfs_retrieval.py +++ b/tests/test_ipfs_retrieval.py @@ -1,7 +1,7 @@ import os import json from typing import Dict, Any # Import Dict and Any -from unittest.mock import patch, mock_open, MagicMock +from unittest.mock import patch, mock_open, MagicMock, ANY import pytest import requests @@ -10,6 +10,7 @@ import dclimate_zarr_client.ipfs_retrieval as ipfs_retrieval from dclimate_zarr_client.ipfs_retrieval import ( fetch_json_from_ipns, + get_dataset_hamt_cid_from_stac, get_ipns_name_hash, update_cache_if_changed, DatasetNotFoundError, @@ -437,6 +438,245 @@ def test_fetch_json_from_ipns_retry_other_exception(self): assert self.mock_requests_get.call_count == 2 +# --- NEW: Tests for get_dataset_hamt_cid_from_stac (Mocked Error Paths) --- + + +@patch("dclimate_zarr_client.ipfs_retrieval.fetch_json_from_ipns") +@patch("dclimate_zarr_client.ipfs_retrieval.fetch_json_from_cid") +def test_get_hamt_cid_root_catalog_fetch_error(mock_fetch_cid, mock_fetch_ipns): + """Test error when fetching the root catalog fails.""" + mock_fetch_ipns.side_effect = IpfsConnectionError("Cannot connect to gateway") + with pytest.raises(StacCatalogError, match="Failed to fetch root catalog"): + get_dataset_hamt_cid_from_stac( + DCLIMATE_STAC_CATALOG_IPNS, KNOWN_STAC_DATASET_ID + ) + mock_fetch_ipns.assert_called_once() + mock_fetch_cid.assert_not_called() + + +@patch("dclimate_zarr_client.ipfs_retrieval.fetch_json_from_ipns") +@patch("dclimate_zarr_client.ipfs_retrieval.fetch_json_from_cid") +def test_get_hamt_cid_invalid_root_catalog_format(mock_fetch_cid, mock_fetch_ipns): + """Test error when root catalog JSON is not a valid STAC Catalog.""" + mock_fetch_ipns.return_value = {"not": "a catalog"} + with pytest.raises(StacCatalogError, match="Invalid root catalog format"): + get_dataset_hamt_cid_from_stac( + DCLIMATE_STAC_CATALOG_IPNS, KNOWN_STAC_DATASET_ID + ) + mock_fetch_ipns.assert_called_once() + mock_fetch_cid.assert_not_called() + + +@patch("dclimate_zarr_client.ipfs_retrieval.fetch_json_from_ipns") +@patch("dclimate_zarr_client.ipfs_retrieval.fetch_json_from_cid") +def test_get_hamt_cid_no_child_links(mock_fetch_cid, mock_fetch_ipns): + """Test error when root catalog has no valid child links.""" + mock_fetch_ipns.return_value = { + "type": "Catalog", + "id": "root", + "links": [{"rel": "self", "href": "."}], # No child links + } + with pytest.raises(StacCatalogError, match="No valid child collection links found"): + get_dataset_hamt_cid_from_stac( + DCLIMATE_STAC_CATALOG_IPNS, KNOWN_STAC_DATASET_ID + ) + mock_fetch_ipns.assert_called_once() + mock_fetch_cid.assert_not_called() + + +@patch("dclimate_zarr_client.ipfs_retrieval.fetch_json_from_ipns") +@patch("dclimate_zarr_client.ipfs_retrieval.fetch_json_from_cid") +def test_get_hamt_cid_invalid_child_link_formats(mock_fetch_cid, mock_fetch_ipns): + """Test skipping various invalid child link formats.""" + mock_fetch_ipns.return_value = { + "type": "Catalog", + "id": "root", + "links": [ + { + "rel": "child", + "type": "application/json", + "href": {"/": 123}, + }, # Invalid CID value + { + "rel": "child", + "type": "application/json", + "href": "/ipfs/legacy_cid_string", + }, # Legacy string (warning expected) + { + "rel": "child", + "type": "application/json", + "href": "not_a_dict_or_ipfs_string", + }, # Invalid format + {"rel": "child", "type": "application/json"}, # Missing href + { + "rel": "child", + "type": "application/xml", + "href": {"/": "cid1"}, + }, # Wrong type + { + "rel": "item", + "type": "application/json", + "href": {"/": "cid2"}, + }, # Wrong rel + ], + } + # Expect DatasetNotFoundError because no *valid* child links lead anywhere + with pytest.raises( + DatasetNotFoundError, match=f"Dataset ID '{KNOWN_STAC_DATASET_ID}' not found" + ): + get_dataset_hamt_cid_from_stac( + DCLIMATE_STAC_CATALOG_IPNS, KNOWN_STAC_DATASET_ID + ) + mock_fetch_ipns.assert_called_once() + mock_fetch_cid.assert_called_once() # One legacy string was allowed + + +@patch("dclimate_zarr_client.ipfs_retrieval.fetch_json_from_ipns") +@patch("dclimate_zarr_client.ipfs_retrieval.fetch_json_from_cid") +def test_get_hamt_cid_collection_fetch_error(mock_fetch_cid, mock_fetch_ipns): + """Test scenario where fetching a collection fails but others might succeed.""" + target_dataset = "dataset-in-good-collection" + good_collection_cid = "bafyGoodCollection" + bad_collection_cid = "bafyBadCollection" + + mock_fetch_ipns.return_value = { + "type": "Catalog", + "id": "root", + "links": [ + { + "rel": "child", + "type": "application/json", + "href": {"/": bad_collection_cid}, + }, + { + "rel": "child", + "type": "application/json", + "href": {"/": good_collection_cid}, + }, + ], + } + # Mock fetch_json_from_cid behavior + good_item_cid = "bafyGoodItem" + good_hamt_cid = "bafyGoodHAMT" + + def fetch_cid_side_effect(cid_str, store): + if cid_str == bad_collection_cid: + raise IpfsConnectionError("Failed to fetch bad collection") + elif cid_str == good_collection_cid: + return { + "type": "Collection", + "id": "good-collection", + "links": [ + { + "rel": "item", + "type": "application/json", + "href": {"/": good_item_cid}, + } + ], + } + elif cid_str == good_item_cid: + return { + "type": "Feature", + "id": target_dataset, + "assets": {"hamt-zarr": {"href": f"/ipfs/{good_hamt_cid}"}}, + } + else: + raise ValueError(f"Unexpected CID requested: {cid_str}") + + mock_fetch_cid.side_effect = fetch_cid_side_effect + + # Should succeed by finding the dataset in the good collection + result_cid = get_dataset_hamt_cid_from_stac( + DCLIMATE_STAC_CATALOG_IPNS, target_dataset + ) + assert result_cid == good_hamt_cid + assert ( + mock_fetch_cid.call_count == 3 + ) # Bad collection fetch fails, good collection + good item fetches succeed + + +@patch("dclimate_zarr_client.ipfs_retrieval.fetch_json_from_ipns") +@patch("dclimate_zarr_client.ipfs_retrieval.fetch_json_from_cid") +def test_get_hamt_cid_invalid_collection_format(mock_fetch_cid, mock_fetch_ipns): + """Test skipping invalid collection format.""" + collection_cid = "bafyValidCollectionLink" + mock_fetch_ipns.return_value = { + "type": "Catalog", + "id": "root", + "links": [ + {"rel": "child", "type": "application/json", "href": {"/": collection_cid}} + ], + } + mock_fetch_cid.return_value = {"not": "a collection"} # Invalid format + + with pytest.raises( + DatasetNotFoundError, match=f"Dataset ID '{KNOWN_STAC_DATASET_ID}' not found" + ): + get_dataset_hamt_cid_from_stac( + DCLIMATE_STAC_CATALOG_IPNS, KNOWN_STAC_DATASET_ID + ) + mock_fetch_cid.assert_called_once_with( + collection_cid, ANY + ) # Assuming default store used internally + + +@patch("dclimate_zarr_client.ipfs_retrieval.fetch_json_from_ipns") +@patch("dclimate_zarr_client.ipfs_retrieval.fetch_json_from_cid") +def test_get_hamt_cid_item_fetch_error(mock_fetch_cid, mock_fetch_ipns): + """Test scenario where fetching an item fails but the target is in another item.""" + target_dataset = KNOWN_STAC_DATASET_ID + collection_cid = "bafyCollectionWithItems" + bad_item_cid = "bafyBadItem" + good_item_cid = "bafyGoodItem" + good_hamt_cid = "bafyTargetHAMT" + + mock_fetch_ipns.return_value = { + "type": "Catalog", + "id": "root", + "links": [ + {"rel": "child", "type": "application/json", "href": {"/": collection_cid}} + ], + } + + def fetch_cid_side_effect(cid_str, store): + if cid_str == collection_cid: + return { + "type": "Collection", + "id": "collection", + "links": [ + { + "rel": "item", + "type": "application/json", + "href": {"/": bad_item_cid}, + }, + { + "rel": "item", + "type": "application/json", + "href": {"/": good_item_cid}, + }, + ], + } + elif cid_str == bad_item_cid: + raise StacCatalogError("Failed to fetch bad item") + elif cid_str == good_item_cid: + return { + "type": "Feature", + "id": target_dataset, + "assets": {"hamt-zarr": {"href": f"/ipfs/{good_hamt_cid}"}}, + } + else: + raise ValueError(f"Unexpected CID requested: {cid_str}") + + mock_fetch_cid.side_effect = fetch_cid_side_effect + + result_cid = get_dataset_hamt_cid_from_stac( + DCLIMATE_STAC_CATALOG_IPNS, target_dataset + ) + assert result_cid == good_hamt_cid + # Called for collection, bad item (failed), good item (success) + assert mock_fetch_cid.call_count == 3 + + # --- Tests for get_ipns_name_hash (Legacy/Utility Function) --- # These tests remain as UNIT tests, mocking requests and file system, # as they test the specific fallback logic of this function, not STAC traversal. From 7bca52efac23e10d1fea5b2f86414408e56846ab Mon Sep 17 00:00:00 2001 From: Faolain Date: Mon, 9 Jun 2025 01:59:51 -0400 Subject: [PATCH 4/6] test: extensive mocking tests --- dclimate_zarr_client/ipfs_retrieval.py | 176 ++++++----- tests/test_geotemporal_utils.py | 32 ++ tests/test_ipfs_retrieval.py | 392 +++++++++++++++++++++++++ 3 files changed, 526 insertions(+), 74 deletions(-) create mode 100644 tests/test_geotemporal_utils.py diff --git a/dclimate_zarr_client/ipfs_retrieval.py b/dclimate_zarr_client/ipfs_retrieval.py index ce21ab2..9bd2b77 100644 --- a/dclimate_zarr_client/ipfs_retrieval.py +++ b/dclimate_zarr_client/ipfs_retrieval.py @@ -260,8 +260,9 @@ def get_dataset_hamt_cid_from_stac( root_catalog_ipns: str, target_dataset_id: str, gateway_uri_stem: str | None = None, - rpc_uri_stem: str - | None = None, # Keep rpc_uri_stem for IPFSStore config if needed by fetch_json_from_cid + rpc_uri_stem: ( + str | None + ) = None, # Keep rpc_uri_stem for IPFSStore config if needed by fetch_json_from_cid ) -> str: """ Traverses the dClimate STAC catalog starting from a root IPNS name @@ -330,14 +331,15 @@ def get_dataset_hamt_cid_from_stac( for collection_cid in collections_to_visit: logger.debug(f"Fetching collection content for CID: {collection_cid}") try: - # *** Use fetch_json_from_cid *** + # --- collection JSON --- collection = fetch_json_from_cid(collection_cid, ipfs_store) if ( not isinstance(collection, dict) or collection.get("type") != "Collection" ): logger.warning( - f"Skipping invalid collection format for CID {collection_cid}. Type: {collection.get('type')}" + f"Skipping invalid collection format for CID {collection_cid}. " + f"Type: {collection.get('type')}" ) continue @@ -345,98 +347,116 @@ def get_dataset_hamt_cid_from_stac( for link in collection.get("links", []): if link.get("rel") == "item" and link.get("type") == "application/json": item_href_obj = link.get("href") - item_cid = None # Reset item_cid for each link + item_cid = None # reset each link - # *** MODIFIED: Handle dict href for item links *** + # --- handle IPLD or legacy /ipfs/ links --- if isinstance(item_href_obj, dict): - item_cid = item_href_obj.get("/") # Extract item CID string + item_cid = item_href_obj.get("/") # IPLD dict elif isinstance(item_href_obj, str) and item_href_obj.startswith( "/ipfs/" ): logger.warning( - f"Found item link with legacy string href format in {collection_cid}: {link}" + f"Found legacy string href in {collection_cid}: {link}" ) - item_cid = item_href_obj[6:] + item_cid = item_href_obj[6:] # strip "/ipfs/" else: logger.warning( - f"Skipping invalid item link format in collection {collection_cid}: {link}" + f"Skipping invalid item link in {collection_cid}: {link}" ) - continue # Skip this link if format is wrong + continue - if isinstance(item_cid, str): - items_found_in_collection += 1 - # logger.debug(f"Fetching item content for CID: {item_cid}") # Can be verbose - try: - # *** Use fetch_json_from_cid with the extracted item CID string *** - item = fetch_json_from_cid(item_cid, ipfs_store) + if not isinstance(item_cid, str): + # already logged warning + continue - if ( - not isinstance(item, dict) - or item.get("type") != "Feature" - ): - logger.warning( - f"Skipping invalid item format for CID {item_cid}. Type: {item.get('type')}" - ) - continue + items_found_in_collection += 1 + item_id: str | None = None # keep in scope for except + try: + # --- item JSON --- + item = fetch_json_from_cid(item_cid, ipfs_store) - item_id = item.get("id") - if item_id == target_dataset_id: - logger.info( - f"Found matching item for '{target_dataset_id}' with CID {item_cid} in collection {collection_cid}" - ) - hamt_asset = item.get("assets", {}).get("hamt-zarr", {}) - hamt_cid_href = hamt_asset.get( - "href" - ) # This should be the /ipfs/ string - - if not isinstance( - hamt_cid_href, str - ) or not hamt_cid_href.startswith("/ipfs/"): - raise StacCatalogError( - f"STAC Item '{item_id}' (CID: {item_cid}) is missing a valid string 'assets.hamt-zarr.href' starting with /ipfs/. Found: '{hamt_cid_href}' (type: {type(hamt_cid_href).__name__})" - ) + if not isinstance(item, dict) or item.get("type") != "Feature": + logger.warning( + f"Skipping invalid item format for CID {item_cid}. " + f"Type: {item.get('type')}" + ) + continue - hamt_cid_str = hamt_cid_href[ - 6: - ] # Slice the /ipfs/ prefix - logger.info( - f"Successfully extracted HAMT CID for '{target_dataset_id}': {hamt_cid_str}" - ) - _stac_hamt_cid_cache[target_dataset_id] = hamt_cid_str - return hamt_cid_str + item_id = item.get("id") + if item_id != target_dataset_id: + # not the dataset we're looking for + continue - except (StacCatalogError, IpfsConnectionError) as item_err: - # Log error but continue searching other items/collections - logger.error( - f"Error processing item {item_cid} in collection {collection_cid}, continuing search: {item_err}" - ) - except ( - Exception - ) as item_err: # Catch unexpected errors during item processing - logger.error( - f"Unexpected error processing item {item_cid} in collection {collection_cid}, continuing search: {type(item_err).__name__}: {item_err}" + logger.info( + f"Found matching item for '{target_dataset_id}' " + f"(CID {item_cid}) in collection {collection_cid}" + ) + hamt_asset = item.get("assets", {}).get("hamt-zarr", {}) + hamt_cid_href = hamt_asset.get("href") # expected "/ipfs/" + + if not isinstance( + hamt_cid_href, str + ) or not hamt_cid_href.startswith("/ipfs/"): + raise StacCatalogError( + f"STAC Item '{item_id}' (CID: {item_cid}) is missing a " + f"valid string 'assets.hamt-zarr.href' starting with " + f"/ipfs/. Found: '{hamt_cid_href}' " + f"(type: {type(hamt_cid_href).__name__})" ) - # else: Invalid item CID extracted, already logged warning + + hamt_cid_str = hamt_cid_href[6:] # drop "/ipfs/" + logger.info( + f"Successfully extracted HAMT CID for '{target_dataset_id}': " + f"{hamt_cid_str}" + ) + _stac_hamt_cid_cache[target_dataset_id] = hamt_cid_str + return hamt_cid_str + + # ── error handling ────────────────────────────────────────── + except StacCatalogError as item_err: + # If the failing item *is* the target dataset, bubble it up. + if item_id == target_dataset_id: + raise item_err + logger.error( + f"Error processing non-target item {item_cid} in collection " + f"{collection_cid}: {item_err}" + ) + except IpfsConnectionError as item_err: + logger.error( + f"IPFS error processing item {item_cid} in collection " + f"{collection_cid}: {item_err}" + ) + except Exception as item_err: + logger.error( + f"Unexpected error processing item {item_cid} in collection " + f"{collection_cid}: {type(item_err).__name__}: {item_err}" + ) logger.debug( - f"Finished searching {items_found_in_collection} items in collection {collection.get('id', collection_cid)}." + f"Finished searching {items_found_in_collection} items in collection " + f"{collection.get('id', collection_cid)}." ) - except (StacCatalogError, IpfsConnectionError) as col_err: - # Log error but continue searching other collections + except StacCatalogError as col_err: + # ← this is the error that means “the target dataset is malformed” + # → let it propagate to the caller so tests (and callers) can see it. + raise col_err + except IpfsConnectionError as col_err: + # ← still swallow network errors so other collections can be tried logger.error( - f"Error processing collection {collection_cid}, continuing search: {col_err}" + f"IPFS error processing collection {collection_cid}, continuing search: " + f"{col_err}" ) - except ( - Exception - ) as col_err: # Catch unexpected errors during collection processing + except Exception as col_err: logger.error( - f"Unexpected error processing collection {collection_cid}, continuing search: {type(col_err).__name__}: {col_err}" + f"Unexpected error processing collection {collection_cid}, continuing " + f"search: {type(col_err).__name__}: {col_err}" ) - # If loop completes without finding the dataset + # If the loop completes without returning raise DatasetNotFoundError( - f"Dataset ID '{target_dataset_id}' not found after searching all collections in the STAC catalog rooted at IPNS '{root_catalog_ipns}'." + f"Dataset ID '{target_dataset_id}' not found after searching all collections " + f"in the STAC catalog rooted at IPNS '{root_catalog_ipns}'." ) @@ -675,6 +695,9 @@ def _get_dataset_by_ipfs_cid( raise StacCatalogError( f"Zarr metadata not found at CID {ipfs_cid}. Is it a valid Zarr root? Error: {e}" ) from e + except ValueError: + # Let ValueErrors propagate, e.g. from invalid CID format + raise except Exception as e: # Catch other potential errors (e.g., Zarr format errors, py-hamt errors) logger.error( @@ -883,10 +906,15 @@ def list_datasets( ) # else: Invalid item CID extracted, already logged warning - except (StacCatalogError, IpfsConnectionError) as col_err: - # Log and skip this specific collection if fetching/parsing fails - logger.warning( - f"Skipping collection {collection_cid} during list due to error: {col_err}" + # 1️⃣ propagate a StacCatalogError that bubbled up from the **target item** + except StacCatalogError as col_err: + raise col_err + + # 2️⃣ still swallow IPFS/network problems so that other collections can be tried + except IpfsConnectionError as col_err: + logger.error( + f"IPFS error processing collection {collection_cid}, continuing search: " + f"{col_err}" ) except Exception as col_err: # Catch unexpected errors logger.warning( diff --git a/tests/test_geotemporal_utils.py b/tests/test_geotemporal_utils.py new file mode 100644 index 0000000..f402d6e --- /dev/null +++ b/tests/test_geotemporal_utils.py @@ -0,0 +1,32 @@ +import numpy as np +import pytest + +from dclimate_zarr_client.geotemporal_data import _haversine, _check_input_parameters +from dclimate_zarr_client import dclimate_zarr_errors as errors + + +def test_haversine_single_points(): + dist = _haversine(0, 0, 0, 1) + assert dist == pytest.approx(111.195, rel=1e-3) + + dist = _haversine(36.12, -86.67, 33.94, -118.40) + assert dist == pytest.approx(2886.44, rel=1e-2) + + +def test_haversine_arrays(): + lats1 = np.array([0, 10]) + lons1 = np.array([0, 0]) + lats2 = np.array([0, 20]) + lons2 = np.array([1, 0]) + dists = _haversine(lats1, lons1, lats2, lons2) + assert np.allclose(dists, [111.195, 1111.95], rtol=1e-3) + + +def test_check_input_parameters_invalid_period(): + with pytest.raises(errors.InvalidTimePeriodError): + _check_input_parameters(time_period="decade") + + +def test_check_input_parameters_invalid_method(): + with pytest.raises(errors.InvalidAggregationMethodError): + _check_input_parameters(agg_method="average") diff --git a/tests/test_ipfs_retrieval.py b/tests/test_ipfs_retrieval.py index 8cde0f5..82dbd6c 100644 --- a/tests/test_ipfs_retrieval.py +++ b/tests/test_ipfs_retrieval.py @@ -1,4 +1,5 @@ import os +import re import json from typing import Dict, Any # Import Dict and Any from unittest.mock import patch, mock_open, MagicMock, ANY @@ -9,10 +10,14 @@ import dclimate_zarr_client.ipfs_retrieval as ipfs_retrieval from dclimate_zarr_client.ipfs_retrieval import ( + _stac_hamt_cid_cache, fetch_json_from_ipns, get_dataset_hamt_cid_from_stac, get_ipns_name_hash, + _get_dataset_by_ipfs_cid, update_cache_if_changed, + _get_single_metadata, + list_datasets, DatasetNotFoundError, IpfsConnectionError, StacCatalogError, @@ -677,6 +682,393 @@ def fetch_cid_side_effect(cid_str, store): assert mock_fetch_cid.call_count == 3 +@patch("dclimate_zarr_client.ipfs_retrieval.fetch_json_from_ipns") +@patch("dclimate_zarr_client.ipfs_retrieval.fetch_json_from_cid") +def test_get_hamt_cid_invalid_item_format(mock_fetch_cid, mock_fetch_ipns): + """Test skipping invalid item format.""" + # --- FIX: Clear cache at the start of the test --- + _stac_hamt_cid_cache.clear() + + collection_cid = "bafyCollection" + item_cid = "bafyItem" + mock_fetch_ipns.return_value = { + "type": "Catalog", + "id": "dclimate-stac-catalog", + "links": [ + {"rel": "child", "type": "application/json", "href": {"/": collection_cid}} + ], + } + mock_fetch_cid.side_effect = [ + # First call returns collection + { + "type": "Collection", + "id": "collection", + "links": [ + {"rel": "item", "type": "application/json", "href": {"/": item_cid}} + ], + }, + # Second call returns invalid item + {"not": "a feature"}, + ] + with pytest.raises( + DatasetNotFoundError, match=f"Dataset ID '{KNOWN_STAC_DATASET_ID}' not found" + ): + get_dataset_hamt_cid_from_stac( + DCLIMATE_STAC_CATALOG_IPNS, KNOWN_STAC_DATASET_ID + ) + assert mock_fetch_cid.call_count == 2 + + +@patch("dclimate_zarr_client.ipfs_retrieval.fetch_json_from_ipns") +@patch("dclimate_zarr_client.ipfs_retrieval.fetch_json_from_cid") +def test_get_hamt_cid_item_missing_hamt_asset(mock_fetch_cid, mock_fetch_ipns): + """Test error if the found item doesn't have the 'hamt-zarr' asset href.""" + target_dataset = KNOWN_STAC_DATASET_ID + collection_cid = "bafyCollection" + item_cid = "bafyItem" + mock_fetch_ipns.return_value = { + "type": "Catalog", + "id": "root", + "links": [ + {"rel": "child", "type": "application/json", "href": {"/": collection_cid}} + ], + } + mock_fetch_cid.side_effect = [ + { + "type": "Collection", + "id": "collection", + "links": [ + {"rel": "item", "type": "application/json", "href": {"/": item_cid}} + ], + }, + { + "type": "Feature", + "id": target_dataset, + "assets": {}, + }, # Missing assets.hamt-zarr.href + ] + with pytest.raises( + StacCatalogError, match="missing a valid string 'assets.hamt-zarr.href'" + ): + get_dataset_hamt_cid_from_stac(DCLIMATE_STAC_CATALOG_IPNS, target_dataset) + assert mock_fetch_cid.call_count == 2 + + +@patch("dclimate_zarr_client.ipfs_retrieval.fetch_json_from_ipns") +@patch("dclimate_zarr_client.ipfs_retrieval.fetch_json_from_cid") +def test_get_hamt_cid_item_invalid_hamt_asset_href(mock_fetch_cid, mock_fetch_ipns): + """Test error if the hamt-zarr href is not a valid /ipfs/ string.""" + target_dataset = KNOWN_STAC_DATASET_ID + collection_cid = "bafyCollection" + item_cid = "bafyItem" + mock_fetch_ipns.return_value = { + "type": "Catalog", + "id": "root", + "links": [ + {"rel": "child", "type": "application/json", "href": {"/": collection_cid}} + ], + } + mock_fetch_cid.side_effect = [ + { + "type": "Collection", + "id": "collection", + "links": [ + {"rel": "item", "type": "application/json", "href": {"/": item_cid}} + ], + }, + { + "type": "Feature", + "id": target_dataset, + "assets": {"hamt-zarr": {"href": "not-an-ipfs-link"}}, + }, + ] + with pytest.raises( + StacCatalogError, match="missing a valid string 'assets.hamt-zarr.href'" + ): + get_dataset_hamt_cid_from_stac(DCLIMATE_STAC_CATALOG_IPNS, target_dataset) + assert mock_fetch_cid.call_count == 2 + + +# --- NEW: Tests for _get_dataset_by_ipfs_cid (Unit/Mocked Error Paths) --- + + +def test_get_dataset_by_ipfs_cid_empty(): + """Test ValueError if ipfs_cid is empty.""" + with pytest.raises(ValueError, match="IPFS CID cannot be empty"): + _get_dataset_by_ipfs_cid("") + + +def test_get_dataset_by_ipfs_cid_invalid_format(): + """Test ValueError if ipfs_cid is not a valid CID format.""" + with pytest.raises(ValueError, match="Invalid IPFS CID format"): + _get_dataset_by_ipfs_cid("this-is-definitely-not-a-cid") + + +@patch("dclimate_zarr_client.ipfs_retrieval.xr.open_zarr") +@patch("dclimate_zarr_client.ipfs_retrieval.IPFSZarr3") +@patch("dclimate_zarr_client.ipfs_retrieval.HAMT") +@patch("dclimate_zarr_client.ipfs_retrieval._get_ipfs_store") +def test_get_dataset_by_ipfs_cid_zarr_not_found( + mock_get_store, mock_hamt, mock_ipfs_zarr3, mock_open_zarr +): + """Test StacCatalogError if Zarr metadata (e.g., .zgroup) is missing.""" + valid_cid = ( + "bafybeicg2rebjoofv4kbyovkw7af3rpiitvnl6i7ckcywaq6za2eflbka4" # Example CID + ) + mock_open_zarr.side_effect = FileNotFoundError( + "[Errno 2] No such file or directory: '.zgroup'" + ) # Simulate xr.open_zarr error + + with pytest.raises( + StacCatalogError, match=f"Zarr metadata not found at CID {valid_cid}" + ): + _get_dataset_by_ipfs_cid(valid_cid) + + mock_get_store.assert_called_once() + mock_hamt.assert_called_once() + mock_ipfs_zarr3.assert_called_once() + mock_open_zarr.assert_called_once() + + +@patch("dclimate_zarr_client.ipfs_retrieval.xr.open_zarr") +@patch("dclimate_zarr_client.ipfs_retrieval.IPFSZarr3") +@patch("dclimate_zarr_client.ipfs_retrieval.HAMT") +@patch("dclimate_zarr_client.ipfs_retrieval._get_ipfs_store") +def test_get_dataset_by_ipfs_cid_connection_error( + mock_get_store, mock_hamt, mock_ipfs_zarr3, mock_open_zarr +): + """Test IpfsConnectionError if loading data fails due to connection issues.""" + valid_cid = "bafybeicg2rebjoofv4kbyovkw7af3rpiitvnl6i7ckcywaq6za2eflbka4" + # Simulate error during xr.open_zarr which tries to read from the store + mock_open_zarr.side_effect = requests.exceptions.ConnectionError( + "Connection refused during zarr open" + ) + + with pytest.raises( + IpfsConnectionError, + match=f"IPFS connection failed while loading dataset from CID {valid_cid}", + ): + _get_dataset_by_ipfs_cid(valid_cid) + + +@patch("dclimate_zarr_client.ipfs_retrieval.xr.open_zarr") +@patch("dclimate_zarr_client.ipfs_retrieval.IPFSZarr3") +@patch("dclimate_zarr_client.ipfs_retrieval.HAMT") +@patch("dclimate_zarr_client.ipfs_retrieval._get_ipfs_store") +def test_get_dataset_by_ipfs_cid_other_runtime_error( + mock_get_store, mock_hamt, mock_ipfs_zarr3, mock_open_zarr +): + """Test generic RuntimeError for other failures during loading.""" + valid_cid = "bafybeicg2rebjoofv4kbyovkw7af3rpiitvnl6i7ckcywaq6za2eflbka4" + mock_open_zarr.side_effect = Exception("Some Zarr parsing error") + + with pytest.raises( + RuntimeError, match=f"Failed to load Zarr dataset from IPFS CID {valid_cid}" + ): + _get_dataset_by_ipfs_cid(valid_cid) + + +# --- NEW: Tests for list_datasets (Mocked Error Paths) --- + + +@patch("dclimate_zarr_client.ipfs_retrieval.fetch_json_from_ipns") +@patch("dclimate_zarr_client.ipfs_retrieval.fetch_json_from_cid") +def test_list_datasets_root_catalog_fetch_error(mock_fetch_cid, mock_fetch_ipns): + """Test list_datasets fails if root catalog fetch fails.""" + mock_fetch_ipns.side_effect = StacCatalogError("Cannot fetch root") + with pytest.raises( + StacCatalogError, + match="Failed to fetch or parse root catalog.*for listing datasets", + ): + list_datasets(root_catalog_ipns=DCLIMATE_STAC_CATALOG_IPNS) + + +@patch("dclimate_zarr_client.ipfs_retrieval.fetch_json_from_ipns") +@patch("dclimate_zarr_client.ipfs_retrieval.fetch_json_from_cid") +def test_list_datasets_no_collections(mock_fetch_cid, mock_fetch_ipns): + """Test list_datasets returns empty list if no child collections found.""" + mock_fetch_ipns.return_value = {"type": "Catalog", "id": "root", "links": []} + result = list_datasets(root_catalog_ipns=DCLIMATE_STAC_CATALOG_IPNS) + assert result == [] + mock_fetch_cid.assert_not_called() + + +@patch("dclimate_zarr_client.ipfs_retrieval.fetch_json_from_ipns") +@patch("dclimate_zarr_client.ipfs_retrieval.fetch_json_from_cid") +def test_list_datasets_fails_on_bad_collection(mock_fetch_cid, mock_fetch_ipns): + """Test list_datasets fails if any collection fails to load.""" + good_collection_cid = "bafyGoodCollection" + bad_collection_cid = "bafyBadCollection" + good_item_cid = "bafyGoodItem" + good_item_id = "good-dataset-id" + + mock_fetch_ipns.return_value = { + "type": "Catalog", + "id": "root", + "links": [ + { + "rel": "child", + "type": "application/json", + "href": {"/": bad_collection_cid}, + }, + { + "rel": "child", + "type": "application/json", + "href": {"/": good_collection_cid}, + }, + ], + } + + def fetch_cid_side_effect(cid_str, store): + if cid_str == bad_collection_cid: + raise StacCatalogError("Cannot load bad collection") + elif cid_str == good_collection_cid: + return { + "type": "Collection", + "id": "good", + "links": [ + { + "rel": "item", + "type": "application/json", + "href": {"/": good_item_cid}, + } + ], + } + elif cid_str == good_item_cid: + return { + "type": "Feature", + "id": good_item_id, + "assets": {"hamt-zarr": {"href": "/ipfs/bafyHAMT"}}, + } + else: + raise ValueError("Unknown CID") + + mock_fetch_cid.side_effect = fetch_cid_side_effect + + with pytest.raises(StacCatalogError, match="Cannot load bad collection"): + list_datasets(root_catalog_ipns=DCLIMATE_STAC_CATALOG_IPNS) + + +@patch("dclimate_zarr_client.ipfs_retrieval.fetch_json_from_ipns") +@patch("dclimate_zarr_client.ipfs_retrieval.fetch_json_from_cid") +def test_list_datasets_skips_bad_item(mock_fetch_cid, mock_fetch_ipns): + """Test list_datasets skips items that fail to load or parse.""" + collection_cid = "bafyCollection" + bad_item_cid = "bafyBadItem" + good_item_cid = "bafyGoodItem" + invalid_format_item_cid = "bafyInvalidFormatItem" + missing_id_item_cid = "bafyMissingIdItem" + + good_item_id = "good-dataset-id" + + mock_fetch_ipns.return_value = { + "type": "Catalog", + "id": "root", + "links": [ + {"rel": "child", "type": "application/json", "href": {"/": collection_cid}} + ], + } + + def fetch_cid_side_effect(cid_str, store): + if cid_str == collection_cid: + return { + "type": "Collection", + "id": "coll", + "links": [ + { + "rel": "item", + "type": "application/json", + "href": {"/": bad_item_cid}, + }, + { + "rel": "item", + "type": "application/json", + "href": {"/": good_item_cid}, + }, + { + "rel": "item", + "type": "application/json", + "href": {"/": invalid_format_item_cid}, + }, + { + "rel": "item", + "type": "application/json", + "href": {"/": missing_id_item_cid}, + }, + ], + } + elif cid_str == bad_item_cid: + raise IpfsConnectionError("Cannot load bad item") + elif cid_str == good_item_cid: + return { + "type": "Feature", + "id": good_item_id, + "assets": {"hamt-zarr": {"href": "/ipfs/bafyHAMT"}}, + } + elif cid_str == invalid_format_item_cid: + return {"not": "a feature"} + elif cid_str == missing_id_item_cid: + return {"type": "Feature", "assets": {}} # Missing ID + else: + raise ValueError("Unknown CID") + + mock_fetch_cid.side_effect = fetch_cid_side_effect + + result = list_datasets(root_catalog_ipns=DCLIMATE_STAC_CATALOG_IPNS) + assert result == [good_item_id] # Only the good dataset is listed + # Called for collection, bad item, good item, invalid item, missing ID item + assert mock_fetch_cid.call_count == 5 + + +@patch("dclimate_zarr_client.ipfs_retrieval.fetch_json_from_ipns") +def test_list_datasets_uses_default_root_catalog(mock_fetch_ipns): + """Test that list_datasets uses DCLIMATE_STAC_CATALOG_IPNS if none provided.""" + # Mock fetch_ipns to return a minimal valid catalog + mock_fetch_ipns.return_value = {"type": "Catalog", "id": "root", "links": []} + list_datasets() # Call without root_catalog_ipns argument + # Assert fetch_json_from_ipns was called with the default IPNS name + mock_fetch_ipns.assert_called_once_with( + DCLIMATE_STAC_CATALOG_IPNS, gateway_uri_stem=None + ) + + +# --- Tests for Legacy/Utility Functions (get_ipns_name_hash, update_cache_if_changed covered) --- +# --- NEW: Tests for other Legacy/Utility Functions (Mocked) --- + + +def test_get_single_metadata_success(mock_requests_get: MagicMock): + """Test _get_single_metadata successfully fetches and parses JSON.""" + ipfs_hash = "QmSomeHash" + expected_metadata = {"prop": "value", "links": []} + mock_response = MagicMock(spec=requests.Response) + mock_response.raise_for_status.return_value = None + mock_response.json.return_value = expected_metadata + mock_requests_get.return_value = mock_response + + metadata = _get_single_metadata(ipfs_hash) + + assert metadata == expected_metadata + expected_url_pattern = rf".*/ipfs/{ipfs_hash}" + call_args, call_kwargs = mock_requests_get.call_args + assert re.match(expected_url_pattern, call_args[0]), ( + f"URL {call_args[0]} does not match pattern {expected_url_pattern}" + ) + mock_response.raise_for_status.assert_called_once() + mock_response.json.assert_called_once() + + +def test_get_single_metadata_http_error(mock_requests_get: MagicMock): + """Test _get_single_metadata raises HTTPError.""" + ipfs_hash = "QmSomeHash" + mock_response = MagicMock(spec=requests.Response) + http_error = requests.exceptions.HTTPError("404 Not Found") + mock_response.raise_for_status.side_effect = http_error + mock_requests_get.return_value = mock_response + + with pytest.raises(requests.exceptions.HTTPError): + _get_single_metadata(ipfs_hash) + + # --- Tests for get_ipns_name_hash (Legacy/Utility Function) --- # These tests remain as UNIT tests, mocking requests and file system, # as they test the specific fallback logic of this function, not STAC traversal. From 5a53ae356f56d768fcce8c4af56705fd7c88d2c8 Mon Sep 17 00:00:00 2001 From: TheGreatAlgo <37487508+TheGreatAlgo@users.noreply.github.com> Date: Wed, 9 Jul 2025 12:13:48 -0400 Subject: [PATCH 5/6] fix: init --- .gitignore | 2 + dclimate_zarr_client/__init__.py | 4 +- dclimate_zarr_client/client.py | 129 +- .../contracts/StacRegistry.sol | 49 + dclimate_zarr_client/ipfs_retrieval.py | 1490 +++++----- dclimate_zarr_client/loader.py | 139 + dclimate_zarr_client/loaders/__init__.py | 0 dclimate_zarr_client/loaders/base.py | 70 + dclimate_zarr_client/loaders/era5.py | 54 + dclimate_zarr_client/registry.py | 178 ++ pyproject.toml | 18 +- tests/test_integration.py | 103 + uv.lock | 2431 ++++++++++++++++- 13 files changed, 3731 insertions(+), 936 deletions(-) create mode 100644 dclimate_zarr_client/contracts/StacRegistry.sol create mode 100644 dclimate_zarr_client/loader.py create mode 100644 dclimate_zarr_client/loaders/__init__.py create mode 100644 dclimate_zarr_client/loaders/base.py create mode 100644 dclimate_zarr_client/loaders/era5.py create mode 100644 dclimate_zarr_client/registry.py create mode 100644 tests/test_integration.py diff --git a/.gitignore b/.gitignore index 87accd9..9a3a91b 100644 --- a/.gitignore +++ b/.gitignore @@ -27,6 +27,8 @@ share/python-wheels/ MANIFEST _version.py +mnemonic.txt + # PyInstaller # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. diff --git a/dclimate_zarr_client/__init__.py b/dclimate_zarr_client/__init__.py index 1cc3d65..4b2d83b 100644 --- a/dclimate_zarr_client/__init__.py +++ b/dclimate_zarr_client/__init__.py @@ -1,6 +1,6 @@ # public API from .client import ( - load_ipfs_via_stac, + # load_ipfs_via_stac, load_s3, geo_temporal_query, ) # Use renamed function @@ -10,7 +10,7 @@ ) __all__ = [ - "load_ipfs_via_stac", + # "load_ipfs_via_stac", "load_s3", "geo_temporal_query", "GeotemporalData", diff --git a/dclimate_zarr_client/client.py b/dclimate_zarr_client/client.py index 8256d24..e28cb4d 100644 --- a/dclimate_zarr_client/client.py +++ b/dclimate_zarr_client/client.py @@ -12,10 +12,9 @@ ) from .geotemporal_data import GeotemporalData, DEFAULT_POINT_LIMIT from .s3_retrieval import get_dataset_from_s3 -from .ipfs_retrieval import ( - get_dataset_hamt_cid_from_stac, - _get_dataset_by_ipfs_cid, -) +# from .ipfs_retrieval import ( +# _get_dataset_by_ipfs_cid, +# ) # Define the top-level dClimate STAC catalog IPNS name DCLIMATE_STAC_CATALOG_IPNS = ( @@ -23,65 +22,65 @@ ) -def load_ipfs_via_stac( - dataset_name: str, - # as_of: typing.Optional[datetime.datetime] = None, # Removed as_of - gateway_uri_stem: str | None = None, - rpc_uri_stem: str | None = None, -) -> GeotemporalData: - """ - Load a Geotemporal dataset from IPFS/IPNS via the dClimate STAC catalog. +# def load_ipfs_via_stac( +# dataset_name: str, +# # as_of: typing.Optional[datetime.datetime] = None, # Removed as_of +# gateway_uri_stem: str | None = None, +# rpc_uri_stem: str | None = None, +# ) -> GeotemporalData: +# """ +# Load a Geotemporal dataset from IPFS/IPNS via the dClimate STAC catalog. - This function finds the dataset's IPNS name by navigating the STAC catalog - starting from a root IPNS name, resolves the dataset's IPNS name to its - current IPFS CID, and then loads the Zarr dataset. +# This function finds the dataset's IPNS name by navigating the STAC catalog +# starting from a root IPNS name, resolves the dataset's IPNS name to its +# current IPFS CID, and then loads the Zarr dataset. - Parameters - ---------- - dataset_name : str - The identifier (e.g., 'cpc-precip-conus') of the dataset as found in the STAC catalog. - as_of: datetime.datetime, optional - Pull in most recent data created before this time. If ``None``, just get most - recent. Defaults to ``None``. - gateway_uri_stem : str, optional - Custom IPFS HTTP Gateway URI stem (e.g., "http://localhost:8080"). - If None, uses the default from py-hamt's IPFSStore. - rpc_uri_stem : str, optional - Custom IPFS RPC API URI stem (e.g., "http://localhost:5001"). - If None, uses the default from py-hamt's IPFSStore. +# Parameters +# ---------- +# dataset_name : str +# The identifier (e.g., 'cpc-precip-conus') of the dataset as found in the STAC catalog. +# as_of: datetime.datetime, optional +# Pull in most recent data created before this time. If ``None``, just get most +# recent. Defaults to ``None``. +# gateway_uri_stem : str, optional +# Custom IPFS HTTP Gateway URI stem (e.g., "http://localhost:8080"). +# If None, uses the default from py-hamt's IPFSStore. +# rpc_uri_stem : str, optional +# Custom IPFS RPC API URI stem (e.g., "http://localhost:5001"). +# If None, uses the default from py-hamt's IPFSStore. - Returns - ------- - GeotemporalData - A wrapper around the loaded Xarray dataset. +# Returns +# ------- +# GeotemporalData +# A wrapper around the loaded Xarray dataset. - Raises - ------ - DatasetNotFoundError - If the dataset cannot be found in the STAC catalog or lacks the HAMT asset. - IpfsConnectionError - If connection to IPFS fails. - StacCatalogError - For issues during STAC parsing or traversal. - """ - # 1. Find the dataset's HAMT root IPFS CID from the STAC catalog - # Calls the renamed function from ipfs_retrieval - dataset_hamt_cid = get_dataset_hamt_cid_from_stac( - root_catalog_ipns=DCLIMATE_STAC_CATALOG_IPNS, - target_dataset_id=dataset_name, - gateway_uri_stem=gateway_uri_stem, # Pass through config - rpc_uri_stem=rpc_uri_stem, # Pass through config - ) +# Raises +# ------ +# DatasetNotFoundError +# If the dataset cannot be found in the STAC catalog or lacks the HAMT asset. +# IpfsConnectionError +# If connection to IPFS fails. +# StacCatalogError +# For issues during STAC parsing or traversal. +# """ +# # 1. Find the dataset's HAMT root IPFS CID from the STAC catalog +# # Calls the renamed function from ipfs_retrieval +# dataset_hamt_cid = await get_dataset_hamt_cid_from_stac( +# root_catalog_ipns=DCLIMATE_STAC_CATALOG_IPNS, +# target_dataset_id=dataset_name, +# gateway_uri_stem=gateway_uri_stem, # Pass through config +# rpc_uri_stem=rpc_uri_stem, # Pass through config +# ) - # 2. Load the dataset using the directly obtained HAMT IPFS CID - # NO LONGER NEED to resolve dataset IPNS name - ds = _get_dataset_by_ipfs_cid( - ipfs_cid=dataset_hamt_cid, - gateway_uri_stem=gateway_uri_stem, - rpc_uri_stem=rpc_uri_stem, - ) +# # 2. Load the dataset using the directly obtained HAMT IPFS CID +# # NO LONGER NEED to resolve dataset IPNS name +# ds = _get_dataset_by_ipfs_cid( +# ipfs_cid=dataset_hamt_cid, +# gateway_uri_stem=gateway_uri_stem, +# rpc_uri_stem=rpc_uri_stem, +# ) - return GeotemporalData(ds, dataset_name=dataset_name) +# return GeotemporalData(ds, dataset_name=dataset_name) def load_s3( @@ -220,14 +219,14 @@ def geo_temporal_query( point_limit = DEFAULT_POINT_LIMIT # Load the dataset based on the source - if source == "ipfs": - # *** CALL THE RENAMED FUNCTION *** - data = load_ipfs_via_stac( - dataset_name, - gateway_uri_stem=gateway_uri_stem, - rpc_uri_stem=rpc_uri_stem, - ) - elif source == "s3": + # if source == "ipfs": + # # *** CALL THE RENAMED FUNCTION *** + # data = load_ipfs_via_stac( + # dataset_name, + # gateway_uri_stem=gateway_uri_stem, + # rpc_uri_stem=rpc_uri_stem, + # ) + if source == "s3": if not bucket_name: raise ValueError("bucket_name is required when source is 's3'") data = load_s3(dataset_name, bucket_name) diff --git a/dclimate_zarr_client/contracts/StacRegistry.sol b/dclimate_zarr_client/contracts/StacRegistry.sol new file mode 100644 index 0000000..19b696b --- /dev/null +++ b/dclimate_zarr_client/contracts/StacRegistry.sol @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +/** + * @title StacRegistry + * @dev A simple contract to store and manage the root CID for a STAC catalog. + * The owner of the contract is the only one who can update the CID. + */ +contract StacRegistry { + address public owner; + string public stacCid; + + event CidUpdated(string newCid, address indexed updater); + + /** + * @dev Sets the initial STAC CID and the contract owner. + * @param initialCid The initial root CID of the STAC catalog. + */ + constructor(string memory initialCid) { + owner = msg.sender; + stacCid = initialCid; + } + + /** + * @dev Allows the owner to update the STAC CID. + * Emits a CidUpdated event. + * @param newCid The new root CID to store. + */ + function updateCid(string memory newCid) public { + require(msg.sender == owner, "Only the owner can update the CID"); + stacCid = newCid; + emit CidUpdated(newCid, msg.sender); + } + + /** + * @dev A public view function to retrieve the current STAC CID. + * @return The current root CID string. + */ + function getCid() public view returns (string memory) { + return stacCid; + } + + // function to change the owner of the contract + function changeOwner(address newOwner) public { + require(msg.sender == owner, "Only the owner can change ownership"); + require(newOwner != address(0), "New owner cannot be the zero address"); + owner = newOwner; + } +} \ No newline at end of file diff --git a/dclimate_zarr_client/ipfs_retrieval.py b/dclimate_zarr_client/ipfs_retrieval.py index 9bd2b77..bc30fda 100644 --- a/dclimate_zarr_client/ipfs_retrieval.py +++ b/dclimate_zarr_client/ipfs_retrieval.py @@ -7,7 +7,8 @@ import json import xarray as xr from multiformats import CID -from py_hamt import HAMT, IPFSStore, IPFSZarr3 +from py_hamt import HAMT, KuboCAS +from py_hamt.zarr_hamt_store import ZarrHAMTStore from .dclimate_zarr_errors import ( DatasetNotFoundError, @@ -30,584 +31,505 @@ _stac_hamt_cid_cache: typing.Dict[str, str] = {} -# --- IPFSStore Configuration --- -def _get_ipfs_store( - gateway_uri_stem: str | None = None, rpc_uri_stem: str | None = None -) -> IPFSStore: - """Creates or retrieves a configured IPFSStore instance.""" - # Use environment variables as defaults if not provided - gateway = gateway_uri_stem or os.environ.get("IPFS_GATEWAY_URI_STEM") - rpc = rpc_uri_stem or os.environ.get("IPFS_RPC_URI_STEM") - - # Simple way to manage store instances per configuration. - # More sophisticated caching/management might be needed in complex apps. - store_kwargs = {} - # Only pass if explicitly set or found in env, otherwise IPFSStore uses its defaults - if gateway: - store_kwargs["gateway_uri_stem"] = gateway - if rpc: - store_kwargs["rpc_uri_stem"] = rpc - - logger.debug(f"Creating IPFSStore with kwargs: {store_kwargs}") - return IPFSStore(**store_kwargs) - - -# --- IPFS Interaction Helpers --- -def fetch_json_from_cid(cid_str: str, ipfs_store: IPFSStore) -> dict: - """Fetches and parses JSON data from an IPFS CID using IPFSStore (gateway or RPC).""" - try: - logger.debug(f"Fetching JSON from CID: {cid_str}") - if cid_str.startswith("/ipfs/"): - cid_str = cid_str[6:] - - # Validate CID format before attempting fetch - try: - cid = CID.decode(cid_str) - except Exception as decode_err: - raise StacCatalogError( - f"Failed to decode CID string '{cid_str}': {decode_err}" - ) from decode_err - - # Use the store's timeout - json_bytes = ipfs_store.load(cid) - - if not json_bytes: - raise StacCatalogError(f"No data returned for CID: {cid_str}") - return json.loads(json_bytes) - except json.JSONDecodeError as e: - raise StacCatalogError(f"Failed to decode JSON from CID {cid_str}: {e}") from e - except requests.exceptions.Timeout as e: - raise IpfsConnectionError( - f"Timeout fetching CID {cid_str} via IPFSStore (using {ipfs_store.gateway_uri_stem or ipfs_store.rpc_uri_stem}). Details: {e}" - ) from e - except Exception as e: - if ( - "Connection refused" in str(e) - or "Max retries exceeded" in str(e) - or "Failed to establish a new connection" in str(e) - ): - raise IpfsConnectionError( - f"Failed to connect via IPFSStore (using {ipfs_store.gateway_uri_stem or ipfs_store.rpc_uri_stem}) to fetch CID {cid_str}. " - f"Is IPFS daemon/gateway running/accessible? Details: {e}" - ) from e - raise StacCatalogError( - f"Error fetching data for CID {cid_str} via IPFSStore: {e}" - ) from e - - -# NEW function to fetch JSON directly from IPNS via Gateway GET request -def fetch_json_from_ipns( - ipns_name: str, gateway_uri_stem: str | None = None, timeout: int = 120 +async def get_dataset_stac( + stac_root_cid: str, + collection: str, + dataset: str, + gateway_uri_stem: str, ) -> dict: """ - Fetches and parses JSON data directly from an IPNS name using an HTTP Gateway GET request. - Includes retry logic (without nocache) on certain failures. + Traverses a STAC catalog on IPFS starting from a root CID to find and + return a specific dataset's STAC Item JSON. + + This function uses KuboCAS for all IPFS interactions. Args: - ipns_name (str): The IPNS name (with or without /ipns/ prefix). - gateway_uri_stem (str, optional): Custom IPFS HTTP Gateway URI stem. Defaults to IPFSStore default. - timeout (int): Timeout in seconds for the request. Defaults to 120. + stac_root_cid (str): The root CID of the STAC catalog. + collection (str): The ID or title of the target collection. + dataset (str): The ID of the target dataset (STAC Item). + gateway_uri_stem (str): The base URL for the IPFS gateway. Returns: - dict: The parsed JSON content. + dict: The STAC Item JSON for the requested dataset. Raises: - IpfsConnectionError: If the IPFS gateway is unreachable or times out. - StacCatalogError: If the response is not valid JSON or other request errors occur. - ValueError: If IPNS name is empty or gateway not configured. + StacCatalogError: If the catalog structure is invalid or a network error occurs. + DatasetNotFoundError: If the collection or dataset cannot be found. """ - if not ipns_name: - raise ValueError("IPNS name cannot be empty.") - - ipfs_store = _get_ipfs_store(gateway_uri_stem=gateway_uri_stem) - gateway_base = ipfs_store.gateway_uri_stem - if not gateway_base: - # IPFSStore provides a default, so this should ideally not happen unless explicitly cleared/misconfigured - raise ValueError("IPFS Gateway URI stem is not configured.") - - if not ipns_name.startswith("/ipns/"): - ipns_name_for_url = f"/ipns/{ipns_name}" - else: - ipns_name_for_url = ipns_name - - target_url = f"{gateway_base.rstrip('/')}{ipns_name_for_url}" - response = None - last_error = None - - # --- Attempt 1: GET with nocache=true --- - try: - params = {"nocache": "true"} - headers = {"Accept": "application/json"} # Be explicit about wanting JSON - logger.debug( - f"Fetching JSON via Gateway GET (nocache=true): {ipns_name_for_url} at {target_url}" - ) - response = requests.get( - target_url, - params=params, - headers=headers, - timeout=timeout, - allow_redirects=True, - ) - response.raise_for_status() # Check for HTTP errors - # Directly parse JSON from response body - json_content = response.json() - logger.info(f"Successfully fetched JSON from IPNS '{ipns_name}' (nocache=true)") - return json_content # Return the parsed dictionary - - except requests.exceptions.ConnectionError as e: - raise IpfsConnectionError( - f"Connection error fetching IPNS '{ipns_name}' via Gateway {gateway_base}. Is Gateway running/accessible? Details: {e}" - ) from e - except requests.exceptions.Timeout as e: - logger.warning( - f"Timeout fetching IPNS {ipns_name_for_url} via Gateway GET (nocache=true): {e}. Will retry without nocache." - ) - last_error = IpfsConnectionError(f"Timeout (nocache=true): {e}") - except requests.exceptions.RequestException as e: # Includes HTTP errors - logger.warning( - f"RequestException fetching IPNS {ipns_name_for_url} via Gateway GET (nocache=true): {e}. Will retry without nocache." - ) - last_error = e - except json.JSONDecodeError as e: - # This can happen if gateway returns HTML error page or non-JSON content - response_text = response.text[:500] if response else "[No Response]" - status_code = response.status_code if response else "[No Status]" - logger.warning( - f"Invalid JSON fetching IPNS {ipns_name_for_url} via Gateway GET (nocache=true, Status: {status_code}, URL: {target_url}): {e}. Response text: {response_text}. Will retry." - ) - last_error = StacCatalogError( - f"JSONDecodeError (nocache=true, Status: {status_code}): {e}. Response: {response_text[:100]}" - ) - except Exception as e: # Catch other unexpected errors - logger.warning( - f"Unexpected error fetching IPNS {ipns_name_for_url} via Gateway GET (nocache=true): {e}. Will retry." - ) - last_error = StacCatalogError( - f"Unexpected error (nocache=true): {type(e).__name__}: {e}" - ) - # --- Attempt 2: GET without nocache (if Attempt 1 failed) --- logger.info( - f"Retrying fetch JSON via Gateway GET without nocache for: {ipns_name_for_url}" + f"Searching STAC catalog for dataset '{dataset}' in collection '{collection}'" ) - try: - params = {} # No nocache - headers = {"Accept": "application/json"} - logger.debug( - f"Fetching JSON via Gateway GET (nocache=false): {ipns_name_for_url} at {target_url}" - ) - response = requests.get( - target_url, - params=params, - headers=headers, - timeout=timeout, - allow_redirects=True, - ) # Retry - response.raise_for_status() - json_content = response.json() # This call may raise JSONDecodeError - logger.info( - f"Successfully fetched JSON from IPNS '{ipns_name}' (nocache=false)" - ) - return json_content - - except json.JSONDecodeError as e: - # Handle JSON decode errors explicitly on the retry attempt. - response_text = response.text[:500] if response else "[No Response]" - status_code = response.status_code if response else "[No Status]" - err_msg = ( - f"Invalid JSON fetching IPNS '{ipns_name}' (retry) via Gateway {gateway_base}: {e}. " - f"Response text: {response_text[:100]}" - ) - if last_error: - err_msg += f" | Initial error (nocache=true): {type(last_error).__name__}: {last_error}" - raise StacCatalogError(err_msg) from e - - except requests.exceptions.ConnectionError as e: - raise IpfsConnectionError( - f"Connection error during IPNS fetch retry for '{ipns_name}' via Gateway {gateway_base}. Details: {e}" - ) from e - - except requests.exceptions.Timeout as e: - raise IpfsConnectionError( - f"Timeout ({timeout}s) during IPNS fetch retry for '{ipns_name}' via Gateway {gateway_base}." - ) from e - - except requests.exceptions.RequestException as e: # Includes HTTP errors on retry - err_msg = ( - f"Error fetching IPNS '{ipns_name}' (retry) via Gateway {gateway_base}: {e}" - ) - response_text = "[No response object]" - status_code = "[No status code]" - if response is not None: - status_code = response.status_code - try: - response_text = response.text[:500] - except Exception: - response_text = "[Could not read response text]" - err_msg += f" Status Code: {status_code}, Response: {response_text}" - if last_error: - err_msg += f" | Initial error (nocache=true): {type(last_error).__name__}: {last_error}" - raise StacCatalogError(err_msg) from e - - except Exception as e: # Catch any other unexpected exceptions - err_msg = f"Unexpected error during IPNS fetch retry for '{ipns_name}' via Gateway {gateway_base}: {e}" - if last_error: - err_msg += f" | Initial error (nocache=true): {type(last_error).__name__}: {last_error}" - raise StacCatalogError(err_msg) from e + async with KuboCAS(gateway_base_url=gateway_uri_stem) as kubo_cas: + try: + # 1. Fetch the root catalog + logger.debug(f"Fetching root catalog from CID: {stac_root_cid}") + root_catalog_bytes = await kubo_cas.load(CID.decode(stac_root_cid)) + root_catalog = json.loads(root_catalog_bytes) + + if not isinstance(root_catalog, dict) or root_catalog.get("type") != "Catalog": + raise StacCatalogError("Invalid root catalog format.") + + # 2. Find the target collection's CID + collection_cid_str = None + for link in root_catalog.get("links", []): + # Match by collection ID or title for flexibility + if link.get("rel") == "child" and ( + link.get("title") == collection or link.get("id") == collection + ): + href_obj = link.get("href") + if isinstance(href_obj, dict) and "/" in href_obj: + collection_cid_str = href_obj["/"] + break + + if not collection_cid_str: + raise DatasetNotFoundError(f"Collection '{collection}' not found in root catalog.") + + # 3. Fetch the collection + logger.debug(f"Fetching collection '{collection}' from CID: {collection_cid_str}") + collection_bytes = await kubo_cas.load(CID.decode(collection_cid_str)) + collection_json = json.loads(collection_bytes) + + if not isinstance(collection_json, dict) or collection_json.get("type") != "Collection": + raise StacCatalogError(f"Invalid format for collection '{collection}'.") + + # 4. Find and return the target dataset (item) or collection + for item_link in collection_json.get("links", []): + if item_link.get("rel") == "item" or item_link.get("rel") == "child": + item_href_obj = item_link.get("href") + item_cid_str = None + if isinstance(item_href_obj, dict) and "/" in item_href_obj: + item_cid_str = item_href_obj["/"] + + if not item_cid_str: + continue + + # Fetch the item to check its ID + item_bytes = await kubo_cas.load(CID.decode(item_cid_str)) + item_json = json.loads(item_bytes) + + if item_json.get("id") == dataset: + logger.info(f"Found matching STAC Item for dataset '{dataset}'.") + return item_json # Return the full STAC item + + # If the loop completes, the dataset was not found in the collection + raise DatasetNotFoundError(f"Dataset '{dataset}' not found in collection '{collection}'.") + + except Exception as e: + # Catch potential errors from KuboCAS (e.g., network issues) or JSON parsing + # and wrap them in a more specific error. + logger.error(f"Failed during STAC traversal: {e}", exc_info=True) + raise StacCatalogError(f"An error occurred during STAC traversal: {e}") from e + +# --- IPFSStore Configuration --- +# async def _get_ipfs_store( +# gateway_uri_stem: str | None = None, rpc_uri_stem: str | None = None +# ) -> KuboCAS: +# """Creates or retrieves a configured IPFSStore instance.""" +# # Use environment variables as defaults if not provided +# gateway = gateway_uri_stem or os.environ.get("IPFS_GATEWAY_URI_STEM") +# rpc = rpc_uri_stem or os.environ.get("IPFS_RPC_URI_STEM") + +# async with KuboCAS(gateway_base_url=gateway, rpc_base_url=rpc) as kubo_cas: +# return kubo_cas + + + +# --- IPFS Interaction Helpers --- +# async def fetch_json_from_cid(cid_str: str, ipfs_store: KuboCAS) -> dict: +# """Fetches and parses JSON data from an IPFS CID using KuboCAS (gateway or RPC).""" +# try: +# logger.debug(f"Fetching JSON from CID: {cid_str}") +# if cid_str.startswith("/ipfs/"): +# cid_str = cid_str[6:] + +# # Validate CID format before attempting fetch +# try: +# cid = CID.decode(cid_str) +# except Exception as decode_err: +# raise StacCatalogError( +# f"Failed to decode CID string '{cid_str}': {decode_err}" +# ) from decode_err + +# # Use the store's timeout +# json_bytes = await ipfs_store.load(cid) + +# if not json_bytes: +# raise StacCatalogError(f"No data returned for CID: {cid_str}") +# return json.loads(json_bytes) +# except json.JSONDecodeError as e: +# raise StacCatalogError(f"Failed to decode JSON from CID {cid_str}: {e}") from e +# except requests.exceptions.Timeout as e: +# raise IpfsConnectionError( +# f"Timeout fetching CID {cid_str} via KuboCAS (using {ipfs_store.gateway_uri_stem or ipfs_store.rpc_uri_stem}). Details: {e}" +# ) from e +# except Exception as e: +# if ( +# "Connection refused" in str(e) +# or "Max retries exceeded" in str(e) +# or "Failed to establish a new connection" in str(e) +# ): +# raise IpfsConnectionError( +# f"Failed to connect via KuboCAS (using {ipfs_store.gateway_uri_stem or ipfs_store.rpc_uri_stem}) to fetch CID {cid_str}. " +# f"Is IPFS daemon/gateway running/accessible? Details: {e}" +# ) from e +# raise StacCatalogError( +# f"Error fetching data for CID {cid_str} via KuboCAS: {e}" +# ) from e # --- STAC Traversal and HAMT CID Retrieval --- -def get_dataset_hamt_cid_from_stac( - root_catalog_ipns: str, - target_dataset_id: str, - gateway_uri_stem: str | None = None, - rpc_uri_stem: ( - str | None - ) = None, # Keep rpc_uri_stem for IPFSStore config if needed by fetch_json_from_cid -) -> str: - """ - Traverses the dClimate STAC catalog starting from a root IPNS name - to find the HAMT root IPFS CID associated with the target dataset ID. - Handles IPLD link format `{"/": "cid_string"}`. - """ - if target_dataset_id in _stac_hamt_cid_cache: - logger.info(f"Found dataset '{target_dataset_id}' HAMT CID in cache.") - return _stac_hamt_cid_cache[target_dataset_id] - - logger.info(f"Searching STAC catalog for dataset: {target_dataset_id}") - # Still need ipfs_store for subsequent CID fetches if rpc differs from gateway - ipfs_store = _get_ipfs_store(gateway_uri_stem, rpc_uri_stem) - - # *** Use fetch_json_from_ipns for the root catalog *** - try: - logger.debug(f"Fetching root catalog content from IPNS: {root_catalog_ipns}") - catalog = fetch_json_from_ipns( - root_catalog_ipns, gateway_uri_stem=gateway_uri_stem - ) - except (IpfsConnectionError, StacCatalogError, ValueError) as e: - raise StacCatalogError( - f"Failed to fetch root catalog from IPNS '{root_catalog_ipns}': {e}" - ) from e - - # Rest of the logic remains similar, using fetch_json_from_cid for /ipfs/ links - if not isinstance(catalog, dict) or catalog.get("type") != "Catalog": - raise StacCatalogError( - f"Invalid root catalog format fetched from IPNS {root_catalog_ipns}" - ) - - collections_to_visit = [] - for link in catalog.get("links", []): - if link.get("rel") == "child" and link.get("type") == "application/json": - href_obj = link.get("href") - if isinstance(href_obj, dict): - collection_cid_str = href_obj.get("/") - if isinstance(collection_cid_str, str): - collections_to_visit.append(collection_cid_str) - logger.debug( - f"Found child collection link (CID): {collection_cid_str}" - ) - else: - logger.warning( - f"Skipping child link with invalid href dict content in root catalog: {link}" - ) - elif isinstance(href_obj, str) and href_obj.startswith("/ipfs/"): - # Allow legacy /ipfs/ string format for backward compatibility? Risky. - # For now, strictly expect dict for IPLD links as per example. - logger.warning( - f"Skipping child link with unexpected string href format (expected dict): {link}" - ) - collections_to_visit.append(href_obj[6:]) - else: - logger.warning( - f"Skipping invalid child link format in root catalog: {link}" - ) - - if not collections_to_visit: - raise StacCatalogError( - f"No valid child collection links found in root catalog fetched from IPNS {root_catalog_ipns}" - ) - - logger.info(f"Found {len(collections_to_visit)} collections to search.") - - for collection_cid in collections_to_visit: - logger.debug(f"Fetching collection content for CID: {collection_cid}") - try: - # --- collection JSON --- - collection = fetch_json_from_cid(collection_cid, ipfs_store) - if ( - not isinstance(collection, dict) - or collection.get("type") != "Collection" - ): - logger.warning( - f"Skipping invalid collection format for CID {collection_cid}. " - f"Type: {collection.get('type')}" - ) - continue - - items_found_in_collection = 0 - for link in collection.get("links", []): - if link.get("rel") == "item" and link.get("type") == "application/json": - item_href_obj = link.get("href") - item_cid = None # reset each link - - # --- handle IPLD or legacy /ipfs/ links --- - if isinstance(item_href_obj, dict): - item_cid = item_href_obj.get("/") # IPLD dict - elif isinstance(item_href_obj, str) and item_href_obj.startswith( - "/ipfs/" - ): - logger.warning( - f"Found legacy string href in {collection_cid}: {link}" - ) - item_cid = item_href_obj[6:] # strip "/ipfs/" - else: - logger.warning( - f"Skipping invalid item link in {collection_cid}: {link}" - ) - continue +# async def get_dataset_hamt_cid_from_stac( +# root_catalog_ipns: str, +# target_dataset_id: str, +# gateway_uri_stem: str | None = None, +# rpc_uri_stem: ( +# str | None +# ) = None, # Keep rpc_uri_stem for IPFSStore config if needed by fetch_json_from_cid +# ) -> str: +# """ +# Traverses the dClimate STAC catalog starting from a root IPNS name +# to find the HAMT root IPFS CID associated with the target dataset ID. +# Handles IPLD link format `{"/": "cid_string"}`. +# """ +# if target_dataset_id in _stac_hamt_cid_cache: +# logger.info(f"Found dataset '{target_dataset_id}' HAMT CID in cache.") +# return _stac_hamt_cid_cache[target_dataset_id] - if not isinstance(item_cid, str): - # already logged warning - continue +# logger.info(f"Searching STAC catalog for dataset: {target_dataset_id}") +# # Still need ipfs_store for subsequent CID fetches if rpc differs from gateway +# ipfs_store = _get_ipfs_store(gateway_uri_stem, rpc_uri_stem) - items_found_in_collection += 1 - item_id: str | None = None # keep in scope for except - try: - # --- item JSON --- - item = fetch_json_from_cid(item_cid, ipfs_store) - - if not isinstance(item, dict) or item.get("type") != "Feature": - logger.warning( - f"Skipping invalid item format for CID {item_cid}. " - f"Type: {item.get('type')}" - ) - continue - - item_id = item.get("id") - if item_id != target_dataset_id: - # not the dataset we're looking for - continue - - logger.info( - f"Found matching item for '{target_dataset_id}' " - f"(CID {item_cid}) in collection {collection_cid}" - ) - hamt_asset = item.get("assets", {}).get("hamt-zarr", {}) - hamt_cid_href = hamt_asset.get("href") # expected "/ipfs/" - - if not isinstance( - hamt_cid_href, str - ) or not hamt_cid_href.startswith("/ipfs/"): - raise StacCatalogError( - f"STAC Item '{item_id}' (CID: {item_cid}) is missing a " - f"valid string 'assets.hamt-zarr.href' starting with " - f"/ipfs/. Found: '{hamt_cid_href}' " - f"(type: {type(hamt_cid_href).__name__})" - ) - - hamt_cid_str = hamt_cid_href[6:] # drop "/ipfs/" - logger.info( - f"Successfully extracted HAMT CID for '{target_dataset_id}': " - f"{hamt_cid_str}" - ) - _stac_hamt_cid_cache[target_dataset_id] = hamt_cid_str - return hamt_cid_str - - # ── error handling ────────────────────────────────────────── - except StacCatalogError as item_err: - # If the failing item *is* the target dataset, bubble it up. - if item_id == target_dataset_id: - raise item_err - logger.error( - f"Error processing non-target item {item_cid} in collection " - f"{collection_cid}: {item_err}" - ) - except IpfsConnectionError as item_err: - logger.error( - f"IPFS error processing item {item_cid} in collection " - f"{collection_cid}: {item_err}" - ) - except Exception as item_err: - logger.error( - f"Unexpected error processing item {item_cid} in collection " - f"{collection_cid}: {type(item_err).__name__}: {item_err}" - ) - - logger.debug( - f"Finished searching {items_found_in_collection} items in collection " - f"{collection.get('id', collection_cid)}." - ) - - except StacCatalogError as col_err: - # ← this is the error that means “the target dataset is malformed” - # → let it propagate to the caller so tests (and callers) can see it. - raise col_err - except IpfsConnectionError as col_err: - # ← still swallow network errors so other collections can be tried - logger.error( - f"IPFS error processing collection {collection_cid}, continuing search: " - f"{col_err}" - ) - except Exception as col_err: - logger.error( - f"Unexpected error processing collection {collection_cid}, continuing " - f"search: {type(col_err).__name__}: {col_err}" - ) - - # If the loop completes without returning - raise DatasetNotFoundError( - f"Dataset ID '{target_dataset_id}' not found after searching all collections " - f"in the STAC catalog rooted at IPNS '{root_catalog_ipns}'." - ) +# # *** Use fetch_json_from_ipns for the root catalog *** +# try: +# logger.debug(f"Fetching root catalog content from IPNS: {root_catalog_ipns}") +# catalog = fetch_json_from_ipns( +# root_catalog_ipns, gateway_uri_stem=gateway_uri_stem +# ) +# except (IpfsConnectionError, StacCatalogError, ValueError) as e: +# raise StacCatalogError( +# f"Failed to fetch root catalog from IPNS '{root_catalog_ipns}': {e}" +# ) from e + +# # Rest of the logic remains similar, using fetch_json_from_cid for /ipfs/ links +# if not isinstance(catalog, dict) or catalog.get("type") != "Catalog": +# raise StacCatalogError( +# f"Invalid root catalog format fetched from IPNS {root_catalog_ipns}" +# ) + +# collections_to_visit = [] +# for link in catalog.get("links", []): +# if link.get("rel") == "child" and link.get("type") == "application/json": +# href_obj = link.get("href") +# if isinstance(href_obj, dict): +# collection_cid_str = href_obj.get("/") +# if isinstance(collection_cid_str, str): +# collections_to_visit.append(collection_cid_str) +# logger.debug( +# f"Found child collection link (CID): {collection_cid_str}" +# ) +# else: +# logger.warning( +# f"Skipping child link with invalid href dict content in root catalog: {link}" +# ) +# elif isinstance(href_obj, str) and href_obj.startswith("/ipfs/"): +# # Allow legacy /ipfs/ string format for backward compatibility? Risky. +# # For now, strictly expect dict for IPLD links as per example. +# logger.warning( +# f"Skipping child link with unexpected string href format (expected dict): {link}" +# ) +# collections_to_visit.append(href_obj[6:]) +# else: +# logger.warning( +# f"Skipping invalid child link format in root catalog: {link}" +# ) + +# if not collections_to_visit: +# raise StacCatalogError( +# f"No valid child collection links found in root catalog fetched from IPNS {root_catalog_ipns}" +# ) + +# logger.info(f"Found {len(collections_to_visit)} collections to search.") + +# for collection_cid in collections_to_visit: +# logger.debug(f"Fetching collection content for CID: {collection_cid}") +# try: +# # --- collection JSON --- +# collection = await fetch_json_from_cid(collection_cid, ipfs_store) +# if ( +# not isinstance(collection, dict) +# or collection.get("type") != "Collection" +# ): +# logger.warning( +# f"Skipping invalid collection format for CID {collection_cid}. " +# f"Type: {collection.get('type')}" +# ) +# continue + +# items_found_in_collection = 0 +# for link in collection.get("links", []): +# if link.get("rel") == "item" and link.get("type") == "application/json": +# item_href_obj = link.get("href") +# item_cid = None # reset each link + +# # --- handle IPLD or legacy /ipfs/ links --- +# if isinstance(item_href_obj, dict): +# item_cid = item_href_obj.get("/") # IPLD dict +# elif isinstance(item_href_obj, str) and item_href_obj.startswith( +# "/ipfs/" +# ): +# logger.warning( +# f"Found legacy string href in {collection_cid}: {link}" +# ) +# item_cid = item_href_obj[6:] # strip "/ipfs/" +# else: +# logger.warning( +# f"Skipping invalid item link in {collection_cid}: {link}" +# ) +# continue + +# if not isinstance(item_cid, str): +# # already logged warning +# continue + +# items_found_in_collection += 1 +# item_id: str | None = None # keep in scope for except +# try: +# # --- item JSON --- +# item = await fetch_json_from_cid(item_cid, ipfs_store) + +# if not isinstance(item, dict) or item.get("type") != "Feature": +# logger.warning( +# f"Skipping invalid item format for CID {item_cid}. " +# f"Type: {item.get('type')}" +# ) +# continue + +# item_id = item.get("id") +# if item_id != target_dataset_id: +# # not the dataset we're looking for +# continue + +# logger.info( +# f"Found matching item for '{target_dataset_id}' " +# f"(CID {item_cid}) in collection {collection_cid}" +# ) +# hamt_asset = item.get("assets", {}).get("hamt-zarr", {}) +# hamt_cid_href = hamt_asset.get("href") # expected "/ipfs/" + +# if not isinstance( +# hamt_cid_href, str +# ) or not hamt_cid_href.startswith("/ipfs/"): +# raise StacCatalogError( +# f"STAC Item '{item_id}' (CID: {item_cid}) is missing a " +# f"valid string 'assets.hamt-zarr.href' starting with " +# f"/ipfs/. Found: '{hamt_cid_href}' " +# f"(type: {type(hamt_cid_href).__name__})" +# ) + +# hamt_cid_str = hamt_cid_href[6:] # drop "/ipfs/" +# logger.info( +# f"Successfully extracted HAMT CID for '{target_dataset_id}': " +# f"{hamt_cid_str}" +# ) +# _stac_hamt_cid_cache[target_dataset_id] = hamt_cid_str +# return hamt_cid_str + +# # ── error handling ────────────────────────────────────────── +# except StacCatalogError as item_err: +# # If the failing item *is* the target dataset, bubble it up. +# if item_id == target_dataset_id: +# raise item_err +# logger.error( +# f"Error processing non-target item {item_cid} in collection " +# f"{collection_cid}: {item_err}" +# ) +# except IpfsConnectionError as item_err: +# logger.error( +# f"IPFS error processing item {item_cid} in collection " +# f"{collection_cid}: {item_err}" +# ) +# except Exception as item_err: +# logger.error( +# f"Unexpected error processing item {item_cid} in collection " +# f"{collection_cid}: {type(item_err).__name__}: {item_err}" +# ) + +# logger.debug( +# f"Finished searching {items_found_in_collection} items in collection " +# f"{collection.get('id', collection_cid)}." +# ) + +# except StacCatalogError as col_err: +# # ← this is the error that means “the target dataset is malformed” +# # → let it propagate to the caller so tests (and callers) can see it. +# raise col_err +# except IpfsConnectionError as col_err: +# # ← still swallow network errors so other collections can be tried +# logger.error( +# f"IPFS error processing collection {collection_cid}, continuing search: " +# f"{col_err}" +# ) +# except Exception as col_err: +# logger.error( +# f"Unexpected error processing collection {collection_cid}, continuing " +# f"search: {type(col_err).__name__}: {col_err}" +# ) + +# # If the loop completes without returning +# raise DatasetNotFoundError( +# f"Dataset ID '{target_dataset_id}' not found after searching all collections " +# f"in the STAC catalog rooted at IPNS '{root_catalog_ipns}'." +# ) + + +# def _get_host(uri: str = "/api/v0"): +# """Parse the ipfs api host address from `IPFS_HOST` environment variable. +# If not found, use localhost:5001/api/v0. +# Args: +# uri (str): the uri where ipfs gateway api listens -def _get_host(uri: str = "/api/v0"): - """Parse the ipfs api host address from `IPFS_HOST` environment variable. - If not found, use localhost:5001/api/v0. +# Returns: +# str: ipfs gateway url - Args: - uri (str): the uri where ipfs gateway api listens +# """ - Returns: - str: ipfs gateway url +# host_from_env = os.getenv("IPFS_HOST") +# return host_from_env + uri if host_from_env else DEFAULT_HOST - """ - host_from_env = os.getenv("IPFS_HOST") - return host_from_env + uri if host_from_env else DEFAULT_HOST +# def _get_single_metadata(ipfs_hash: str) -> dict: +# """Get metadata for given ipfs hash over ipld +# Args: +# ipfs_hash (str): ipfs hash for which to get metadata -def _get_single_metadata(ipfs_hash: str) -> dict: - """Get metadata for given ipfs hash over ipld +# Returns: +# dict: dict of metadata for hash +# """ - Args: - ipfs_hash (str): ipfs hash for which to get metadata +# r = requests.get(f"{_get_host()}/ipfs/{ipfs_hash}") +# r.raise_for_status() +# return r.json() - Returns: - dict: dict of metadata for hash - """ - r = requests.get(f"{_get_host()}/ipfs/{ipfs_hash}") - r.raise_for_status() - return r.json() +# def _get_previous_hash_from_metadata(metadata: dict) -> typing.Optional[str]: +# """Pull in last updated hash from STAC metadata +# Args: +# metadata (dict): STAC metadata -def _get_previous_hash_from_metadata(metadata: dict) -> typing.Optional[str]: - """Pull in last updated hash from STAC metadata +# Returns: +# str: Previous hash, or None if given root metadata +# """ +# links = metadata["links"] +# try: +# link_to_previous = [ +# link for link in links if link["rel"] in {"prev", "previous"} +# ][0] +# except IndexError: +# return None +# return link_to_previous["metadata href"]["/"] - Args: - metadata (dict): STAC metadata - Returns: - str: Previous hash, or None if given root metadata - """ - links = metadata["links"] - try: - link_to_previous = [ - link for link in links if link["rel"] in {"prev", "previous"} - ][0] - except IndexError: - return None - return link_to_previous["metadata href"]["/"] +# def _resolve_ipns_name_hash(ipns_name_hash: str) -> str: +# """Find the latest IPFS hash corresponding to a stable ipns name hash +# Args: +# ipfs_name_hash (str): stable IPNS name hash -def _resolve_ipns_name_hash(ipns_name_hash: str) -> str: - """Find the latest IPFS hash corresponding to a stable ipns name hash +# Returns: +# str: ipfs hash corresponding to this ipns name hash +# """ +# r = requests.get(f"{_get_host()}/ipns/{ipns_name_hash}", params={"offline": True}) +# r.raise_for_status() +# return r.json()["Path"].split("/")[-1] - Args: - ipfs_name_hash (str): stable IPNS name hash - Returns: - str: ipfs hash corresponding to this ipns name hash - """ - r = requests.get(f"{_get_host()}/ipns/{ipns_name_hash}", params={"offline": True}) - r.raise_for_status() - return r.json()["Path"].split("/")[-1] +# def update_cache_if_changed(new_data: dict) -> None: +# """Update the local cache file only if the new data differs from what is cached.""" +# cache_file = os.path.join(os.path.dirname(__file__), "cids.json") +# try: +# with open(cache_file, "r") as f: +# cached_data = json.load(f) +# except (FileNotFoundError, json.JSONDecodeError): +# cached_data = None +# if cached_data != new_data: +# with open(cache_file, "w") as f: +# json.dump(new_data, f) -def update_cache_if_changed(new_data: dict) -> None: - """Update the local cache file only if the new data differs from what is cached.""" - cache_file = os.path.join(os.path.dirname(__file__), "cids.json") - try: - with open(cache_file, "r") as f: - cached_data = json.load(f) - except (FileNotFoundError, json.JSONDecodeError): - cached_data = None - if cached_data != new_data: - with open(cache_file, "w") as f: - json.dump(new_data, f) +# def get_ipns_name_hash(ipns_key_str: str) -> str: +# """Find the latest IPNS name hash corresponding to a string (key) +# Args: +# ipfs_key_str (str): a string (key) identifying a dataset -def get_ipns_name_hash(ipns_key_str: str) -> str: - """Find the latest IPNS name hash corresponding to a string (key) +# Raises: +# KeyError: raised if no IPNS key string is found in the IPNS keys list - Args: - ipfs_key_str (str): a string (key) identifying a dataset +# Returns: +# str: ipfsname hash corresponding to the provided string +# """ - Raises: - KeyError: raised if no IPNS key string is found in the IPNS keys list +# try: +# # 1) Try to fetch from endpoint +# r = requests.get(CID_ENDPOINT, params={"decoder": "json"}) +# r.raise_for_status() +# json_cid = r.json() # raises JSONDecodeError if endpoint returns malformed JSON - Returns: - str: ipfsname hash corresponding to the provided string - """ +# # Update cache only if there is a change +# update_cache_if_changed(json_cid) - try: - # 1) Try to fetch from endpoint - r = requests.get(CID_ENDPOINT, params={"decoder": "json"}) - r.raise_for_status() - json_cid = r.json() # raises JSONDecodeError if endpoint returns malformed JSON - - # Update cache only if there is a change - update_cache_if_changed(json_cid) - - for entry in json_cid: - if entry == ipns_key_str: - return json_cid[entry] - - except (requests.RequestException, KeyError, json.JSONDecodeError): - # 2) If remote fails or is malformed, try local fallback - cache_file = os.path.join(os.path.dirname(__file__), "cids.json") - if os.path.exists(cache_file): - try: - with open(cache_file, "r") as f: - json_cid = json.load( - f - ) # <-- can raise JSONDecodeError if file is empty/corrupt - for entry in json_cid: - if entry == ipns_key_str: - return json_cid[entry] - except (KeyError, json.JSONDecodeError) as err: - # We tried local, but it’s also invalid (bad JSON or missing key) - raise DatasetNotFoundError("Invalid dataset name") from err - - # 3) If we get here, local file either doesn't exist or didn't have the key - raise DatasetNotFoundError("Invalid dataset name") from None - - -def _get_relevant_metadata(ipfs_head_hash: str, as_of: datetime.datetime) -> dict: - """Iterates through STAC metadata until metadata generated before as_of is found +# for entry in json_cid: +# if entry == ipns_key_str: +# return json_cid[entry] - Args: - ipfs_head_hash (str): first hash in chain - as_of (datetime.datetime): cutoff date for finding metadata +# except (requests.RequestException, KeyError, json.JSONDecodeError): +# # 2) If remote fails or is malformed, try local fallback +# cache_file = os.path.join(os.path.dirname(__file__), "cids.json") +# if os.path.exists(cache_file): +# try: +# with open(cache_file, "r") as f: +# json_cid = json.load( +# f +# ) # <-- can raise JSONDecodeError if file is empty/corrupt +# for entry in json_cid: +# if entry == ipns_key_str: +# return json_cid[entry] +# except (KeyError, json.JSONDecodeError) as err: +# # We tried local, but it’s also invalid (bad JSON or missing key) +# raise DatasetNotFoundError("Invalid dataset name") from err - Raises: - NoMetadataFoundError: raised if no metadata older than cutoff date is found +# # 3) If we get here, local file either doesn't exist or didn't have the key +# raise DatasetNotFoundError("Invalid dataset name") from None - Returns: - dict: relevant metadata - """ - cur_metadata = _get_single_metadata(ipfs_head_hash) - while True: - time_generated = datetime.datetime.strptime( - cur_metadata["properties"]["updated"], "%Y-%m-%dT%H:%M:%SZ" - ) - if time_generated <= as_of: - return cur_metadata - prev_hash = _get_previous_hash_from_metadata(cur_metadata) - if prev_hash is None: - raise NoMetadataFoundError(f"No metadata found after as_of: {as_of}") - cur_metadata = _get_single_metadata(prev_hash) + +# def _get_relevant_metadata(ipfs_head_hash: str, as_of: datetime.datetime) -> dict: +# """Iterates through STAC metadata until metadata generated before as_of is found + +# Args: +# ipfs_head_hash (str): first hash in chain +# as_of (datetime.datetime): cutoff date for finding metadata + +# Raises: +# NoMetadataFoundError: raised if no metadata older than cutoff date is found + +# Returns: +# dict: relevant metadata +# """ +# cur_metadata = _get_single_metadata(ipfs_head_hash) +# while True: +# time_generated = datetime.datetime.strptime( +# cur_metadata["properties"]["updated"], "%Y-%m-%dT%H:%M:%SZ" +# ) +# if time_generated <= as_of: +# return cur_metadata +# prev_hash = _get_previous_hash_from_metadata(cur_metadata) +# if prev_hash is None: +# raise NoMetadataFoundError(f"No metadata found after as_of: {as_of}") +# cur_metadata = _get_single_metadata(prev_hash) # def get_dataset_by_ipfs_hash(ipfs_hash: str) -> xr.Dataset: @@ -625,102 +547,102 @@ def _get_relevant_metadata(ipfs_head_hash: str, as_of: datetime.datetime) -> dic # --- Zarr Dataset Loading --- # Renamed from get_dataset_by_ipns_hash -def _get_dataset_by_ipfs_cid( - ipfs_cid: str, - gateway_uri_stem: str | None = None, - rpc_uri_stem: str | None = None, -) -> xr.Dataset: - """ - Gets an xarray dataset directly from its Zarr root IPFS CID using py-hamt. +# def _get_dataset_by_ipfs_cid( +# ipfs_cid: str, +# gateway_uri_stem: str | None = None, +# rpc_uri_stem: str | None = None, +# ) -> xr.Dataset: +# """ +# Gets an xarray dataset directly from its Zarr root IPFS CID using py-hamt. - Args: - ipfs_cid (str): The IPFS CID of the Zarr dataset's root node (e.g., HAMT root). - gateway_uri_stem (str, optional): Custom IPFS HTTP Gateway URI stem. - rpc_uri_stem (str, optional): Custom IPFS RPC API URI stem. +# Args: +# ipfs_cid (str): The IPFS CID of the Zarr dataset's root node (e.g., HAMT root). +# gateway_uri_stem (str, optional): Custom IPFS HTTP Gateway URI stem. +# rpc_uri_stem (str, optional): Custom IPFS RPC API URI stem. - Returns: - xr.Dataset: The loaded dataset. +# Returns: +# xr.Dataset: The loaded dataset. - Raises: - IpfsConnectionError: If connection to IPFS fails during loading. - Exception: Other errors during Zarr parsing or IPFS interaction. - """ - if not ipfs_cid: - raise ValueError("IPFS CID cannot be empty.") +# Raises: +# IpfsConnectionError: If connection to IPFS fails during loading. +# Exception: Other errors during Zarr parsing or IPFS interaction. +# """ +# if not ipfs_cid: +# raise ValueError("IPFS CID cannot be empty.") - logger.info(f"Loading Zarr dataset from IPFS CID: {ipfs_cid}") - ipfs_store = _get_ipfs_store(gateway_uri_stem, rpc_uri_stem) +# logger.info(f"Loading Zarr dataset from IPFS CID: {ipfs_cid}") +# ipfs_store = _get_ipfs_store(gateway_uri_stem, rpc_uri_stem) - try: - # root_node_id expects a CID object or string representation - cid_obj = None - try: - cid_obj = CID.decode(ipfs_cid) - except Exception as decode_err: - raise ValueError( - f"Invalid IPFS CID format: {ipfs_cid}. Error: {decode_err}" - ) from decode_err - - # Initialize HAMT store - hamt_store = HAMT(store=ipfs_store, root_node_id=cid_obj, read_only=True) - - # Wrap with IPFSZarr3 store adapter - ipfszarr3_store = IPFSZarr3(hamt_store, read_only=True) - - # Ensure registered codecs (like encryption) are available if needed - # zarr.registry.get_codec(...) or ensure they are registered beforehand - - # consolidated=False is typical for HAMT stores, but could be True if explicitly created that way - ds = xr.open_zarr(store=ipfszarr3_store, chunks=None) - logger.info(f"Successfully loaded dataset from CID: {ipfs_cid}") - return ds - except (requests.exceptions.RequestException, IpfsConnectionError) as e: - # Catch connection/network errors during loading - if ( - "Connection refused" in str(e) - or "Max retries exceeded" in str(e) - or "Timeout" in str(e) - ): - raise IpfsConnectionError( - f"IPFS connection failed while loading dataset from CID {ipfs_cid}. " - f"Gateway: {ipfs_store.gateway_uri_stem}, RPC: {ipfs_store.rpc_uri_stem}. Details: {e}" - ) from e - else: - # Other network errors - raise RuntimeError( - f"Network error loading Zarr dataset from IPFS CID {ipfs_cid}: {e}" - ) from e - except FileNotFoundError as e: - # xarray/zarr raises FileNotFoundError if root metadata (.zgroup, .zarray) is missing - raise StacCatalogError( - f"Zarr metadata not found at CID {ipfs_cid}. Is it a valid Zarr root? Error: {e}" - ) from e - except ValueError: - # Let ValueErrors propagate, e.g. from invalid CID format - raise - except Exception as e: - # Catch other potential errors (e.g., Zarr format errors, py-hamt errors) - logger.error( - f"Failed to load Zarr dataset from IPFS CID {ipfs_cid}: {type(e).__name__}: {e}", - exc_info=True, - ) - raise RuntimeError( - f"Failed to load Zarr dataset from IPFS CID {ipfs_cid}" - ) from e - - -def get_metadata_by_key(key: str) -> dict: - """Get STAC metadata for specific dataset +# try: +# # root_node_id expects a CID object or string representation +# cid_obj = None +# try: +# cid_obj = CID.decode(ipfs_cid) +# except Exception as decode_err: +# raise ValueError( +# f"Invalid IPFS CID format: {ipfs_cid}. Error: {decode_err}" +# ) from decode_err + +# # Initialize HAMT store +# hamt_store = HAMT(store=ipfs_store, root_node_id=cid_obj, read_only=True) + +# # Wrap with ZarrHAMTStore adapter +# zarr_hamt__store = ZarrHAMTStore(hamt_store, read_only=True) + +# # Ensure registered codecs (like encryption) are available if needed +# # zarr.registry.get_codec(...) or ensure they are registered beforehand + +# # consolidated=False is typical for HAMT stores, but could be True if explicitly created that way +# ds = xr.open_zarr(store=zarr_hamt__store, chunks=None) +# logger.info(f"Successfully loaded dataset from CID: {ipfs_cid}") +# return ds +# except (requests.exceptions.RequestException, IpfsConnectionError) as e: +# # Catch connection/network errors during loading +# if ( +# "Connection refused" in str(e) +# or "Max retries exceeded" in str(e) +# or "Timeout" in str(e) +# ): +# raise IpfsConnectionError( +# f"IPFS connection failed while loading dataset from CID {ipfs_cid}. " +# f"Gateway: {ipfs_store.gateway_uri_stem}, RPC: {ipfs_store.rpc_uri_stem}. Details: {e}" +# ) from e +# else: +# # Other network errors +# raise RuntimeError( +# f"Network error loading Zarr dataset from IPFS CID {ipfs_cid}: {e}" +# ) from e +# except FileNotFoundError as e: +# # xarray/zarr raises FileNotFoundError if root metadata (.zgroup, .zarray) is missing +# raise StacCatalogError( +# f"Zarr metadata not found at CID {ipfs_cid}. Is it a valid Zarr root? Error: {e}" +# ) from e +# except ValueError: +# # Let ValueErrors propagate, e.g. from invalid CID format +# raise +# except Exception as e: +# # Catch other potential errors (e.g., Zarr format errors, py-hamt errors) +# logger.error( +# f"Failed to load Zarr dataset from IPFS CID {ipfs_cid}: {type(e).__name__}: {e}", +# exc_info=True, +# ) +# raise RuntimeError( +# f"Failed to load Zarr dataset from IPFS CID {ipfs_cid}" +# ) from e + + +# def get_metadata_by_key(key: str) -> dict: +# """Get STAC metadata for specific dataset - Args: - key (str): dataset key +# Args: +# key (str): dataset key - Returns: - dict: STAC metadata corresponding to key - """ - ipns_name = get_ipns_name_hash(key) - ipfs_hash = _resolve_ipns_name_hash(ipns_name) - return _get_single_metadata(ipfs_hash) +# Returns: +# dict: STAC metadata corresponding to key +# """ +# ipns_name = get_ipns_name_hash(key) +# ipfs_hash = _resolve_ipns_name_hash(ipns_name) +# return _get_single_metadata(ipfs_hash) # def list_datasets() -> typing.List[str]: @@ -760,169 +682,169 @@ def get_metadata_by_key(key: str) -> dict: # --- Metadata and Listing --- -def list_datasets( - root_catalog_ipns: str | None = None, - gateway_uri_stem: str | None = None, - rpc_uri_stem: str | None = None, -) -> typing.List[str]: - """ - Lists available dataset IDs by traversing the STAC catalog starting from IPNS. - Handles IPLD link format `{"/": "cid_string"}`. - Also populates the internal HAMT CID cache as a side effect. - """ - if root_catalog_ipns is None: - # Avoid circular import, get constant dynamically if needed - from .client import DCLIMATE_STAC_CATALOG_IPNS +# async def list_datasets( +# root_catalog_ipns: str | None = None, +# gateway_uri_stem: str | None = None, +# rpc_uri_stem: str | None = None, +# ) -> typing.List[str]: +# """ +# Lists available dataset IDs by traversing the STAC catalog starting from IPNS. +# Handles IPLD link format `{"/": "cid_string"}`. +# Also populates the internal HAMT CID cache as a side effect. +# """ +# if root_catalog_ipns is None: +# # Avoid circular import, get constant dynamically if needed +# from .client import DCLIMATE_STAC_CATALOG_IPNS - root_catalog_ipns = DCLIMATE_STAC_CATALOG_IPNS - if not root_catalog_ipns: - raise ValueError("Root catalog IPNS name is not defined.") +# root_catalog_ipns = DCLIMATE_STAC_CATALOG_IPNS +# if not root_catalog_ipns: +# raise ValueError("Root catalog IPNS name is not defined.") - logger.info( - f"Listing datasets by traversing STAC catalog from IPNS: {root_catalog_ipns}" - ) - ipfs_store = _get_ipfs_store(gateway_uri_stem, rpc_uri_stem) - dataset_ids = set() - - # *** Use fetch_json_from_ipns for the root catalog *** - try: - catalog = fetch_json_from_ipns( - root_catalog_ipns, gateway_uri_stem=gateway_uri_stem - ) - except (IpfsConnectionError, StacCatalogError, ValueError) as e: - # Make error message slightly more specific for listing context - raise StacCatalogError( - f"Failed to fetch or parse root catalog from IPNS '{root_catalog_ipns}' for listing datasets: {e}" - ) from e - - if not isinstance(catalog, dict) or catalog.get("type") != "Catalog": - raise StacCatalogError( - f"Invalid root catalog format fetched from IPNS {root_catalog_ipns} for listing. Type: {catalog.get('type')}" - ) - - collections_to_visit = [] - for link in catalog.get("links", []): - if link.get("rel") == "child" and link.get("type") == "application/json": - href_obj = link.get("href") - # *** MODIFIED: Handle dict href for IPLD links *** - if isinstance(href_obj, dict): - collection_cid_str = href_obj.get("/") - if isinstance(collection_cid_str, str): - collections_to_visit.append(collection_cid_str) - else: - logger.warning( - f"Skipping child link with invalid href dict content in root catalog during list: {link}" - ) - # Add handling for legacy string format if necessary/desired - # elif isinstance(href_obj, str) and href_obj.startswith("/ipfs/"): ... - else: - logger.warning( - f"Skipping invalid child link format in root catalog during list: {link}" - ) - - if not collections_to_visit: - logger.warning( - f"No child collection links found in root catalog {root_catalog_ipns} during list." - ) - return [] # Return empty list if no collections to check - - logger.info(f"Found {len(collections_to_visit)} collections to scan for items.") - - for collection_cid in collections_to_visit: - try: - # *** Use fetch_json_from_cid with the extracted CID string *** - collection = fetch_json_from_cid(collection_cid, ipfs_store) - - if ( - not isinstance(collection, dict) - or collection.get("type") != "Collection" - ): - logger.warning( - f"Skipping invalid collection format during list (CID: {collection_cid}). Type: {collection.get('type')}" - ) - continue - - for link in collection.get("links", []): - if link.get("rel") == "item" and link.get("type") == "application/json": - item_href_obj = link.get("href") - item_cid = None - - # *** MODIFIED: Handle dict href for item links *** - if isinstance(item_href_obj, dict): - item_cid = item_href_obj.get("/") - # Add handling for legacy string format if necessary/desired - # elif isinstance(item_href_obj, str) and item_href_obj.startswith("/ipfs/"): ... - else: - logger.warning( - f"Skipping invalid item link format in collection {collection_cid} during list: {link}" - ) - continue +# logger.info( +# f"Listing datasets by traversing STAC catalog from IPNS: {root_catalog_ipns}" +# ) +# ipfs_store = _get_ipfs_store(gateway_uri_stem, rpc_uri_stem) +# dataset_ids = set() - if isinstance(item_cid, str): - try: - # Fetch item JSON *only* to get the ID and cache the HAMT CID - # Avoids deeper processing if only listing - item = fetch_json_from_cid(item_cid, ipfs_store) - - if ( - not isinstance(item, dict) - or item.get("type") != "Feature" - ): - logger.warning( - f"Skipping invalid item format during list (CID: {item_cid}). Type: {item.get('type')}" - ) - continue - - item_id = item.get("id") - if isinstance(item_id, str) and item_id: - dataset_ids.add(item_id) # Add valid ID to our set - - # Populate cache as a side effect - hamt_asset = item.get("assets", {}).get("hamt-zarr", {}) - hamt_cid_href = hamt_asset.get("href") - if ( - item_id not in _stac_hamt_cid_cache - and isinstance(hamt_cid_href, str) - and hamt_cid_href.startswith("/ipfs/") - ): - hamt_cid_str = hamt_cid_href[6:] - _stac_hamt_cid_cache[item_id] = hamt_cid_str - logger.debug( - f"Cached HAMT CID {hamt_cid_str} for {item_id} during list." - ) - else: - logger.warning( - f"Item {item_cid} has missing or invalid 'id'. Skipping." - ) - - except (StacCatalogError, IpfsConnectionError) as item_err: - # Log and skip this specific item if fetching/parsing fails - logger.warning( - f"Skipping item {item_cid} during list due to error: {item_err}" - ) - except Exception as item_err: # Catch unexpected errors - logger.warning( - f"Skipping item {item_cid} during list due to unexpected error: {type(item_err).__name__}: {item_err}" - ) - # else: Invalid item CID extracted, already logged warning - - # 1️⃣ propagate a StacCatalogError that bubbled up from the **target item** - except StacCatalogError as col_err: - raise col_err - - # 2️⃣ still swallow IPFS/network problems so that other collections can be tried - except IpfsConnectionError as col_err: - logger.error( - f"IPFS error processing collection {collection_cid}, continuing search: " - f"{col_err}" - ) - except Exception as col_err: # Catch unexpected errors - logger.warning( - f"Skipping collection {collection_cid} during list due to unexpected error: {type(col_err).__name__}: {col_err}" - ) - - found_datasets = sorted(list(dataset_ids)) - logger.info( - f"Finished listing datasets. Found {len(found_datasets)} unique dataset IDs." - ) - return found_datasets +# # *** Use fetch_json_from_ipns for the root catalog *** +# try: +# catalog = fetch_json_from_ipns( +# root_catalog_ipns, gateway_uri_stem=gateway_uri_stem +# ) +# except (IpfsConnectionError, StacCatalogError, ValueError) as e: +# # Make error message slightly more specific for listing context +# raise StacCatalogError( +# f"Failed to fetch or parse root catalog from IPNS '{root_catalog_ipns}' for listing datasets: {e}" +# ) from e + +# if not isinstance(catalog, dict) or catalog.get("type") != "Catalog": +# raise StacCatalogError( +# f"Invalid root catalog format fetched from IPNS {root_catalog_ipns} for listing. Type: {catalog.get('type')}" +# ) + +# collections_to_visit = [] +# for link in catalog.get("links", []): +# if link.get("rel") == "child" and link.get("type") == "application/json": +# href_obj = link.get("href") +# # *** MODIFIED: Handle dict href for IPLD links *** +# if isinstance(href_obj, dict): +# collection_cid_str = href_obj.get("/") +# if isinstance(collection_cid_str, str): +# collections_to_visit.append(collection_cid_str) +# else: +# logger.warning( +# f"Skipping child link with invalid href dict content in root catalog during list: {link}" +# ) +# # Add handling for legacy string format if necessary/desired +# # elif isinstance(href_obj, str) and href_obj.startswith("/ipfs/"): ... +# else: +# logger.warning( +# f"Skipping invalid child link format in root catalog during list: {link}" +# ) + +# if not collections_to_visit: +# logger.warning( +# f"No child collection links found in root catalog {root_catalog_ipns} during list." +# ) +# return [] # Return empty list if no collections to check + +# logger.info(f"Found {len(collections_to_visit)} collections to scan for items.") + +# for collection_cid in collections_to_visit: +# try: +# # *** Use fetch_json_from_cid with the extracted CID string *** +# collection = await fetch_json_from_cid(collection_cid, ipfs_store) + +# if ( +# not isinstance(collection, dict) +# or collection.get("type") != "Collection" +# ): +# logger.warning( +# f"Skipping invalid collection format during list (CID: {collection_cid}). Type: {collection.get('type')}" +# ) +# continue + +# for link in collection.get("links", []): +# if link.get("rel") == "item" and link.get("type") == "application/json": +# item_href_obj = link.get("href") +# item_cid = None + +# # *** MODIFIED: Handle dict href for item links *** +# if isinstance(item_href_obj, dict): +# item_cid = item_href_obj.get("/") +# # Add handling for legacy string format if necessary/desired +# # elif isinstance(item_href_obj, str) and item_href_obj.startswith("/ipfs/"): ... +# else: +# logger.warning( +# f"Skipping invalid item link format in collection {collection_cid} during list: {link}" +# ) +# continue + +# if isinstance(item_cid, str): +# try: +# # Fetch item JSON *only* to get the ID and cache the HAMT CID +# # Avoids deeper processing if only listing +# item = await fetch_json_from_cid(item_cid, ipfs_store) + +# if ( +# not isinstance(item, dict) +# or item.get("type") != "Feature" +# ): +# logger.warning( +# f"Skipping invalid item format during list (CID: {item_cid}). Type: {item.get('type')}" +# ) +# continue + +# item_id = item.get("id") +# if isinstance(item_id, str) and item_id: +# dataset_ids.add(item_id) # Add valid ID to our set + +# # Populate cache as a side effect +# hamt_asset = item.get("assets", {}).get("hamt-zarr", {}) +# hamt_cid_href = hamt_asset.get("href") +# if ( +# item_id not in _stac_hamt_cid_cache +# and isinstance(hamt_cid_href, str) +# and hamt_cid_href.startswith("/ipfs/") +# ): +# hamt_cid_str = hamt_cid_href[6:] +# _stac_hamt_cid_cache[item_id] = hamt_cid_str +# logger.debug( +# f"Cached HAMT CID {hamt_cid_str} for {item_id} during list." +# ) +# else: +# logger.warning( +# f"Item {item_cid} has missing or invalid 'id'. Skipping." +# ) + +# except (StacCatalogError, IpfsConnectionError) as item_err: +# # Log and skip this specific item if fetching/parsing fails +# logger.warning( +# f"Skipping item {item_cid} during list due to error: {item_err}" +# ) +# except Exception as item_err: # Catch unexpected errors +# logger.warning( +# f"Skipping item {item_cid} during list due to unexpected error: {type(item_err).__name__}: {item_err}" +# ) +# # else: Invalid item CID extracted, already logged warning + +# # 1️⃣ propagate a StacCatalogError that bubbled up from the **target item** +# except StacCatalogError as col_err: +# raise col_err + +# # 2️⃣ still swallow IPFS/network problems so that other collections can be tried +# except IpfsConnectionError as col_err: +# logger.error( +# f"IPFS error processing collection {collection_cid}, continuing search: " +# f"{col_err}" +# ) +# except Exception as col_err: # Catch unexpected errors +# logger.warning( +# f"Skipping collection {collection_cid} during list due to unexpected error: {type(col_err).__name__}: {col_err}" +# ) + +# found_datasets = sorted(list(dataset_ids)) +# logger.info( +# f"Finished listing datasets. Found {len(found_datasets)} unique dataset IDs." +# ) +# return found_datasets diff --git a/dclimate_zarr_client/loader.py b/dclimate_zarr_client/loader.py new file mode 100644 index 0000000..9632290 --- /dev/null +++ b/dclimate_zarr_client/loader.py @@ -0,0 +1,139 @@ +""" +Functions that will map to endpoints in the flask app +""" +from .loaders.era5 import ERA5Loader +from .geotemporal_data import GeotemporalData +import xarray as xr +from .registry import get_cid_from_registry +from py_hamt import KuboCAS +from multiformats import CID +import json + + +# Mapping of collections to their specific loader classes +LOADER_STRATEGIES = { + "era5": ERA5Loader, + # Add other collections here, e.g., "cpc": CPCLoader +} + +DCLIMATE_GATEWAY_URL = "https://ipfs-gateway.dclimate.net/" + + +async def dclimate_dataset_loader(collection: str, dataset: str, options: dict = None, gateway_url: str = DCLIMATE_GATEWAY_URL) -> xr.Dataset: + """ + Loads a dClimate dataset by dynamically fetching the STAC catalog CID + from an on-chain registry. + + Args: + collection (str): The name of the data collection (e.g., "era5"). + dataset (str): The name of the dataset (e.g., "2m_temperature"). + options (dict, optional): Options for loading, e.g., {'finalized_only': True}. + + Returns: + xr.Dataset: The loaded Xarray dataset. + """ + options = options or {} + + # Select the appropriate loading strategy + loader_class = LOADER_STRATEGIES.get(collection) + loader = loader_class( + gateway_url=gateway_url, + ) + + return await loader.load(dataset=dataset, options=options) + +async def get_geo_temporal_dataset( + collection: str, + dataset: str, + options: dict = None, + gateway_url: str = DCLIMATE_GATEWAY_URL +) -> GeotemporalData: + """ + Loads a geo-temporal dataset from dClimate by dynamically fetching the STAC catalog CID + from an on-chain registry. + """ + options = options or {} + + # Select the appropriate loading strategy + loader_class = LOADER_STRATEGIES.get(collection) + loader = loader_class( + gateway_url=gateway_url, + ) + + ds = await loader.load(dataset=dataset, options=options) + return GeotemporalData(ds, dataset_name=dataset) + + + +async def _load_stac_json_from_cid(cid: str) -> dict: + """Loads a STAC JSON from a given CID.""" + async with KuboCAS(gateway_base_url=DCLIMATE_GATEWAY_URL) as kubo_cas: + try: + item_bytes = await kubo_cas.load(CID.decode(cid)) + item = json.loads(item_bytes) + return item + except Exception as e: + print(f"Error loading STAC JSON from CID {cid}: {e}") + raise + +# Get all datasets in the catalog +async def get_all_datasets(collection: str, gateway_url: str = DCLIMATE_GATEWAY_URL) -> list: + """ + Fetches all datasets available in a specific collection from dClimate. + + Args: + collection (str): The name of the data collection (e.g., "era5"). + gateway_url (str): The IPFS gateway URL to use for fetching data. + + Returns: + list: A list of dataset names available in the specified collection. + """ + stac_root_cid = get_cid_from_registry() + + stac_json = await _load_stac_json_from_cid(stac_root_cid) + if not stac_json: + raise ValueError(f"No STAC JSON found for collection: {collection}") + + collection_cid = None + + # Capitalize all letters in collection + collection = collection.upper() + + # The first links that are of rel child are all the collections + for link in stac_json.get("links", []): + if link.get("rel") == "child" and link.get("title") == collection: + collection_cid = link.get("href")["/"] + break + + if not collection_cid: + raise ValueError(f"No datasets found for collection: {collection}") + + # Get the stac for the collection + collection_stac = await _load_stac_json_from_cid(collection_cid) + if not collection_stac: + raise ValueError(f"No STAC JSON found for collection CID: {collection_cid}") + # Extract dataset names from the collection STAC + datasets = [] + for item in collection_stac.get("links", []): + if item.get("rel") == "child": + datasets.append(item.get("title")) + if item.get("rel") == "item": + datasets.append(item.get("title")) + return datasets + +async def get_all_collections(gateway_url: str = DCLIMATE_GATEWAY_URL) -> list: + """ + Fetches all collections available in the dClimate catalog. + """ + stac_root_cid = get_cid_from_registry() + + stac_json = await _load_stac_json_from_cid(stac_root_cid) + if not stac_json: + raise ValueError(f"No STAC JSON found for collection: {collection}") + + collections = [] + for link in stac_json.get("links", []): + if link.get("rel") == "child": + collections.append(link.get("title")) + + return collections \ No newline at end of file diff --git a/dclimate_zarr_client/loaders/__init__.py b/dclimate_zarr_client/loaders/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/dclimate_zarr_client/loaders/base.py b/dclimate_zarr_client/loaders/base.py new file mode 100644 index 0000000..c00f445 --- /dev/null +++ b/dclimate_zarr_client/loaders/base.py @@ -0,0 +1,70 @@ +from abc import ABC, abstractmethod +import xarray as xr +from web3 import Web3 +from py_hamt import KuboCAS, ShardedZarrStore +from multiformats import CID +import json + +STAC_REGISTRY_ABI = """ +[ + { + "inputs": [{"internalType": "string", "name": "initialCid", "type": "string"}], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + {"indexed": false, "internalType": "string", "name": "newCid", "type": "string"}, + {"indexed": true, "internalType": "address", "name": "updater", "type": "address"} + ], + "name": "CidUpdated", + "type": "event" + }, + { + "inputs": [], + "name": "getCid", + "outputs": [{"internalType": "string", "name": "", "type": "string"}], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{"internalType": "address", "name": "newOwner", "type": "address"}], + "name": "changeOwner", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] +""" + +class BaseLoader(ABC): + + def __init__(self, gateway_url: str): + self.gateway_url = gateway_url + + async def _load_stac_json_from_cid(self, cid: str) -> dict: + """Loads a STAC JSON from a given CID.""" + async with KuboCAS(gateway_base_url=self.gateway_url) as kubo_cas: + try: + item_bytes = await kubo_cas.load(CID.decode(cid)) + item = json.loads(item_bytes) + return item + except Exception as e: + print(f"Error loading STAC JSON from CID {cid}: {e}") + raise + + async def _load_zarr_from_cid(self, cid: str) -> xr.Dataset: + """Loads a sharded Zarr store from a given CID.""" + async with KuboCAS(gateway_base_url=self.gateway_url) as kubo_cas: + store = await ShardedZarrStore.open( + cas=kubo_cas, + read_only=True, + root_cid=cid + ) + return xr.open_zarr(store=store, chunks="auto") + + @abstractmethod + async def load(self, dataset: str, options: dict) -> xr.Dataset: + """Abstract method to be implemented by each strategy.""" + pass \ No newline at end of file diff --git a/dclimate_zarr_client/loaders/era5.py b/dclimate_zarr_client/loaders/era5.py new file mode 100644 index 0000000..1731e93 --- /dev/null +++ b/dclimate_zarr_client/loaders/era5.py @@ -0,0 +1,54 @@ +import xarray as xr +from .base import BaseLoader +from dclimate_zarr_client.ipfs_retrieval import get_dataset_stac +import numpy as np +import pandas as pd +from dclimate_zarr_client.registry import get_cid_from_registry + +class ERA5Loader(BaseLoader): + collection = "ERA5" + + # Helps get the finalized or unfinalized dataset CID + async def _get_dataset_type_cid(self, stac: dict, type: str) -> str: + links = stac.get("links", []) + dataset_cid = None + for link in links: + if link.get("rel") == "item" and link.get("title") == type: + dataset_cid = link.get("href")["/"] + break + finalized_stac_item = await self._load_stac_json_from_cid(dataset_cid) + finalized_stac_cid = finalized_stac_item.get("assets", {}).get("sharded-zarr", {}).get("href", None) + return finalized_stac_cid + + async def load(self, dataset: str, options: dict) -> xr.Dataset: + finalized_only = options.get('finalized_only', False) + root_cid = get_cid_from_registry() + + # # 1. --- Get STAC metadata for the dataset collection --- + dataset_stac = await get_dataset_stac( + stac_root_cid=root_cid, + collection=self.collection, + dataset=dataset, + gateway_uri_stem=self.gateway_url + ) + + # # 2. --- Lazily Open Finalized Dataset --- + finalized_stac_cid = await self._get_dataset_type_cid(dataset_stac, "finalized") + ds_finalized = await self._load_zarr_from_cid(finalized_stac_cid) + + if finalized_only: + return ds_finalized + + # # 2. --- Lazily Open Unfinalized Dataset --- + unfinalized_stac_cid = await self._get_dataset_type_cid(dataset_stac, "non-finalized") + ds_unfinalized = await self._load_zarr_from_cid(unfinalized_stac_cid) + + # 3. --- Perform Lazy Slicing and Concatenation --- + # This operation is lazy. It reads only the 'time' coordinate, not the full data. + finalization_date = np.datetime64(ds_finalized.time.max().values) + start_time = finalization_date + np.timedelta64(1, 'h') + ds_unfinalized_sliced = ds_unfinalized.sel(time=slice(start_time, None)) + combined_ds = xr.concat([ds_finalized, ds_unfinalized_sliced], dim="time") + + return combined_ds + \ No newline at end of file diff --git a/dclimate_zarr_client/registry.py b/dclimate_zarr_client/registry.py new file mode 100644 index 0000000..d214b3f --- /dev/null +++ b/dclimate_zarr_client/registry.py @@ -0,0 +1,178 @@ +import os +from pathlib import Path +from eth_account import Account +from ape import accounts, Project, networks +from ape.exceptions import AccountsError +from ape_accounts import import_account_from_mnemonic +from web3 import Web3 + +# The path to the embedded Ape project +CONTRACTS_PROJECT_PATH = Path(__file__).parent / "contracts" +STAC_REGISTRY_CONTRACT_ADDRESS = os.environ.get("STAC_REGISTRY_CONTRACT_ADDRESS", "0xabe7441E21bDb6cCf4E517E0072c5E962F5f0B2d") +RPC_URL = os.environ.get("RPC_URL", "https://sepolia.base.org") +STAC_REGISTRY_ABI = """ +[ + { + "inputs": [{"internalType": "string", "name": "initialCid", "type": "string"}], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + {"indexed": false, "internalType": "string", "name": "newCid", "type": "string"}, + {"indexed": true, "internalType": "address", "name": "updater", "type": "address"} + ], + "name": "CidUpdated", + "type": "event" + }, + { + "inputs": [], + "name": "getCid", + "outputs": [{"internalType": "string", "name": "", "type": "string"}], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{"internalType": "address", "name": "newOwner", "type": "address"}], + "name": "changeOwner", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [{"internalType": "string", "name": "newCid", "type": "string"}], + "name": "updateCid", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] +""" + + +def _get_web3_instance() -> Web3: + """Initializes and returns a Web3 instance.""" + return Web3(Web3.HTTPProvider(RPC_URL)) + +def _load_account_from_mnemonic_ape(mnemonic_path: str): + """Loads a deployer/owner account from a mnemonic file.""" + mnemonic_file = Path(mnemonic_path) + if not mnemonic_file.is_file(): + raise FileNotFoundError(f"Mnemonic file not found at: {mnemonic_path}") + + # Use a unique alias to avoid collisions if multiple mnemonics are used + alias = f"etl_deployer_{mnemonic_file.stem}" + + try: + # Try to load if it already exists in Ape's accounts + return accounts.load(alias) + except KeyError: + print(f"Account '{alias}' not found. Importing from mnemonic...") + return import_account_from_mnemonic( + alias=alias, # Note the parameter name change to 'account_alias' + mnemonic=mnemonic_file.read_text().strip(), + passphrase=os.environ.get("APE_ACCOUNT_PASSPHRASE", "test") # Provide a default + ) + +def _load_account_from_mnemonic(mnemonic_path: str): + """Loads an account from a mnemonic file and returns the account object.""" + mnemonic_file = Path(mnemonic_path) + if not mnemonic_file.is_file(): + raise FileNotFoundError(f"Mnemonic file not found at: {mnemonic_path}") + + Account.enable_unaudited_hdwallet_features() + mnemonic = mnemonic_file.read_text().strip() + return Account.from_mnemonic(mnemonic) + +def deploy_registry(initial_cid: str, mnemonic_path: str, network: str = "base:sepolia") -> str: + """ + Deploys the StacRegistry contract programmatically. + + Args: + initial_cid (str): The initial root CID of the STAC catalog. + mnemonic_path (str): The path to the mnemonic.txt file for the deployer account. + network (str): The network to deploy on (e.g., 'base:sepolia'). + + Returns: + str: The address of the newly deployed contract. + """ + project = Project(path=CONTRACTS_PROJECT_PATH) + deployer = _load_account_from_mnemonic_ape(mnemonic_path) + + with networks.parse_network_choice(network) as provider: + print(f"Using network: {provider.name}") + print(f"Deployer address: {deployer.address}") + + StacRegistry = project.StacRegistry + + print(f"Deploying StacRegistry with initial CID: {initial_cid}") + contract_instance = deployer.deploy(StacRegistry, initial_cid) + + print(f"✓ Deployment successful!") + return contract_instance.address + +def _get_web3_instance() -> Web3: + """Initializes and returns a Web3 instance.""" + return Web3(Web3.HTTPProvider(RPC_URL)) + +def _load_account_from_mnemonic(mnemonic_path: str): + """Loads an account from a mnemonic file and returns the account object.""" + mnemonic_file = Path(mnemonic_path) + if not mnemonic_file.is_file(): + raise FileNotFoundError(f"Mnemonic file not found at: {mnemonic_path}") + + Account.enable_unaudited_hdwallet_features() + mnemonic = mnemonic_file.read_text().strip() + return Account.from_mnemonic(mnemonic) + +def update_registry(contract_address: str, new_cid: str, mnemonic_path: str) -> str: + """ + Updates the STAC CID in an existing StacRegistry contract using web3.py. + """ + w3 = _get_web3_instance() + owner_account = _load_account_from_mnemonic(mnemonic_path) + + contract = w3.eth.contract(address=contract_address, abi=STAC_REGISTRY_ABI) + + print(f"Using network: {RPC_URL}") + print(f"Owner address: {owner_account.address}") + print(f"Updating STAC CID on contract {contract_address} to: {new_cid}") + + # 1. Build the transaction + nonce = w3.eth.get_transaction_count(owner_account.address) + tx = contract.functions.updateCid(new_cid).build_transaction({ + 'chainId': w3.eth.chain_id, + 'from': owner_account.address, + 'nonce': nonce, + 'gas': 200000, # You might need to adjust gas estimation + 'gasPrice': w3.eth.gas_price, + }) + + # 2. Sign the transaction + signed_tx = w3.eth.account.sign_transaction(tx, private_key=owner_account.key) + + # 3. Send the raw transaction + tx_hash = w3.eth.send_raw_transaction(signed_tx.rawTransaction) + + # 4. Wait for the transaction receipt (optional, but good practice) + receipt = w3.eth.wait_for_transaction_receipt(tx_hash) + + print("✓ Update successful!") + print(f"Transaction hash: {receipt.transactionHash.hex()}") + + return receipt.transactionHash.hex() + +def get_cid_from_registry() -> str: + """ + Retrieves the current STAC CID from the StacRegistry contract. + (This function already uses web3.py correctly) + """ + w3 = _get_web3_instance() + contract = w3.eth.contract( + address=STAC_REGISTRY_CONTRACT_ADDRESS, + abi=STAC_REGISTRY_ABI + ) + stac_cid = contract.functions.getCid().call() + print(f"Current STAC CID from registry: {stac_cid}") + return stac_cid diff --git a/pyproject.toml b/pyproject.toml index b774661..b265afe 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,15 +11,16 @@ authors = [ { name = "Evan Schechter", email = "evan@arbol.io" }, { name = "Chris Rossi", email = "chris@christophermrossi.com" }, { name = "Robert Banick", email = "robert.banick@arbol.io" }, + { name = "Phil Van Meppelen scheppink", email = "phil@dclimate.net" }, ] requires-python = ">=3.12" dependencies = [ "aiobotocore", - "xarray", + "xarray[complete]>=2025.3.0", "rioxarray", - "zarr", + "zarr>=3.0.8", "numpy>=2.1.3", - "py_hamt @ git+https://github.com/dClimate/py-hamt", + "py-hamt>=3.2.0", "requests", "pyarrow", "geopandas", @@ -28,8 +29,19 @@ dependencies = [ "shapely", "scipy", "pycryptodome>=3.21.0", + "eth-ape>=0.8.4", + "web3>=6.20.1", + "pytest-asyncio>=1.0.0", + "ape-base>=0.8.1", + "ape-solidity>=0.8.5", + "eth-account>=0.11.3", ] +[tool.uv.sources] +py-hamt = { git = "https://github.com/dClimate/py-hamt", rev = "a60039b4e2305847c8b9241557a35f96038d7ca0" } +# local path for py-hamt +# py-hamt = { path = "../py-hamt", editable = true } + [project.urls] Homepage = "https://dclimate.net/" diff --git a/tests/test_integration.py b/tests/test_integration.py new file mode 100644 index 0000000..23a1b34 --- /dev/null +++ b/tests/test_integration.py @@ -0,0 +1,103 @@ +import pytest +import xarray as xr +import numpy as np +import pandas as pd +from unittest.mock import patch +from pathlib import Path + +from dclimate_zarr_client import registry, loader + +import xarray as xr +import numpy as np +import pandas as pd +from py_hamt import KuboCAS, ShardedZarrStore + +# Mark all tests in this file as asyncio +pytestmark = pytest.mark.asyncio + + +@pytest.fixture(scope="module") +def mnemonic(tmp_path_factory): + """ + Fixture to create a temporary mnemonic file for testing. + + IMPORTANT: You must manually create a `tests/mnemonic.txt` file containing + a seed phrase for a wallet funded with Base Goerli ETH. + This test will fail if the account has no funds. + """ + # This fixture now points to a user-created mnemonic file + # for the live testnet. + mnemonic_path = Path("mnemonic.txt") + if not mnemonic_path.exists(): + pytest.fail( + "A `mnemonic.txt` file is required for live testnet tests. " + "Please create it and fund the associated account with Base Goerli ETH." + ) + return str(mnemonic_path) + + +# This test is now parameterized to run ONLY on the Base Goerli testnet. +# NOTE: This will be slower and will consume real testnet ETH. +@pytest.mark.parametrize("network", ["base:sepolia"]) +async def test_deployment_and_update_on_testnet(mnemonic, network): + """ + Tests the full contract deployment and update lifecycle on the Base Goerli testnet. + """ + print(f"\n--- Running Integration Test on {network} ---") + + # 1. Deploy the contract + initial_cid = "baguqehra7gqmpt6j4rfhekln7pel4log7p3qu6iike2qgj3ypl74g7aj4isa" + contract_address = registry.deploy_registry( + initial_cid=initial_cid, + mnemonic_path=mnemonic, + network=network + ) + + assert isinstance(contract_address, str) + assert contract_address.startswith("0x") + print(f"✅ Deployment successful: {contract_address}") + + # 2. Update the contract + new_cid = "bafybeihweq2w7v7nnsywn2vqn33y3o6q52rl7mvk2idxmozm2v2c6a2d5y" + tx_hash = registry.update_registry( + contract_address=contract_address, + new_cid=new_cid, + mnemonic_path=mnemonic, + ) + + assert isinstance(tx_hash, str) + assert tx_hash.startswith("0x") + print(f"✅ Update successful: {tx_hash}") + + +def create_dummy_dataset(): + """Helper to create a simple xarray.Dataset for mocking.""" + times = pd.date_range("2024-01-01", periods=10, freq="h") + lats = np.arange(40, 42, 1) + lons = np.arange(-80, -78, 1) + temp = np.random.rand(len(times), len(lats), len(lons)) + return xr.Dataset( + {"2m_temperature": (("time", "latitude", "longitude"), temp)}, + coords={"time": times, "latitude": lats, "longitude": lons}, + ) + + +async def test_era5_loader_with_mocks(): + """ + This test remains unchanged. It tests the Python logic of the data loader + by mocking blockchain and IPFS calls. It is fast and does not depend on + any network connection, which is a testing best practice. + """ + all_datasets = await loader.get_all_collections() + print(f"All : {all_datasets}") + # era5_ds = await loader.get_geo_temporal_dataset( + # collection="era5", + # dataset="2m_temperature", + # options={'finalized_only': False} + # ) + # # Download data from latitude 0 and longitude 0 at 2023-01-01T00:00:00 + # data = era5_ds.sel( + # latitude=0, longitude=0, time="2023-01-01T00:00:00" + # ).load() # Load the data into memory + # print(f"Data at (0, 0) on 2023-01-01T00:00:00: {data['2m_temperature'].values}") + \ No newline at end of file diff --git a/uv.lock b/uv.lock index 143f13a..0940a53 100644 --- a/uv.lock +++ b/uv.lock @@ -1,6 +1,11 @@ version = 1 revision = 1 requires-python = ">=3.12" +resolution-markers = [ + "implementation_name == 'cpython'", + "implementation_name == 'pypy'", + "implementation_name != 'cpython' and implementation_name != 'pypy'", +] [[package]] name = "affine" @@ -108,6 +113,82 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ec/6a/bc7e17a3e87a2985d3e8f4da4cd0f481060eb78fb08596c42be62c90a4d9/aiosignal-1.3.2-py2.py3-none-any.whl", hash = "sha256:45cde58e409a301715980c2b01d0c28bdde3770d8290b5eb2173759d9acb31a5", size = 7597 }, ] +[[package]] +name = "annotated-types" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643 }, +] + +[[package]] +name = "anyio" +version = "4.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna" }, + { name = "sniffio" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/95/7d/4c1bd541d4dffa1b52bd83fb8527089e097a106fc90b467a7313b105f840/anyio-4.9.0.tar.gz", hash = "sha256:673c0c244e15788651a4ff38710fea9675823028a6f08a5eda409e0c9840a028", size = 190949 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a1/ee/48ca1a7c89ffec8b6a0c5d02b89c305671d5ffd8d3c94acf8b8c408575bb/anyio-4.9.0-py3-none-any.whl", hash = "sha256:9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c", size = 100916 }, +] + +[[package]] +name = "ape-base" +version = "0.8.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ape-optimism" }, + { name = "eth-ape" }, + { name = "ethpm-types" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/26/64/4b3e35c494e7e7c3c1516e4cf98d2f8d5e2791881bbe2a8ce3d3ee0b2ace/ape-base-0.8.1.tar.gz", hash = "sha256:cbcc505743c62b097348c1e0a24e12e55d3a829a4a691e3c925a19f980a72465", size = 17711 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/52/5a/2132104828bd2761951322de2de56ae4ba7ad54fd92f76d82c0d0f837e1a/ape_base-0.8.1-py3-none-any.whl", hash = "sha256:9c3cc4a9644949f860581cf62dcf0100e30c5943089e857daf46f40d64023a23", size = 9395 }, +] + +[[package]] +name = "ape-optimism" +version = "0.8.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "eth-ape" }, + { name = "ethpm-types" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/72/ff/6c0e186cde8c565343bb6a376ccec25ff111780c461640d98d9328aa6d15/ape_optimism-0.8.3.tar.gz", hash = "sha256:eb48e34923759761a9c27b9d314c61530dbea1c4627be48fc4ca8fed623d7f72", size = 17641 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fe/1a/8c9444e555756dd13879c5d8eba8b085569dd49bd17aec6539cd4b3e2987/ape_optimism-0.8.3-py3-none-any.whl", hash = "sha256:4d263764922096debbd061eae8e5a3bb520573923958c1512fe518b894d8ac5d", size = 9068 }, +] + +[[package]] +name = "ape-solidity" +version = "0.8.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "eth-ape" }, + { name = "eth-pydantic-types" }, + { name = "ethpm-types" }, + { name = "packaging" }, + { name = "py-solc-x" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/4a/9b/41d50b7ec9a06f58f299bbbc003b6d59e16d19050534c216151b7e65c2c8/ape-solidity-0.8.5.tar.gz", hash = "sha256:8200ca4d0d1df0608f14418a16cd98a730d65c3073450ad76e458bfda64e1535", size = 418307 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6d/2e/d07b6dba5a86bfd4f2cced0f5ada112a55dbb886552187ddc86b94aef7d4/ape_solidity-0.8.5-py3-none-any.whl", hash = "sha256:75094fd996ad0914cb2425598e0ec9329adc881050d55b4224d74f75dd994794", size = 29047 }, +] + +[[package]] +name = "asttokens" +version = "3.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4a/e7/82da0a03e7ba5141f05cce0d302e6eed121ae055e0456ca228bf693984bc/asttokens-3.0.0.tar.gz", hash = "sha256:0dcd8baa8d62b0c1d118b399b2ddba3c4aff271d0d7a9e0d4c1681c79035bbc7", size = 61978 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/25/8a/c46dcc25341b5bce5472c718902eb3d38600a903b14fa6aeecef3f21a46f/asttokens-3.0.0-py3-none-any.whl", hash = "sha256:e3078351a059199dd5138cb1c706e6430c05eff2ff136af5eb4790f9d28932e2", size = 26918 }, +] + [[package]] name = "attrs" version = "25.3.0" @@ -117,6 +198,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/77/06/bb80f5f86020c4551da315d78b3ab75e8228f89f0162f2c3a819e407941a/attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3", size = 63815 }, ] +[[package]] +name = "base58" +version = "1.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c7/59/610af520e9f4d1b28c975bece4768d700884e38929e7ba0dcb25c9f6f87c/base58-1.0.3.tar.gz", hash = "sha256:9a793c599979c497800eb414c852b80866f28daaed5494703fc129592cc83e60", size = 3865 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/09/b2/21ac9591f055acc145afead895edeb73bbd69d95cf366fc5c2233f2434cb/base58-1.0.3-py3-none-any.whl", hash = "sha256:6aa0553e477478993588303c54659d15e3c17ae062508c854a8b752d07c716bd", size = 3072 }, +] + [[package]] name = "bases" version = "0.3.0" @@ -130,6 +220,42 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b4/15/7bcf28a3f971e1b0523fab46ae3ca935a589249544187558e5a8e70af393/bases-0.3.0-py3-none-any.whl", hash = "sha256:a2fef3366f3e522ff473d2e95c21523fe8e44251038d5c6150c01481585ebf5b", size = 36053 }, ] +[[package]] +name = "bitarray" +version = "3.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0d/0f/6ecf00ec04622b8309aca3cfbdba20d5399d9e4e5a4b156d9ffd2e5610d3/bitarray-3.5.0.tar.gz", hash = "sha256:e10ae216416c36500c86c08ffceaf7589f6ad54056a7007845cdd907813e7d25", size = 148024 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4d/47/3b2fd638a96da2e42a53647d694b3ec6faced228cae608889e685f03351b/bitarray-3.5.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:cc918ff7beaa389055f38d7bb096b00b95434d80a6f4784624dbca4fb919464c", size = 144424 }, + { url = "https://files.pythonhosted.org/packages/98/13/e4f2211629cad18655a677c1b0135d8e82c0ab6c851d496227f6ea8cd35d/bitarray-3.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5964f319ef78cf69f24eebb9061e06462eed1a9ca62bdbbb6dcad611691f271d", size = 141304 }, + { url = "https://files.pythonhosted.org/packages/d4/79/e5625aabf5ca1f79b44d6b5c5f4c7fd611f3d574afe241edacd2b31a3531/bitarray-3.5.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e0099251d50ddcab672d6ddf415eccf1b1117f0ab54e9321f905685d9ff910a6", size = 321007 }, + { url = "https://files.pythonhosted.org/packages/5c/63/dde5698a9f3a3bb794f48f16f8d23ff42f26ea99a2fa7a7632dc3ea275e7/bitarray-3.5.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:923fa335dcc3ecbb516f12b2b70f5991773ffe05484f8c8d9d31bd419c311e90", size = 336578 }, + { url = "https://files.pythonhosted.org/packages/ca/8c/7e0dd6dee3e4311224ddf95601cd60c0378db3d591cdc135b70abc7f3999/bitarray-3.5.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:352a6f7a5501ab4f74fda2275258fb7607b740ca74f5768d10774efca4215d4e", size = 330179 }, + { url = "https://files.pythonhosted.org/packages/bb/da/8b43649052792e9786b4cbf895191d7ee9d907a5bc802059ff3eeb57e2af/bitarray-3.5.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:403b2045f43d70b184f559bfc39cb98fef02ee03aafd1cbe6d77db345a8c8275", size = 323080 }, + { url = "https://files.pythonhosted.org/packages/fa/a9/c647eb2dd958550173c01f4859a8406b41ac8caa37a00d2be48711a31828/bitarray-3.5.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:25946a0faf22f16619a1eae82ae28c0e5e09815e7d5629c507df7b4e3bc7807a", size = 310444 }, + { url = "https://files.pythonhosted.org/packages/c0/46/115dff1e6c21461a0b1681fb4ec666d50bdda5dce8c6c0e1b384e753b145/bitarray-3.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:18573b66d9c08b692e21abff8b77f32716ec77ab8aff60916590107d725bf648", size = 315128 }, + { url = "https://files.pythonhosted.org/packages/70/78/33e485dbe8e499677271b9b7d244b3cb57d57b0396672682a071a2b22cde/bitarray-3.5.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:39d8b00e5031d0ab8101c2e6349b04cd753d2426b963279858ed1d5b0d454cd8", size = 306860 }, + { url = "https://files.pythonhosted.org/packages/ce/11/50234d6d1c057aee92a0aa7849a7276352db3772267818ac066aee24fefa/bitarray-3.5.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:e75f9150b59a4f5d205bd55e5ddb741e295045f17ebb1d4599e7e68563e98955", size = 331374 }, + { url = "https://files.pythonhosted.org/packages/12/38/2bc7a39182fbfd7fbe16b7f400034984af29e9dc640f56a349529fe13d27/bitarray-3.5.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:67343d181a4181ee608afe3a284b21d8be0ed11bdfbf9dd0779d57403556fedf", size = 335026 }, + { url = "https://files.pythonhosted.org/packages/f5/d3/b4c16d161d7b8bbf019532faf933ad7306ecbf1ae24c10d1a0ec75bb0906/bitarray-3.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:333c707bbc58028750e56f2da253838a71ec47c6dd6f8302c461381258e266f7", size = 314989 }, + { url = "https://files.pythonhosted.org/packages/5e/01/94be9b1fbe81143a8d842cbfa8b14fdca12b96c848239b71b944059aa310/bitarray-3.5.0-cp312-cp312-win32.whl", hash = "sha256:891817b3006f56e542f889d844c4c5c4715ea7b6e0e5825fc51dd1f7de76c14c", size = 138373 }, + { url = "https://files.pythonhosted.org/packages/9c/20/58a93d971e9d89a94c65d83a740a161370492ed51139ab42260d93de85ab/bitarray-3.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:86f043ed3de669eaa1ace14bc0c3b2c1b21f53728e48f00699117f94c8c0e02b", size = 145176 }, + { url = "https://files.pythonhosted.org/packages/26/02/a3946dcf6176363df87f2e0a7e4f001ac25e54fef0218ae5660d763e1ded/bitarray-3.5.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9bf8439bc80fd7a075c180a27d7b951e2ee133be6c1c102443de6e0237390284", size = 144406 }, + { url = "https://files.pythonhosted.org/packages/ec/21/b14f7ca5726ab569c3c61f3661de7bea3719d12a2988c2c653ca0fa19f81/bitarray-3.5.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3ef64b8a8715b8d2393ab94b5e9c0463e43eba0dbe1e01dde93bbadb73c39cc3", size = 141298 }, + { url = "https://files.pythonhosted.org/packages/58/98/79eced6eddfc71508b021d20d385413938347d5ff805eddb15ae4c4f47e7/bitarray-3.5.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eca3558001e7228ffd917a4db91992e0955d02c6ae878e9788460ee7499f1665", size = 320951 }, + { url = "https://files.pythonhosted.org/packages/a2/07/4b696f7c2b01706ced6d1f06f8c68b12cb1822b78e8dc85909397b3d8a2e/bitarray-3.5.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8a1cd11d51721bc2402d8bd5b0d17b855cecb63366f711cf016546da84bb4548", size = 336462 }, + { url = "https://files.pythonhosted.org/packages/79/7d/7898927a7f4eaa9408c65dbecd4dab6e5d9273e86b02cf74f86113f97363/bitarray-3.5.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3a23cc993ff727e7e29058ca8c663613ffc3725e02e0eba6c0650bb538a1b2e7", size = 330024 }, + { url = "https://files.pythonhosted.org/packages/f0/db/b49e7aa669bfcb176099b296b4dafdc7783633e482fed4b6dc0f4f19a1aa/bitarray-3.5.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f47e2f676a3cdc8c21771d3a7e736af5c8385122a8de4f0c11a965ac0905888", size = 322969 }, + { url = "https://files.pythonhosted.org/packages/25/b8/5c1110467b142809c80db5eb9234c1592ae24a0feb4ed47e8590da841866/bitarray-3.5.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:287ee0717df4d70a7260495006c7dfbbeb42ba9e067224359f923e2ce76f1f0e", size = 310293 }, + { url = "https://files.pythonhosted.org/packages/8c/74/9750132b2ca7d6d3a213ce144281f1bee7b1bfba2e8693f1b219d6c0ca16/bitarray-3.5.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7f45aee9532f0f4f3b0dc7058eb1730661c0a83d29a2cc00c6fbb80c3080e85b", size = 315140 }, + { url = "https://files.pythonhosted.org/packages/84/05/69616222f43b11f4ae4d88408f30b551de96c6d4d4fe98b70774004aee2c/bitarray-3.5.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:a4a8dc574ad6928dc327bffbd32d3bab8cdc21e90b9c773a7e40f8c2627f572e", size = 306878 }, + { url = "https://files.pythonhosted.org/packages/b2/6e/39bf609510ae3b30ba42f3f26b906da155ed4abda4d48bf9418e0791df8b/bitarray-3.5.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:02243c8db4a43c2f1ac66396073ac68d1e41863eef248a1f5fdf859c0b3edc33", size = 331380 }, + { url = "https://files.pythonhosted.org/packages/5f/b6/f636072cc9d63598088a2869dd2011078b89b4c4293865c4482cb075b93d/bitarray-3.5.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:8fdd640b86637d9f20e620cce4efad36e1b147feb978153222195a0703a5c013", size = 334983 }, + { url = "https://files.pythonhosted.org/packages/6a/c1/a800f2d0149ca944fb18827c328803bbc9ed8bb84a80489d2ca7dbe0062a/bitarray-3.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:57312af2775edf4842f2561e996341394bc9650330bd352f38b6144238f95ec3", size = 315012 }, + { url = "https://files.pythonhosted.org/packages/38/9a/814c9ea5b3c9ab300a06f19a7e3f1a74424f1d46659d56b7409e989adb1a/bitarray-3.5.0-cp313-cp313-win32.whl", hash = "sha256:190eb9ce58f6ca01ba959800019d1a9ba30b2996409df938667c718a73d5c794", size = 138380 }, + { url = "https://files.pythonhosted.org/packages/3e/92/53e801988a392f4c1bf6c0dcb6d6ccb247b7988e195e30566e775fe4d44a/bitarray-3.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:15cd52d6dfe4374e31fca6b2d0b50f7ed6a98c316c2befdd2f8be80bb703061c", size = 145240 }, +] + [[package]] name = "blake3" version = "1.0.4" @@ -174,6 +300,27 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/83/86/f27f21a589e7d45a578ec8c64dd6e7a0c6bc5472f72c8897b41166952438/blake3-1.0.4-cp313-cp313t-win_amd64.whl", hash = "sha256:95b2223177be6e269ab5f39bf1f2c186dc4852d546f15500bb7dcc114cf681f0", size = 211884 }, ] +[[package]] +name = "bokeh" +version = "3.7.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "contourpy" }, + { name = "jinja2" }, + { name = "narwhals" }, + { name = "numpy" }, + { name = "packaging" }, + { name = "pandas" }, + { name = "pillow" }, + { name = "pyyaml" }, + { name = "tornado", marker = "sys_platform != 'emscripten'" }, + { name = "xyzservices" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/75/18/12d0d6024177ad18ba65deffc363046d0cbafe116f8b964a9efa85d2800f/bokeh-3.7.3.tar.gz", hash = "sha256:70a89a9f797b103d5ee6ad15fb7944adda115cf0da996ed0b75cfba61cb12f2b", size = 6366610 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/91/48/08b2382e739236aa3360b7976360ba3e0c043b6234e25951c18c1eb6fa06/bokeh-3.7.3-py3-none-any.whl", hash = "sha256:b0e79dd737f088865212e4fdcb0f3b95d087f0f088bf8ca186a300ab1641e2c7", size = 7031447 }, +] + [[package]] name = "botocore" version = "1.37.1" @@ -188,6 +335,71 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/3d/20/352b2bf99f93ba18986615841786cbd0d38f7856bd49d4e154a540f04afe/botocore-1.37.1-py3-none-any.whl", hash = "sha256:c1db1bfc5d8c6b3b6d1ca6794f605294b4264e82a7e727b88e0fef9c2b9fbb9c", size = 13359164 }, ] +[[package]] +name = "bottleneck" +version = "1.5.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/80/82/dd20e69b97b9072ed2d26cc95c0a573461986bf62f7fde7ac59143490918/bottleneck-1.5.0.tar.gz", hash = "sha256:c860242cf20e69d5aab2ec3c5d6c8c2a15f19e4b25b28b8fca2c2a12cefae9d8", size = 104177 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8e/64/127e174cec548ab98bc0fa868b4f5d3ae5276e25c856d31d235d83d885a8/bottleneck-1.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:dbb0f0d38feda63050aa253cf9435e81a0ecfac954b0df84896636be9eabd9b6", size = 99640 }, + { url = "https://files.pythonhosted.org/packages/59/89/6e0b6463a36fd4771a9227d22ea904f892b80d95154399dd3e89fb6001f8/bottleneck-1.5.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:613165ce39bf6bd80f5307da0f05842ba534b213a89526f1eba82ea0099592fc", size = 358009 }, + { url = "https://files.pythonhosted.org/packages/f7/d6/7d1795a4a9e6383d3710a94c44010c7f2a8ba58cb5f2d9e2834a1c179afe/bottleneck-1.5.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f218e4dae6511180dcc4f06d8300e0c81e7f3df382091f464c5a919d289fab8e", size = 362875 }, + { url = "https://files.pythonhosted.org/packages/2b/1b/bab35ef291b9379a97e2fb986ce75f32eda38a47fc4954177b43590ee85e/bottleneck-1.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3886799cceb271eb67d057f6ecb13fb4582bda17a3b13b4fa0334638c59637c6", size = 361194 }, + { url = "https://files.pythonhosted.org/packages/d5/f3/a416fed726b81d2093578bc2112077f011c9f57b31e7ff3a1a9b00cce3d3/bottleneck-1.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dc8d553d4bf033d3e025cd32d4c034d2daf10709e31ced3909811d1c843e451c", size = 373253 }, + { url = "https://files.pythonhosted.org/packages/0a/40/c372f9e59b3ce340d170fbdc24c12df3d2b3c22c4809b149b7129044180b/bottleneck-1.5.0-cp312-cp312-win32.whl", hash = "sha256:0dca825048a3076f34c4a35409e3277b31ceeb3cbb117bbe2a13ff5c214bcabc", size = 107915 }, + { url = "https://files.pythonhosted.org/packages/28/5a/57571a3cd4e356bbd636bb2225fbe916f29adc2235ba3dc77cd4085c91c8/bottleneck-1.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:f26005740e6ef6013eba8a48241606a963e862a601671eab064b7835cd12ef3d", size = 112148 }, + { url = "https://files.pythonhosted.org/packages/e9/8c/1e1fb2d30a906bc009f3fe070fa188a9593d73e5aa5e815e55edbb96eeec/bottleneck-1.5.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:97285cfedf3545d9a010b2db2123f9750bf920081e29364cc465052973bd0b5a", size = 99653 }, + { url = "https://files.pythonhosted.org/packages/56/8c/876c41988a9806020dc9b150f7ad5764a3d14857c11b637285eb7431c25f/bottleneck-1.5.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1043d95674566063f638582cc8700c24c4427f532f86b9e7cfc9f9ec84abc1ff", size = 357965 }, + { url = "https://files.pythonhosted.org/packages/07/ed/2d807648157a3e94405dc7759e434cbb10784003cd8fa34d992dee02e519/bottleneck-1.5.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:abc6a24a41f55765215005cec97dd69f41ac747ed0f4d446caa508531957eeda", size = 362845 }, + { url = "https://files.pythonhosted.org/packages/f9/7a/ef9c3d9578c4a627d71d107f5e6f7074b98047536edd8adf0e17be5a0c5b/bottleneck-1.5.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7967e0189defe9f49025bd6469ff0fe22af5463926af55c7ba1e4592051d8ef8", size = 361425 }, + { url = "https://files.pythonhosted.org/packages/88/e0/131f6a6adf8b2e2521e3880a29c5ec0e8ebf57b9e1f37fc2337df5c041b8/bottleneck-1.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:27e38e829497ca0a5eebdb79d3293aaa424f3c31c13806e5c607fd414536b7c3", size = 373411 }, + { url = "https://files.pythonhosted.org/packages/13/e4/4c24ec1347c4fa492f757b126be877201ae90bddbcdc9da17cce09de0d55/bottleneck-1.5.0-cp313-cp313-win32.whl", hash = "sha256:1214a2bf3b36c66e3898aab821ad8366a3062db6f83a8f083e2f799d202e86ea", size = 107925 }, + { url = "https://files.pythonhosted.org/packages/8b/44/5e4a73bbe79e16ddbbbdfa1bc6af49b03cbb07aec5e323a14fa3acfc58bd/bottleneck-1.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:436a402f0d60a9d6541d7adb0929501225a151ad03b96b756e0b607db6a106f1", size = 112158 }, + { url = "https://files.pythonhosted.org/packages/25/b2/f3ceb3c177dc9bddfb5e71e0e5949af13c5bfee283740ec81b429a4ce19a/bottleneck-1.5.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:fc0c0b661005b059fcb09988f8b5e2cd5e9c702e1bed24819ed38f85145140b5", size = 100852 }, + { url = "https://files.pythonhosted.org/packages/3e/e7/4f5e212b482529847877558ae770be8eedd985b07d3eb0209dcbdd082595/bottleneck-1.5.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:48c2657102f3288e178cc341f000475a32f49a3cd8b7067e091d5446fa899383", size = 372068 }, + { url = "https://files.pythonhosted.org/packages/a8/fa/7760ecdb2b37b7097e28a2028a1d5cad6ca0b7eae3806ed795c3a12fd87d/bottleneck-1.5.0-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5c4c94cfcba46adfe71894c63c4b186c847965e73727dbaf5fd9ade41ef38e6e", size = 376524 }, + { url = "https://files.pythonhosted.org/packages/24/38/ee90a6e39ed04a8256ecd36baaf1abcea72787214e822675bfdd9b9e896e/bottleneck-1.5.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:3f3e308416886e29441a0b71bce8f3eb4c7a4943be541fd918244aaf25534d36", size = 375452 }, + { url = "https://files.pythonhosted.org/packages/3f/96/82837f96be9abd2651565b9becc2f393eefe6d5a515606d662c6962df4b0/bottleneck-1.5.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:bda7c475d4a7e271dbd0b1d4bbce29065edc8891361857105b7212fe383c9a36", size = 386361 }, + { url = "https://files.pythonhosted.org/packages/73/40/ddf00a9b0065d037792d6d2095a4998f15787f2c60fbdcf638767a75c37f/bottleneck-1.5.0-cp313-cp313t-win32.whl", hash = "sha256:a107ed8b5f998918c24a1e476dbd2dfc3514ab0082df7132c460b01e6ffd8cf4", size = 109355 }, + { url = "https://files.pythonhosted.org/packages/33/56/c05fd1459f2b65941fcaf2697b81fe7d3428855c8e66ab1951eed04a13e2/bottleneck-1.5.0-cp313-cp313t-win_amd64.whl", hash = "sha256:816c910c5d1fb53adb32581c52a513b206f503ae253ace70cb32d1fe4e45af1d", size = 113739 }, +] + +[[package]] +name = "cached-property" +version = "2.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/76/4b/3d870836119dbe9a5e3c9a61af8cc1a8b69d75aea564572e385882d5aefb/cached_property-2.0.1.tar.gz", hash = "sha256:484d617105e3ee0e4f1f58725e72a8ef9e93deee462222dbd51cd91230897641", size = 10574 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/11/0e/7d8225aab3bc1a0f5811f8e1b557aa034ac04bdf641925b30d3caf586b28/cached_property-2.0.1-py3-none-any.whl", hash = "sha256:f617d70ab1100b7bcf6e42228f9ddcb78c676ffa167278d9f730d1c2fba69ccb", size = 7428 }, +] + +[[package]] +name = "cartopy" +version = "0.24.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "matplotlib" }, + { name = "numpy" }, + { name = "packaging" }, + { name = "pyproj" }, + { name = "pyshp" }, + { name = "shapely" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e0/75/94aff4fef338887641aa780d13795609861e6e9f9593bd66d4917ab7954b/cartopy-0.24.1.tar.gz", hash = "sha256:01c910d5634c69a7efdec46e0a17d473d2328767f001d4dc0b5c4b48e585c8bd", size = 10741277 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6e/76/774a4f808c6a4fc19b87c2cc38dd8731d413aad606689451c017ff93ad12/Cartopy-0.24.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a984e33977daed8f760c09c331c8368a6af060db1190af89d74a027c272e39c3", size = 10983939 }, + { url = "https://files.pythonhosted.org/packages/2f/48/8517d5d1cc56ce5c4abda1de6454593474a23412115a543f7981aa7e4377/Cartopy-0.24.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:71d8a6d061d0764aba3baf357a68f3d73796a8a46d34b8c9fb241171b273c69e", size = 10972374 }, + { url = "https://files.pythonhosted.org/packages/c8/84/cb1577d5ac2f0deb002001c6e25b291735151c8c3033c97f212dc482ef72/Cartopy-0.24.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2f354a1d902a8d6ee33b099acc86ac2e1af528bbc0ea718b834111c97e604981", size = 11715215 }, + { url = "https://files.pythonhosted.org/packages/11/95/40c7abae8789aae22ad2a5da3974d3270dc3526b46cee253f680f72ee6cc/Cartopy-0.24.1-cp312-cp312-win_amd64.whl", hash = "sha256:b1bb2d02b31884ee1d4f14e5b436bbf95745eac39c6fc0d6c67c83bb907b55b3", size = 10959875 }, + { url = "https://files.pythonhosted.org/packages/e6/e8/38e00eb35743f22d4ee9bcb131ab273fb47ec39cc03ce5144686a3142756/Cartopy-0.24.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d279968b845f72e3423e454b2b0b985fb2389e6ccd18fb73324abeca4e43f516", size = 10982533 }, + { url = "https://files.pythonhosted.org/packages/66/60/cc852a0835a053db18085dec1d1dd6f9cedc764d1524bfe30fd8be5ee3a7/Cartopy-0.24.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f0963b80a048252815c56fbd21bc4e5d163618a9eaa36c8898ce2c60b6c03979", size = 10971183 }, + { url = "https://files.pythonhosted.org/packages/a3/5b/476c8f3a277d7c78e5a5318bd32f234b994bfdc5d7731ae84218f2fa8a8f/Cartopy-0.24.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dfdde0a6e0e56c5fc46f4e7d332237eb31bbd9908417f0f190fda5d322754184", size = 11709060 }, + { url = "https://files.pythonhosted.org/packages/93/31/50bf07ba820c5aa5d4e674d72cdb5da90bbd012ba1b9c6c95c3f96afe233/Cartopy-0.24.1-cp313-cp313-win_amd64.whl", hash = "sha256:b17cf23dd74d0a922c2a5682dacef3c0bf89fa8c0bd0eae96b87fb684f966b15", size = 10959830 }, +] + [[package]] name = "certifi" version = "2025.1.31" @@ -206,6 +418,29 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c5/55/51844dd50c4fc7a33b653bfaba4c2456f06955289ca770a5dbd5fd267374/cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9", size = 7249 }, ] +[[package]] +name = "cftime" +version = "1.6.4.post1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ab/c8/1155d1d58003105307c7e5985f422ae5bcb2ca0cbc553cc828f3c5a934a7/cftime-1.6.4.post1.tar.gz", hash = "sha256:50ac76cc9f10ab7bd46e44a71c51a6927051b499b4407df4f29ab13d741b942f", size = 54631 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/50/81/0bb28d54088a61592f61a11e7fcabcea6d261c47af79e18d0f9cbcd940ae/cftime-1.6.4.post1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a590f73506f4704ba5e154ef55bfbaed5e1b4ac170f3caeb8c58e4f2c619ee4e", size = 226615 }, + { url = "https://files.pythonhosted.org/packages/f3/1e/38dbbf8a828dfb5e0e6e5c912818b77aacf2e7bcb97b262ac6126beeb29f/cftime-1.6.4.post1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:933cb10e1af4e362e77f513e3eb92b34a688729ddbf938bbdfa5ac20a7f44ba0", size = 209193 }, + { url = "https://files.pythonhosted.org/packages/9b/60/0db884c76311ecaaf31f628aa9358beae5fcb0fbbdc2eb0b790a93aa258f/cftime-1.6.4.post1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf17a1b36f62e9e73c4c9363dd811e1bbf1170f5ac26d343fb26012ccf482908", size = 1320215 }, + { url = "https://files.pythonhosted.org/packages/8d/7d/2d5fc7af06da4f3bdea59a204f741bf7a30bc5019355991b2f083e557e4e/cftime-1.6.4.post1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8e18021f421aa26527bad8688c1acf0c85fa72730beb6efce969c316743294f2", size = 1367426 }, + { url = "https://files.pythonhosted.org/packages/5d/ab/e8b26d05323fc5629356c82a7f64026248f121ea1361b49df441bbc8f2d7/cftime-1.6.4.post1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5835b9d622f9304d1c23a35603a0f068739f428d902860f25e6e7e5a1b7cd8ea", size = 1385593 }, + { url = "https://files.pythonhosted.org/packages/af/7b/ca72a075a3f660315b031d62d39a3e9cfef71f7929da2621d5120077a75f/cftime-1.6.4.post1-cp312-cp312-win_amd64.whl", hash = "sha256:7f50bf0d1b664924aaee636eb2933746b942417d1f8b82ab6c1f6e8ba0da6885", size = 178918 }, + { url = "https://files.pythonhosted.org/packages/da/d8/81f086dbdc6f5a4e0bb068263471f1d12861b72562fe8c18df38268e4e29/cftime-1.6.4.post1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5c89766ebf088c097832ea618c24ed5075331f0b7bf8e9c2d4144aefbf2f1850", size = 223418 }, + { url = "https://files.pythonhosted.org/packages/4a/cc/60a825d92a4023655e330470758280a31e7b82665ef77d0e2a0fe71ea958/cftime-1.6.4.post1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7f27113f7ccd1ca32881fdcb9a4bec806a5f54ae621fc1c374f1171f3ed98ef2", size = 207395 }, + { url = "https://files.pythonhosted.org/packages/ca/90/f5b26949899decce262fc76a1e64915b92050473114e0160cd6f7297f854/cftime-1.6.4.post1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da367b23eea7cf4df071c88e014a1600d6c5bbf22e3393a4af409903fa397e28", size = 1318113 }, + { url = "https://files.pythonhosted.org/packages/c3/f8/6f13d37abb7ade46e65a08acc31af776a96dde0eb569e05d4c4b01422ba6/cftime-1.6.4.post1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6579c5c83cdf09d73aa94c7bc34925edd93c5f2c7dd28e074f568f7e376271a0", size = 1366034 }, + { url = "https://files.pythonhosted.org/packages/fa/08/335cb17f3b708f9a24f96ca4abb00889c7aa20b0ae273313e7c11faf1f97/cftime-1.6.4.post1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6b731c7133d17b479ca0c3c46a7a04f96197f0a4d753f4c2284c3ff0447279b4", size = 1390156 }, + { url = "https://files.pythonhosted.org/packages/f3/2d/980323fb5ec1ef369604b61ba259a41d0336cc1a85b639ed7bd210bd1290/cftime-1.6.4.post1-cp313-cp313-win_amd64.whl", hash = "sha256:d2a8c223faea7f1248ab469cc0d7795dd46f2a423789038f439fee7190bae259", size = 178496 }, +] + [[package]] name = "charset-normalizer" version = "3.4.1" @@ -241,6 +476,23 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/0e/f6/65ecc6878a89bb1c23a086ea335ad4bf21a588990c3f535a227b9eea9108/charset_normalizer-3.4.1-py3-none-any.whl", hash = "sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85", size = 49767 }, ] +[[package]] +name = "ckzg" +version = "1.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/13/bf/ddd817e8b455b577b206fbfee951df1f4964826e9d4f2fc3148550d592c4/ckzg-1.0.2.tar.gz", hash = "sha256:4295acc380f8d42ebea4a4a0a68c424a322bb335a33bad05c72ead8cbb28d118", size = 840347 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8e/06/959cfafae47190d4f1930d8993653538c3de7bb1a3a32e917aa47ac9c8f0/ckzg-1.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:e3cb2f8c767aee57e88944f90848e8689ce43993b9ff21589cfb97a562208fe7", size = 100499 }, + { url = "https://files.pythonhosted.org/packages/a6/27/f9b73f240bc2c4a7995a43f9b7850cd8e6931f396206f7e38d6df3e8d8d7/ckzg-1.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5b29889f5bc5db530f766871c0ff4133e7270ecf63aaa3ca756d3b2731980802", size = 85778 }, + { url = "https://files.pythonhosted.org/packages/c6/79/cf1bb8d02703222b1177596a9de3e25c829db5a852a5824b5ea898396ed4/ckzg-1.0.2-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bfcc70fb76b3d36125d646110d5001f2aa89c1c09ff5537a4550cdb7951f44d4", size = 146327 }, + { url = "https://files.pythonhosted.org/packages/ff/eb/a43b49ac53c581f7b8be88596c98db558c3059f8c8bcc339f4dac560dac2/ckzg-1.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1ca8a256cdd56d06bc5ef24caac64845240dbabca402c5a1966d519b2514b4ec", size = 132109 }, + { url = "https://files.pythonhosted.org/packages/d5/6f/7051894626806a98c1c9d9608fa1ffaafc811f460e2490bcd90cc60b07a3/ckzg-1.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6ea91b0236384f93ad1df01d530672f09e254bd8c3cf097ebf486aebb97f6c8c", size = 140481 }, + { url = "https://files.pythonhosted.org/packages/19/ec/bcf995869a47ef2ca645d16b2bf0052af4581079d7c622d30ba721d088c7/ckzg-1.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:65311e72780105f239d1d66512629a9f468b7c9f2609b8567fc68963ac638ef9", size = 139204 }, + { url = "https://files.pythonhosted.org/packages/61/2a/0a86fb062a6415b5ad73665051f3e5c891fe4250edd17037c9837b999c79/ckzg-1.0.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:0d7600ce7a73ac41d348712d0c1fe5e4cb6caa329377064cfa3a6fd8fbffb410", size = 149095 }, + { url = "https://files.pythonhosted.org/packages/ef/88/bcb1f42b6fb6f3392025d12c434acdee7b1667b4b455851aaef72bb99dce/ckzg-1.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:19893ee7bd7da8688382cb134cb9ee7bce5c38e3a9386e3ed99bb010487d2d17", size = 144915 }, + { url = "https://files.pythonhosted.org/packages/61/44/0a53aec8ba1a8c0987b73f64262c4e500fbed97c2533630c77b63d32afb3/ckzg-1.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:c3e1a9a72695e777497e95bb2213316a1138f82d1bb5d67b9c029a522d24908e", size = 69856 }, +] + [[package]] name = "click" version = "8.1.8" @@ -277,6 +529,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/73/86/43fa9f15c5b9fb6e82620428827cd3c284aa933431405d1bcf5231ae3d3e/cligj-0.7.2-py3-none-any.whl", hash = "sha256:c1ca117dbce1fe20a5809dc96f01e1c2840f6dcc939b3ddbb1111bf330ba82df", size = 7069 }, ] +[[package]] +name = "cloudpickle" +version = "3.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/52/39/069100b84d7418bc358d81669d5748efb14b9cceacd2f9c75f550424132f/cloudpickle-3.1.1.tar.gz", hash = "sha256:b216fa8ae4019d5482a8ac3c95d8f6346115d8835911fd4aefd1a445e4242c64", size = 22113 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/e8/64c37fadfc2816a7701fa8a6ed8d87327c7d54eacfbfb6edab14a2f2be75/cloudpickle-3.1.1-py3-none-any.whl", hash = "sha256:c8c5a44295039331ee9dad40ba100a9c7297b6f988e50e87ccdf3765a668350e", size = 20992 }, +] + [[package]] name = "colorama" version = "0.4.6" @@ -286,6 +547,47 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, ] +[[package]] +name = "contourpy" +version = "1.3.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/54/eb9bfc647b19f2009dd5c7f5ec51c4e6ca831725f1aea7a993034f483147/contourpy-1.3.2.tar.gz", hash = "sha256:b6945942715a034c671b7fc54f9588126b0b8bf23db2696e3ca8328f3ff0ab54", size = 13466130 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/34/f7/44785876384eff370c251d58fd65f6ad7f39adce4a093c934d4a67a7c6b6/contourpy-1.3.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4caf2bcd2969402bf77edc4cb6034c7dd7c0803213b3523f111eb7460a51b8d2", size = 271580 }, + { url = "https://files.pythonhosted.org/packages/93/3b/0004767622a9826ea3d95f0e9d98cd8729015768075d61f9fea8eeca42a8/contourpy-1.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:82199cb78276249796419fe36b7386bd8d2cc3f28b3bc19fe2454fe2e26c4c15", size = 255530 }, + { url = "https://files.pythonhosted.org/packages/e7/bb/7bd49e1f4fa805772d9fd130e0d375554ebc771ed7172f48dfcd4ca61549/contourpy-1.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:106fab697af11456fcba3e352ad50effe493a90f893fca6c2ca5c033820cea92", size = 307688 }, + { url = "https://files.pythonhosted.org/packages/fc/97/e1d5dbbfa170725ef78357a9a0edc996b09ae4af170927ba8ce977e60a5f/contourpy-1.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d14f12932a8d620e307f715857107b1d1845cc44fdb5da2bc8e850f5ceba9f87", size = 347331 }, + { url = "https://files.pythonhosted.org/packages/6f/66/e69e6e904f5ecf6901be3dd16e7e54d41b6ec6ae3405a535286d4418ffb4/contourpy-1.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:532fd26e715560721bb0d5fc7610fce279b3699b018600ab999d1be895b09415", size = 318963 }, + { url = "https://files.pythonhosted.org/packages/a8/32/b8a1c8965e4f72482ff2d1ac2cd670ce0b542f203c8e1d34e7c3e6925da7/contourpy-1.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f26b383144cf2d2c29f01a1e8170f50dacf0eac02d64139dcd709a8ac4eb3cfe", size = 323681 }, + { url = "https://files.pythonhosted.org/packages/30/c6/12a7e6811d08757c7162a541ca4c5c6a34c0f4e98ef2b338791093518e40/contourpy-1.3.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c49f73e61f1f774650a55d221803b101d966ca0c5a2d6d5e4320ec3997489441", size = 1308674 }, + { url = "https://files.pythonhosted.org/packages/2a/8a/bebe5a3f68b484d3a2b8ffaf84704b3e343ef1addea528132ef148e22b3b/contourpy-1.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3d80b2c0300583228ac98d0a927a1ba6a2ba6b8a742463c564f1d419ee5b211e", size = 1380480 }, + { url = "https://files.pythonhosted.org/packages/34/db/fcd325f19b5978fb509a7d55e06d99f5f856294c1991097534360b307cf1/contourpy-1.3.2-cp312-cp312-win32.whl", hash = "sha256:90df94c89a91b7362e1142cbee7568f86514412ab8a2c0d0fca72d7e91b62912", size = 178489 }, + { url = "https://files.pythonhosted.org/packages/01/c8/fadd0b92ffa7b5eb5949bf340a63a4a496a6930a6c37a7ba0f12acb076d6/contourpy-1.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:8c942a01d9163e2e5cfb05cb66110121b8d07ad438a17f9e766317bcb62abf73", size = 223042 }, + { url = "https://files.pythonhosted.org/packages/2e/61/5673f7e364b31e4e7ef6f61a4b5121c5f170f941895912f773d95270f3a2/contourpy-1.3.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:de39db2604ae755316cb5967728f4bea92685884b1e767b7c24e983ef5f771cb", size = 271630 }, + { url = "https://files.pythonhosted.org/packages/ff/66/a40badddd1223822c95798c55292844b7e871e50f6bfd9f158cb25e0bd39/contourpy-1.3.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3f9e896f447c5c8618f1edb2bafa9a4030f22a575ec418ad70611450720b5b08", size = 255670 }, + { url = "https://files.pythonhosted.org/packages/1e/c7/cf9fdee8200805c9bc3b148f49cb9482a4e3ea2719e772602a425c9b09f8/contourpy-1.3.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71e2bd4a1c4188f5c2b8d274da78faab884b59df20df63c34f74aa1813c4427c", size = 306694 }, + { url = "https://files.pythonhosted.org/packages/dd/e7/ccb9bec80e1ba121efbffad7f38021021cda5be87532ec16fd96533bb2e0/contourpy-1.3.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de425af81b6cea33101ae95ece1f696af39446db9682a0b56daaa48cfc29f38f", size = 345986 }, + { url = "https://files.pythonhosted.org/packages/dc/49/ca13bb2da90391fa4219fdb23b078d6065ada886658ac7818e5441448b78/contourpy-1.3.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:977e98a0e0480d3fe292246417239d2d45435904afd6d7332d8455981c408b85", size = 318060 }, + { url = "https://files.pythonhosted.org/packages/c8/65/5245ce8c548a8422236c13ffcdcdada6a2a812c361e9e0c70548bb40b661/contourpy-1.3.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:434f0adf84911c924519d2b08fc10491dd282b20bdd3fa8f60fd816ea0b48841", size = 322747 }, + { url = "https://files.pythonhosted.org/packages/72/30/669b8eb48e0a01c660ead3752a25b44fdb2e5ebc13a55782f639170772f9/contourpy-1.3.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c66c4906cdbc50e9cba65978823e6e00b45682eb09adbb78c9775b74eb222422", size = 1308895 }, + { url = "https://files.pythonhosted.org/packages/05/5a/b569f4250decee6e8d54498be7bdf29021a4c256e77fe8138c8319ef8eb3/contourpy-1.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8b7fc0cd78ba2f4695fd0a6ad81a19e7e3ab825c31b577f384aa9d7817dc3bef", size = 1379098 }, + { url = "https://files.pythonhosted.org/packages/19/ba/b227c3886d120e60e41b28740ac3617b2f2b971b9f601c835661194579f1/contourpy-1.3.2-cp313-cp313-win32.whl", hash = "sha256:15ce6ab60957ca74cff444fe66d9045c1fd3e92c8936894ebd1f3eef2fff075f", size = 178535 }, + { url = "https://files.pythonhosted.org/packages/12/6e/2fed56cd47ca739b43e892707ae9a13790a486a3173be063681ca67d2262/contourpy-1.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:e1578f7eafce927b168752ed7e22646dad6cd9bca673c60bff55889fa236ebf9", size = 223096 }, + { url = "https://files.pythonhosted.org/packages/54/4c/e76fe2a03014a7c767d79ea35c86a747e9325537a8b7627e0e5b3ba266b4/contourpy-1.3.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0475b1f6604896bc7c53bb070e355e9321e1bc0d381735421a2d2068ec56531f", size = 285090 }, + { url = "https://files.pythonhosted.org/packages/7b/e2/5aba47debd55d668e00baf9651b721e7733975dc9fc27264a62b0dd26eb8/contourpy-1.3.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:c85bb486e9be652314bb5b9e2e3b0d1b2e643d5eec4992c0fbe8ac71775da739", size = 268643 }, + { url = "https://files.pythonhosted.org/packages/a1/37/cd45f1f051fe6230f751cc5cdd2728bb3a203f5619510ef11e732109593c/contourpy-1.3.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:745b57db7758f3ffc05a10254edd3182a2a83402a89c00957a8e8a22f5582823", size = 310443 }, + { url = "https://files.pythonhosted.org/packages/8b/a2/36ea6140c306c9ff6dd38e3bcec80b3b018474ef4d17eb68ceecd26675f4/contourpy-1.3.2-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:970e9173dbd7eba9b4e01aab19215a48ee5dd3f43cef736eebde064a171f89a5", size = 349865 }, + { url = "https://files.pythonhosted.org/packages/95/b7/2fc76bc539693180488f7b6cc518da7acbbb9e3b931fd9280504128bf956/contourpy-1.3.2-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c6c4639a9c22230276b7bffb6a850dfc8258a2521305e1faefe804d006b2e532", size = 321162 }, + { url = "https://files.pythonhosted.org/packages/f4/10/76d4f778458b0aa83f96e59d65ece72a060bacb20cfbee46cf6cd5ceba41/contourpy-1.3.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc829960f34ba36aad4302e78eabf3ef16a3a100863f0d4eeddf30e8a485a03b", size = 327355 }, + { url = "https://files.pythonhosted.org/packages/43/a3/10cf483ea683f9f8ab096c24bad3cce20e0d1dd9a4baa0e2093c1c962d9d/contourpy-1.3.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:d32530b534e986374fc19eaa77fcb87e8a99e5431499949b828312bdcd20ac52", size = 1307935 }, + { url = "https://files.pythonhosted.org/packages/78/73/69dd9a024444489e22d86108e7b913f3528f56cfc312b5c5727a44188471/contourpy-1.3.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:e298e7e70cf4eb179cc1077be1c725b5fd131ebc81181bf0c03525c8abc297fd", size = 1372168 }, + { url = "https://files.pythonhosted.org/packages/0f/1b/96d586ccf1b1a9d2004dd519b25fbf104a11589abfd05484ff12199cca21/contourpy-1.3.2-cp313-cp313t-win32.whl", hash = "sha256:d0e589ae0d55204991450bb5c23f571c64fe43adaa53f93fc902a84c96f52fe1", size = 189550 }, + { url = "https://files.pythonhosted.org/packages/b0/e6/6000d0094e8a5e32ad62591c8609e269febb6e4db83a1c75ff8868b42731/contourpy-1.3.2-cp313-cp313t-win_amd64.whl", hash = "sha256:78e9253c3de756b3f6a5174d024c4835acd59eb3f8e2ca13e775dbffe1558f69", size = 238214 }, +] + [[package]] name = "coverage" version = "7.7.1" @@ -366,6 +668,54 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/19/c4/0b3eee04dac195f4730d102d7a9fbea894ae7a32ce075f84336df96a385d/crc32c-2.7.1-cp313-cp313t-win_amd64.whl", hash = "sha256:eee2a43b663feb6c79a6c1c6e5eae339c2b72cfac31ee54ec0209fa736cf7ee5", size = 39781 }, ] +[[package]] +name = "cycler" +version = "0.12.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a9/95/a3dbbb5028f35eafb79008e7522a75244477d2838f38cbb722248dabc2a8/cycler-0.12.1.tar.gz", hash = "sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c", size = 7615 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30", size = 8321 }, +] + +[[package]] +name = "cytoolz" +version = "1.0.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "toolz", marker = "implementation_name == 'cpython'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a7/f9/3243eed3a6545c2a33a21f74f655e3fcb5d2192613cd3db81a93369eb339/cytoolz-1.0.1.tar.gz", hash = "sha256:89cc3161b89e1bb3ed7636f74ed2e55984fd35516904fc878cae216e42b2c7d6", size = 626652 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d8/e8/218098344ed2cb5f8441fade9b2428e435e7073962374a9c71e59ac141a7/cytoolz-1.0.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fcb8f7d0d65db1269022e7e0428471edee8c937bc288ebdcb72f13eaa67c2fe4", size = 414121 }, + { url = "https://files.pythonhosted.org/packages/de/27/4d729a5653718109262b758fec1a959aa9facb74c15460d9074dc76d6635/cytoolz-1.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:207d4e4b445e087e65556196ff472ff134370d9a275d591724142e255f384662", size = 390904 }, + { url = "https://files.pythonhosted.org/packages/72/c0/cbabfa788bab9c6038953bf9478adaec06e88903a726946ea7c88092f5c4/cytoolz-1.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21cdf6bac6fd843f3b20280a66fd8df20dea4c58eb7214a2cd8957ec176f0bb3", size = 2090734 }, + { url = "https://files.pythonhosted.org/packages/c3/66/369262c60f9423c2da82a60864a259c852f1aa122aced4acd2c679af58c0/cytoolz-1.0.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4a55ec098036c0dea9f3bdc021f8acd9d105a945227d0811589f0573f21c9ce1", size = 2155933 }, + { url = "https://files.pythonhosted.org/packages/aa/4e/ee55186802f8d24b5fbf9a11405ccd1203b30eded07cc17750618219b94e/cytoolz-1.0.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a13ab79ff4ce202e03ab646a2134696988b554b6dc4b71451e948403db1331d8", size = 2171903 }, + { url = "https://files.pythonhosted.org/packages/a1/96/bd1a9f3396e9b7f618db8cd08d15630769ce3c8b7d0534f92cd639c977ae/cytoolz-1.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e2d944799026e1ff08a83241f1027a2d9276c41f7a74224cd98b7df6e03957d", size = 2125270 }, + { url = "https://files.pythonhosted.org/packages/28/48/2a3762873091c88a69e161111cfbc6c222ff145d57ff011a642b169f04f1/cytoolz-1.0.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88ba85834cd523b91fdf10325e1e6d71c798de36ea9bdc187ca7bd146420de6f", size = 1973967 }, + { url = "https://files.pythonhosted.org/packages/e4/50/500bd69774bdc49a4d78ec8779eb6ac7c1a9d706bfd91cf2a1dba604373a/cytoolz-1.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5a750b1af7e8bf6727f588940b690d69e25dc47cce5ce467925a76561317eaf7", size = 2021695 }, + { url = "https://files.pythonhosted.org/packages/e4/4e/ba5a0ce34869495eb50653de8d676847490cf13a2cac1760fc4d313e78de/cytoolz-1.0.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:44a71870f7eae31d263d08b87da7c2bf1176f78892ed8bdade2c2850478cb126", size = 2010177 }, + { url = "https://files.pythonhosted.org/packages/87/57/615c630b3089a13adb15351d958d227430cf624f03b1dd39eb52c34c1f59/cytoolz-1.0.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c8231b9abbd8e368e036f4cc2e16902c9482d4cf9e02a6147ed0e9a3cd4a9ab0", size = 2154321 }, + { url = "https://files.pythonhosted.org/packages/7f/0f/fe1aa2d931e3b35ecc05215bd75da945ea7346095b3b6f6027164e602d5a/cytoolz-1.0.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:aa87599ccc755de5a096a4d6c34984de6cd9dc928a0c5eaa7607457317aeaf9b", size = 2188374 }, + { url = "https://files.pythonhosted.org/packages/de/fa/fd363d97a641b6d0e2fd1d5c35b8fd41d9ccaeb4df56302f53bf23a58e3a/cytoolz-1.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:67cd16537df51baabde3baa770ab7b8d16839c4d21219d5b96ac59fb012ebd2d", size = 2077911 }, + { url = "https://files.pythonhosted.org/packages/d9/68/0a22946b98ae5201b54ccb4e651295285c0fb79406022b6ee8b2f791940c/cytoolz-1.0.1-cp312-cp312-win32.whl", hash = "sha256:fb988c333f05ee30ad4693fe4da55d95ec0bb05775d2b60191236493ea2e01f9", size = 321903 }, + { url = "https://files.pythonhosted.org/packages/62/1a/f3903197956055032f8cb297342e2dff07e50f83991aebfe5b4c4fcb55e4/cytoolz-1.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:8f89c48d8e5aec55ffd566a8ec858706d70ed0c6a50228eca30986bfa5b4da8b", size = 364490 }, + { url = "https://files.pythonhosted.org/packages/aa/2e/a9f069db0107749e9e72baf6c21abe3f006841a3bcfdc9b8420e22ef31eb/cytoolz-1.0.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:6944bb93b287032a4c5ca6879b69bcd07df46f3079cf8393958cf0b0454f50c0", size = 407365 }, + { url = "https://files.pythonhosted.org/packages/a9/9b/5e87dd0e31f54c778b4f9f34cc14c1162d3096c8d746b0f8be97d70dd73c/cytoolz-1.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e027260fd2fc5cb041277158ac294fc13dca640714527219f702fb459a59823a", size = 385233 }, + { url = "https://files.pythonhosted.org/packages/63/00/2fd32b16284cdb97cfe092822179bc0c3bcdd5e927dd39f986169a517642/cytoolz-1.0.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88662c0e07250d26f5af9bc95911e6137e124a5c1ec2ce4a5d74de96718ab242", size = 2062903 }, + { url = "https://files.pythonhosted.org/packages/85/39/b3cbb5a9847ba59584a263772ad4f8ca2dbfd2a0e11efd09211d1219804c/cytoolz-1.0.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:309dffa78b0961b4c0cf55674b828fbbc793cf2d816277a5c8293c0c16155296", size = 2139517 }, + { url = "https://files.pythonhosted.org/packages/ea/39/bfcab4a46d50c467e36fe704f19d8904efead417787806ee210327f68390/cytoolz-1.0.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:edb34246e6eb40343c5860fc51b24937698e4fa1ee415917a73ad772a9a1746b", size = 2154849 }, + { url = "https://files.pythonhosted.org/packages/fd/42/3bc6ee61b0aa47e1cb40819adc1a456d7efa809f0dea9faddacb43fdde8f/cytoolz-1.0.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0a54da7a8e4348a18d45d4d5bc84af6c716d7f131113a4f1cc45569d37edff1b", size = 2102302 }, + { url = "https://files.pythonhosted.org/packages/00/66/3f636c6ddea7b18026b90a8c238af472e423b86e427b11df02213689b012/cytoolz-1.0.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:241c679c3b1913c0f7259cf1d9639bed5084c86d0051641d537a0980548aa266", size = 1960872 }, + { url = "https://files.pythonhosted.org/packages/40/36/cb3b7cdd651007b69f9c48e9d104cec7cb8dc53afa1d6a720e5ad08022fa/cytoolz-1.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5bfc860251a8f280ac79696fc3343cfc3a7c30b94199e0240b6c9e5b6b01a2a5", size = 2014430 }, + { url = "https://files.pythonhosted.org/packages/88/3f/2e9bd2a16cfd269808922147551dcb2d8b68ba54a2c4deca2fa6a6cd0d5f/cytoolz-1.0.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:c8edd1547014050c1bdad3ff85d25c82bd1c2a3c96830c6181521eb78b9a42b3", size = 2003127 }, + { url = "https://files.pythonhosted.org/packages/c4/7d/08604ff940aa784df8343c387fdf2489b948b714a6afb587775ae94da912/cytoolz-1.0.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:b349bf6162e8de215403d7f35f8a9b4b1853dc2a48e6e1a609a5b1a16868b296", size = 2142369 }, + { url = "https://files.pythonhosted.org/packages/d2/c6/39919a0645bdbdf720e97cae107f959ea9d1267fbc3b0d94fc6e1d12ac8f/cytoolz-1.0.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:1b18b35256219b6c3dd0fa037741b85d0bea39c552eab0775816e85a52834140", size = 2180427 }, + { url = "https://files.pythonhosted.org/packages/d8/03/dbb9d47556ee54337e7e0ac209d17ceff2d2a197c34de08005abc7a7449b/cytoolz-1.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:738b2350f340ff8af883eb301054eb724997f795d20d90daec7911c389d61581", size = 2069785 }, + { url = "https://files.pythonhosted.org/packages/ea/f8/11bb7b8947002231faae3ec2342df5896afbc19eb783a332cce6d219ff79/cytoolz-1.0.1-cp313-cp313-win32.whl", hash = "sha256:9cbd9c103df54fcca42be55ef40e7baea624ac30ee0b8bf1149f21146d1078d9", size = 320685 }, + { url = "https://files.pythonhosted.org/packages/40/eb/dde173cf2357084ca9423950be1f2f11ab11d65d8bd30165bfb8fd4213e9/cytoolz-1.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:90e577e08d3a4308186d9e1ec06876d4756b1e8164b92971c69739ea17e15297", size = 362898 }, +] + [[package]] name = "dag-cbor" version = "0.3.3" @@ -380,24 +730,68 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/14/8e/3fa50f0e4d709610be14b8b8049f5bdd9dbeddf5147286a4c59ba55f8af0/dag_cbor-0.3.3-py3-none-any.whl", hash = "sha256:e0d1c45427ed1fa21bbd103163d4b250f4ce9066337d6ffb976da1fac3a67454", size = 25457 }, ] +[[package]] +name = "dask" +version = "2025.5.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "cloudpickle" }, + { name = "fsspec" }, + { name = "packaging" }, + { name = "partd" }, + { name = "pyyaml" }, + { name = "toolz" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3d/29/05feb8e2531c46d763547c66b7f5deb39b53d99b3be1b4ddddbd1cec6567/dask-2025.5.1.tar.gz", hash = "sha256:979d9536549de0e463f4cab8a8c66c3a2ef55791cd740d07d9bf58fab1d1076a", size = 10969324 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/38/30/53b0844a7a4c6b041b111b24ca15cc9b8661a86fe1f6aaeb2d0d7f0fb1f2/dask-2025.5.1-py3-none-any.whl", hash = "sha256:3b85fdaa5f6f989dde49da6008415b1ae996985ebdfb1e40de2c997d9010371d", size = 1474226 }, +] + +[package.optional-dependencies] +complete = [ + { name = "bokeh" }, + { name = "distributed" }, + { name = "jinja2" }, + { name = "lz4" }, + { name = "numpy" }, + { name = "pandas" }, + { name = "pyarrow" }, +] + +[[package]] +name = "dataclassy" +version = "0.11.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/87/71/849a7708be159ff815ba9f2a671202c7ed7951f6810bb86555e379882935/dataclassy-0.11.1.tar.gz", hash = "sha256:ad6622cb91e644d13f68768558983fbc22c90a8ff7e355638485d18b9baf1198", size = 28166 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ce/0c/0ef792a555612974aedcd164a93c11fbbb83fc8ed5bfefa9799ea09ddc6b/dataclassy-0.11.1-py3-none-any.whl", hash = "sha256:bcb030d3d700cf9b1597042bbc8375b92773e6f68f65675a7071862c0ddb87f5", size = 23942 }, +] + [[package]] name = "dclimate-zarr-client" version = "0.1.0" source = { editable = "." } dependencies = [ { name = "aiobotocore" }, + { name = "ape-base" }, + { name = "ape-solidity" }, + { name = "eth-account" }, + { name = "eth-ape" }, { name = "geopandas" }, { name = "numpy" }, { name = "pandas" }, { name = "py-hamt" }, { name = "pyarrow" }, { name = "pycryptodome" }, + { name = "pytest-asyncio" }, { name = "requests" }, { name = "rioxarray" }, { name = "s3fs" }, { name = "scipy" }, { name = "shapely" }, - { name = "xarray" }, + { name = "web3" }, + { name = "xarray", extra = ["complete"] }, { name = "zarr" }, ] @@ -415,14 +809,19 @@ testing = [ [package.metadata] requires-dist = [ { name = "aiobotocore" }, + { name = "ape-base", specifier = ">=0.8.1" }, + { name = "ape-solidity", specifier = ">=0.8.5" }, + { name = "eth-account", specifier = ">=0.11.3" }, + { name = "eth-ape", specifier = ">=0.8.4" }, { name = "geopandas" }, { name = "numpy", specifier = ">=2.1.3" }, { name = "pandas" }, { name = "pre-commit", marker = "extra == 'dev'", specifier = ">=4.1.0" }, - { name = "py-hamt", git = "https://github.com/dClimate/py-hamt" }, + { name = "py-hamt", git = "https://github.com/dClimate/py-hamt?rev=a60039b4e2305847c8b9241557a35f96038d7ca0" }, { name = "pyarrow" }, { name = "pycryptodome", specifier = ">=3.21.0" }, { name = "pytest", marker = "extra == 'testing'" }, + { name = "pytest-asyncio", specifier = ">=1.0.0" }, { name = "pytest-cov", marker = "extra == 'testing'" }, { name = "pytest-mock", marker = "extra == 'testing'" }, { name = "requests" }, @@ -431,11 +830,21 @@ requires-dist = [ { name = "s3fs" }, { name = "scipy" }, { name = "shapely" }, - { name = "xarray" }, - { name = "zarr" }, + { name = "web3", specifier = ">=6.20.1" }, + { name = "xarray", extras = ["complete"], specifier = ">=2025.3.0" }, + { name = "zarr", specifier = ">=3.0.8" }, ] provides-extras = ["testing", "dev"] +[[package]] +name = "decorator" +version = "5.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/fa/6d96a0978d19e17b68d634497769987b16c8f4cd0a7a05048bec693caa6b/decorator-5.2.1.tar.gz", hash = "sha256:65f266143752f734b0a7cc83c46f4618af75b8c5911b00ccb61d0ac9b6da0360", size = 56711 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4e/8c/f3147f5c4b73e7550fe5f9352eaa956ae838d5c51eb58e7a25b9f3e2643b/decorator-5.2.1-py3-none-any.whl", hash = "sha256:d316bb415a2d9e2d2b3abcc4084c6502fc09240e292cd76a76afc106a1c8e04a", size = 9190 }, +] + [[package]] name = "deprecated" version = "1.2.18" @@ -457,6 +866,32 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/91/a1/cf2472db20f7ce4a6be1253a81cfdf85ad9c7885ffbed7047fb72c24cf87/distlib-0.3.9-py2.py3-none-any.whl", hash = "sha256:47f8c22fd27c27e25a65601af709b38e4f0a45ea4fc2e710f65755fa8caaaf87", size = 468973 }, ] +[[package]] +name = "distributed" +version = "2025.5.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "cloudpickle" }, + { name = "dask" }, + { name = "jinja2" }, + { name = "locket" }, + { name = "msgpack" }, + { name = "packaging" }, + { name = "psutil" }, + { name = "pyyaml" }, + { name = "sortedcontainers" }, + { name = "tblib" }, + { name = "toolz" }, + { name = "tornado" }, + { name = "urllib3" }, + { name = "zict" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/29/ba/45950f405d023a520a4d10753ef40209a465b86c8fdc131236ec29bcb15c/distributed-2025.5.1.tar.gz", hash = "sha256:cf1d62a2c17a0a9fc1544bd10bb7afd39f22f24aaa9e3df3209c44d2cfb16703", size = 1107874 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/25/65/89601dcc7383f0e5109e59eab90677daa9abb260d821570cd6089c8894bf/distributed-2025.5.1-py3-none-any.whl", hash = "sha256:74782b965ddb24ce59c6441fa777e944b5962d82325cc41f228537b59bb7fbbe", size = 1014789 }, +] + [[package]] name = "donfig" version = "0.8.1.post1" @@ -470,113 +905,817 @@ wheels = [ ] [[package]] -name = "filelock" -version = "3.18.0" +name = "eip712" +version = "0.2.13" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/0a/10/c23352565a6544bdc5353e0b15fc1c563352101f30e24bf500207a54df9a/filelock-3.18.0.tar.gz", hash = "sha256:adbc88eabb99d2fec8c9c1b229b171f18afa655400173ddc653d5d01501fb9f2", size = 18075 } +dependencies = [ + { name = "dataclassy" }, + { name = "eth-abi" }, + { name = "eth-account" }, + { name = "eth-typing" }, + { name = "eth-utils" }, + { name = "hexbytes" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f5/95/aebdac7e295388f6fb953eeb85741a0b87cc5508cc86fc4e32a26d3596ad/eip712-0.2.13.tar.gz", hash = "sha256:291ab5082199469dc088e5fdce7fa07b2e64dcccde434e10a9f1c53be2d18175", size = 63372 } wheels = [ - { url = "https://files.pythonhosted.org/packages/4d/36/2a115987e2d8c300a974597416d9de88f2444426de9571f4b59b2cca3acc/filelock-3.18.0-py3-none-any.whl", hash = "sha256:c401f4f8377c4464e6db25fff06205fd89bdd83b65eb0488ed1b160f780e21de", size = 16215 }, + { url = "https://files.pythonhosted.org/packages/b1/0f/5449346b45658cb1979731f75d1fae46c468cb06cc25250ac542422af515/eip712-0.2.13-py3-none-any.whl", hash = "sha256:92bcaa1f47a48cf6f0369b88b7bac8c88962b212cfb3b9d6a54639de0616e17c", size = 10681 }, ] [[package]] -name = "frozenlist" -version = "1.5.0" +name = "eth-abi" +version = "5.2.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/8f/ed/0f4cec13a93c02c47ec32d81d11c0c1efbadf4a471e3f3ce7cad366cbbd3/frozenlist-1.5.0.tar.gz", hash = "sha256:81d5af29e61b9c8348e876d442253723928dce6433e0e76cd925cd83f1b4b817", size = 39930 } +dependencies = [ + { name = "eth-typing" }, + { name = "eth-utils" }, + { name = "parsimonious" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/00/71/d9e1380bd77fd22f98b534699af564f189b56d539cc2b9dab908d4e4c242/eth_abi-5.2.0.tar.gz", hash = "sha256:178703fa98c07d8eecd5ae569e7e8d159e493ebb6eeb534a8fe973fbc4e40ef0", size = 49797 } wheels = [ - { url = "https://files.pythonhosted.org/packages/79/73/fa6d1a96ab7fd6e6d1c3500700963eab46813847f01ef0ccbaa726181dd5/frozenlist-1.5.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:31115ba75889723431aa9a4e77d5f398f5cf976eea3bdf61749731f62d4a4a21", size = 94026 }, - { url = "https://files.pythonhosted.org/packages/ab/04/ea8bf62c8868b8eada363f20ff1b647cf2e93377a7b284d36062d21d81d1/frozenlist-1.5.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7437601c4d89d070eac8323f121fcf25f88674627505334654fd027b091db09d", size = 54150 }, - { url = "https://files.pythonhosted.org/packages/d0/9a/8e479b482a6f2070b26bda572c5e6889bb3ba48977e81beea35b5ae13ece/frozenlist-1.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7948140d9f8ece1745be806f2bfdf390127cf1a763b925c4a805c603df5e697e", size = 51927 }, - { url = "https://files.pythonhosted.org/packages/e3/12/2aad87deb08a4e7ccfb33600871bbe8f0e08cb6d8224371387f3303654d7/frozenlist-1.5.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:feeb64bc9bcc6b45c6311c9e9b99406660a9c05ca8a5b30d14a78555088b0b3a", size = 282647 }, - { url = "https://files.pythonhosted.org/packages/77/f2/07f06b05d8a427ea0060a9cef6e63405ea9e0d761846b95ef3fb3be57111/frozenlist-1.5.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:683173d371daad49cffb8309779e886e59c2f369430ad28fe715f66d08d4ab1a", size = 289052 }, - { url = "https://files.pythonhosted.org/packages/bd/9f/8bf45a2f1cd4aa401acd271b077989c9267ae8463e7c8b1eb0d3f561b65e/frozenlist-1.5.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7d57d8f702221405a9d9b40f9da8ac2e4a1a8b5285aac6100f3393675f0a85ee", size = 291719 }, - { url = "https://files.pythonhosted.org/packages/41/d1/1f20fd05a6c42d3868709b7604c9f15538a29e4f734c694c6bcfc3d3b935/frozenlist-1.5.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:30c72000fbcc35b129cb09956836c7d7abf78ab5416595e4857d1cae8d6251a6", size = 267433 }, - { url = "https://files.pythonhosted.org/packages/af/f2/64b73a9bb86f5a89fb55450e97cd5c1f84a862d4ff90d9fd1a73ab0f64a5/frozenlist-1.5.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:000a77d6034fbad9b6bb880f7ec073027908f1b40254b5d6f26210d2dab1240e", size = 283591 }, - { url = "https://files.pythonhosted.org/packages/29/e2/ffbb1fae55a791fd6c2938dd9ea779509c977435ba3940b9f2e8dc9d5316/frozenlist-1.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5d7f5a50342475962eb18b740f3beecc685a15b52c91f7d975257e13e029eca9", size = 273249 }, - { url = "https://files.pythonhosted.org/packages/2e/6e/008136a30798bb63618a114b9321b5971172a5abddff44a100c7edc5ad4f/frozenlist-1.5.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:87f724d055eb4785d9be84e9ebf0f24e392ddfad00b3fe036e43f489fafc9039", size = 271075 }, - { url = "https://files.pythonhosted.org/packages/ae/f0/4e71e54a026b06724cec9b6c54f0b13a4e9e298cc8db0f82ec70e151f5ce/frozenlist-1.5.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:6e9080bb2fb195a046e5177f10d9d82b8a204c0736a97a153c2466127de87784", size = 285398 }, - { url = "https://files.pythonhosted.org/packages/4d/36/70ec246851478b1c0b59f11ef8ade9c482ff447c1363c2bd5fad45098b12/frozenlist-1.5.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:9b93d7aaa36c966fa42efcaf716e6b3900438632a626fb09c049f6a2f09fc631", size = 294445 }, - { url = "https://files.pythonhosted.org/packages/37/e0/47f87544055b3349b633a03c4d94b405956cf2437f4ab46d0928b74b7526/frozenlist-1.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:52ef692a4bc60a6dd57f507429636c2af8b6046db8b31b18dac02cbc8f507f7f", size = 280569 }, - { url = "https://files.pythonhosted.org/packages/f9/7c/490133c160fb6b84ed374c266f42800e33b50c3bbab1652764e6e1fc498a/frozenlist-1.5.0-cp312-cp312-win32.whl", hash = "sha256:29d94c256679247b33a3dc96cce0f93cbc69c23bf75ff715919332fdbb6a32b8", size = 44721 }, - { url = "https://files.pythonhosted.org/packages/b1/56/4e45136ffc6bdbfa68c29ca56ef53783ef4c2fd395f7cbf99a2624aa9aaa/frozenlist-1.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:8969190d709e7c48ea386db202d708eb94bdb29207a1f269bab1196ce0dcca1f", size = 51329 }, - { url = "https://files.pythonhosted.org/packages/da/3b/915f0bca8a7ea04483622e84a9bd90033bab54bdf485479556c74fd5eaf5/frozenlist-1.5.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:7a1a048f9215c90973402e26c01d1cff8a209e1f1b53f72b95c13db61b00f953", size = 91538 }, - { url = "https://files.pythonhosted.org/packages/c7/d1/a7c98aad7e44afe5306a2b068434a5830f1470675f0e715abb86eb15f15b/frozenlist-1.5.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:dd47a5181ce5fcb463b5d9e17ecfdb02b678cca31280639255ce9d0e5aa67af0", size = 52849 }, - { url = "https://files.pythonhosted.org/packages/3a/c8/76f23bf9ab15d5f760eb48701909645f686f9c64fbb8982674c241fbef14/frozenlist-1.5.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1431d60b36d15cda188ea222033eec8e0eab488f39a272461f2e6d9e1a8e63c2", size = 50583 }, - { url = "https://files.pythonhosted.org/packages/1f/22/462a3dd093d11df623179d7754a3b3269de3b42de2808cddef50ee0f4f48/frozenlist-1.5.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6482a5851f5d72767fbd0e507e80737f9c8646ae7fd303def99bfe813f76cf7f", size = 265636 }, - { url = "https://files.pythonhosted.org/packages/80/cf/e075e407fc2ae7328155a1cd7e22f932773c8073c1fc78016607d19cc3e5/frozenlist-1.5.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:44c49271a937625619e862baacbd037a7ef86dd1ee215afc298a417ff3270608", size = 270214 }, - { url = "https://files.pythonhosted.org/packages/a1/58/0642d061d5de779f39c50cbb00df49682832923f3d2ebfb0fedf02d05f7f/frozenlist-1.5.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:12f78f98c2f1c2429d42e6a485f433722b0061d5c0b0139efa64f396efb5886b", size = 273905 }, - { url = "https://files.pythonhosted.org/packages/ab/66/3fe0f5f8f2add5b4ab7aa4e199f767fd3b55da26e3ca4ce2cc36698e50c4/frozenlist-1.5.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ce3aa154c452d2467487765e3adc730a8c153af77ad84096bc19ce19a2400840", size = 250542 }, - { url = "https://files.pythonhosted.org/packages/f6/b8/260791bde9198c87a465224e0e2bb62c4e716f5d198fc3a1dacc4895dbd1/frozenlist-1.5.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9b7dc0c4338e6b8b091e8faf0db3168a37101943e687f373dce00959583f7439", size = 267026 }, - { url = "https://files.pythonhosted.org/packages/2e/a4/3d24f88c527f08f8d44ade24eaee83b2627793fa62fa07cbb7ff7a2f7d42/frozenlist-1.5.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:45e0896250900b5aa25180f9aec243e84e92ac84bd4a74d9ad4138ef3f5c97de", size = 257690 }, - { url = "https://files.pythonhosted.org/packages/de/9a/d311d660420b2beeff3459b6626f2ab4fb236d07afbdac034a4371fe696e/frozenlist-1.5.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:561eb1c9579d495fddb6da8959fd2a1fca2c6d060d4113f5844b433fc02f2641", size = 253893 }, - { url = "https://files.pythonhosted.org/packages/c6/23/e491aadc25b56eabd0f18c53bb19f3cdc6de30b2129ee0bc39cd387cd560/frozenlist-1.5.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:df6e2f325bfee1f49f81aaac97d2aa757c7646534a06f8f577ce184afe2f0a9e", size = 267006 }, - { url = "https://files.pythonhosted.org/packages/08/c4/ab918ce636a35fb974d13d666dcbe03969592aeca6c3ab3835acff01f79c/frozenlist-1.5.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:140228863501b44b809fb39ec56b5d4071f4d0aa6d216c19cbb08b8c5a7eadb9", size = 276157 }, - { url = "https://files.pythonhosted.org/packages/c0/29/3b7a0bbbbe5a34833ba26f686aabfe982924adbdcafdc294a7a129c31688/frozenlist-1.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7707a25d6a77f5d27ea7dc7d1fc608aa0a478193823f88511ef5e6b8a48f9d03", size = 264642 }, - { url = "https://files.pythonhosted.org/packages/ab/42/0595b3dbffc2e82d7fe658c12d5a5bafcd7516c6bf2d1d1feb5387caa9c1/frozenlist-1.5.0-cp313-cp313-win32.whl", hash = "sha256:31a9ac2b38ab9b5a8933b693db4939764ad3f299fcaa931a3e605bc3460e693c", size = 44914 }, - { url = "https://files.pythonhosted.org/packages/17/c4/b7db1206a3fea44bf3b838ca61deb6f74424a8a5db1dd53ecb21da669be6/frozenlist-1.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:11aabdd62b8b9c4b84081a3c246506d1cddd2dd93ff0ad53ede5defec7886b28", size = 51167 }, - { url = "https://files.pythonhosted.org/packages/c6/c8/a5be5b7550c10858fcf9b0ea054baccab474da77d37f1e828ce043a3a5d4/frozenlist-1.5.0-py3-none-any.whl", hash = "sha256:d994863bba198a4a518b467bb971c56e1db3f180a25c6cf7bb1949c267f748c3", size = 11901 }, + { url = "https://files.pythonhosted.org/packages/7a/b4/2f3982c4cbcbf5eeb6aec62df1533c0e63c653b3021ff338d44944405676/eth_abi-5.2.0-py3-none-any.whl", hash = "sha256:17abe47560ad753f18054f5b3089fcb588f3e3a092136a416b6c1502cb7e8877", size = 28511 }, ] [[package]] -name = "fsspec" -version = "2025.3.0" +name = "eth-account" +version = "0.11.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/34/f4/5721faf47b8c499e776bc34c6a8fc17efdf7fdef0b00f398128bc5dcb4ac/fsspec-2025.3.0.tar.gz", hash = "sha256:a935fd1ea872591f2b5148907d103488fc523295e6c64b835cfad8c3eca44972", size = 298491 } +dependencies = [ + { name = "bitarray" }, + { name = "ckzg" }, + { name = "eth-abi" }, + { name = "eth-keyfile" }, + { name = "eth-keys" }, + { name = "eth-rlp" }, + { name = "eth-utils" }, + { name = "hexbytes" }, + { name = "rlp" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/73/42/2d1e2f1cb8b3f40f8c85f7df33e78ac0fc5f947c955607238e2e4a0d418b/eth_account-0.11.3.tar.gz", hash = "sha256:a712a9534638a7cfaa4cc069f1b9d5cefeee70362cfc3a7b0a2534ee61ce76c9", size = 712791 } wheels = [ - { url = "https://files.pythonhosted.org/packages/56/53/eb690efa8513166adef3e0669afd31e95ffde69fb3c52ec2ac7223ed6018/fsspec-2025.3.0-py3-none-any.whl", hash = "sha256:efb87af3efa9103f94ca91a7f8cb7a4df91af9f74fc106c9c7ea0efd7277c1b3", size = 193615 }, + { url = "https://files.pythonhosted.org/packages/bc/de/a850f7d3d47f7c14c50cda73c8646a9ab140608c553008bffa949d74afab/eth_account-0.11.3-py3-none-any.whl", hash = "sha256:16cf58aabc65171fc206489899b7e5546e3215e1a4debc12dbd55345c979081e", size = 355394 }, ] [[package]] -name = "geopandas" -version = "1.0.1" +name = "eth-ape" +version = "0.8.4" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "numpy" }, + { name = "click" }, + { name = "eip712" }, + { name = "eth-abi" }, + { name = "eth-account" }, + { name = "eth-pydantic-types" }, + { name = "eth-typing" }, + { name = "eth-utils" }, + { name = "ethpm-types" }, + { name = "evm-trace" }, + { name = "evmchains" }, + { name = "hexbytes" }, + { name = "ijson" }, + { name = "ipython" }, + { name = "lazyasd" }, { name = "packaging" }, { name = "pandas" }, - { name = "pyogrio" }, - { name = "pyproj" }, - { name = "shapely" }, + { name = "pluggy" }, + { name = "py-geth" }, + { name = "pydantic" }, + { name = "pydantic-settings" }, + { name = "pytest" }, + { name = "python-dateutil" }, + { name = "pyyaml" }, + { name = "requests" }, + { name = "rich" }, + { name = "sqlalchemy" }, + { name = "tqdm" }, + { name = "traitlets" }, + { name = "trie" }, + { name = "urllib3" }, + { name = "watchdog" }, + { name = "web3", extra = ["tester"] }, ] -sdist = { url = "https://files.pythonhosted.org/packages/39/08/2cf5d85356e45b10b8d066cf4c3ba1e9e3185423c48104eed87e8afd0455/geopandas-1.0.1.tar.gz", hash = "sha256:b8bf70a5534588205b7a56646e2082fb1de9a03599651b3d80c99ea4c2ca08ab", size = 317736 } +sdist = { url = "https://files.pythonhosted.org/packages/6a/0d/055b265b4c0eda246f57dc3e1c275ec2cb222f699e55a6d2cab93b74f63c/eth-ape-0.8.4.tar.gz", hash = "sha256:777a4e889e06fc419021256dda7905b1d1927f6a8390a5e45049a2e5fed63538", size = 1108518 } wheels = [ - { url = "https://files.pythonhosted.org/packages/c4/64/7d344cfcef5efddf9cf32f59af7f855828e9d74b5f862eddf5bfd9f25323/geopandas-1.0.1-py3-none-any.whl", hash = "sha256:01e147d9420cc374d26f51fc23716ac307f32b49406e4bd8462c07e82ed1d3d6", size = 323587 }, + { url = "https://files.pythonhosted.org/packages/4b/5f/5d33851b4be473274ffdf1c764c4693ad048afd97e19323cc78f495da488/eth_ape-0.8.4-py3-none-any.whl", hash = "sha256:294f2dc04ef7d7b024095ffd86bb268d57ae08f7df85790dd1e837caa0e7d53e", size = 344322 }, ] [[package]] -name = "identify" -version = "2.6.9" +name = "eth-bloom" +version = "3.1.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/9b/98/a71ab060daec766acc30fb47dfca219d03de34a70d616a79a38c6066c5bf/identify-2.6.9.tar.gz", hash = "sha256:d40dfe3142a1421d8518e3d3985ef5ac42890683e32306ad614a29490abeb6bf", size = 99249 } +dependencies = [ + { name = "eth-hash", extra = ["pycryptodome"] }, +] +sdist = { url = "https://files.pythonhosted.org/packages/89/d6/9c345601de27b536dc8b0f4c1d6cb84fe76a47de9ac02753f58deae59c86/eth_bloom-3.1.0.tar.gz", hash = "sha256:4bc918f6fde44334e92b23cfb345db961e2e3af620535cbc872444f7a143cb88", size = 9807 } wheels = [ - { url = "https://files.pythonhosted.org/packages/07/ce/0845144ed1f0e25db5e7a79c2354c1da4b5ce392b8966449d5db8dca18f1/identify-2.6.9-py2.py3-none-any.whl", hash = "sha256:c98b4322da415a8e5a70ff6e51fbc2d2932c015532d77e9f8537b4ba7813b150", size = 99101 }, + { url = "https://files.pythonhosted.org/packages/81/e5/251bda1d3f3cff230fa39e95d41141c1b44e8f55c101c5f593ccf5a31b63/eth_bloom-3.1.0-py3-none-any.whl", hash = "sha256:c96b2dd6cafa407373bca1a9d74b650378ba672d5b17f2771bf7d3c3aaa7651c", size = 5785 }, ] [[package]] -name = "idna" -version = "3.10" +name = "eth-hash" +version = "0.7.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490 } +sdist = { url = "https://files.pythonhosted.org/packages/ee/38/577b7bc9380ef9dff0f1dffefe0c9a1ded2385e7a06c306fd95afb6f9451/eth_hash-0.7.1.tar.gz", hash = "sha256:d2411a403a0b0a62e8247b4117932d900ffb4c8c64b15f92620547ca5ce46be5", size = 12227 } wheels = [ - { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 }, + { url = "https://files.pythonhosted.org/packages/eb/db/f8775490669d28aca24871c67dd56b3e72105cb3bcae9a4ec65dd70859b3/eth_hash-0.7.1-py3-none-any.whl", hash = "sha256:0fb1add2adf99ef28883fd6228eb447ef519ea72933535ad1a0b28c6f65f868a", size = 8028 }, ] -[[package]] -name = "iniconfig" -version = "2.1.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050 }, +[package.optional-dependencies] +pycryptodome = [ + { name = "pycryptodome" }, +] +pysha3 = [ + { name = "safe-pysha3", marker = "implementation_name == 'cpython'" }, ] [[package]] -name = "jmespath" -version = "1.0.1" +name = "eth-keyfile" +version = "0.9.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/00/2a/e867e8531cf3e36b41201936b7fa7ba7b5702dbef42922193f05c8976cd6/jmespath-1.0.1.tar.gz", hash = "sha256:90261b206d6defd58fdd5e85f478bf633a2901798906be2ad389150c5c60edbe", size = 25843 } +dependencies = [ + { name = "eth-keys" }, + { name = "eth-utils" }, + { name = "py-ecc" }, + { name = "pycryptodome" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/08/e4/3f0c20b020786e1fa6e1ecd81806c54167fa2b0839e0020086b95a6e8faf/eth_keyfile-0.9.1.tar.gz", hash = "sha256:c7a8bc6af4527d1ab2eb1d1b949d59925252e17663eaf90087da121327b51df6", size = 19787 } wheels = [ - { url = "https://files.pythonhosted.org/packages/31/b4/b9b800c45527aadd64d5b442f9b932b00648617eb5d63d2c7a6587b7cafc/jmespath-1.0.1-py3-none-any.whl", hash = "sha256:02e2e4cc71b5bcab88332eebf907519190dd9e6e82107fa7f83b1003a6252980", size = 20256 }, + { url = "https://files.pythonhosted.org/packages/5f/08/9c8bf617b39e1dd56303593292e8b4eb66497a5f0f5b997a4b291e5343c0/eth_keyfile-0.9.1-py3-none-any.whl", hash = "sha256:9789c3b4fa0bb6e2616cdc2bdd71b8755b42947d78ef1e900a0149480fabb5c2", size = 9866 }, +] + +[[package]] +name = "eth-keys" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "eth-typing" }, + { name = "eth-utils" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/58/11/1ed831c50bd74f57829aa06e58bd82a809c37e070ee501c953b9ac1f1552/eth_keys-0.7.0.tar.gz", hash = "sha256:79d24fd876201df67741de3e3fefb3f4dbcbb6ace66e47e6fe662851a4547814", size = 30166 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4d/25/0ae00f2b0095e559d61ad3dc32171bd5a29dfd95ab04b4edd641f7c75f72/eth_keys-0.7.0-py3-none-any.whl", hash = "sha256:b0cdda8ffe8e5ba69c7c5ca33f153828edcace844f67aabd4542d7de38b159cf", size = 20656 }, +] + +[[package]] +name = "eth-pydantic-types" +version = "0.1.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "eth-typing" }, + { name = "eth-utils" }, + { name = "hexbytes" }, + { name = "pydantic" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/19/3209c4c20832a95c57611f4fb5be51db18836929f2e6c80affc07b533616/eth-pydantic-types-0.1.3.tar.gz", hash = "sha256:4764ba82434ab6d0d98d3b7aee4ace0cc7b12cf47c7e53424525c33fb9b872af", size = 65715 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3f/b2/e44f9e3be45768c33f83080d7c2bfcdf698fdeaec810c98866724eb44846/eth_pydantic_types-0.1.3-py3-none-any.whl", hash = "sha256:7a54f253f32bb3c01b21dbbfc7022d476b53ad5b7da96dcc4dae24997ec69469", size = 14624 }, +] + +[[package]] +name = "eth-rlp" +version = "1.0.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "eth-utils" }, + { name = "hexbytes" }, + { name = "rlp" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9b/2e/fb9c2e0a2d0e249b61abf462828f3f8039305dfbe5844e138ab1a3b3a413/eth-rlp-1.0.1.tar.gz", hash = "sha256:d61dbda892ee1220f28fb3663c08f6383c305db9f1f5624dc585c9cd05115027", size = 7261 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/33/7f/583c8286530a52d9d5c07f3895c2184e36399379d1284dc1c2c8309a8e9d/eth_rlp-1.0.1-py3-none-any.whl", hash = "sha256:dd76515d71654277377d48876b88e839d61553aaf56952e580bb7cebef2b1517", size = 4922 }, +] + +[[package]] +name = "eth-tester" +version = "0.11.0b2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "eth-abi" }, + { name = "eth-account" }, + { name = "eth-keys" }, + { name = "eth-utils" }, + { name = "rlp" }, + { name = "semantic-version" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a2/7b/249a37b69da25d73b5040adcdd40c5f661f6dc74e5c8f788e58cd7f2f416/eth_tester-0.11.0b2.tar.gz", hash = "sha256:b46e9acfb5cb5f3f62fd729d796ca0af231f117e35ce6f2f2f5bb55d4108edcd", size = 98972 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/19/e8/d910312d33c0e582371644eae2bdde949d2e75aabc06966180e1bbca23dc/eth_tester-0.11.0b2-py3-none-any.whl", hash = "sha256:d352cd1f99511dac6f38a0c449f77315a1ddbe234c8a3f3fe6ff90ac172622f3", size = 75193 }, +] + +[package.optional-dependencies] +py-evm = [ + { name = "eth-hash", marker = "implementation_name == 'cpython' or implementation_name == 'pypy'" }, + { name = "eth-hash", extra = ["pycryptodome"], marker = "implementation_name == 'pypy'" }, + { name = "eth-hash", extra = ["pysha3"], marker = "implementation_name == 'cpython'" }, + { name = "py-evm" }, +] + +[[package]] +name = "eth-typing" +version = "3.5.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c8/52/f5d10803345dd06f9301b4871e8ebb39971a4238b60c61f84ffc83379611/eth-typing-3.5.2.tar.gz", hash = "sha256:22bf051ddfaa35ff827c30090de167e5c5b8cc6d343f7f35c9b1c7553f6ab64d", size = 16850 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d0/36/50c5d5f070b7e4318dc906f8bf642719418ffef741db94f85ab6447563e1/eth_typing-3.5.2-py3-none-any.whl", hash = "sha256:1842e628fb1ffa929b94f89a9d33caafbeb9978dc96abb6036a12bc91f1c624b", size = 14481 }, +] + +[[package]] +name = "eth-utils" +version = "2.3.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cytoolz", marker = "implementation_name == 'cpython'" }, + { name = "eth-hash" }, + { name = "eth-typing" }, + { name = "toolz", marker = "implementation_name == 'pypy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b8/b0/ec73fd8b9a836a1b0f09db8e9ca22e3c1f7be1e4a36ce4a9e254819ee37a/eth_utils-2.3.2.tar.gz", hash = "sha256:1986d704b29202386c9bc4b27b948a134320c11c8104c45ca367e4663ae7d10e", size = 74656 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/e8/1c15f2f97ac144b7d7cb851d72b52aa717b284251981ec74773e99573e26/eth_utils-2.3.2-py3-none-any.whl", hash = "sha256:4470be372674a25b8440b69cb35bda634a079876930853814ea307248c3d198b", size = 77767 }, +] + +[[package]] +name = "ethpm-types" +version = "0.6.25" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "eth-abi" }, + { name = "eth-pydantic-types" }, + { name = "eth-utils" }, + { name = "py-cid" }, + { name = "pydantic" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/49/2d/27b1d081db0e8cf46b4b34c7060d277ebe4c39f05263f629b77d9a5053dc/ethpm_types-0.6.25.tar.gz", hash = "sha256:8fdef2ea36b6086682072d23a4172329c8fb5de9e0c5ee761477aab4c8131874", size = 1227238 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a9/75/39b6a14b15b17ecc4523aa45edfb50d6aa8cc00b7589813324f27b6694d8/ethpm_types-0.6.25-py3-none-any.whl", hash = "sha256:fe61b4f0fec3102f5a99b9972a3baeb6ff6e72fe96a29a692a122184c21ae297", size = 35154 }, +] + +[[package]] +name = "evm-trace" +version = "0.1.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "eth-pydantic-types" }, + { name = "eth-utils" }, + { name = "msgspec" }, + { name = "py-evm" }, + { name = "pydantic" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ce/78/7241a26ecf4b6a0334da407f4e0a57a67f09a05b2e8b87f5a4b5f7dbd75b/evm-trace-0.1.5.tar.gz", hash = "sha256:a6f8158a96b8440d42b02bafebc0bf4e7e504b9e7ca661de96968177d225cbc5", size = 130696 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a6/fa/824368e499ee2759918602bfd1b960e92dc1f3ac11703be473a56da57cb0/evm_trace-0.1.5-py3-none-any.whl", hash = "sha256:aed1c82c68e586c54286436944e2021bf0ff7b95cefb540b9b27ba38322b7e0a", size = 19789 }, +] + +[[package]] +name = "evmchains" +version = "0.0.13" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7a/8c/552415f1132054f6fce262d8b9eaac56a7ada787f55cda9c1ab479e98a9a/evmchains-0.0.13.tar.gz", hash = "sha256:f017bff69bbdf3d822148c5500969e600e28218df55a7c19a9d9c34e7e36c179", size = 23777 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/66/44/dd79fbcd5dac6a297c5c1a7eaab0ac9a991530b18818684959ed65979668/evmchains-0.0.13-py3-none-any.whl", hash = "sha256:01f49c7e1e9f8095c4ec64b33db5c45b7ba4ebecc7607e8a1104b2721c57ec85", size = 16610 }, +] + +[[package]] +name = "executing" +version = "2.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/91/50/a9d80c47ff289c611ff12e63f7c5d13942c65d68125160cefd768c73e6e4/executing-2.2.0.tar.gz", hash = "sha256:5d108c028108fe2551d1a7b2e8b713341e2cb4fc0aa7dcf966fa4327a5226755", size = 978693 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7b/8f/c4d9bafc34ad7ad5d8dc16dd1347ee0e507a52c3adb6bfa8887e1c6a26ba/executing-2.2.0-py2.py3-none-any.whl", hash = "sha256:11387150cad388d62750327a53d3339fad4888b39a6fe233c3afbb54ecffd3aa", size = 26702 }, +] + +[[package]] +name = "filelock" +version = "3.18.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0a/10/c23352565a6544bdc5353e0b15fc1c563352101f30e24bf500207a54df9a/filelock-3.18.0.tar.gz", hash = "sha256:adbc88eabb99d2fec8c9c1b229b171f18afa655400173ddc653d5d01501fb9f2", size = 18075 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4d/36/2a115987e2d8c300a974597416d9de88f2444426de9571f4b59b2cca3acc/filelock-3.18.0-py3-none-any.whl", hash = "sha256:c401f4f8377c4464e6db25fff06205fd89bdd83b65eb0488ed1b160f780e21de", size = 16215 }, +] + +[[package]] +name = "flox" +version = "0.10.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, + { name = "numpy-groupies" }, + { name = "packaging" }, + { name = "pandas" }, + { name = "scipy" }, + { name = "toolz" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6e/34/6eea00e3f1de745c8adad5a3dafd46c3481294cff8699c20a9b8d80502ed/flox-0.10.4.tar.gz", hash = "sha256:2ccb6b497607857cfa68917584c5850005b27bf4748abdc24c106b10d5ce9056", size = 718663 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/96/dc/3489211b2f4e9d81693a2fbecbf6ead23a2fc5fa10fc4b6cd458d0d888dc/flox-0.10.4-py3-none-any.whl", hash = "sha256:bc07f74706c86d3bf4eae99002cf23e2223fa415224c5dd90e4f7b7f05a7e21a", size = 78446 }, +] + +[[package]] +name = "fonttools" +version = "4.58.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/52/97/5735503e58d3816b0989955ef9b2df07e4c99b246469bd8b3823a14095da/fonttools-4.58.5.tar.gz", hash = "sha256:b2a35b0a19f1837284b3a23dd64fd7761b8911d50911ecd2bdbaf5b2d1b5df9c", size = 3526243 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d6/68/66b498ee66f3e7e92fd68476c2509508082b7f57d68c0cdb4b8573f44331/fonttools-4.58.5-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:c3af3fefaafb570a03051a0d6899b8374dcf8e6a4560e42575843aef33bdbad6", size = 2754751 }, + { url = "https://files.pythonhosted.org/packages/f1/1e/edbc14b79290980c3944a1f43098624bc8965f534964aa03d52041f24cb4/fonttools-4.58.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:688137789dbd44e8757ad77b49a771539d8069195ffa9a8bcf18176e90bbd86d", size = 2322342 }, + { url = "https://files.pythonhosted.org/packages/c1/d7/3c87cf147185d91c2e946460a5cf68c236427b4a23ab96793ccb7d8017c9/fonttools-4.58.5-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2af65836cf84cd7cb882d0b353bdc73643a497ce23b7414c26499bb8128ca1af", size = 4897011 }, + { url = "https://files.pythonhosted.org/packages/a0/d6/fbb44cc85d4195fe54356658bd9f934328b4f74ae14addd90b4b5558b5c9/fonttools-4.58.5-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d2d79cfeb456bf438cb9fb87437634d4d6f228f27572ca5c5355e58472d5519d", size = 4942291 }, + { url = "https://files.pythonhosted.org/packages/4d/c8/453f82e21aedf25cdc2ae619c03a73512398cec9bd8b6c3b1c571e0b6632/fonttools-4.58.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0feac9dda9a48a7a342a593f35d50a5cee2dbd27a03a4c4a5192834a4853b204", size = 4886824 }, + { url = "https://files.pythonhosted.org/packages/40/54/e9190001b8e22d123f78925b2f508c866d9d18531694b979277ad45d59b0/fonttools-4.58.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:36555230e168511e83ad8637232268649634b8dfff6ef58f46e1ebc057a041ad", size = 5038510 }, + { url = "https://files.pythonhosted.org/packages/cf/9c/07cdad4774841a6304aabae939f8cbb9538cb1d8e97f5016b334da98e73a/fonttools-4.58.5-cp312-cp312-win32.whl", hash = "sha256:26ec05319353842d127bd02516eacb25b97ca83966e40e9ad6fab85cab0576f4", size = 2188459 }, + { url = "https://files.pythonhosted.org/packages/0e/4d/1eaaad22781d55f49d1b184563842172aeb6a4fe53c029e503be81114314/fonttools-4.58.5-cp312-cp312-win_amd64.whl", hash = "sha256:778a632e538f82c1920579c0c01566a8f83dc24470c96efbf2fbac698907f569", size = 2236565 }, + { url = "https://files.pythonhosted.org/packages/3a/ee/764dd8b99891f815241f449345863cfed9e546923d9cef463f37fd1d7168/fonttools-4.58.5-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f4b6f1360da13cecc88c0d60716145b31e1015fbe6a59e32f73a4404e2ea92cf", size = 2745867 }, + { url = "https://files.pythonhosted.org/packages/e2/23/8fef484c02fef55e226dfeac4339a015c5480b6a496064058491759ac71e/fonttools-4.58.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4a036822e915692aa2c03e2decc60f49a8190f8111b639c947a4f4e5774d0d7a", size = 2317933 }, + { url = "https://files.pythonhosted.org/packages/ab/47/f92b135864fa777e11ad68420bf89446c91a572fe2782745586f8e6aac0c/fonttools-4.58.5-cp313-cp313-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:a6d7709fcf4577b0f294ee6327088884ca95046e1eccde87c53bbba4d5008541", size = 4877844 }, + { url = "https://files.pythonhosted.org/packages/3e/65/6c1a83511d8ac32411930495645edb3f8dfabebcb78f08cf6009ba2585ec/fonttools-4.58.5-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b9b5099ca99b79d6d67162778b1b1616fc0e1de02c1a178248a0da8d78a33852", size = 4940106 }, + { url = "https://files.pythonhosted.org/packages/fa/90/df8eb77d6cf266cbbba01866a1349a3e9121e0a63002cf8d6754e994f755/fonttools-4.58.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:3f2c05a8d82a4d15aebfdb3506e90793aea16e0302cec385134dd960647a36c0", size = 4879458 }, + { url = "https://files.pythonhosted.org/packages/26/b1/e32f8de51b7afcfea6ad62780da2fa73212c43a32cd8cafcc852189d7949/fonttools-4.58.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:79f0c4b1cc63839b61deeac646d8dba46f8ed40332c2ac1b9997281462c2e4ba", size = 5021917 }, + { url = "https://files.pythonhosted.org/packages/89/72/578aa7fe32918dd763c62f447aaed672d665ee10e3eeb1725f4d6493fe96/fonttools-4.58.5-cp313-cp313-win32.whl", hash = "sha256:a1a9a2c462760976882131cbab7d63407813413a2d32cd699e86a1ff22bf7aa5", size = 2186827 }, + { url = "https://files.pythonhosted.org/packages/71/a3/21e921b16cb9c029d3308e0cb79c9a937e9ff1fc1ee28c2419f0957b9e7c/fonttools-4.58.5-cp313-cp313-win_amd64.whl", hash = "sha256:bca61b14031a4b7dc87e14bf6ca34c275f8e4b9f7a37bc2fe746b532a924cf30", size = 2235706 }, + { url = "https://files.pythonhosted.org/packages/d7/d4/1d85a1996b6188cd2713230e002d79a6f3a289bb17cef600cba385848b72/fonttools-4.58.5-py3-none-any.whl", hash = "sha256:e48a487ed24d9b611c5c4b25db1e50e69e9854ca2670e39a3486ffcd98863ec4", size = 1115318 }, +] + +[[package]] +name = "frozenlist" +version = "1.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8f/ed/0f4cec13a93c02c47ec32d81d11c0c1efbadf4a471e3f3ce7cad366cbbd3/frozenlist-1.5.0.tar.gz", hash = "sha256:81d5af29e61b9c8348e876d442253723928dce6433e0e76cd925cd83f1b4b817", size = 39930 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/79/73/fa6d1a96ab7fd6e6d1c3500700963eab46813847f01ef0ccbaa726181dd5/frozenlist-1.5.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:31115ba75889723431aa9a4e77d5f398f5cf976eea3bdf61749731f62d4a4a21", size = 94026 }, + { url = "https://files.pythonhosted.org/packages/ab/04/ea8bf62c8868b8eada363f20ff1b647cf2e93377a7b284d36062d21d81d1/frozenlist-1.5.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7437601c4d89d070eac8323f121fcf25f88674627505334654fd027b091db09d", size = 54150 }, + { url = "https://files.pythonhosted.org/packages/d0/9a/8e479b482a6f2070b26bda572c5e6889bb3ba48977e81beea35b5ae13ece/frozenlist-1.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7948140d9f8ece1745be806f2bfdf390127cf1a763b925c4a805c603df5e697e", size = 51927 }, + { url = "https://files.pythonhosted.org/packages/e3/12/2aad87deb08a4e7ccfb33600871bbe8f0e08cb6d8224371387f3303654d7/frozenlist-1.5.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:feeb64bc9bcc6b45c6311c9e9b99406660a9c05ca8a5b30d14a78555088b0b3a", size = 282647 }, + { url = "https://files.pythonhosted.org/packages/77/f2/07f06b05d8a427ea0060a9cef6e63405ea9e0d761846b95ef3fb3be57111/frozenlist-1.5.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:683173d371daad49cffb8309779e886e59c2f369430ad28fe715f66d08d4ab1a", size = 289052 }, + { url = "https://files.pythonhosted.org/packages/bd/9f/8bf45a2f1cd4aa401acd271b077989c9267ae8463e7c8b1eb0d3f561b65e/frozenlist-1.5.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7d57d8f702221405a9d9b40f9da8ac2e4a1a8b5285aac6100f3393675f0a85ee", size = 291719 }, + { url = "https://files.pythonhosted.org/packages/41/d1/1f20fd05a6c42d3868709b7604c9f15538a29e4f734c694c6bcfc3d3b935/frozenlist-1.5.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:30c72000fbcc35b129cb09956836c7d7abf78ab5416595e4857d1cae8d6251a6", size = 267433 }, + { url = "https://files.pythonhosted.org/packages/af/f2/64b73a9bb86f5a89fb55450e97cd5c1f84a862d4ff90d9fd1a73ab0f64a5/frozenlist-1.5.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:000a77d6034fbad9b6bb880f7ec073027908f1b40254b5d6f26210d2dab1240e", size = 283591 }, + { url = "https://files.pythonhosted.org/packages/29/e2/ffbb1fae55a791fd6c2938dd9ea779509c977435ba3940b9f2e8dc9d5316/frozenlist-1.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5d7f5a50342475962eb18b740f3beecc685a15b52c91f7d975257e13e029eca9", size = 273249 }, + { url = "https://files.pythonhosted.org/packages/2e/6e/008136a30798bb63618a114b9321b5971172a5abddff44a100c7edc5ad4f/frozenlist-1.5.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:87f724d055eb4785d9be84e9ebf0f24e392ddfad00b3fe036e43f489fafc9039", size = 271075 }, + { url = "https://files.pythonhosted.org/packages/ae/f0/4e71e54a026b06724cec9b6c54f0b13a4e9e298cc8db0f82ec70e151f5ce/frozenlist-1.5.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:6e9080bb2fb195a046e5177f10d9d82b8a204c0736a97a153c2466127de87784", size = 285398 }, + { url = "https://files.pythonhosted.org/packages/4d/36/70ec246851478b1c0b59f11ef8ade9c482ff447c1363c2bd5fad45098b12/frozenlist-1.5.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:9b93d7aaa36c966fa42efcaf716e6b3900438632a626fb09c049f6a2f09fc631", size = 294445 }, + { url = "https://files.pythonhosted.org/packages/37/e0/47f87544055b3349b633a03c4d94b405956cf2437f4ab46d0928b74b7526/frozenlist-1.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:52ef692a4bc60a6dd57f507429636c2af8b6046db8b31b18dac02cbc8f507f7f", size = 280569 }, + { url = "https://files.pythonhosted.org/packages/f9/7c/490133c160fb6b84ed374c266f42800e33b50c3bbab1652764e6e1fc498a/frozenlist-1.5.0-cp312-cp312-win32.whl", hash = "sha256:29d94c256679247b33a3dc96cce0f93cbc69c23bf75ff715919332fdbb6a32b8", size = 44721 }, + { url = "https://files.pythonhosted.org/packages/b1/56/4e45136ffc6bdbfa68c29ca56ef53783ef4c2fd395f7cbf99a2624aa9aaa/frozenlist-1.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:8969190d709e7c48ea386db202d708eb94bdb29207a1f269bab1196ce0dcca1f", size = 51329 }, + { url = "https://files.pythonhosted.org/packages/da/3b/915f0bca8a7ea04483622e84a9bd90033bab54bdf485479556c74fd5eaf5/frozenlist-1.5.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:7a1a048f9215c90973402e26c01d1cff8a209e1f1b53f72b95c13db61b00f953", size = 91538 }, + { url = "https://files.pythonhosted.org/packages/c7/d1/a7c98aad7e44afe5306a2b068434a5830f1470675f0e715abb86eb15f15b/frozenlist-1.5.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:dd47a5181ce5fcb463b5d9e17ecfdb02b678cca31280639255ce9d0e5aa67af0", size = 52849 }, + { url = "https://files.pythonhosted.org/packages/3a/c8/76f23bf9ab15d5f760eb48701909645f686f9c64fbb8982674c241fbef14/frozenlist-1.5.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1431d60b36d15cda188ea222033eec8e0eab488f39a272461f2e6d9e1a8e63c2", size = 50583 }, + { url = "https://files.pythonhosted.org/packages/1f/22/462a3dd093d11df623179d7754a3b3269de3b42de2808cddef50ee0f4f48/frozenlist-1.5.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6482a5851f5d72767fbd0e507e80737f9c8646ae7fd303def99bfe813f76cf7f", size = 265636 }, + { url = "https://files.pythonhosted.org/packages/80/cf/e075e407fc2ae7328155a1cd7e22f932773c8073c1fc78016607d19cc3e5/frozenlist-1.5.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:44c49271a937625619e862baacbd037a7ef86dd1ee215afc298a417ff3270608", size = 270214 }, + { url = "https://files.pythonhosted.org/packages/a1/58/0642d061d5de779f39c50cbb00df49682832923f3d2ebfb0fedf02d05f7f/frozenlist-1.5.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:12f78f98c2f1c2429d42e6a485f433722b0061d5c0b0139efa64f396efb5886b", size = 273905 }, + { url = "https://files.pythonhosted.org/packages/ab/66/3fe0f5f8f2add5b4ab7aa4e199f767fd3b55da26e3ca4ce2cc36698e50c4/frozenlist-1.5.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ce3aa154c452d2467487765e3adc730a8c153af77ad84096bc19ce19a2400840", size = 250542 }, + { url = "https://files.pythonhosted.org/packages/f6/b8/260791bde9198c87a465224e0e2bb62c4e716f5d198fc3a1dacc4895dbd1/frozenlist-1.5.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9b7dc0c4338e6b8b091e8faf0db3168a37101943e687f373dce00959583f7439", size = 267026 }, + { url = "https://files.pythonhosted.org/packages/2e/a4/3d24f88c527f08f8d44ade24eaee83b2627793fa62fa07cbb7ff7a2f7d42/frozenlist-1.5.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:45e0896250900b5aa25180f9aec243e84e92ac84bd4a74d9ad4138ef3f5c97de", size = 257690 }, + { url = "https://files.pythonhosted.org/packages/de/9a/d311d660420b2beeff3459b6626f2ab4fb236d07afbdac034a4371fe696e/frozenlist-1.5.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:561eb1c9579d495fddb6da8959fd2a1fca2c6d060d4113f5844b433fc02f2641", size = 253893 }, + { url = "https://files.pythonhosted.org/packages/c6/23/e491aadc25b56eabd0f18c53bb19f3cdc6de30b2129ee0bc39cd387cd560/frozenlist-1.5.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:df6e2f325bfee1f49f81aaac97d2aa757c7646534a06f8f577ce184afe2f0a9e", size = 267006 }, + { url = "https://files.pythonhosted.org/packages/08/c4/ab918ce636a35fb974d13d666dcbe03969592aeca6c3ab3835acff01f79c/frozenlist-1.5.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:140228863501b44b809fb39ec56b5d4071f4d0aa6d216c19cbb08b8c5a7eadb9", size = 276157 }, + { url = "https://files.pythonhosted.org/packages/c0/29/3b7a0bbbbe5a34833ba26f686aabfe982924adbdcafdc294a7a129c31688/frozenlist-1.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7707a25d6a77f5d27ea7dc7d1fc608aa0a478193823f88511ef5e6b8a48f9d03", size = 264642 }, + { url = "https://files.pythonhosted.org/packages/ab/42/0595b3dbffc2e82d7fe658c12d5a5bafcd7516c6bf2d1d1feb5387caa9c1/frozenlist-1.5.0-cp313-cp313-win32.whl", hash = "sha256:31a9ac2b38ab9b5a8933b693db4939764ad3f299fcaa931a3e605bc3460e693c", size = 44914 }, + { url = "https://files.pythonhosted.org/packages/17/c4/b7db1206a3fea44bf3b838ca61deb6f74424a8a5db1dd53ecb21da669be6/frozenlist-1.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:11aabdd62b8b9c4b84081a3c246506d1cddd2dd93ff0ad53ede5defec7886b28", size = 51167 }, + { url = "https://files.pythonhosted.org/packages/c6/c8/a5be5b7550c10858fcf9b0ea054baccab474da77d37f1e828ce043a3a5d4/frozenlist-1.5.0-py3-none-any.whl", hash = "sha256:d994863bba198a4a518b467bb971c56e1db3f180a25c6cf7bb1949c267f748c3", size = 11901 }, +] + +[[package]] +name = "fsspec" +version = "2025.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/34/f4/5721faf47b8c499e776bc34c6a8fc17efdf7fdef0b00f398128bc5dcb4ac/fsspec-2025.3.0.tar.gz", hash = "sha256:a935fd1ea872591f2b5148907d103488fc523295e6c64b835cfad8c3eca44972", size = 298491 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/56/53/eb690efa8513166adef3e0669afd31e95ffde69fb3c52ec2ac7223ed6018/fsspec-2025.3.0-py3-none-any.whl", hash = "sha256:efb87af3efa9103f94ca91a7f8cb7a4df91af9f74fc106c9c7ea0efd7277c1b3", size = 193615 }, +] + +[[package]] +name = "geopandas" +version = "1.0.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, + { name = "packaging" }, + { name = "pandas" }, + { name = "pyogrio" }, + { name = "pyproj" }, + { name = "shapely" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/39/08/2cf5d85356e45b10b8d066cf4c3ba1e9e3185423c48104eed87e8afd0455/geopandas-1.0.1.tar.gz", hash = "sha256:b8bf70a5534588205b7a56646e2082fb1de9a03599651b3d80c99ea4c2ca08ab", size = 317736 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c4/64/7d344cfcef5efddf9cf32f59af7f855828e9d74b5f862eddf5bfd9f25323/geopandas-1.0.1-py3-none-any.whl", hash = "sha256:01e147d9420cc374d26f51fc23716ac307f32b49406e4bd8462c07e82ed1d3d6", size = 323587 }, +] + +[[package]] +name = "greenlet" +version = "3.2.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c9/92/bb85bd6e80148a4d2e0c59f7c0c2891029f8fd510183afc7d8d2feeed9b6/greenlet-3.2.3.tar.gz", hash = "sha256:8b0dd8ae4c0d6f5e54ee55ba935eeb3d735a9b58a8a1e5b5cbab64e01a39f365", size = 185752 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f3/94/ad0d435f7c48debe960c53b8f60fb41c2026b1d0fa4a99a1cb17c3461e09/greenlet-3.2.3-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:25ad29caed5783d4bd7a85c9251c651696164622494c00802a139c00d639242d", size = 271992 }, + { url = "https://files.pythonhosted.org/packages/93/5d/7c27cf4d003d6e77749d299c7c8f5fd50b4f251647b5c2e97e1f20da0ab5/greenlet-3.2.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:88cd97bf37fe24a6710ec6a3a7799f3f81d9cd33317dcf565ff9950c83f55e0b", size = 638820 }, + { url = "https://files.pythonhosted.org/packages/c6/7e/807e1e9be07a125bb4c169144937910bf59b9d2f6d931578e57f0bce0ae2/greenlet-3.2.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:baeedccca94880d2f5666b4fa16fc20ef50ba1ee353ee2d7092b383a243b0b0d", size = 653046 }, + { url = "https://files.pythonhosted.org/packages/9d/ab/158c1a4ea1068bdbc78dba5a3de57e4c7aeb4e7fa034320ea94c688bfb61/greenlet-3.2.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:be52af4b6292baecfa0f397f3edb3c6092ce071b499dd6fe292c9ac9f2c8f264", size = 647701 }, + { url = "https://files.pythonhosted.org/packages/cc/0d/93729068259b550d6a0288da4ff72b86ed05626eaf1eb7c0d3466a2571de/greenlet-3.2.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0cc73378150b8b78b0c9fe2ce56e166695e67478550769536a6742dca3651688", size = 649747 }, + { url = "https://files.pythonhosted.org/packages/f6/f6/c82ac1851c60851302d8581680573245c8fc300253fc1ff741ae74a6c24d/greenlet-3.2.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:706d016a03e78df129f68c4c9b4c4f963f7d73534e48a24f5f5a7101ed13dbbb", size = 605461 }, + { url = "https://files.pythonhosted.org/packages/98/82/d022cf25ca39cf1200650fc58c52af32c90f80479c25d1cbf57980ec3065/greenlet-3.2.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:419e60f80709510c343c57b4bb5a339d8767bf9aef9b8ce43f4f143240f88b7c", size = 1121190 }, + { url = "https://files.pythonhosted.org/packages/f5/e1/25297f70717abe8104c20ecf7af0a5b82d2f5a980eb1ac79f65654799f9f/greenlet-3.2.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:93d48533fade144203816783373f27a97e4193177ebaaf0fc396db19e5d61163", size = 1149055 }, + { url = "https://files.pythonhosted.org/packages/1f/8f/8f9e56c5e82eb2c26e8cde787962e66494312dc8cb261c460e1f3a9c88bc/greenlet-3.2.3-cp312-cp312-win_amd64.whl", hash = "sha256:7454d37c740bb27bdeddfc3f358f26956a07d5220818ceb467a483197d84f849", size = 297817 }, + { url = "https://files.pythonhosted.org/packages/b1/cf/f5c0b23309070ae93de75c90d29300751a5aacefc0a3ed1b1d8edb28f08b/greenlet-3.2.3-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:500b8689aa9dd1ab26872a34084503aeddefcb438e2e7317b89b11eaea1901ad", size = 270732 }, + { url = "https://files.pythonhosted.org/packages/48/ae/91a957ba60482d3fecf9be49bc3948f341d706b52ddb9d83a70d42abd498/greenlet-3.2.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a07d3472c2a93117af3b0136f246b2833fdc0b542d4a9799ae5f41c28323faef", size = 639033 }, + { url = "https://files.pythonhosted.org/packages/6f/df/20ffa66dd5a7a7beffa6451bdb7400d66251374ab40b99981478c69a67a8/greenlet-3.2.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:8704b3768d2f51150626962f4b9a9e4a17d2e37c8a8d9867bbd9fa4eb938d3b3", size = 652999 }, + { url = "https://files.pythonhosted.org/packages/51/b4/ebb2c8cb41e521f1d72bf0465f2f9a2fd803f674a88db228887e6847077e/greenlet-3.2.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:5035d77a27b7c62db6cf41cf786cfe2242644a7a337a0e155c80960598baab95", size = 647368 }, + { url = "https://files.pythonhosted.org/packages/8e/6a/1e1b5aa10dced4ae876a322155705257748108b7fd2e4fae3f2a091fe81a/greenlet-3.2.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2d8aa5423cd4a396792f6d4580f88bdc6efcb9205891c9d40d20f6e670992efb", size = 650037 }, + { url = "https://files.pythonhosted.org/packages/26/f2/ad51331a157c7015c675702e2d5230c243695c788f8f75feba1af32b3617/greenlet-3.2.3-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2c724620a101f8170065d7dded3f962a2aea7a7dae133a009cada42847e04a7b", size = 608402 }, + { url = "https://files.pythonhosted.org/packages/26/bc/862bd2083e6b3aff23300900a956f4ea9a4059de337f5c8734346b9b34fc/greenlet-3.2.3-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:873abe55f134c48e1f2a6f53f7d1419192a3d1a4e873bace00499a4e45ea6af0", size = 1119577 }, + { url = "https://files.pythonhosted.org/packages/86/94/1fc0cc068cfde885170e01de40a619b00eaa8f2916bf3541744730ffb4c3/greenlet-3.2.3-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:024571bbce5f2c1cfff08bf3fbaa43bbc7444f580ae13b0099e95d0e6e67ed36", size = 1147121 }, + { url = "https://files.pythonhosted.org/packages/27/1a/199f9587e8cb08a0658f9c30f3799244307614148ffe8b1e3aa22f324dea/greenlet-3.2.3-cp313-cp313-win_amd64.whl", hash = "sha256:5195fb1e75e592dd04ce79881c8a22becdfa3e6f500e7feb059b1e6fdd54d3e3", size = 297603 }, + { url = "https://files.pythonhosted.org/packages/d8/ca/accd7aa5280eb92b70ed9e8f7fd79dc50a2c21d8c73b9a0856f5b564e222/greenlet-3.2.3-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:3d04332dddb10b4a211b68111dabaee2e1a073663d117dc10247b5b1642bac86", size = 271479 }, + { url = "https://files.pythonhosted.org/packages/55/71/01ed9895d9eb49223280ecc98a557585edfa56b3d0e965b9fa9f7f06b6d9/greenlet-3.2.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8186162dffde068a465deab08fc72c767196895c39db26ab1c17c0b77a6d8b97", size = 683952 }, + { url = "https://files.pythonhosted.org/packages/ea/61/638c4bdf460c3c678a0a1ef4c200f347dff80719597e53b5edb2fb27ab54/greenlet-3.2.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f4bfbaa6096b1b7a200024784217defedf46a07c2eee1a498e94a1b5f8ec5728", size = 696917 }, + { url = "https://files.pythonhosted.org/packages/22/cc/0bd1a7eb759d1f3e3cc2d1bc0f0b487ad3cc9f34d74da4b80f226fde4ec3/greenlet-3.2.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:ed6cfa9200484d234d8394c70f5492f144b20d4533f69262d530a1a082f6ee9a", size = 692443 }, + { url = "https://files.pythonhosted.org/packages/67/10/b2a4b63d3f08362662e89c103f7fe28894a51ae0bc890fabf37d1d780e52/greenlet-3.2.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:02b0df6f63cd15012bed5401b47829cfd2e97052dc89da3cfaf2c779124eb892", size = 692995 }, + { url = "https://files.pythonhosted.org/packages/5a/c6/ad82f148a4e3ce9564056453a71529732baf5448ad53fc323e37efe34f66/greenlet-3.2.3-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:86c2d68e87107c1792e2e8d5399acec2487a4e993ab76c792408e59394d52141", size = 655320 }, + { url = "https://files.pythonhosted.org/packages/5c/4f/aab73ecaa6b3086a4c89863d94cf26fa84cbff63f52ce9bc4342b3087a06/greenlet-3.2.3-cp314-cp314-win_amd64.whl", hash = "sha256:8c47aae8fbbfcf82cc13327ae802ba13c9c36753b67e760023fd116bc124a62a", size = 301236 }, +] + +[[package]] +name = "h11" +version = "0.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515 }, +] + +[[package]] +name = "h2" +version = "4.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "hpack" }, + { name = "hyperframe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1b/38/d7f80fd13e6582fb8e0df8c9a653dcc02b03ca34f4d72f34869298c5baf8/h2-4.2.0.tar.gz", hash = "sha256:c8a52129695e88b1a0578d8d2cc6842bbd79128ac685463b887ee278126ad01f", size = 2150682 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d0/9e/984486f2d0a0bd2b024bf4bc1c62688fcafa9e61991f041fb0e2def4a982/h2-4.2.0-py3-none-any.whl", hash = "sha256:479a53ad425bb29af087f3458a61d30780bc818e4ebcf01f0b536ba916462ed0", size = 60957 }, +] + +[[package]] +name = "h5netcdf" +version = "1.6.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "h5py" }, + { name = "packaging" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/46/9b/16f61d006227059451ad7388235a547a2dcf6f36503545d06df36bb4da97/h5netcdf-1.6.3.tar.gz", hash = "sha256:a8fededcc30f933389168ece94963bee54545546772d9e4dccadbb990dd5651e", size = 65622 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4e/27/ca28b77f4cf613282e4ea9bac360cdfa8db8b4d2154e2f0bd82fd26baeb3/h5netcdf-1.6.3-py3-none-any.whl", hash = "sha256:b79bc24d1f8b1cdc1f16b213753209411e08af04e35b0bcf5ce5cf7fb4023572", size = 50531 }, +] + +[[package]] +name = "h5py" +version = "3.14.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5d/57/dfb3c5c3f1bf5f5ef2e59a22dec4ff1f3d7408b55bfcefcfb0ea69ef21c6/h5py-3.14.0.tar.gz", hash = "sha256:2372116b2e0d5d3e5e705b7f663f7c8d96fa79a4052d250484ef91d24d6a08f4", size = 424323 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3e/77/8f651053c1843391e38a189ccf50df7e261ef8cd8bfd8baba0cbe694f7c3/h5py-3.14.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:e0045115d83272090b0717c555a31398c2c089b87d212ceba800d3dc5d952e23", size = 3312740 }, + { url = "https://files.pythonhosted.org/packages/ff/10/20436a6cf419b31124e59fefc78d74cb061ccb22213226a583928a65d715/h5py-3.14.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6da62509b7e1d71a7d110478aa25d245dd32c8d9a1daee9d2a42dba8717b047a", size = 2829207 }, + { url = "https://files.pythonhosted.org/packages/3f/19/c8bfe8543bfdd7ccfafd46d8cfd96fce53d6c33e9c7921f375530ee1d39a/h5py-3.14.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:554ef0ced3571366d4d383427c00c966c360e178b5fb5ee5bb31a435c424db0c", size = 4708455 }, + { url = "https://files.pythonhosted.org/packages/86/f9/f00de11c82c88bfc1ef22633557bfba9e271e0cb3189ad704183fc4a2644/h5py-3.14.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0cbd41f4e3761f150aa5b662df991868ca533872c95467216f2bec5fcad84882", size = 4929422 }, + { url = "https://files.pythonhosted.org/packages/7a/6d/6426d5d456f593c94b96fa942a9b3988ce4d65ebaf57d7273e452a7222e8/h5py-3.14.0-cp312-cp312-win_amd64.whl", hash = "sha256:bf4897d67e613ecf5bdfbdab39a1158a64df105827da70ea1d90243d796d367f", size = 2862845 }, + { url = "https://files.pythonhosted.org/packages/6c/c2/7efe82d09ca10afd77cd7c286e42342d520c049a8c43650194928bcc635c/h5py-3.14.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:aa4b7bbce683379b7bf80aaba68e17e23396100336a8d500206520052be2f812", size = 3289245 }, + { url = "https://files.pythonhosted.org/packages/4f/31/f570fab1239b0d9441024b92b6ad03bb414ffa69101a985e4c83d37608bd/h5py-3.14.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ef9603a501a04fcd0ba28dd8f0995303d26a77a980a1f9474b3417543d4c6174", size = 2807335 }, + { url = "https://files.pythonhosted.org/packages/0d/ce/3a21d87896bc7e3e9255e0ad5583ae31ae9e6b4b00e0bcb2a67e2b6acdbc/h5py-3.14.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8cbaf6910fa3983c46172666b0b8da7b7bd90d764399ca983236f2400436eeb", size = 4700675 }, + { url = "https://files.pythonhosted.org/packages/e7/ec/86f59025306dcc6deee5fda54d980d077075b8d9889aac80f158bd585f1b/h5py-3.14.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d90e6445ab7c146d7f7981b11895d70bc1dd91278a4f9f9028bc0c95e4a53f13", size = 4921632 }, + { url = "https://files.pythonhosted.org/packages/3f/6d/0084ed0b78d4fd3e7530c32491f2884140d9b06365dac8a08de726421d4a/h5py-3.14.0-cp313-cp313-win_amd64.whl", hash = "sha256:ae18e3de237a7a830adb76aaa68ad438d85fe6e19e0d99944a3ce46b772c69b3", size = 2852929 }, +] + +[[package]] +name = "hexbytes" +version = "0.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c1/94/fbfd526e8964652eec6a7b74ae18d1426e225ab602553858531ec6567d05/hexbytes-0.3.1.tar.gz", hash = "sha256:a3fe35c6831ee8fafd048c4c086b986075fc14fd46258fa24ecb8d65745f9a9d", size = 6188 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0b/9e/fdfe374c28d448a58563e7e43f569f8cf8cf600db092efac2e8ac2f86782/hexbytes-0.3.1-py3-none-any.whl", hash = "sha256:383595ad75026cf00abd570f44b368c6cdac0c6becfae5c39ff88829877f8a59", size = 5944 }, +] + +[[package]] +name = "hpack" +version = "4.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2c/48/71de9ed269fdae9c8057e5a4c0aa7402e8bb16f2c6e90b3aa53327b113f8/hpack-4.1.0.tar.gz", hash = "sha256:ec5eca154f7056aa06f196a557655c5b009b382873ac8d1e66e79e87535f1dca", size = 51276 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/07/c6/80c95b1b2b94682a72cbdbfb85b81ae2daffa4291fbfa1b1464502ede10d/hpack-4.1.0-py3-none-any.whl", hash = "sha256:157ac792668d995c657d93111f46b4535ed114f0c9c8d672271bbec7eae1b496", size = 34357 }, +] + +[[package]] +name = "httpcore" +version = "1.0.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784 }, +] + +[[package]] +name = "httpx" +version = "0.28.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "certifi" }, + { name = "httpcore" }, + { name = "idna" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517 }, +] + +[package.optional-dependencies] +http2 = [ + { name = "h2" }, +] + +[[package]] +name = "hyperframe" +version = "6.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/02/e7/94f8232d4a74cc99514c13a9f995811485a6903d48e5d952771ef6322e30/hyperframe-6.1.0.tar.gz", hash = "sha256:f630908a00854a7adeabd6382b43923a4c4cd4b821fcb527e6ab9e15382a3b08", size = 26566 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/48/30/47d0bf6072f7252e6521f3447ccfa40b421b6824517f82854703d0f5a98b/hyperframe-6.1.0-py3-none-any.whl", hash = "sha256:b03380493a519fce58ea5af42e4a42317bf9bd425596f7a0835ffce80f1a42e5", size = 13007 }, +] + +[[package]] +name = "identify" +version = "2.6.9" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9b/98/a71ab060daec766acc30fb47dfca219d03de34a70d616a79a38c6066c5bf/identify-2.6.9.tar.gz", hash = "sha256:d40dfe3142a1421d8518e3d3985ef5ac42890683e32306ad614a29490abeb6bf", size = 99249 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/07/ce/0845144ed1f0e25db5e7a79c2354c1da4b5ce392b8966449d5db8dca18f1/identify-2.6.9-py2.py3-none-any.whl", hash = "sha256:c98b4322da415a8e5a70ff6e51fbc2d2932c015532d77e9f8537b4ba7813b150", size = 99101 }, +] + +[[package]] +name = "idna" +version = "3.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 }, +] + +[[package]] +name = "ijson" +version = "3.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a3/4f/1cfeada63f5fce87536651268ddf5cca79b8b4bbb457aee4e45777964a0a/ijson-3.4.0.tar.gz", hash = "sha256:5f74dcbad9d592c428d3ca3957f7115a42689ee7ee941458860900236ae9bb13", size = 65782 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f8/ec/317ee5b2d13e50448833ead3aa906659a32b376191f6abc2a7c6112d2b27/ijson-3.4.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:956b148f88259a80a9027ffbe2d91705fae0c004fbfba3e5a24028fbe72311a9", size = 87212 }, + { url = "https://files.pythonhosted.org/packages/f8/43/b06c96ced30cacecc5d518f89b0fd1c98c294a30ff88848b70ed7b7f72a1/ijson-3.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:06b89960f5c721106394c7fba5760b3f67c515b8eb7d80f612388f5eca2f4621", size = 59175 }, + { url = "https://files.pythonhosted.org/packages/e9/df/b4aeafb7ecde463130840ee9be36130823ec94a00525049bf700883378b8/ijson-3.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9a0bb591cf250dd7e9dfab69d634745a7f3272d31cfe879f9156e0a081fd97ee", size = 59011 }, + { url = "https://files.pythonhosted.org/packages/e3/7c/a80b8e361641609507f62022089626d4b8067f0826f51e1c09e4ba86eba8/ijson-3.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:72e92de999977f4c6b660ffcf2b8d59604ccd531edcbfde05b642baf283e0de8", size = 146094 }, + { url = "https://files.pythonhosted.org/packages/01/44/fa416347b9a802e3646c6ff377fc3278bd7d6106e17beb339514b6a3184e/ijson-3.4.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9e9602157a5b869d44b6896e64f502c712a312fcde044c2e586fccb85d3e316e", size = 137903 }, + { url = "https://files.pythonhosted.org/packages/24/c6/41a9ad4d42df50ff6e70fdce79b034f09b914802737ebbdc141153d8d791/ijson-3.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1e83660edb931a425b7ff662eb49db1f10d30ca6d4d350e5630edbed098bc01", size = 148339 }, + { url = "https://files.pythonhosted.org/packages/5f/6f/7d01efda415b8502dce67e067ed9e8a124f53e763002c02207e542e1a2f1/ijson-3.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:49bf8eac1c7b7913073865a859c215488461f7591b4fa6a33c14b51cb73659d0", size = 149383 }, + { url = "https://files.pythonhosted.org/packages/95/6c/0d67024b9ecb57916c5e5ab0350251c9fe2f86dc9c8ca2b605c194bdad6a/ijson-3.4.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:160b09273cb42019f1811469508b0a057d19f26434d44752bde6f281da6d3f32", size = 141580 }, + { url = "https://files.pythonhosted.org/packages/06/43/e10edcc1c6a3b619294de835e7678bfb3a1b8a75955f3689fd66a1e9e7b4/ijson-3.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2019ff4e6f354aa00c76c8591bd450899111c61f2354ad55cc127e2ce2492c44", size = 150280 }, + { url = "https://files.pythonhosted.org/packages/07/84/1cbeee8e8190a1ebe6926569a92cf1fa80ddb380c129beb6f86559e1bb24/ijson-3.4.0-cp312-cp312-win32.whl", hash = "sha256:931c007bf6bb8330705429989b2deed6838c22b63358a330bf362b6e458ba0bf", size = 51512 }, + { url = "https://files.pythonhosted.org/packages/66/13/530802bc391c95be6fe9f96e9aa427d94067e7c0b7da7a9092344dc44c4b/ijson-3.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:71523f2b64cb856a820223e94d23e88369f193017ecc789bb4de198cc9d349eb", size = 54081 }, + { url = "https://files.pythonhosted.org/packages/77/b3/b1d2eb2745e5204ec7a25365a6deb7868576214feb5e109bce368fb692c9/ijson-3.4.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e8d96f88d75196a61c9d9443de2b72c2d4a7ba9456ff117b57ae3bba23a54256", size = 87216 }, + { url = "https://files.pythonhosted.org/packages/b1/cd/cd6d340087617f8cc9bedbb21d974542fe2f160ed0126b8288d3499a469b/ijson-3.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c45906ce2c1d3b62f15645476fc3a6ca279549127f01662a39ca5ed334a00cf9", size = 59170 }, + { url = "https://files.pythonhosted.org/packages/3e/4d/32d3a9903b488d3306e3c8288f6ee4217d2eea82728261db03a1045eb5d1/ijson-3.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4ab4bc2119b35c4363ea49f29563612237cae9413d2fbe54b223be098b97bc9e", size = 59013 }, + { url = "https://files.pythonhosted.org/packages/d5/c8/db15465ab4b0b477cee5964c8bfc94bf8c45af8e27a23e1ad78d1926e587/ijson-3.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:97b0a9b5a15e61dfb1f14921ea4e0dba39f3a650df6d8f444ddbc2b19b479ff1", size = 146564 }, + { url = "https://files.pythonhosted.org/packages/c4/d8/0755545bc122473a9a434ab90e0f378780e603d75495b1ca3872de757873/ijson-3.4.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e3047bb994dabedf11de11076ed1147a307924b6e5e2df6784fb2599c4ad8c60", size = 137917 }, + { url = "https://files.pythonhosted.org/packages/d0/c6/aeb89c8939ebe3f534af26c8c88000c5e870dbb6ae33644c21a4531f87d2/ijson-3.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:68c83161b052e9f5dc8191acbc862bb1e63f8a35344cb5cd0db1afd3afd487a6", size = 148897 }, + { url = "https://files.pythonhosted.org/packages/be/0e/7ef6e9b372106f2682a4a32b3c65bf86bb471a1670e4dac242faee4a7d3f/ijson-3.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1eebd9b6c20eb1dffde0ae1f0fbb4aeacec2eb7b89adb5c7c0449fc9fd742760", size = 149711 }, + { url = "https://files.pythonhosted.org/packages/d1/5d/9841c3ed75bcdabf19b3202de5f862a9c9c86ce5c7c9d95fa32347fdbf5f/ijson-3.4.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:13fb6d5c35192c541421f3ee81239d91fc15a8d8f26c869250f941f4b346a86c", size = 141691 }, + { url = "https://files.pythonhosted.org/packages/d5/d2/ce74e17218dba292e9be10a44ed0c75439f7958cdd263adb0b5b92d012d5/ijson-3.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:28b7196ff7b37c4897c547a28fa4876919696739fc91c1f347651c9736877c69", size = 150738 }, + { url = "https://files.pythonhosted.org/packages/4e/43/dcc480f94453b1075c9911d4755b823f3ace275761bb37b40139f22109ca/ijson-3.4.0-cp313-cp313-win32.whl", hash = "sha256:3c2691d2da42629522140f77b99587d6f5010440d58d36616f33bc7bdc830cc3", size = 51512 }, + { url = "https://files.pythonhosted.org/packages/35/dd/d8c5f15efd85ba51e6e11451ebe23d779361a9ec0d192064c2a8c3cdfcb8/ijson-3.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:c4554718c275a044c47eb3874f78f2c939f300215d9031e785a6711cc51b83fc", size = 54074 }, + { url = "https://files.pythonhosted.org/packages/79/73/24ad8cd106203419c4d22bed627e02e281d66b83e91bc206a371893d0486/ijson-3.4.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:915a65e3f3c0eee2ea937bc62aaedb6c14cc1e8f0bb9f3f4fb5a9e2bbfa4b480", size = 91694 }, + { url = "https://files.pythonhosted.org/packages/17/2d/f7f680984bcb7324a46a4c2df3bd73cf70faef0acfeb85a3f811abdfd590/ijson-3.4.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:afbe9748707684b6c5adc295c4fdcf27765b300aec4d484e14a13dca4e5c0afa", size = 61390 }, + { url = "https://files.pythonhosted.org/packages/09/a1/f3ca7bab86f95bdb82494739e71d271410dfefce4590785d511669127145/ijson-3.4.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:d823f8f321b4d8d5fa020d0a84f089fec5d52b7c0762430476d9f8bf95bbc1a9", size = 61140 }, + { url = "https://files.pythonhosted.org/packages/51/79/dd340df3d4fc7771c95df29997956b92ed0570fe7b616d1792fea9ad93f2/ijson-3.4.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b8a0a2c54f3becf76881188beefd98b484b1d3bd005769a740d5b433b089fa23", size = 214739 }, + { url = "https://files.pythonhosted.org/packages/59/f0/85380b7f51d1f5fb7065d76a7b623e02feca920cc678d329b2eccc0011e0/ijson-3.4.0-cp313-cp313t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ced19a83ab09afa16257a0b15bc1aa888dbc555cb754be09d375c7f8d41051f2", size = 198338 }, + { url = "https://files.pythonhosted.org/packages/a5/cd/313264cf2ec42e0f01d198c49deb7b6fadeb793b3685e20e738eb6b3fa13/ijson-3.4.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8100f9885eff1f38d35cef80ef759a1bbf5fc946349afa681bd7d0e681b7f1a0", size = 207515 }, + { url = "https://files.pythonhosted.org/packages/12/94/bf14457aa87ea32641f2db577c9188ef4e4ae373478afef422b31fc7f309/ijson-3.4.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:d7bcc3f7f21b0f703031ecd15209b1284ea51b2a329d66074b5261de3916c1eb", size = 210081 }, + { url = "https://files.pythonhosted.org/packages/7d/b4/eaee39e290e40e52d665db9bd1492cfdce86bd1e47948e0440db209c6023/ijson-3.4.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:2dcb190227b09dd171bdcbfe4720fddd574933c66314818dfb3960c8a6246a77", size = 199253 }, + { url = "https://files.pythonhosted.org/packages/c5/9c/e09c7b9ac720a703ab115b221b819f149ed54c974edfff623c1e925e57da/ijson-3.4.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:eda4cfb1d49c6073a901735aaa62e39cb7ab47f3ad7bb184862562f776f1fa8a", size = 203816 }, + { url = "https://files.pythonhosted.org/packages/7c/14/acd304f412e32d16a2c12182b9d78206bb0ae35354d35664f45db05c1b3b/ijson-3.4.0-cp313-cp313t-win32.whl", hash = "sha256:0772638efa1f3b72b51736833404f1cbd2f5beeb9c1a3d392e7d385b9160cba7", size = 53760 }, + { url = "https://files.pythonhosted.org/packages/2f/24/93dd0a467191590a5ed1fc2b35842bca9d09900d001e00b0b497c0208ef6/ijson-3.4.0-cp313-cp313t-win_amd64.whl", hash = "sha256:3d8a0d67f36e4fb97c61a724456ef0791504b16ce6f74917a31c2e92309bbeb9", size = 56948 }, +] + +[[package]] +name = "iniconfig" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050 }, +] + +[[package]] +name = "ipython" +version = "8.37.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "decorator" }, + { name = "jedi" }, + { name = "matplotlib-inline" }, + { name = "pexpect", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" }, + { name = "prompt-toolkit" }, + { name = "pygments" }, + { name = "stack-data" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/85/31/10ac88f3357fc276dc8a64e8880c82e80e7459326ae1d0a211b40abf6665/ipython-8.37.0.tar.gz", hash = "sha256:ca815841e1a41a1e6b73a0b08f3038af9b2252564d01fc405356d34033012216", size = 5606088 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/91/d0/274fbf7b0b12643cbbc001ce13e6a5b1607ac4929d1b11c72460152c9fc3/ipython-8.37.0-py3-none-any.whl", hash = "sha256:ed87326596b878932dbcb171e3e698845434d8c61b8d8cd474bf663041a9dcf2", size = 831864 }, +] + +[[package]] +name = "jedi" +version = "0.19.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "parso" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/72/3a/79a912fbd4d8dd6fbb02bf69afd3bb72cf0c729bb3063c6f4498603db17a/jedi-0.19.2.tar.gz", hash = "sha256:4770dc3de41bde3966b02eb84fbcf557fb33cce26ad23da12c742fb50ecb11f0", size = 1231287 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl", hash = "sha256:a8ef22bde8490f57fe5c7681a3c83cb58874daf72b4784de3cce5b6ef6edb5b9", size = 1572278 }, +] + +[[package]] +name = "jinja2" +version = "3.1.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899 }, +] + +[[package]] +name = "jmespath" +version = "1.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/00/2a/e867e8531cf3e36b41201936b7fa7ba7b5702dbef42922193f05c8976cd6/jmespath-1.0.1.tar.gz", hash = "sha256:90261b206d6defd58fdd5e85f478bf633a2901798906be2ad389150c5c60edbe", size = 25843 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/31/b4/b9b800c45527aadd64d5b442f9b932b00648617eb5d63d2c7a6587b7cafc/jmespath-1.0.1-py3-none-any.whl", hash = "sha256:02e2e4cc71b5bcab88332eebf907519190dd9e6e82107fa7f83b1003a6252980", size = 20256 }, +] + +[[package]] +name = "jsonschema" +version = "4.24.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "jsonschema-specifications" }, + { name = "referencing" }, + { name = "rpds-py" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bf/d3/1cf5326b923a53515d8f3a2cd442e6d7e94fcc444716e879ea70a0ce3177/jsonschema-4.24.0.tar.gz", hash = "sha256:0b4e8069eb12aedfa881333004bccaec24ecef5a8a6a4b6df142b2cc9599d196", size = 353480 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a2/3d/023389198f69c722d039351050738d6755376c8fd343e91dc493ea485905/jsonschema-4.24.0-py3-none-any.whl", hash = "sha256:a462455f19f5faf404a7902952b6f0e3ce868f3ee09a359b05eca6673bd8412d", size = 88709 }, +] + +[[package]] +name = "jsonschema-specifications" +version = "2025.4.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "referencing" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bf/ce/46fbd9c8119cfc3581ee5643ea49464d168028cfb5caff5fc0596d0cf914/jsonschema_specifications-2025.4.1.tar.gz", hash = "sha256:630159c9f4dbea161a6a2205c3011cc4f18ff381b189fff48bb39b9bf26ae608", size = 15513 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/01/0e/b27cdbaccf30b890c40ed1da9fd4a3593a5cf94dae54fb34f8a4b74fcd3f/jsonschema_specifications-2025.4.1-py3-none-any.whl", hash = "sha256:4653bffbd6584f7de83a67e0d620ef16900b390ddc7939d56684d6c81e33f1af", size = 18437 }, +] + +[[package]] +name = "kiwisolver" +version = "1.4.8" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/82/59/7c91426a8ac292e1cdd53a63b6d9439abd573c875c3f92c146767dd33faf/kiwisolver-1.4.8.tar.gz", hash = "sha256:23d5f023bdc8c7e54eb65f03ca5d5bb25b601eac4d7f1a042888a1f45237987e", size = 97538 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fc/aa/cea685c4ab647f349c3bc92d2daf7ae34c8e8cf405a6dcd3a497f58a2ac3/kiwisolver-1.4.8-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d6af5e8815fd02997cb6ad9bbed0ee1e60014438ee1a5c2444c96f87b8843502", size = 124152 }, + { url = "https://files.pythonhosted.org/packages/c5/0b/8db6d2e2452d60d5ebc4ce4b204feeb16176a851fd42462f66ade6808084/kiwisolver-1.4.8-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:bade438f86e21d91e0cf5dd7c0ed00cda0f77c8c1616bd83f9fc157fa6760d31", size = 66555 }, + { url = "https://files.pythonhosted.org/packages/60/26/d6a0db6785dd35d3ba5bf2b2df0aedc5af089962c6eb2cbf67a15b81369e/kiwisolver-1.4.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b83dc6769ddbc57613280118fb4ce3cd08899cc3369f7d0e0fab518a7cf37fdb", size = 65067 }, + { url = "https://files.pythonhosted.org/packages/c9/ed/1d97f7e3561e09757a196231edccc1bcf59d55ddccefa2afc9c615abd8e0/kiwisolver-1.4.8-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:111793b232842991be367ed828076b03d96202c19221b5ebab421ce8bcad016f", size = 1378443 }, + { url = "https://files.pythonhosted.org/packages/29/61/39d30b99954e6b46f760e6289c12fede2ab96a254c443639052d1b573fbc/kiwisolver-1.4.8-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:257af1622860e51b1a9d0ce387bf5c2c4f36a90594cb9514f55b074bcc787cfc", size = 1472728 }, + { url = "https://files.pythonhosted.org/packages/0c/3e/804163b932f7603ef256e4a715e5843a9600802bb23a68b4e08c8c0ff61d/kiwisolver-1.4.8-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:69b5637c3f316cab1ec1c9a12b8c5f4750a4c4b71af9157645bf32830e39c03a", size = 1478388 }, + { url = "https://files.pythonhosted.org/packages/8a/9e/60eaa75169a154700be74f875a4d9961b11ba048bef315fbe89cb6999056/kiwisolver-1.4.8-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:782bb86f245ec18009890e7cb8d13a5ef54dcf2ebe18ed65f795e635a96a1c6a", size = 1413849 }, + { url = "https://files.pythonhosted.org/packages/bc/b3/9458adb9472e61a998c8c4d95cfdfec91c73c53a375b30b1428310f923e4/kiwisolver-1.4.8-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc978a80a0db3a66d25767b03688f1147a69e6237175c0f4ffffaaedf744055a", size = 1475533 }, + { url = "https://files.pythonhosted.org/packages/e4/7a/0a42d9571e35798de80aef4bb43a9b672aa7f8e58643d7bd1950398ffb0a/kiwisolver-1.4.8-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:36dbbfd34838500a31f52c9786990d00150860e46cd5041386f217101350f0d3", size = 2268898 }, + { url = "https://files.pythonhosted.org/packages/d9/07/1255dc8d80271400126ed8db35a1795b1a2c098ac3a72645075d06fe5c5d/kiwisolver-1.4.8-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:eaa973f1e05131de5ff3569bbba7f5fd07ea0595d3870ed4a526d486fe57fa1b", size = 2425605 }, + { url = "https://files.pythonhosted.org/packages/84/df/5a3b4cf13780ef6f6942df67b138b03b7e79e9f1f08f57c49957d5867f6e/kiwisolver-1.4.8-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:a66f60f8d0c87ab7f59b6fb80e642ebb29fec354a4dfad687ca4092ae69d04f4", size = 2375801 }, + { url = "https://files.pythonhosted.org/packages/8f/10/2348d068e8b0f635c8c86892788dac7a6b5c0cb12356620ab575775aad89/kiwisolver-1.4.8-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:858416b7fb777a53f0c59ca08190ce24e9abbd3cffa18886a5781b8e3e26f65d", size = 2520077 }, + { url = "https://files.pythonhosted.org/packages/32/d8/014b89fee5d4dce157d814303b0fce4d31385a2af4c41fed194b173b81ac/kiwisolver-1.4.8-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:085940635c62697391baafaaeabdf3dd7a6c3643577dde337f4d66eba021b2b8", size = 2338410 }, + { url = "https://files.pythonhosted.org/packages/bd/72/dfff0cc97f2a0776e1c9eb5bef1ddfd45f46246c6533b0191887a427bca5/kiwisolver-1.4.8-cp312-cp312-win_amd64.whl", hash = "sha256:01c3d31902c7db5fb6182832713d3b4122ad9317c2c5877d0539227d96bb2e50", size = 71853 }, + { url = "https://files.pythonhosted.org/packages/dc/85/220d13d914485c0948a00f0b9eb419efaf6da81b7d72e88ce2391f7aed8d/kiwisolver-1.4.8-cp312-cp312-win_arm64.whl", hash = "sha256:a3c44cb68861de93f0c4a8175fbaa691f0aa22550c331fefef02b618a9dcb476", size = 65424 }, + { url = "https://files.pythonhosted.org/packages/79/b3/e62464a652f4f8cd9006e13d07abad844a47df1e6537f73ddfbf1bc997ec/kiwisolver-1.4.8-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:1c8ceb754339793c24aee1c9fb2485b5b1f5bb1c2c214ff13368431e51fc9a09", size = 124156 }, + { url = "https://files.pythonhosted.org/packages/8d/2d/f13d06998b546a2ad4f48607a146e045bbe48030774de29f90bdc573df15/kiwisolver-1.4.8-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:54a62808ac74b5e55a04a408cda6156f986cefbcf0ada13572696b507cc92fa1", size = 66555 }, + { url = "https://files.pythonhosted.org/packages/59/e3/b8bd14b0a54998a9fd1e8da591c60998dc003618cb19a3f94cb233ec1511/kiwisolver-1.4.8-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:68269e60ee4929893aad82666821aaacbd455284124817af45c11e50a4b42e3c", size = 65071 }, + { url = "https://files.pythonhosted.org/packages/f0/1c/6c86f6d85ffe4d0ce04228d976f00674f1df5dc893bf2dd4f1928748f187/kiwisolver-1.4.8-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:34d142fba9c464bc3bbfeff15c96eab0e7310343d6aefb62a79d51421fcc5f1b", size = 1378053 }, + { url = "https://files.pythonhosted.org/packages/4e/b9/1c6e9f6dcb103ac5cf87cb695845f5fa71379021500153566d8a8a9fc291/kiwisolver-1.4.8-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ddc373e0eef45b59197de815b1b28ef89ae3955e7722cc9710fb91cd77b7f47", size = 1472278 }, + { url = "https://files.pythonhosted.org/packages/ee/81/aca1eb176de671f8bda479b11acdc42c132b61a2ac861c883907dde6debb/kiwisolver-1.4.8-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:77e6f57a20b9bd4e1e2cedda4d0b986ebd0216236f0106e55c28aea3d3d69b16", size = 1478139 }, + { url = "https://files.pythonhosted.org/packages/49/f4/e081522473671c97b2687d380e9e4c26f748a86363ce5af48b4a28e48d06/kiwisolver-1.4.8-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:08e77738ed7538f036cd1170cbed942ef749137b1311fa2bbe2a7fda2f6bf3cc", size = 1413517 }, + { url = "https://files.pythonhosted.org/packages/8f/e9/6a7d025d8da8c4931522922cd706105aa32b3291d1add8c5427cdcd66e63/kiwisolver-1.4.8-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a5ce1e481a74b44dd5e92ff03ea0cb371ae7a0268318e202be06c8f04f4f1246", size = 1474952 }, + { url = "https://files.pythonhosted.org/packages/82/13/13fa685ae167bee5d94b415991c4fc7bb0a1b6ebea6e753a87044b209678/kiwisolver-1.4.8-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:fc2ace710ba7c1dfd1a3b42530b62b9ceed115f19a1656adefce7b1782a37794", size = 2269132 }, + { url = "https://files.pythonhosted.org/packages/ef/92/bb7c9395489b99a6cb41d502d3686bac692586db2045adc19e45ee64ed23/kiwisolver-1.4.8-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:3452046c37c7692bd52b0e752b87954ef86ee2224e624ef7ce6cb21e8c41cc1b", size = 2425997 }, + { url = "https://files.pythonhosted.org/packages/ed/12/87f0e9271e2b63d35d0d8524954145837dd1a6c15b62a2d8c1ebe0f182b4/kiwisolver-1.4.8-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:7e9a60b50fe8b2ec6f448fe8d81b07e40141bfced7f896309df271a0b92f80f3", size = 2376060 }, + { url = "https://files.pythonhosted.org/packages/02/6e/c8af39288edbce8bf0fa35dee427b082758a4b71e9c91ef18fa667782138/kiwisolver-1.4.8-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:918139571133f366e8362fa4a297aeba86c7816b7ecf0bc79168080e2bd79957", size = 2520471 }, + { url = "https://files.pythonhosted.org/packages/13/78/df381bc7b26e535c91469f77f16adcd073beb3e2dd25042efd064af82323/kiwisolver-1.4.8-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e063ef9f89885a1d68dd8b2e18f5ead48653176d10a0e324e3b0030e3a69adeb", size = 2338793 }, + { url = "https://files.pythonhosted.org/packages/d0/dc/c1abe38c37c071d0fc71c9a474fd0b9ede05d42f5a458d584619cfd2371a/kiwisolver-1.4.8-cp313-cp313-win_amd64.whl", hash = "sha256:a17b7c4f5b2c51bb68ed379defd608a03954a1845dfed7cc0117f1cc8a9b7fd2", size = 71855 }, + { url = "https://files.pythonhosted.org/packages/a0/b6/21529d595b126ac298fdd90b705d87d4c5693de60023e0efcb4f387ed99e/kiwisolver-1.4.8-cp313-cp313-win_arm64.whl", hash = "sha256:3cd3bc628b25f74aedc6d374d5babf0166a92ff1317f46267f12d2ed54bc1d30", size = 65430 }, + { url = "https://files.pythonhosted.org/packages/34/bd/b89380b7298e3af9b39f49334e3e2a4af0e04819789f04b43d560516c0c8/kiwisolver-1.4.8-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:370fd2df41660ed4e26b8c9d6bbcad668fbe2560462cba151a721d49e5b6628c", size = 126294 }, + { url = "https://files.pythonhosted.org/packages/83/41/5857dc72e5e4148eaac5aa76e0703e594e4465f8ab7ec0fc60e3a9bb8fea/kiwisolver-1.4.8-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:84a2f830d42707de1d191b9490ac186bf7997a9495d4e9072210a1296345f7dc", size = 67736 }, + { url = "https://files.pythonhosted.org/packages/e1/d1/be059b8db56ac270489fb0b3297fd1e53d195ba76e9bbb30e5401fa6b759/kiwisolver-1.4.8-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:7a3ad337add5148cf51ce0b55642dc551c0b9d6248458a757f98796ca7348712", size = 66194 }, + { url = "https://files.pythonhosted.org/packages/e1/83/4b73975f149819eb7dcf9299ed467eba068ecb16439a98990dcb12e63fdd/kiwisolver-1.4.8-cp313-cp313t-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7506488470f41169b86d8c9aeff587293f530a23a23a49d6bc64dab66bedc71e", size = 1465942 }, + { url = "https://files.pythonhosted.org/packages/c7/2c/30a5cdde5102958e602c07466bce058b9d7cb48734aa7a4327261ac8e002/kiwisolver-1.4.8-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f0121b07b356a22fb0414cec4666bbe36fd6d0d759db3d37228f496ed67c880", size = 1595341 }, + { url = "https://files.pythonhosted.org/packages/ff/9b/1e71db1c000385aa069704f5990574b8244cce854ecd83119c19e83c9586/kiwisolver-1.4.8-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d6d6bd87df62c27d4185de7c511c6248040afae67028a8a22012b010bc7ad062", size = 1598455 }, + { url = "https://files.pythonhosted.org/packages/85/92/c8fec52ddf06231b31cbb779af77e99b8253cd96bd135250b9498144c78b/kiwisolver-1.4.8-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:291331973c64bb9cce50bbe871fb2e675c4331dab4f31abe89f175ad7679a4d7", size = 1522138 }, + { url = "https://files.pythonhosted.org/packages/0b/51/9eb7e2cd07a15d8bdd976f6190c0164f92ce1904e5c0c79198c4972926b7/kiwisolver-1.4.8-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:893f5525bb92d3d735878ec00f781b2de998333659507d29ea4466208df37bed", size = 1582857 }, + { url = "https://files.pythonhosted.org/packages/0f/95/c5a00387a5405e68ba32cc64af65ce881a39b98d73cc394b24143bebc5b8/kiwisolver-1.4.8-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b47a465040146981dc9db8647981b8cb96366fbc8d452b031e4f8fdffec3f26d", size = 2293129 }, + { url = "https://files.pythonhosted.org/packages/44/83/eeb7af7d706b8347548313fa3a3a15931f404533cc54fe01f39e830dd231/kiwisolver-1.4.8-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:99cea8b9dd34ff80c521aef46a1dddb0dcc0283cf18bde6d756f1e6f31772165", size = 2421538 }, + { url = "https://files.pythonhosted.org/packages/05/f9/27e94c1b3eb29e6933b6986ffc5fa1177d2cd1f0c8efc5f02c91c9ac61de/kiwisolver-1.4.8-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:151dffc4865e5fe6dafce5480fab84f950d14566c480c08a53c663a0020504b6", size = 2390661 }, + { url = "https://files.pythonhosted.org/packages/d9/d4/3c9735faa36ac591a4afcc2980d2691000506050b7a7e80bcfe44048daa7/kiwisolver-1.4.8-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:577facaa411c10421314598b50413aa1ebcf5126f704f1e5d72d7e4e9f020d90", size = 2546710 }, + { url = "https://files.pythonhosted.org/packages/4c/fa/be89a49c640930180657482a74970cdcf6f7072c8d2471e1babe17a222dc/kiwisolver-1.4.8-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:be4816dc51c8a471749d664161b434912eee82f2ea66bd7628bd14583a833e85", size = 2349213 }, +] + +[[package]] +name = "lazyasd" +version = "0.1.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b3/15/c9f68c4f477fbaa9fc5ea5e271547956da321787cf7aef79d6971dcb4c84/lazyasd-0.1.4.tar.gz", hash = "sha256:a3196f05cff27f952ad05767e5735fd564b4ea4e89b23f5ea1887229c3db145b", size = 8380 } + +[[package]] +name = "llvmlite" +version = "0.44.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/89/6a/95a3d3610d5c75293d5dbbb2a76480d5d4eeba641557b69fe90af6c5b84e/llvmlite-0.44.0.tar.gz", hash = "sha256:07667d66a5d150abed9157ab6c0b9393c9356f229784a4385c02f99e94fc94d4", size = 171880 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/15/86/e3c3195b92e6e492458f16d233e58a1a812aa2bfbef9bdd0fbafcec85c60/llvmlite-0.44.0-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:1d671a56acf725bf1b531d5ef76b86660a5ab8ef19bb6a46064a705c6ca80aad", size = 28132297 }, + { url = "https://files.pythonhosted.org/packages/d6/53/373b6b8be67b9221d12b24125fd0ec56b1078b660eeae266ec388a6ac9a0/llvmlite-0.44.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5f79a728e0435493611c9f405168682bb75ffd1fbe6fc360733b850c80a026db", size = 26201105 }, + { url = "https://files.pythonhosted.org/packages/cb/da/8341fd3056419441286c8e26bf436923021005ece0bff5f41906476ae514/llvmlite-0.44.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0143a5ef336da14deaa8ec26c5449ad5b6a2b564df82fcef4be040b9cacfea9", size = 42361901 }, + { url = "https://files.pythonhosted.org/packages/53/ad/d79349dc07b8a395a99153d7ce8b01d6fcdc9f8231355a5df55ded649b61/llvmlite-0.44.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d752f89e31b66db6f8da06df8b39f9b91e78c5feea1bf9e8c1fba1d1c24c065d", size = 41184247 }, + { url = "https://files.pythonhosted.org/packages/e2/3b/a9a17366af80127bd09decbe2a54d8974b6d8b274b39bf47fbaedeec6307/llvmlite-0.44.0-cp312-cp312-win_amd64.whl", hash = "sha256:eae7e2d4ca8f88f89d315b48c6b741dcb925d6a1042da694aa16ab3dd4cbd3a1", size = 30332380 }, + { url = "https://files.pythonhosted.org/packages/89/24/4c0ca705a717514c2092b18476e7a12c74d34d875e05e4d742618ebbf449/llvmlite-0.44.0-cp313-cp313-macosx_10_14_x86_64.whl", hash = "sha256:319bddd44e5f71ae2689859b7203080716448a3cd1128fb144fe5c055219d516", size = 28132306 }, + { url = "https://files.pythonhosted.org/packages/01/cf/1dd5a60ba6aee7122ab9243fd614abcf22f36b0437cbbe1ccf1e3391461c/llvmlite-0.44.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9c58867118bad04a0bb22a2e0068c693719658105e40009ffe95c7000fcde88e", size = 26201090 }, + { url = "https://files.pythonhosted.org/packages/d2/1b/656f5a357de7135a3777bd735cc7c9b8f23b4d37465505bd0eaf4be9befe/llvmlite-0.44.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:46224058b13c96af1365290bdfebe9a6264ae62fb79b2b55693deed11657a8bf", size = 42361904 }, + { url = "https://files.pythonhosted.org/packages/d8/e1/12c5f20cb9168fb3464a34310411d5ad86e4163c8ff2d14a2b57e5cc6bac/llvmlite-0.44.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aa0097052c32bf721a4efc03bd109d335dfa57d9bffb3d4c24cc680711b8b4fc", size = 41184245 }, + { url = "https://files.pythonhosted.org/packages/d0/81/e66fc86539293282fd9cb7c9417438e897f369e79ffb62e1ae5e5154d4dd/llvmlite-0.44.0-cp313-cp313-win_amd64.whl", hash = "sha256:2fb7c4f2fb86cbae6dca3db9ab203eeea0e22d73b99bc2341cdf9de93612e930", size = 30331193 }, +] + +[[package]] +name = "locket" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2f/83/97b29fe05cb6ae28d2dbd30b81e2e402a3eed5f460c26e9eaa5895ceacf5/locket-1.0.0.tar.gz", hash = "sha256:5c0d4c052a8bbbf750e056a8e65ccd309086f4f0f18a2eac306a8dfa4112a632", size = 4350 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/db/bc/83e112abc66cd466c6b83f99118035867cecd41802f8d044638aa78a106e/locket-1.0.0-py2.py3-none-any.whl", hash = "sha256:b6c819a722f7b6bd955b80781788e4a66a55628b858d347536b7e81325a3a5e3", size = 4398 }, +] + +[[package]] +name = "lru-dict" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/83/63/21480e8ecc218b9b15672d194ea79da8a7389737c21d8406254306733cac/lru-dict-1.2.0.tar.gz", hash = "sha256:13c56782f19d68ddf4d8db0170041192859616514c706b126d0df2ec72a11bd7", size = 10895 } + +[[package]] +name = "lz4" +version = "4.4.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c6/5a/945f5086326d569f14c84ac6f7fcc3229f0b9b1e8cc536b951fd53dfb9e1/lz4-4.4.4.tar.gz", hash = "sha256:070fd0627ec4393011251a094e08ed9fdcc78cb4e7ab28f507638eee4e39abda", size = 171884 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f7/2d/5523b4fabe11cd98f040f715728d1932eb7e696bfe94391872a823332b94/lz4-4.4.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:23ae267494fdd80f0d2a131beff890cf857f1b812ee72dbb96c3204aab725553", size = 220669 }, + { url = "https://files.pythonhosted.org/packages/91/06/1a5bbcacbfb48d8ee5b6eb3fca6aa84143a81d92946bdb5cd6b005f1863e/lz4-4.4.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fff9f3a1ed63d45cb6514bfb8293005dc4141341ce3500abdfeb76124c0b9b2e", size = 189661 }, + { url = "https://files.pythonhosted.org/packages/fa/08/39eb7ac907f73e11a69a11576a75a9e36406b3241c0ba41453a7eb842abb/lz4-4.4.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1ea7f07329f85a8eda4d8cf937b87f27f0ac392c6400f18bea2c667c8b7f8ecc", size = 1238775 }, + { url = "https://files.pythonhosted.org/packages/e9/26/05840fbd4233e8d23e88411a066ab19f1e9de332edddb8df2b6a95c7fddc/lz4-4.4.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8ccab8f7f7b82f9fa9fc3b0ba584d353bd5aa818d5821d77d5b9447faad2aaad", size = 1265143 }, + { url = "https://files.pythonhosted.org/packages/b7/5d/5f2db18c298a419932f3ab2023deb689863cf8fd7ed875b1c43492479af2/lz4-4.4.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e43e9d48b2daf80e486213128b0763deed35bbb7a59b66d1681e205e1702d735", size = 1185032 }, + { url = "https://files.pythonhosted.org/packages/c4/e6/736ab5f128694b0f6aac58343bcf37163437ac95997276cd0be3ea4c3342/lz4-4.4.4-cp312-cp312-win32.whl", hash = "sha256:33e01e18e4561b0381b2c33d58e77ceee850a5067f0ece945064cbaac2176962", size = 88284 }, + { url = "https://files.pythonhosted.org/packages/40/b8/243430cb62319175070e06e3a94c4c7bd186a812e474e22148ae1290d47d/lz4-4.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:d21d1a2892a2dcc193163dd13eaadabb2c1b803807a5117d8f8588b22eaf9f12", size = 99918 }, + { url = "https://files.pythonhosted.org/packages/6c/e1/0686c91738f3e6c2e1a243e0fdd4371667c4d2e5009b0a3605806c2aa020/lz4-4.4.4-cp312-cp312-win_arm64.whl", hash = "sha256:2f4f2965c98ab254feddf6b5072854a6935adab7bc81412ec4fe238f07b85f62", size = 89736 }, + { url = "https://files.pythonhosted.org/packages/3b/3c/d1d1b926d3688263893461e7c47ed7382a969a0976fc121fc678ec325fc6/lz4-4.4.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ed6eb9f8deaf25ee4f6fad9625d0955183fdc90c52b6f79a76b7f209af1b6e54", size = 220678 }, + { url = "https://files.pythonhosted.org/packages/26/89/8783d98deb058800dabe07e6cdc90f5a2a8502a9bad8c5343c641120ace2/lz4-4.4.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:18ae4fe3bafb344dbd09f976d45cbf49c05c34416f2462828f9572c1fa6d5af7", size = 189670 }, + { url = "https://files.pythonhosted.org/packages/22/ab/a491ace69a83a8914a49f7391e92ca0698f11b28d5ce7b2ececa2be28e9a/lz4-4.4.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:57fd20c5fc1a49d1bbd170836fccf9a338847e73664f8e313dce6ac91b8c1e02", size = 1238746 }, + { url = "https://files.pythonhosted.org/packages/97/12/a1f2f4fdc6b7159c0d12249456f9fe454665b6126e98dbee9f2bd3cf735c/lz4-4.4.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e9cb387c33f014dae4db8cb4ba789c8d2a0a6d045ddff6be13f6c8d9def1d2a6", size = 1265119 }, + { url = "https://files.pythonhosted.org/packages/50/6e/e22e50f5207649db6ea83cd31b79049118305be67e96bec60becf317afc6/lz4-4.4.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d0be9f68240231e1e44118a4ebfecd8a5d4184f0bdf5c591c98dd6ade9720afd", size = 1184954 }, + { url = "https://files.pythonhosted.org/packages/4c/c4/2a458039645fcc6324ece731d4d1361c5daf960b553d1fcb4261ba07d51c/lz4-4.4.4-cp313-cp313-win32.whl", hash = "sha256:e9ec5d45ea43684f87c316542af061ef5febc6a6b322928f059ce1fb289c298a", size = 88289 }, + { url = "https://files.pythonhosted.org/packages/00/96/b8e24ea7537ab418074c226279acfcaa470e1ea8271003e24909b6db942b/lz4-4.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:a760a175b46325b2bb33b1f2bbfb8aa21b48e1b9653e29c10b6834f9bb44ead4", size = 99925 }, + { url = "https://files.pythonhosted.org/packages/a5/a5/f9838fe6aa132cfd22733ed2729d0592259fff074cefb80f19aa0607367b/lz4-4.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:f4c21648d81e0dda38b4720dccc9006ae33b0e9e7ffe88af6bf7d4ec124e2fba", size = 89743 }, ] [[package]] @@ -591,6 +1730,93 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528 }, ] +[[package]] +name = "markupsafe" +version = "3.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/22/09/d1f21434c97fc42f09d290cbb6350d44eb12f09cc62c9476effdb33a18aa/MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf", size = 14274 }, + { url = "https://files.pythonhosted.org/packages/6b/b0/18f76bba336fa5aecf79d45dcd6c806c280ec44538b3c13671d49099fdd0/MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225", size = 12348 }, + { url = "https://files.pythonhosted.org/packages/e0/25/dd5c0f6ac1311e9b40f4af06c78efde0f3b5cbf02502f8ef9501294c425b/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028", size = 24149 }, + { url = "https://files.pythonhosted.org/packages/f3/f0/89e7aadfb3749d0f52234a0c8c7867877876e0a20b60e2188e9850794c17/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8", size = 23118 }, + { url = "https://files.pythonhosted.org/packages/d5/da/f2eeb64c723f5e3777bc081da884b414671982008c47dcc1873d81f625b6/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c", size = 22993 }, + { url = "https://files.pythonhosted.org/packages/da/0e/1f32af846df486dce7c227fe0f2398dc7e2e51d4a370508281f3c1c5cddc/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557", size = 24178 }, + { url = "https://files.pythonhosted.org/packages/c4/f6/bb3ca0532de8086cbff5f06d137064c8410d10779c4c127e0e47d17c0b71/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22", size = 23319 }, + { url = "https://files.pythonhosted.org/packages/a2/82/8be4c96ffee03c5b4a034e60a31294daf481e12c7c43ab8e34a1453ee48b/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48", size = 23352 }, + { url = "https://files.pythonhosted.org/packages/51/ae/97827349d3fcffee7e184bdf7f41cd6b88d9919c80f0263ba7acd1bbcb18/MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30", size = 15097 }, + { url = "https://files.pythonhosted.org/packages/c1/80/a61f99dc3a936413c3ee4e1eecac96c0da5ed07ad56fd975f1a9da5bc630/MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87", size = 15601 }, + { url = "https://files.pythonhosted.org/packages/83/0e/67eb10a7ecc77a0c2bbe2b0235765b98d164d81600746914bebada795e97/MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd", size = 14274 }, + { url = "https://files.pythonhosted.org/packages/2b/6d/9409f3684d3335375d04e5f05744dfe7e9f120062c9857df4ab490a1031a/MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430", size = 12352 }, + { url = "https://files.pythonhosted.org/packages/d2/f5/6eadfcd3885ea85fe2a7c128315cc1bb7241e1987443d78c8fe712d03091/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094", size = 24122 }, + { url = "https://files.pythonhosted.org/packages/0c/91/96cf928db8236f1bfab6ce15ad070dfdd02ed88261c2afafd4b43575e9e9/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396", size = 23085 }, + { url = "https://files.pythonhosted.org/packages/c2/cf/c9d56af24d56ea04daae7ac0940232d31d5a8354f2b457c6d856b2057d69/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79", size = 22978 }, + { url = "https://files.pythonhosted.org/packages/2a/9f/8619835cd6a711d6272d62abb78c033bda638fdc54c4e7f4272cf1c0962b/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a", size = 24208 }, + { url = "https://files.pythonhosted.org/packages/f9/bf/176950a1792b2cd2102b8ffeb5133e1ed984547b75db47c25a67d3359f77/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca", size = 23357 }, + { url = "https://files.pythonhosted.org/packages/ce/4f/9a02c1d335caabe5c4efb90e1b6e8ee944aa245c1aaaab8e8a618987d816/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c", size = 23344 }, + { url = "https://files.pythonhosted.org/packages/ee/55/c271b57db36f748f0e04a759ace9f8f759ccf22b4960c270c78a394f58be/MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1", size = 15101 }, + { url = "https://files.pythonhosted.org/packages/29/88/07df22d2dd4df40aba9f3e402e6dc1b8ee86297dddbad4872bd5e7b0094f/MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f", size = 15603 }, + { url = "https://files.pythonhosted.org/packages/62/6a/8b89d24db2d32d433dffcd6a8779159da109842434f1dd2f6e71f32f738c/MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c", size = 14510 }, + { url = "https://files.pythonhosted.org/packages/7a/06/a10f955f70a2e5a9bf78d11a161029d278eeacbd35ef806c3fd17b13060d/MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb", size = 12486 }, + { url = "https://files.pythonhosted.org/packages/34/cf/65d4a571869a1a9078198ca28f39fba5fbb910f952f9dbc5220afff9f5e6/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c", size = 25480 }, + { url = "https://files.pythonhosted.org/packages/0c/e3/90e9651924c430b885468b56b3d597cabf6d72be4b24a0acd1fa0e12af67/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d", size = 23914 }, + { url = "https://files.pythonhosted.org/packages/66/8c/6c7cf61f95d63bb866db39085150df1f2a5bd3335298f14a66b48e92659c/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe", size = 23796 }, + { url = "https://files.pythonhosted.org/packages/bb/35/cbe9238ec3f47ac9a7c8b3df7a808e7cb50fe149dc7039f5f454b3fba218/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5", size = 25473 }, + { url = "https://files.pythonhosted.org/packages/e6/32/7621a4382488aa283cc05e8984a9c219abad3bca087be9ec77e89939ded9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a", size = 24114 }, + { url = "https://files.pythonhosted.org/packages/0d/80/0985960e4b89922cb5a0bac0ed39c5b96cbc1a536a99f30e8c220a996ed9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9", size = 24098 }, + { url = "https://files.pythonhosted.org/packages/82/78/fedb03c7d5380df2427038ec8d973587e90561b2d90cd472ce9254cf348b/MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6", size = 15208 }, + { url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739 }, +] + +[[package]] +name = "matplotlib" +version = "3.10.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "contourpy" }, + { name = "cycler" }, + { name = "fonttools" }, + { name = "kiwisolver" }, + { name = "numpy" }, + { name = "packaging" }, + { name = "pillow" }, + { name = "pyparsing" }, + { name = "python-dateutil" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/26/91/d49359a21893183ed2a5b6c76bec40e0b1dcbf8ca148f864d134897cfc75/matplotlib-3.10.3.tar.gz", hash = "sha256:2f82d2c5bb7ae93aaaa4cd42aca65d76ce6376f83304fa3a630b569aca274df0", size = 34799811 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/eb/43/6b80eb47d1071f234ef0c96ca370c2ca621f91c12045f1401b5c9b28a639/matplotlib-3.10.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0ab1affc11d1f495ab9e6362b8174a25afc19c081ba5b0775ef00533a4236eea", size = 8179689 }, + { url = "https://files.pythonhosted.org/packages/0f/70/d61a591958325c357204870b5e7b164f93f2a8cca1dc6ce940f563909a13/matplotlib-3.10.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2a818d8bdcafa7ed2eed74487fdb071c09c1ae24152d403952adad11fa3c65b4", size = 8050466 }, + { url = "https://files.pythonhosted.org/packages/e7/75/70c9d2306203148cc7902a961240c5927dd8728afedf35e6a77e105a2985/matplotlib-3.10.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:748ebc3470c253e770b17d8b0557f0aa85cf8c63fd52f1a61af5b27ec0b7ffee", size = 8456252 }, + { url = "https://files.pythonhosted.org/packages/c4/91/ba0ae1ff4b3f30972ad01cd4a8029e70a0ec3b8ea5be04764b128b66f763/matplotlib-3.10.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ed70453fd99733293ace1aec568255bc51c6361cb0da94fa5ebf0649fdb2150a", size = 8601321 }, + { url = "https://files.pythonhosted.org/packages/d2/88/d636041eb54a84b889e11872d91f7cbf036b3b0e194a70fa064eb8b04f7a/matplotlib-3.10.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dbed9917b44070e55640bd13419de83b4c918e52d97561544814ba463811cbc7", size = 9406972 }, + { url = "https://files.pythonhosted.org/packages/b1/79/0d1c165eac44405a86478082e225fce87874f7198300bbebc55faaf6d28d/matplotlib-3.10.3-cp312-cp312-win_amd64.whl", hash = "sha256:cf37d8c6ef1a48829443e8ba5227b44236d7fcaf7647caa3178a4ff9f7a5be05", size = 8067954 }, + { url = "https://files.pythonhosted.org/packages/3b/c1/23cfb566a74c696a3b338d8955c549900d18fe2b898b6e94d682ca21e7c2/matplotlib-3.10.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9f2efccc8dcf2b86fc4ee849eea5dcaecedd0773b30f47980dc0cbeabf26ec84", size = 8180318 }, + { url = "https://files.pythonhosted.org/packages/6c/0c/02f1c3b66b30da9ee343c343acbb6251bef5b01d34fad732446eaadcd108/matplotlib-3.10.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3ddbba06a6c126e3301c3d272a99dcbe7f6c24c14024e80307ff03791a5f294e", size = 8051132 }, + { url = "https://files.pythonhosted.org/packages/b4/ab/8db1a5ac9b3a7352fb914133001dae889f9fcecb3146541be46bed41339c/matplotlib-3.10.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:748302b33ae9326995b238f606e9ed840bf5886ebafcb233775d946aa8107a15", size = 8457633 }, + { url = "https://files.pythonhosted.org/packages/f5/64/41c4367bcaecbc03ef0d2a3ecee58a7065d0a36ae1aa817fe573a2da66d4/matplotlib-3.10.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a80fcccbef63302c0efd78042ea3c2436104c5b1a4d3ae20f864593696364ac7", size = 8601031 }, + { url = "https://files.pythonhosted.org/packages/12/6f/6cc79e9e5ab89d13ed64da28898e40fe5b105a9ab9c98f83abd24e46d7d7/matplotlib-3.10.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:55e46cbfe1f8586adb34f7587c3e4f7dedc59d5226719faf6cb54fc24f2fd52d", size = 9406988 }, + { url = "https://files.pythonhosted.org/packages/b1/0f/eed564407bd4d935ffabf561ed31099ed609e19287409a27b6d336848653/matplotlib-3.10.3-cp313-cp313-win_amd64.whl", hash = "sha256:151d89cb8d33cb23345cd12490c76fd5d18a56581a16d950b48c6ff19bb2ab93", size = 8068034 }, + { url = "https://files.pythonhosted.org/packages/3e/e5/2f14791ff69b12b09e9975e1d116d9578ac684460860ce542c2588cb7a1c/matplotlib-3.10.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:c26dd9834e74d164d06433dc7be5d75a1e9890b926b3e57e74fa446e1a62c3e2", size = 8218223 }, + { url = "https://files.pythonhosted.org/packages/5c/08/30a94afd828b6e02d0a52cae4a29d6e9ccfcf4c8b56cc28b021d3588873e/matplotlib-3.10.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:24853dad5b8c84c8c2390fc31ce4858b6df504156893292ce8092d190ef8151d", size = 8094985 }, + { url = "https://files.pythonhosted.org/packages/89/44/f3bc6b53066c889d7a1a3ea8094c13af6a667c5ca6220ec60ecceec2dabe/matplotlib-3.10.3-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68f7878214d369d7d4215e2a9075fef743be38fa401d32e6020bab2dfabaa566", size = 8483109 }, + { url = "https://files.pythonhosted.org/packages/ba/c7/473bc559beec08ebee9f86ca77a844b65747e1a6c2691e8c92e40b9f42a8/matplotlib-3.10.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6929fc618cb6db9cb75086f73b3219bbb25920cb24cee2ea7a12b04971a4158", size = 8618082 }, + { url = "https://files.pythonhosted.org/packages/d8/e9/6ce8edd264c8819e37bbed8172e0ccdc7107fe86999b76ab5752276357a4/matplotlib-3.10.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6c7818292a5cc372a2dc4c795e5c356942eb8350b98ef913f7fda51fe175ac5d", size = 9413699 }, + { url = "https://files.pythonhosted.org/packages/1b/92/9a45c91089c3cf690b5badd4be81e392ff086ccca8a1d4e3a08463d8a966/matplotlib-3.10.3-cp313-cp313t-win_amd64.whl", hash = "sha256:4f23ffe95c5667ef8a2b56eea9b53db7f43910fa4a2d5472ae0f72b64deab4d5", size = 8139044 }, +] + +[[package]] +name = "matplotlib-inline" +version = "0.1.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/99/5b/a36a337438a14116b16480db471ad061c36c3694df7c2084a0da7ba538b7/matplotlib_inline-0.1.7.tar.gz", hash = "sha256:8423b23ec666be3d16e16b60bdd8ac4e86e840ebd1dd11a30b9f117f2fa0ab90", size = 8159 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8f/8e/9ad090d3553c280a8060fbf6e24dc1c0c29704ee7d1c372f0c174aa59285/matplotlib_inline-0.1.7-py3-none-any.whl", hash = "sha256:df192d39a4ff8f21b1895d72e6a13f5fcc5099f00fa84384e0ea28c2cc0653ca", size = 9899 }, +] + [[package]] name = "mdurl" version = "0.1.2" @@ -640,6 +1866,42 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/16/71/4ad9a42f2772793a03cb698f0fc42499f04e6e8d2560ba2f7da0fb059a8e/mmh3-5.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:b22fe2e54be81f6c07dcb36b96fa250fb72effe08aa52fbb83eade6e1e2d5fd7", size = 38890 }, ] +[[package]] +name = "morphys" +version = "1.0" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f9/4f/cb781d0ac5d079adabc77dc4f0bc99fc81c390029bd33c6e70552139e762/morphys-1.0-py2.py3-none-any.whl", hash = "sha256:76d6dbaa4d65f597e59d332c81da786d83e4669387b9b2a750cfec74e7beec20", size = 5618 }, +] + +[[package]] +name = "msgpack" +version = "1.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/45/b1/ea4f68038a18c77c9467400d166d74c4ffa536f34761f7983a104357e614/msgpack-1.1.1.tar.gz", hash = "sha256:77b79ce34a2bdab2594f490c8e80dd62a02d650b91a75159a63ec413b8d104cd", size = 173555 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e3/26/389b9c593eda2b8551b2e7126ad3a06af6f9b44274eb3a4f054d48ff7e47/msgpack-1.1.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ae497b11f4c21558d95de9f64fff7053544f4d1a17731c866143ed6bb4591238", size = 82359 }, + { url = "https://files.pythonhosted.org/packages/ab/65/7d1de38c8a22cf8b1551469159d4b6cf49be2126adc2482de50976084d78/msgpack-1.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:33be9ab121df9b6b461ff91baac6f2731f83d9b27ed948c5b9d1978ae28bf157", size = 79172 }, + { url = "https://files.pythonhosted.org/packages/0f/bd/cacf208b64d9577a62c74b677e1ada005caa9b69a05a599889d6fc2ab20a/msgpack-1.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f64ae8fe7ffba251fecb8408540c34ee9df1c26674c50c4544d72dbf792e5ce", size = 425013 }, + { url = "https://files.pythonhosted.org/packages/4d/ec/fd869e2567cc9c01278a736cfd1697941ba0d4b81a43e0aa2e8d71dab208/msgpack-1.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a494554874691720ba5891c9b0b39474ba43ffb1aaf32a5dac874effb1619e1a", size = 426905 }, + { url = "https://files.pythonhosted.org/packages/55/2a/35860f33229075bce803a5593d046d8b489d7ba2fc85701e714fc1aaf898/msgpack-1.1.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cb643284ab0ed26f6957d969fe0dd8bb17beb567beb8998140b5e38a90974f6c", size = 407336 }, + { url = "https://files.pythonhosted.org/packages/8c/16/69ed8f3ada150bf92745fb4921bd621fd2cdf5a42e25eb50bcc57a5328f0/msgpack-1.1.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d275a9e3c81b1093c060c3837e580c37f47c51eca031f7b5fb76f7b8470f5f9b", size = 409485 }, + { url = "https://files.pythonhosted.org/packages/c6/b6/0c398039e4c6d0b2e37c61d7e0e9d13439f91f780686deb8ee64ecf1ae71/msgpack-1.1.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:4fd6b577e4541676e0cc9ddc1709d25014d3ad9a66caa19962c4f5de30fc09ef", size = 412182 }, + { url = "https://files.pythonhosted.org/packages/b8/d0/0cf4a6ecb9bc960d624c93effaeaae75cbf00b3bc4a54f35c8507273cda1/msgpack-1.1.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:bb29aaa613c0a1c40d1af111abf025f1732cab333f96f285d6a93b934738a68a", size = 419883 }, + { url = "https://files.pythonhosted.org/packages/62/83/9697c211720fa71a2dfb632cad6196a8af3abea56eece220fde4674dc44b/msgpack-1.1.1-cp312-cp312-win32.whl", hash = "sha256:870b9a626280c86cff9c576ec0d9cbcc54a1e5ebda9cd26dab12baf41fee218c", size = 65406 }, + { url = "https://files.pythonhosted.org/packages/c0/23/0abb886e80eab08f5e8c485d6f13924028602829f63b8f5fa25a06636628/msgpack-1.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:5692095123007180dca3e788bb4c399cc26626da51629a31d40207cb262e67f4", size = 72558 }, + { url = "https://files.pythonhosted.org/packages/a1/38/561f01cf3577430b59b340b51329803d3a5bf6a45864a55f4ef308ac11e3/msgpack-1.1.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:3765afa6bd4832fc11c3749be4ba4b69a0e8d7b728f78e68120a157a4c5d41f0", size = 81677 }, + { url = "https://files.pythonhosted.org/packages/09/48/54a89579ea36b6ae0ee001cba8c61f776451fad3c9306cd80f5b5c55be87/msgpack-1.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8ddb2bcfd1a8b9e431c8d6f4f7db0773084e107730ecf3472f1dfe9ad583f3d9", size = 78603 }, + { url = "https://files.pythonhosted.org/packages/a0/60/daba2699b308e95ae792cdc2ef092a38eb5ee422f9d2fbd4101526d8a210/msgpack-1.1.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:196a736f0526a03653d829d7d4c5500a97eea3648aebfd4b6743875f28aa2af8", size = 420504 }, + { url = "https://files.pythonhosted.org/packages/20/22/2ebae7ae43cd8f2debc35c631172ddf14e2a87ffcc04cf43ff9df9fff0d3/msgpack-1.1.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d592d06e3cc2f537ceeeb23d38799c6ad83255289bb84c2e5792e5a8dea268a", size = 423749 }, + { url = "https://files.pythonhosted.org/packages/40/1b/54c08dd5452427e1179a40b4b607e37e2664bca1c790c60c442c8e972e47/msgpack-1.1.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4df2311b0ce24f06ba253fda361f938dfecd7b961576f9be3f3fbd60e87130ac", size = 404458 }, + { url = "https://files.pythonhosted.org/packages/2e/60/6bb17e9ffb080616a51f09928fdd5cac1353c9becc6c4a8abd4e57269a16/msgpack-1.1.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e4141c5a32b5e37905b5940aacbc59739f036930367d7acce7a64e4dec1f5e0b", size = 405976 }, + { url = "https://files.pythonhosted.org/packages/ee/97/88983e266572e8707c1f4b99c8fd04f9eb97b43f2db40e3172d87d8642db/msgpack-1.1.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b1ce7f41670c5a69e1389420436f41385b1aa2504c3b0c30620764b15dded2e7", size = 408607 }, + { url = "https://files.pythonhosted.org/packages/bc/66/36c78af2efaffcc15a5a61ae0df53a1d025f2680122e2a9eb8442fed3ae4/msgpack-1.1.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4147151acabb9caed4e474c3344181e91ff7a388b888f1e19ea04f7e73dc7ad5", size = 424172 }, + { url = "https://files.pythonhosted.org/packages/8c/87/a75eb622b555708fe0427fab96056d39d4c9892b0c784b3a721088c7ee37/msgpack-1.1.1-cp313-cp313-win32.whl", hash = "sha256:500e85823a27d6d9bba1d057c871b4210c1dd6fb01fbb764e37e4e8847376323", size = 65347 }, + { url = "https://files.pythonhosted.org/packages/ca/91/7dc28d5e2a11a5ad804cf2b7f7a5fcb1eb5a4966d66a5d2b41aee6376543/msgpack-1.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:6d489fba546295983abd142812bda76b57e33d0b9f5d5b71c09a583285506f69", size = 72341 }, +] + [[package]] name = "msgspec" version = "0.19.0" @@ -752,6 +2014,52 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f3/9b/c21a9c1d5ea4847989f1eb00e3147e38e79aaea7c4b4d1cbd4f1afae9740/multiformats_config-0.3.1-py3-none-any.whl", hash = "sha256:dec4c9d42ed0d9305889b67440f72e8e8d74b82b80abd7219667764b5b0a8e1d", size = 17153 }, ] +[[package]] +name = "narwhals" +version = "1.46.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/06/7f/dd8c5f7978c3136de4d660877a5279e4688ad0c56dbc15ee003c2fe981cd/narwhals-1.46.0.tar.gz", hash = "sha256:fd7e53860b233c2b5566d8b4e1b3e8e9c01b5a87649a9f9a322742000f207a60", size = 512060 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/75/64/c46ba7517d90e330c4f35af1256d4b12ba037e2ef17d4aa4d4f11b4a143d/narwhals-1.46.0-py3-none-any.whl", hash = "sha256:f15d2255695d7e99f624f76aa5b765eb3fff8a509d3215049707af3a3feebc90", size = 373394 }, +] + +[[package]] +name = "nc-time-axis" +version = "1.4.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cftime" }, + { name = "matplotlib" }, + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/39/c7/ceaba2047ef4e08660a5a89a71cea30547bddb0e51236dab2dcb771a6fe1/nc-time-axis-1.4.1.tar.gz", hash = "sha256:72d80f492f34bbf59490838d8cb3d92f14e88900b0ee35498b2b33c82795eb81", size = 66231 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/01/89/dbeab77a217f8fbda97a637acf1e3f0ce8c9c9fb3f5e5d1ff843da859520/nc_time_axis-1.4.1-py3-none-any.whl", hash = "sha256:96a6fb28cede0d07998fcd666599f76e51a086e1929fbcbfb758c1d0f3e7b0d1", size = 17757 }, +] + +[[package]] +name = "netcdf4" +version = "1.7.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "cftime" }, + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/71/ed/4d27fcfa40ebfdad3d2088a3de7ee48dbff7f35163e815ec1870d2a7398c/netcdf4-1.7.2.tar.gz", hash = "sha256:a4c6375540b19989896136943abb6d44850ff6f1fa7d3f063253b1ad3f8b7fce", size = 835064 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/52/7f/3a0f18a39efca0e093b54d634b66573c25ecab5c482d73138ae14aa55c6d/netCDF4-1.7.2-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:e73e3baa0b74afc414e53ff5095748fdbec7fb346eda351e567c23f2f0d247f1", size = 2952127 }, + { url = "https://files.pythonhosted.org/packages/ed/c4/8aac0f8ca95a41bdf1364d34ff4e9bcc24494bfe69a1157301d884c2e392/netCDF4-1.7.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:a51da09258b31776f474c1d47e484fc7214914cdc59edf4cee789ba632184591", size = 2460781 }, + { url = "https://files.pythonhosted.org/packages/2d/1a/32b7427aaf62fed3d4e4456f874b25ce39373dbddf6cfde9edbcfc2417fc/netCDF4-1.7.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb95b11804fe051897d1f2044b05d82a1847bc2549631cdd2f655dde7de77a9c", size = 9377415 }, + { url = "https://files.pythonhosted.org/packages/fd/bf/5e671495c8bdf6b628e091aa8980793579474a10e51bc6ba302a3af6a778/netCDF4-1.7.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f9d8a848373723f41ef662590b4f5e1832227501c9fd4513e8ad8da58c269977", size = 9260579 }, + { url = "https://files.pythonhosted.org/packages/d4/57/0a0bcdebcfaf72e96e7bcaa512f80ee096bf71945a3318d38253338e9c25/netCDF4-1.7.2-cp312-cp312-win_amd64.whl", hash = "sha256:568ea369e00b581302d77fc5fd0b8f78e520c7e08d0b5af5219ba51f3f1cd694", size = 6991523 }, + { url = "https://files.pythonhosted.org/packages/e6/7a/ce4f9038d8726c9c90e07b2d3a404ae111a27720d712cfcded0c8ef160e8/netCDF4-1.7.2-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:205a5f1de3ddb993c7c97fb204a923a22408cc2e5facf08d75a8eb89b3e7e1a8", size = 2948911 }, + { url = "https://files.pythonhosted.org/packages/58/3e/5736880a607edabca4c4fc49f1ccf9a2bb2485f84478e4cd19ba11c3b803/netCDF4-1.7.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:96653fc75057df196010818367c63ba6d7e9af603df0a7fe43fcdad3fe0e9e56", size = 2455078 }, + { url = "https://files.pythonhosted.org/packages/71/96/d5d8859a6dac29f8ebc815ff8e75770bd513db9f08d7a711e21ae562a948/netCDF4-1.7.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:30d20e56b9ba2c48884eb89c91b63e6c0612b4927881707e34402719153ef17f", size = 9378149 }, + { url = "https://files.pythonhosted.org/packages/d1/80/b9c19f1bb4ac6c5fa6f94a4f278bc68a778473d1814a86a375d7cffa193a/netCDF4-1.7.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d6bfd38ba0bde04d56f06c1554714a2ea9dab75811c89450dc3ec57a9d36b80", size = 9254471 }, + { url = "https://files.pythonhosted.org/packages/66/b5/e04550fd53de57001dbd5a87242da7ff784c80790adc48897977b6ccf891/netCDF4-1.7.2-cp313-cp313-win_amd64.whl", hash = "sha256:5c5fbee6134ee1246c397e1508e5297d825aa19221fdf3fa8dc9727ad824d7a5", size = 6990521 }, +] + [[package]] name = "nodeenv" version = "1.9.1" @@ -761,6 +2069,41 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9", size = 22314 }, ] +[[package]] +name = "numba" +version = "0.61.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "llvmlite" }, + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1c/a0/e21f57604304aa03ebb8e098429222722ad99176a4f979d34af1d1ee80da/numba-0.61.2.tar.gz", hash = "sha256:8750ee147940a6637b80ecf7f95062185ad8726c8c28a2295b8ec1160a196f7d", size = 2820615 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b4/a0/c6b7b9c615cfa3b98c4c63f4316e3f6b3bbe2387740277006551784218cd/numba-0.61.2-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:34fba9406078bac7ab052efbf0d13939426c753ad72946baaa5bf9ae0ebb8dd2", size = 2776626 }, + { url = "https://files.pythonhosted.org/packages/92/4a/fe4e3c2ecad72d88f5f8cd04e7f7cff49e718398a2fac02d2947480a00ca/numba-0.61.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4ddce10009bc097b080fc96876d14c051cc0c7679e99de3e0af59014dab7dfe8", size = 2779287 }, + { url = "https://files.pythonhosted.org/packages/9a/2d/e518df036feab381c23a624dac47f8445ac55686ec7f11083655eb707da3/numba-0.61.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5b1bb509d01f23d70325d3a5a0e237cbc9544dd50e50588bc581ba860c213546", size = 3885928 }, + { url = "https://files.pythonhosted.org/packages/10/0f/23cced68ead67b75d77cfcca3df4991d1855c897ee0ff3fe25a56ed82108/numba-0.61.2-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:48a53a3de8f8793526cbe330f2a39fe9a6638efcbf11bd63f3d2f9757ae345cd", size = 3577115 }, + { url = "https://files.pythonhosted.org/packages/68/1d/ddb3e704c5a8fb90142bf9dc195c27db02a08a99f037395503bfbc1d14b3/numba-0.61.2-cp312-cp312-win_amd64.whl", hash = "sha256:97cf4f12c728cf77c9c1d7c23707e4d8fb4632b46275f8f3397de33e5877af18", size = 2831929 }, + { url = "https://files.pythonhosted.org/packages/0b/f3/0fe4c1b1f2569e8a18ad90c159298d862f96c3964392a20d74fc628aee44/numba-0.61.2-cp313-cp313-macosx_10_14_x86_64.whl", hash = "sha256:3a10a8fc9afac40b1eac55717cece1b8b1ac0b946f5065c89e00bde646b5b154", size = 2771785 }, + { url = "https://files.pythonhosted.org/packages/e9/71/91b277d712e46bd5059f8a5866862ed1116091a7cb03bd2704ba8ebe015f/numba-0.61.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7d3bcada3c9afba3bed413fba45845f2fb9cd0d2b27dd58a1be90257e293d140", size = 2773289 }, + { url = "https://files.pythonhosted.org/packages/0d/e0/5ea04e7ad2c39288c0f0f9e8d47638ad70f28e275d092733b5817cf243c9/numba-0.61.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:bdbca73ad81fa196bd53dc12e3aaf1564ae036e0c125f237c7644fe64a4928ab", size = 3893918 }, + { url = "https://files.pythonhosted.org/packages/17/58/064f4dcb7d7e9412f16ecf80ed753f92297e39f399c905389688cf950b81/numba-0.61.2-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:5f154aaea625fb32cfbe3b80c5456d514d416fcdf79733dd69c0df3a11348e9e", size = 3584056 }, + { url = "https://files.pythonhosted.org/packages/af/a4/6d3a0f2d3989e62a18749e1e9913d5fa4910bbb3e3311a035baea6caf26d/numba-0.61.2-cp313-cp313-win_amd64.whl", hash = "sha256:59321215e2e0ac5fa928a8020ab00b8e57cda8a97384963ac0dfa4d4e6aa54e7", size = 2831846 }, +] + +[[package]] +name = "numbagg" +version = "0.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numba" }, + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/27/6f/6e2c5004fdf26691c2ef645208b2f365508c57611cd24c79aae90e8cb3bf/numbagg-0.9.0.tar.gz", hash = "sha256:45ba41077b7a621e35eaa4c294d90b22e75e8513b8c211f59d2b9be840fc1175", size = 97455 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/42/06/9016e13b12ddc4353880479ef570d2b988e1adf6c68b977a42647a2267ee/numbagg-0.9.0-py3-none-any.whl", hash = "sha256:000968facb1b433a0722e99664f9a6077f061edb3625d992084188607e016e91", size = 51041 }, +] + [[package]] name = "numcodecs" version = "0.15.1" @@ -824,13 +2167,34 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/3e/05/eb7eec66b95cf697f08c754ef26c3549d03ebd682819f794cb039574a0a6/numpy-2.2.4-cp313-cp313t-win_amd64.whl", hash = "sha256:188dcbca89834cc2e14eb2f106c96d6d46f200fe0200310fc29089657379c58d", size = 12739119 }, ] +[[package]] +name = "numpy-groupies" +version = "0.11.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7d/ff/0559b586423d9a59feac52c2261501106dcd61e45214862de5fbb03b78cb/numpy_groupies-0.11.3.tar.gz", hash = "sha256:aed4afdad55e856b9e737fe4b4673c77e47c2f887c3663a18baaa200407c23e0", size = 159159 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b0/e0/760e73c111193db5ca37712a148e4807d1b0c60302ab31e4ada6528ca34d/numpy_groupies-0.11.3-py3-none-any.whl", hash = "sha256:d4065dd5d56fda941ad5a7c80a7f80b49f671ed148aaa3e243a0e4caa71adcb3", size = 40784 }, +] + +[[package]] +name = "opt-einsum" +version = "3.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8c/b9/2ac072041e899a52f20cf9510850ff58295003aa75525e58343591b0cbfb/opt_einsum-3.4.0.tar.gz", hash = "sha256:96ca72f1b886d148241348783498194c577fa30a8faac108586b14f1ba4473ac", size = 63004 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/23/cd/066e86230ae37ed0be70aae89aabf03ca8d9f39c8aea0dec8029455b5540/opt_einsum-3.4.0-py3-none-any.whl", hash = "sha256:69bb92469f86a1565195ece4ac0323943e83477171b91d24c35afe028a90d7cd", size = 71932 }, +] + [[package]] name = "packaging" -version = "24.2" +version = "23.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d0/63/68dbb6eb2de9cb10ee4c9c14a0148804425e13c4fb20d61cce69f53106da/packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f", size = 163950 } +sdist = { url = "https://files.pythonhosted.org/packages/fb/2b/9b9c33ffed44ee921d0967086d653047286054117d584f1b1a7c22ceaf7b/packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5", size = 146714 } wheels = [ - { url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451 }, + { url = "https://files.pythonhosted.org/packages/ec/1a/610693ac4ee14fcdf2d9bf3c493370e4f2ef7ae2e19217d7a237ff42367d/packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7", size = 53011 }, ] [[package]] @@ -867,6 +2231,118 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ab/5f/b38085618b950b79d2d9164a711c52b10aefc0ae6833b96f626b7021b2ed/pandas-2.2.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:ad5b65698ab28ed8d7f18790a0dc58005c7629f227be9ecc1072aa74c0c1d43a", size = 13098436 }, ] +[[package]] +name = "parsimonious" +version = "0.10.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "regex" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7b/91/abdc50c4ef06fdf8d047f60ee777ca9b2a7885e1a9cea81343fbecda52d7/parsimonious-0.10.0.tar.gz", hash = "sha256:8281600da180ec8ae35427a4ab4f7b82bfec1e3d1e52f80cb60ea82b9512501c", size = 52172 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/aa/0f/c8b64d9b54ea631fcad4e9e3c8dbe8c11bb32a623be94f22974c88e71eaf/parsimonious-0.10.0-py3-none-any.whl", hash = "sha256:982ab435fabe86519b57f6b35610aa4e4e977e9f02a14353edf4bbc75369fc0f", size = 48427 }, +] + +[[package]] +name = "parso" +version = "0.8.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/66/94/68e2e17afaa9169cf6412ab0f28623903be73d1b32e208d9e8e541bb086d/parso-0.8.4.tar.gz", hash = "sha256:eb3a7b58240fb99099a345571deecc0f9540ea5f4dd2fe14c2a99d6b281ab92d", size = 400609 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c6/ac/dac4a63f978e4dcb3c6d3a78c4d8e0192a113d288502a1216950c41b1027/parso-0.8.4-py2.py3-none-any.whl", hash = "sha256:a418670a20291dacd2dddc80c377c5c3791378ee1e8d12bffc35420643d43f18", size = 103650 }, +] + +[[package]] +name = "partd" +version = "1.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "locket" }, + { name = "toolz" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b2/3a/3f06f34820a31257ddcabdfafc2672c5816be79c7e353b02c1f318daa7d4/partd-1.4.2.tar.gz", hash = "sha256:d022c33afbdc8405c226621b015e8067888173d85f7f5ecebb3cafed9a20f02c", size = 21029 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/71/e7/40fb618334dcdf7c5a316c0e7343c5cd82d3d866edc100d98e29bc945ecd/partd-1.4.2-py3-none-any.whl", hash = "sha256:978e4ac767ec4ba5b86c6eaa52e5a2a3bc748a2ca839e8cc798f1cc6ce6efb0f", size = 18905 }, +] + +[[package]] +name = "pexpect" +version = "4.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ptyprocess" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/42/92/cc564bf6381ff43ce1f4d06852fc19a2f11d180f23dc32d9588bee2f149d/pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f", size = 166450 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/c3/059298687310d527a58bb01f3b1965787ee3b40dce76752eda8b44e9a2c5/pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523", size = 63772 }, +] + +[[package]] +name = "pillow" +version = "11.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/0d/d0d6dea55cd152ce3d6767bb38a8fc10e33796ba4ba210cbab9354b6d238/pillow-11.3.0.tar.gz", hash = "sha256:3828ee7586cd0b2091b6209e5ad53e20d0649bbe87164a459d0676e035e8f523", size = 47113069 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/40/fe/1bc9b3ee13f68487a99ac9529968035cca2f0a51ec36892060edcc51d06a/pillow-11.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fdae223722da47b024b867c1ea0be64e0df702c5e0a60e27daad39bf960dd1e4", size = 5278800 }, + { url = "https://files.pythonhosted.org/packages/2c/32/7e2ac19b5713657384cec55f89065fb306b06af008cfd87e572035b27119/pillow-11.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:921bd305b10e82b4d1f5e802b6850677f965d8394203d182f078873851dada69", size = 4686296 }, + { url = "https://files.pythonhosted.org/packages/8e/1e/b9e12bbe6e4c2220effebc09ea0923a07a6da1e1f1bfbc8d7d29a01ce32b/pillow-11.3.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:eb76541cba2f958032d79d143b98a3a6b3ea87f0959bbe256c0b5e416599fd5d", size = 5871726 }, + { url = "https://files.pythonhosted.org/packages/8d/33/e9200d2bd7ba00dc3ddb78df1198a6e80d7669cce6c2bdbeb2530a74ec58/pillow-11.3.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:67172f2944ebba3d4a7b54f2e95c786a3a50c21b88456329314caaa28cda70f6", size = 7644652 }, + { url = "https://files.pythonhosted.org/packages/41/f1/6f2427a26fc683e00d985bc391bdd76d8dd4e92fac33d841127eb8fb2313/pillow-11.3.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97f07ed9f56a3b9b5f49d3661dc9607484e85c67e27f3e8be2c7d28ca032fec7", size = 5977787 }, + { url = "https://files.pythonhosted.org/packages/e4/c9/06dd4a38974e24f932ff5f98ea3c546ce3f8c995d3f0985f8e5ba48bba19/pillow-11.3.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:676b2815362456b5b3216b4fd5bd89d362100dc6f4945154ff172e206a22c024", size = 6645236 }, + { url = "https://files.pythonhosted.org/packages/40/e7/848f69fb79843b3d91241bad658e9c14f39a32f71a301bcd1d139416d1be/pillow-11.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3e184b2f26ff146363dd07bde8b711833d7b0202e27d13540bfe2e35a323a809", size = 6086950 }, + { url = "https://files.pythonhosted.org/packages/0b/1a/7cff92e695a2a29ac1958c2a0fe4c0b2393b60aac13b04a4fe2735cad52d/pillow-11.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6be31e3fc9a621e071bc17bb7de63b85cbe0bfae91bb0363c893cbe67247780d", size = 6723358 }, + { url = "https://files.pythonhosted.org/packages/26/7d/73699ad77895f69edff76b0f332acc3d497f22f5d75e5360f78cbcaff248/pillow-11.3.0-cp312-cp312-win32.whl", hash = "sha256:7b161756381f0918e05e7cb8a371fff367e807770f8fe92ecb20d905d0e1c149", size = 6275079 }, + { url = "https://files.pythonhosted.org/packages/8c/ce/e7dfc873bdd9828f3b6e5c2bbb74e47a98ec23cc5c74fc4e54462f0d9204/pillow-11.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:a6444696fce635783440b7f7a9fc24b3ad10a9ea3f0ab66c5905be1c19ccf17d", size = 6986324 }, + { url = "https://files.pythonhosted.org/packages/16/8f/b13447d1bf0b1f7467ce7d86f6e6edf66c0ad7cf44cf5c87a37f9bed9936/pillow-11.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:2aceea54f957dd4448264f9bf40875da0415c83eb85f55069d89c0ed436e3542", size = 2423067 }, + { url = "https://files.pythonhosted.org/packages/1e/93/0952f2ed8db3a5a4c7a11f91965d6184ebc8cd7cbb7941a260d5f018cd2d/pillow-11.3.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:1c627742b539bba4309df89171356fcb3cc5a9178355b2727d1b74a6cf155fbd", size = 2128328 }, + { url = "https://files.pythonhosted.org/packages/4b/e8/100c3d114b1a0bf4042f27e0f87d2f25e857e838034e98ca98fe7b8c0a9c/pillow-11.3.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:30b7c02f3899d10f13d7a48163c8969e4e653f8b43416d23d13d1bbfdc93b9f8", size = 2170652 }, + { url = "https://files.pythonhosted.org/packages/aa/86/3f758a28a6e381758545f7cdb4942e1cb79abd271bea932998fc0db93cb6/pillow-11.3.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:7859a4cc7c9295f5838015d8cc0a9c215b77e43d07a25e460f35cf516df8626f", size = 2227443 }, + { url = "https://files.pythonhosted.org/packages/01/f4/91d5b3ffa718df2f53b0dc109877993e511f4fd055d7e9508682e8aba092/pillow-11.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ec1ee50470b0d050984394423d96325b744d55c701a439d2bd66089bff963d3c", size = 5278474 }, + { url = "https://files.pythonhosted.org/packages/f9/0e/37d7d3eca6c879fbd9dba21268427dffda1ab00d4eb05b32923d4fbe3b12/pillow-11.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7db51d222548ccfd274e4572fdbf3e810a5e66b00608862f947b163e613b67dd", size = 4686038 }, + { url = "https://files.pythonhosted.org/packages/ff/b0/3426e5c7f6565e752d81221af9d3676fdbb4f352317ceafd42899aaf5d8a/pillow-11.3.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2d6fcc902a24ac74495df63faad1884282239265c6839a0a6416d33faedfae7e", size = 5864407 }, + { url = "https://files.pythonhosted.org/packages/fc/c1/c6c423134229f2a221ee53f838d4be9d82bab86f7e2f8e75e47b6bf6cd77/pillow-11.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f0f5d8f4a08090c6d6d578351a2b91acf519a54986c055af27e7a93feae6d3f1", size = 7639094 }, + { url = "https://files.pythonhosted.org/packages/ba/c9/09e6746630fe6372c67c648ff9deae52a2bc20897d51fa293571977ceb5d/pillow-11.3.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c37d8ba9411d6003bba9e518db0db0c58a680ab9fe5179f040b0463644bc9805", size = 5973503 }, + { url = "https://files.pythonhosted.org/packages/d5/1c/a2a29649c0b1983d3ef57ee87a66487fdeb45132df66ab30dd37f7dbe162/pillow-11.3.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:13f87d581e71d9189ab21fe0efb5a23e9f28552d5be6979e84001d3b8505abe8", size = 6642574 }, + { url = "https://files.pythonhosted.org/packages/36/de/d5cc31cc4b055b6c6fd990e3e7f0f8aaf36229a2698501bcb0cdf67c7146/pillow-11.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:023f6d2d11784a465f09fd09a34b150ea4672e85fb3d05931d89f373ab14abb2", size = 6084060 }, + { url = "https://files.pythonhosted.org/packages/d5/ea/502d938cbaeec836ac28a9b730193716f0114c41325db428e6b280513f09/pillow-11.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:45dfc51ac5975b938e9809451c51734124e73b04d0f0ac621649821a63852e7b", size = 6721407 }, + { url = "https://files.pythonhosted.org/packages/45/9c/9c5e2a73f125f6cbc59cc7087c8f2d649a7ae453f83bd0362ff7c9e2aee2/pillow-11.3.0-cp313-cp313-win32.whl", hash = "sha256:a4d336baed65d50d37b88ca5b60c0fa9d81e3a87d4a7930d3880d1624d5b31f3", size = 6273841 }, + { url = "https://files.pythonhosted.org/packages/23/85/397c73524e0cd212067e0c969aa245b01d50183439550d24d9f55781b776/pillow-11.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:0bce5c4fd0921f99d2e858dc4d4d64193407e1b99478bc5cacecba2311abde51", size = 6978450 }, + { url = "https://files.pythonhosted.org/packages/17/d2/622f4547f69cd173955194b78e4d19ca4935a1b0f03a302d655c9f6aae65/pillow-11.3.0-cp313-cp313-win_arm64.whl", hash = "sha256:1904e1264881f682f02b7f8167935cce37bc97db457f8e7849dc3a6a52b99580", size = 2423055 }, + { url = "https://files.pythonhosted.org/packages/dd/80/a8a2ac21dda2e82480852978416cfacd439a4b490a501a288ecf4fe2532d/pillow-11.3.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:4c834a3921375c48ee6b9624061076bc0a32a60b5532b322cc0ea64e639dd50e", size = 5281110 }, + { url = "https://files.pythonhosted.org/packages/44/d6/b79754ca790f315918732e18f82a8146d33bcd7f4494380457ea89eb883d/pillow-11.3.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5e05688ccef30ea69b9317a9ead994b93975104a677a36a8ed8106be9260aa6d", size = 4689547 }, + { url = "https://files.pythonhosted.org/packages/49/20/716b8717d331150cb00f7fdd78169c01e8e0c219732a78b0e59b6bdb2fd6/pillow-11.3.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1019b04af07fc0163e2810167918cb5add8d74674b6267616021ab558dc98ced", size = 5901554 }, + { url = "https://files.pythonhosted.org/packages/74/cf/a9f3a2514a65bb071075063a96f0a5cf949c2f2fce683c15ccc83b1c1cab/pillow-11.3.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f944255db153ebb2b19c51fe85dd99ef0ce494123f21b9db4877ffdfc5590c7c", size = 7669132 }, + { url = "https://files.pythonhosted.org/packages/98/3c/da78805cbdbee9cb43efe8261dd7cc0b4b93f2ac79b676c03159e9db2187/pillow-11.3.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1f85acb69adf2aaee8b7da124efebbdb959a104db34d3a2cb0f3793dbae422a8", size = 6005001 }, + { url = "https://files.pythonhosted.org/packages/6c/fa/ce044b91faecf30e635321351bba32bab5a7e034c60187fe9698191aef4f/pillow-11.3.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:05f6ecbeff5005399bb48d198f098a9b4b6bdf27b8487c7f38ca16eeb070cd59", size = 6668814 }, + { url = "https://files.pythonhosted.org/packages/7b/51/90f9291406d09bf93686434f9183aba27b831c10c87746ff49f127ee80cb/pillow-11.3.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a7bc6e6fd0395bc052f16b1a8670859964dbd7003bd0af2ff08342eb6e442cfe", size = 6113124 }, + { url = "https://files.pythonhosted.org/packages/cd/5a/6fec59b1dfb619234f7636d4157d11fb4e196caeee220232a8d2ec48488d/pillow-11.3.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:83e1b0161c9d148125083a35c1c5a89db5b7054834fd4387499e06552035236c", size = 6747186 }, + { url = "https://files.pythonhosted.org/packages/49/6b/00187a044f98255225f172de653941e61da37104a9ea60e4f6887717e2b5/pillow-11.3.0-cp313-cp313t-win32.whl", hash = "sha256:2a3117c06b8fb646639dce83694f2f9eac405472713fcb1ae887469c0d4f6788", size = 6277546 }, + { url = "https://files.pythonhosted.org/packages/e8/5c/6caaba7e261c0d75bab23be79f1d06b5ad2a2ae49f028ccec801b0e853d6/pillow-11.3.0-cp313-cp313t-win_amd64.whl", hash = "sha256:857844335c95bea93fb39e0fa2726b4d9d758850b34075a7e3ff4f4fa3aa3b31", size = 6985102 }, + { url = "https://files.pythonhosted.org/packages/f3/7e/b623008460c09a0cb38263c93b828c666493caee2eb34ff67f778b87e58c/pillow-11.3.0-cp313-cp313t-win_arm64.whl", hash = "sha256:8797edc41f3e8536ae4b10897ee2f637235c94f27404cac7297f7b607dd0716e", size = 2424803 }, + { url = "https://files.pythonhosted.org/packages/73/f4/04905af42837292ed86cb1b1dabe03dce1edc008ef14c473c5c7e1443c5d/pillow-11.3.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:d9da3df5f9ea2a89b81bb6087177fb1f4d1c7146d583a3fe5c672c0d94e55e12", size = 5278520 }, + { url = "https://files.pythonhosted.org/packages/41/b0/33d79e377a336247df6348a54e6d2a2b85d644ca202555e3faa0cf811ecc/pillow-11.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0b275ff9b04df7b640c59ec5a3cb113eefd3795a8df80bac69646ef699c6981a", size = 4686116 }, + { url = "https://files.pythonhosted.org/packages/49/2d/ed8bc0ab219ae8768f529597d9509d184fe8a6c4741a6864fea334d25f3f/pillow-11.3.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0743841cabd3dba6a83f38a92672cccbd69af56e3e91777b0ee7f4dba4385632", size = 5864597 }, + { url = "https://files.pythonhosted.org/packages/b5/3d/b932bb4225c80b58dfadaca9d42d08d0b7064d2d1791b6a237f87f661834/pillow-11.3.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2465a69cf967b8b49ee1b96d76718cd98c4e925414ead59fdf75cf0fd07df673", size = 7638246 }, + { url = "https://files.pythonhosted.org/packages/09/b5/0487044b7c096f1b48f0d7ad416472c02e0e4bf6919541b111efd3cae690/pillow-11.3.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:41742638139424703b4d01665b807c6468e23e699e8e90cffefe291c5832b027", size = 5973336 }, + { url = "https://files.pythonhosted.org/packages/a8/2d/524f9318f6cbfcc79fbc004801ea6b607ec3f843977652fdee4857a7568b/pillow-11.3.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:93efb0b4de7e340d99057415c749175e24c8864302369e05914682ba642e5d77", size = 6642699 }, + { url = "https://files.pythonhosted.org/packages/6f/d2/a9a4f280c6aefedce1e8f615baaa5474e0701d86dd6f1dede66726462bbd/pillow-11.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7966e38dcd0fa11ca390aed7c6f20454443581d758242023cf36fcb319b1a874", size = 6083789 }, + { url = "https://files.pythonhosted.org/packages/fe/54/86b0cd9dbb683a9d5e960b66c7379e821a19be4ac5810e2e5a715c09a0c0/pillow-11.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:98a9afa7b9007c67ed84c57c9e0ad86a6000da96eaa638e4f8abe5b65ff83f0a", size = 6720386 }, + { url = "https://files.pythonhosted.org/packages/e7/95/88efcaf384c3588e24259c4203b909cbe3e3c2d887af9e938c2022c9dd48/pillow-11.3.0-cp314-cp314-win32.whl", hash = "sha256:02a723e6bf909e7cea0dac1b0e0310be9d7650cd66222a5f1c571455c0a45214", size = 6370911 }, + { url = "https://files.pythonhosted.org/packages/2e/cc/934e5820850ec5eb107e7b1a72dd278140731c669f396110ebc326f2a503/pillow-11.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:a418486160228f64dd9e9efcd132679b7a02a5f22c982c78b6fc7dab3fefb635", size = 7117383 }, + { url = "https://files.pythonhosted.org/packages/d6/e9/9c0a616a71da2a5d163aa37405e8aced9a906d574b4a214bede134e731bc/pillow-11.3.0-cp314-cp314-win_arm64.whl", hash = "sha256:155658efb5e044669c08896c0c44231c5e9abcaadbc5cd3648df2f7c0b96b9a6", size = 2511385 }, + { url = "https://files.pythonhosted.org/packages/1a/33/c88376898aff369658b225262cd4f2659b13e8178e7534df9e6e1fa289f6/pillow-11.3.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:59a03cdf019efbfeeed910bf79c7c93255c3d54bc45898ac2a4140071b02b4ae", size = 5281129 }, + { url = "https://files.pythonhosted.org/packages/1f/70/d376247fb36f1844b42910911c83a02d5544ebd2a8bad9efcc0f707ea774/pillow-11.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f8a5827f84d973d8636e9dc5764af4f0cf2318d26744b3d902931701b0d46653", size = 4689580 }, + { url = "https://files.pythonhosted.org/packages/eb/1c/537e930496149fbac69efd2fc4329035bbe2e5475b4165439e3be9cb183b/pillow-11.3.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ee92f2fd10f4adc4b43d07ec5e779932b4eb3dbfbc34790ada5a6669bc095aa6", size = 5902860 }, + { url = "https://files.pythonhosted.org/packages/bd/57/80f53264954dcefeebcf9dae6e3eb1daea1b488f0be8b8fef12f79a3eb10/pillow-11.3.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c96d333dcf42d01f47b37e0979b6bd73ec91eae18614864622d9b87bbd5bbf36", size = 7670694 }, + { url = "https://files.pythonhosted.org/packages/70/ff/4727d3b71a8578b4587d9c276e90efad2d6fe0335fd76742a6da08132e8c/pillow-11.3.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4c96f993ab8c98460cd0c001447bff6194403e8b1d7e149ade5f00594918128b", size = 6005888 }, + { url = "https://files.pythonhosted.org/packages/05/ae/716592277934f85d3be51d7256f3636672d7b1abfafdc42cf3f8cbd4b4c8/pillow-11.3.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:41342b64afeba938edb034d122b2dda5db2139b9a4af999729ba8818e0056477", size = 6670330 }, + { url = "https://files.pythonhosted.org/packages/e7/bb/7fe6cddcc8827b01b1a9766f5fdeb7418680744f9082035bdbabecf1d57f/pillow-11.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:068d9c39a2d1b358eb9f245ce7ab1b5c3246c7c8c7d9ba58cfa5b43146c06e50", size = 6114089 }, + { url = "https://files.pythonhosted.org/packages/8b/f5/06bfaa444c8e80f1a8e4bff98da9c83b37b5be3b1deaa43d27a0db37ef84/pillow-11.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a1bc6ba083b145187f648b667e05a2534ecc4b9f2784c2cbe3089e44868f2b9b", size = 6748206 }, + { url = "https://files.pythonhosted.org/packages/f0/77/bc6f92a3e8e6e46c0ca78abfffec0037845800ea38c73483760362804c41/pillow-11.3.0-cp314-cp314t-win32.whl", hash = "sha256:118ca10c0d60b06d006be10a501fd6bbdfef559251ed31b794668ed569c87e12", size = 6377370 }, + { url = "https://files.pythonhosted.org/packages/4a/82/3a721f7d69dca802befb8af08b7c79ebcab461007ce1c18bd91a5d5896f9/pillow-11.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:8924748b688aa210d79883357d102cd64690e56b923a186f35a82cbc10f997db", size = 7121500 }, + { url = "https://files.pythonhosted.org/packages/89/c7/5572fa4a3f45740eaab6ae86fcdf7195b55beac1371ac8c619d880cfe948/pillow-11.3.0-cp314-cp314t-win_arm64.whl", hash = "sha256:79ea0d14d3ebad43ec77ad5272e6ff9bba5b679ef73375ea760261207fa8e0aa", size = 2512835 }, +] + [[package]] name = "platformdirs" version = "4.3.7" @@ -885,6 +2361,20 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556 }, ] +[[package]] +name = "pooch" +version = "1.8.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, + { name = "platformdirs" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c6/77/b3d3e00c696c16cf99af81ef7b1f5fe73bd2a307abca41bd7605429fe6e5/pooch-1.8.2.tar.gz", hash = "sha256:76561f0de68a01da4df6af38e9955c4c9d1a5c90da73f7e40276a5728ec83d10", size = 59353 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a8/87/77cc11c7a9ea9fd05503def69e3d18605852cd0d4b0d3b8f15bbeb3ef1d1/pooch-1.8.2-py3-none-any.whl", hash = "sha256:3529a57096f7198778a5ceefd5ac3ef0e4d06a6ddaf9fc2d609b806f25302c47", size = 64574 }, +] + [[package]] name = "pre-commit" version = "4.2.0" @@ -901,6 +2391,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/88/74/a88bf1b1efeae488a0c0b7bdf71429c313722d1fc0f377537fbe554e6180/pre_commit-4.2.0-py2.py3-none-any.whl", hash = "sha256:a009ca7205f1eb497d10b845e52c838a98b6cdd2102a6c8e4540e94ee75c58bd", size = 220707 }, ] +[[package]] +name = "prompt-toolkit" +version = "3.0.51" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "wcwidth" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bb/6e/9d084c929dfe9e3bfe0c6a47e31f78a25c54627d64a66e884a8bf5474f1c/prompt_toolkit-3.0.51.tar.gz", hash = "sha256:931a162e3b27fc90c86f1b48bb1fb2c528c2761475e57c9c06de13311c7b54ed", size = 428940 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ce/4f/5249960887b1fbe561d9ff265496d170b55a735b76724f10ef19f9e40716/prompt_toolkit-3.0.51-py3-none-any.whl", hash = "sha256:52742911fde84e2d423e2f9a4cf1de7d7ac4e51958f648d9540e0fb8db077b07", size = 387810 }, +] + [[package]] name = "propcache" version = "0.3.1" @@ -958,19 +2460,184 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b8/d3/c3cb8f1d6ae3b37f83e1de806713a9b3642c5895f0215a62e1a4bd6e5e34/propcache-0.3.1-py3-none-any.whl", hash = "sha256:9a8ecf38de50a7f518c21568c80f985e776397b902f1ce0b01f799aba1608b40", size = 12376 }, ] +[[package]] +name = "protobuf" +version = "6.31.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/52/f3/b9655a711b32c19720253f6f06326faf90580834e2e83f840472d752bc8b/protobuf-6.31.1.tar.gz", hash = "sha256:d8cac4c982f0b957a4dc73a80e2ea24fab08e679c0de9deb835f4a12d69aca9a", size = 441797 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f3/6f/6ab8e4bf962fd5570d3deaa2d5c38f0a363f57b4501047b5ebeb83ab1125/protobuf-6.31.1-cp310-abi3-win32.whl", hash = "sha256:7fa17d5a29c2e04b7d90e5e32388b8bfd0e7107cd8e616feef7ed3fa6bdab5c9", size = 423603 }, + { url = "https://files.pythonhosted.org/packages/44/3a/b15c4347dd4bf3a1b0ee882f384623e2063bb5cf9fa9d57990a4f7df2fb6/protobuf-6.31.1-cp310-abi3-win_amd64.whl", hash = "sha256:426f59d2964864a1a366254fa703b8632dcec0790d8862d30034d8245e1cd447", size = 435283 }, + { url = "https://files.pythonhosted.org/packages/6a/c9/b9689a2a250264a84e66c46d8862ba788ee7a641cdca39bccf64f59284b7/protobuf-6.31.1-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:6f1227473dc43d44ed644425268eb7c2e488ae245d51c6866d19fe158e207402", size = 425604 }, + { url = "https://files.pythonhosted.org/packages/76/a1/7a5a94032c83375e4fe7e7f56e3976ea6ac90c5e85fac8576409e25c39c3/protobuf-6.31.1-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:a40fc12b84c154884d7d4c4ebd675d5b3b5283e155f324049ae396b95ddebc39", size = 322115 }, + { url = "https://files.pythonhosted.org/packages/fa/b1/b59d405d64d31999244643d88c45c8241c58f17cc887e73bcb90602327f8/protobuf-6.31.1-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:4ee898bf66f7a8b0bd21bce523814e6fbd8c6add948045ce958b73af7e8878c6", size = 321070 }, + { url = "https://files.pythonhosted.org/packages/f7/af/ab3c51ab7507a7325e98ffe691d9495ee3d3aa5f589afad65ec920d39821/protobuf-6.31.1-py3-none-any.whl", hash = "sha256:720a6c7e6b77288b85063569baae8536671b39f15cc22037ec7045658d80489e", size = 168724 }, +] + +[[package]] +name = "psutil" +version = "7.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2a/80/336820c1ad9286a4ded7e845b2eccfcb27851ab8ac6abece774a6ff4d3de/psutil-7.0.0.tar.gz", hash = "sha256:7be9c3eba38beccb6495ea33afd982a44074b78f28c434a1f51cc07fd315c456", size = 497003 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ed/e6/2d26234410f8b8abdbf891c9da62bee396583f713fb9f3325a4760875d22/psutil-7.0.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:101d71dc322e3cffd7cea0650b09b3d08b8e7c4109dd6809fe452dfd00e58b25", size = 238051 }, + { url = "https://files.pythonhosted.org/packages/04/8b/30f930733afe425e3cbfc0e1468a30a18942350c1a8816acfade80c005c4/psutil-7.0.0-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:39db632f6bb862eeccf56660871433e111b6ea58f2caea825571951d4b6aa3da", size = 239535 }, + { url = "https://files.pythonhosted.org/packages/2a/ed/d362e84620dd22876b55389248e522338ed1bf134a5edd3b8231d7207f6d/psutil-7.0.0-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1fcee592b4c6f146991ca55919ea3d1f8926497a713ed7faaf8225e174581e91", size = 275004 }, + { url = "https://files.pythonhosted.org/packages/bf/b9/b0eb3f3cbcb734d930fdf839431606844a825b23eaf9a6ab371edac8162c/psutil-7.0.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b1388a4f6875d7e2aff5c4ca1cc16c545ed41dd8bb596cefea80111db353a34", size = 277986 }, + { url = "https://files.pythonhosted.org/packages/eb/a2/709e0fe2f093556c17fbafda93ac032257242cabcc7ff3369e2cb76a97aa/psutil-7.0.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5f098451abc2828f7dc6b58d44b532b22f2088f4999a937557b603ce72b1993", size = 279544 }, + { url = "https://files.pythonhosted.org/packages/50/e6/eecf58810b9d12e6427369784efe814a1eec0f492084ce8eb8f4d89d6d61/psutil-7.0.0-cp37-abi3-win32.whl", hash = "sha256:ba3fcef7523064a6c9da440fc4d6bd07da93ac726b5733c29027d7dc95b39d99", size = 241053 }, + { url = "https://files.pythonhosted.org/packages/50/1b/6921afe68c74868b4c9fa424dad3be35b095e16687989ebbb50ce4fceb7c/psutil-7.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:4cf3d4eb1aa9b348dec30105c55cd9b7d4629285735a102beb4441e38db90553", size = 244885 }, +] + +[[package]] +name = "ptyprocess" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/20/e5/16ff212c1e452235a90aeb09066144d0c5a6a8c0834397e03f5224495c4e/ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220", size = 70762 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/22/a6/858897256d0deac81a172289110f31629fc4cee19b6f01283303e18c8db3/ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35", size = 13993 }, +] + +[[package]] +name = "pure-eval" +version = "0.2.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/05/0a34433a064256a578f1783a10da6df098ceaa4a57bbeaa96a6c0352786b/pure_eval-0.2.3.tar.gz", hash = "sha256:5f4e983f40564c576c7c8635ae88db5956bb2229d7e9237d03b3c0b0190eaf42", size = 19752 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl", hash = "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0", size = 11842 }, +] + +[[package]] +name = "py-cid" +version = "0.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "base58" }, + { name = "morphys" }, + { name = "py-multibase" }, + { name = "py-multicodec" }, + { name = "py-multihash" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b7/65/1d57b5519b4201f39bf4a9cc3e2d554f25446c56df88c0e9abbffe28cdbe/py-cid-0.3.0.tar.gz", hash = "sha256:22f432cc6fb68d12a9c35dbdc92c95484fc49e31dfcb9e0efb0082233c5394e3", size = 24147 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/11/72/8fd8ac6e79b00d171499a1808d5af99c109d9780ca4982ff69f1baf784e0/py_cid-0.3.0-py2.py3-none-any.whl", hash = "sha256:7c48a6ee0bc50fd114d4b24849cd689a31d3ad5bdf8fa073bf68f846fd58c5da", size = 7893 }, +] + +[[package]] +name = "py-ecc" +version = "8.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "eth-typing" }, + { name = "eth-utils" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1c/96/e73075d5c885274efada2fbc5db6377022036c2f5b4b470dbcf4106e07d5/py_ecc-8.0.0.tar.gz", hash = "sha256:56aca19e5dc37294f60c1cc76666c03c2276e7666412b9a559fa0145d099933d", size = 51193 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/58/383335eac96d2f1aba78741c6ce128c54e7eba2ea1dc47408257d751d35c/py_ecc-8.0.0-py3-none-any.whl", hash = "sha256:c0b2dfc4bde67a55122a392591a10e851a986d5128f680628c80b405f7663e13", size = 47814 }, +] + +[[package]] +name = "py-evm" +version = "0.10.1b1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cached-property" }, + { name = "ckzg" }, + { name = "eth-bloom" }, + { name = "eth-keys" }, + { name = "eth-typing" }, + { name = "eth-utils" }, + { name = "lru-dict" }, + { name = "py-ecc" }, + { name = "rlp" }, + { name = "trie" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/82/b4/0dd6f0660c93123685f9d162f94aa98bb2de211d8edb74a1fbfb667853c4/py_evm-0.10.1b1.tar.gz", hash = "sha256:aeb889514af12b6a8cb5091fe93008642eadf7c19999859dad3191eaf451647c", size = 608366 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5a/6c/8811c3eaeeb432bd4c9614c4d1b064a4a344910d610fe858878636dc03d3/py_evm-0.10.1b1-py3-none-any.whl", hash = "sha256:f0fc4a4b904917b40e6a06f87925017dc48ea6582e95f88d28be38f3566e2bae", size = 551293 }, +] + +[[package]] +name = "py-geth" +version = "4.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "semantic-version" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cc/4b/3229e00852ac771a942feb8e3676d5203508ef8dea72dbf7f5cf444c7b1e/py-geth-4.4.0.tar.gz", hash = "sha256:c08d84f6dad4f86a9b8ffd74c0a0f160d600db0ee45dfc2a66d5e13522aeb039", size = 30465 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3c/9f/7ea1741c5eb80a99540f17a22562455d41da2237e0ac9435e6ac6e576887/py_geth-4.4.0-py3-none-any.whl", hash = "sha256:ae8771b028c68f6710e6434d2aa1310ce2ba3cc9099d244d9bb5a9e340786a92", size = 24379 }, +] + [[package]] name = "py-hamt" -version = "2.4.0" -source = { git = "https://github.com/dClimate/py-hamt#3ac5d99d537b4727480ff388f91d2dc0e7b9a58b" } +version = "3.2.0" +source = { git = "https://github.com/dClimate/py-hamt?rev=a60039b4e2305847c8b9241557a35f96038d7ca0#a60039b4e2305847c8b9241557a35f96038d7ca0" } dependencies = [ { name = "dag-cbor" }, + { name = "httpx", extra = ["http2"] }, { name = "msgspec" }, { name = "multiformats", extra = ["full"] }, { name = "pycryptodome" }, - { name = "requests" }, { name = "zarr" }, ] +[[package]] +name = "py-multibase" +version = "1.0.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "morphys" }, + { name = "python-baseconv" }, + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/58/9c/8b1b78e900f5e94b1d48359efffcaa1ba9ddd0448e6aac94d4d27dbd67b7/py-multibase-1.0.3.tar.gz", hash = "sha256:d28a20efcbb61eec28f55827a0bf329c7cea80fffd933aecaea6ae8431267fe4", size = 23376 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/21/7b6b3bba9c702daf6991f2bd89c92db6fa899a444fab24ba5b99f1f486f7/py_multibase-1.0.3-py2.py3-none-any.whl", hash = "sha256:2677c1fafcc0ae15ddb9c7f444c5becc2530b3889124fd4fa2959ddfefb8c15b", size = 6378 }, +] + +[[package]] +name = "py-multicodec" +version = "0.2.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "morphys" }, + { name = "six" }, + { name = "varint" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/77/33/ff388b34002239c367f6544678667efff079d29aabf7e3c4a3ec392310be/py-multicodec-0.2.1.tar.gz", hash = "sha256:83021ffe8c0e272d19b5b86bc5b39efa67c8e9f4735ce6cafdbc1ace767ec647", size = 22542 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6c/96/3ffb721c2fe100f6613a0d2db3b4b3229c6de52b279fd41aab0e84ea0725/py_multicodec-0.2.1-py2.py3-none-any.whl", hash = "sha256:55b6bb53088a63e56c434cb11b29795e8805652bac43d50a8f2a9bcf5ca84e1f", size = 8536 }, +] + +[[package]] +name = "py-multihash" +version = "0.2.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "base58" }, + { name = "morphys" }, + { name = "six" }, + { name = "varint" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ba/e5/d9bb73cc981d29446bfc3b547ddc67a1b5d07f27b277add2f46c12392fa0/py-multihash-0.2.3.tar.gz", hash = "sha256:f0ade4de820afdc4b4aaa40464ec86c9da5cae3a4578cda2daab4b0eb7e5b18d", size = 21358 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/31/04/9c0d173afcf3f06149b35de179e0e5d174db2c9e6466bac01215b45e9bb4/py_multihash-0.2.3-py2.py3-none-any.whl", hash = "sha256:a0602c99093587dfbf1634e2e8c7726de39374b0d68587a36093b4c237af6969", size = 7929 }, +] + +[[package]] +name = "py-solc-x" +version = "2.0.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b9/72/92c6c96584cf9b1e7f1da50978d44910e7545ce0df646041ce683993ee18/py-solc-x-2.0.4.tar.gz", hash = "sha256:6f6f7f47c8520bde73679624568ec343db9baca6914212b371896fad8697deca", size = 77779 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/97/3a/063e034e0b283933fc1b8359ba5982b5cb853520e32829f1d0e7881fe47c/py_solc_x-2.0.4-py3-none-any.whl", hash = "sha256:fe7baf788ed2f23b3c793c20661560cceca6f8a1de9d6d479fe3ea9c6c1b2a9e", size = 18560 }, +] + [[package]] name = "pyarrow" version = "19.0.1" @@ -1035,6 +2702,77 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/15/c8/79ab16e5b95a8988caee792236a776beceabcaa2518979d4e21b6ee20f57/pycryptodomex-3.22.0-cp37-abi3-win_amd64.whl", hash = "sha256:ff46212fda7ee86ec2f4a64016c994e8ad80f11ef748131753adb67e9b722ebd", size = 1797989 }, ] +[[package]] +name = "pydantic" +version = "2.11.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-types" }, + { name = "pydantic-core" }, + { name = "typing-extensions" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/00/dd/4325abf92c39ba8623b5af936ddb36ffcfe0beae70405d456ab1fb2f5b8c/pydantic-2.11.7.tar.gz", hash = "sha256:d989c3c6cb79469287b1569f7447a17848c998458d49ebe294e975b9baf0f0db", size = 788350 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6a/c0/ec2b1c8712ca690e5d61979dee872603e92b8a32f94cc1b72d53beab008a/pydantic-2.11.7-py3-none-any.whl", hash = "sha256:dde5df002701f6de26248661f6835bbe296a47bf73990135c7d07ce741b9623b", size = 444782 }, +] + +[[package]] +name = "pydantic-core" +version = "2.33.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ad/88/5f2260bdfae97aabf98f1778d43f69574390ad787afb646292a638c923d4/pydantic_core-2.33.2.tar.gz", hash = "sha256:7cb8bc3605c29176e1b105350d2e6474142d7c1bd1d9327c4a9bdb46bf827acc", size = 435195 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/8a/2b41c97f554ec8c71f2a8a5f85cb56a8b0956addfe8b0efb5b3d77e8bdc3/pydantic_core-2.33.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a7ec89dc587667f22b6a0b6579c249fca9026ce7c333fc142ba42411fa243cdc", size = 2009000 }, + { url = "https://files.pythonhosted.org/packages/a1/02/6224312aacb3c8ecbaa959897af57181fb6cf3a3d7917fd44d0f2917e6f2/pydantic_core-2.33.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3c6db6e52c6d70aa0d00d45cdb9b40f0433b96380071ea80b09277dba021ddf7", size = 1847996 }, + { url = "https://files.pythonhosted.org/packages/d6/46/6dcdf084a523dbe0a0be59d054734b86a981726f221f4562aed313dbcb49/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e61206137cbc65e6d5256e1166f88331d3b6238e082d9f74613b9b765fb9025", size = 1880957 }, + { url = "https://files.pythonhosted.org/packages/ec/6b/1ec2c03837ac00886ba8160ce041ce4e325b41d06a034adbef11339ae422/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eb8c529b2819c37140eb51b914153063d27ed88e3bdc31b71198a198e921e011", size = 1964199 }, + { url = "https://files.pythonhosted.org/packages/2d/1d/6bf34d6adb9debd9136bd197ca72642203ce9aaaa85cfcbfcf20f9696e83/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c52b02ad8b4e2cf14ca7b3d918f3eb0ee91e63b3167c32591e57c4317e134f8f", size = 2120296 }, + { url = "https://files.pythonhosted.org/packages/e0/94/2bd0aaf5a591e974b32a9f7123f16637776c304471a0ab33cf263cf5591a/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:96081f1605125ba0855dfda83f6f3df5ec90c61195421ba72223de35ccfb2f88", size = 2676109 }, + { url = "https://files.pythonhosted.org/packages/f9/41/4b043778cf9c4285d59742281a769eac371b9e47e35f98ad321349cc5d61/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f57a69461af2a5fa6e6bbd7a5f60d3b7e6cebb687f55106933188e79ad155c1", size = 2002028 }, + { url = "https://files.pythonhosted.org/packages/cb/d5/7bb781bf2748ce3d03af04d5c969fa1308880e1dca35a9bd94e1a96a922e/pydantic_core-2.33.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:572c7e6c8bb4774d2ac88929e3d1f12bc45714ae5ee6d9a788a9fb35e60bb04b", size = 2100044 }, + { url = "https://files.pythonhosted.org/packages/fe/36/def5e53e1eb0ad896785702a5bbfd25eed546cdcf4087ad285021a90ed53/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:db4b41f9bd95fbe5acd76d89920336ba96f03e149097365afe1cb092fceb89a1", size = 2058881 }, + { url = "https://files.pythonhosted.org/packages/01/6c/57f8d70b2ee57fc3dc8b9610315949837fa8c11d86927b9bb044f8705419/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:fa854f5cf7e33842a892e5c73f45327760bc7bc516339fda888c75ae60edaeb6", size = 2227034 }, + { url = "https://files.pythonhosted.org/packages/27/b9/9c17f0396a82b3d5cbea4c24d742083422639e7bb1d5bf600e12cb176a13/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5f483cfb75ff703095c59e365360cb73e00185e01aaea067cd19acffd2ab20ea", size = 2234187 }, + { url = "https://files.pythonhosted.org/packages/b0/6a/adf5734ffd52bf86d865093ad70b2ce543415e0e356f6cacabbc0d9ad910/pydantic_core-2.33.2-cp312-cp312-win32.whl", hash = "sha256:9cb1da0f5a471435a7bc7e439b8a728e8b61e59784b2af70d7c169f8dd8ae290", size = 1892628 }, + { url = "https://files.pythonhosted.org/packages/43/e4/5479fecb3606c1368d496a825d8411e126133c41224c1e7238be58b87d7e/pydantic_core-2.33.2-cp312-cp312-win_amd64.whl", hash = "sha256:f941635f2a3d96b2973e867144fde513665c87f13fe0e193c158ac51bfaaa7b2", size = 1955866 }, + { url = "https://files.pythonhosted.org/packages/0d/24/8b11e8b3e2be9dd82df4b11408a67c61bb4dc4f8e11b5b0fc888b38118b5/pydantic_core-2.33.2-cp312-cp312-win_arm64.whl", hash = "sha256:cca3868ddfaccfbc4bfb1d608e2ccaaebe0ae628e1416aeb9c4d88c001bb45ab", size = 1888894 }, + { url = "https://files.pythonhosted.org/packages/46/8c/99040727b41f56616573a28771b1bfa08a3d3fe74d3d513f01251f79f172/pydantic_core-2.33.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:1082dd3e2d7109ad8b7da48e1d4710c8d06c253cbc4a27c1cff4fbcaa97a9e3f", size = 2015688 }, + { url = "https://files.pythonhosted.org/packages/3a/cc/5999d1eb705a6cefc31f0b4a90e9f7fc400539b1a1030529700cc1b51838/pydantic_core-2.33.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f517ca031dfc037a9c07e748cefd8d96235088b83b4f4ba8939105d20fa1dcd6", size = 1844808 }, + { url = "https://files.pythonhosted.org/packages/6f/5e/a0a7b8885c98889a18b6e376f344da1ef323d270b44edf8174d6bce4d622/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a9f2c9dd19656823cb8250b0724ee9c60a82f3cdf68a080979d13092a3b0fef", size = 1885580 }, + { url = "https://files.pythonhosted.org/packages/3b/2a/953581f343c7d11a304581156618c3f592435523dd9d79865903272c256a/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2b0a451c263b01acebe51895bfb0e1cc842a5c666efe06cdf13846c7418caa9a", size = 1973859 }, + { url = "https://files.pythonhosted.org/packages/e6/55/f1a813904771c03a3f97f676c62cca0c0a4138654107c1b61f19c644868b/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ea40a64d23faa25e62a70ad163571c0b342b8bf66d5fa612ac0dec4f069d916", size = 2120810 }, + { url = "https://files.pythonhosted.org/packages/aa/c3/053389835a996e18853ba107a63caae0b9deb4a276c6b472931ea9ae6e48/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fb2d542b4d66f9470e8065c5469ec676978d625a8b7a363f07d9a501a9cb36a", size = 2676498 }, + { url = "https://files.pythonhosted.org/packages/eb/3c/f4abd740877a35abade05e437245b192f9d0ffb48bbbbd708df33d3cda37/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdac5d6ffa1b5a83bca06ffe7583f5576555e6c8b3a91fbd25ea7780f825f7d", size = 2000611 }, + { url = "https://files.pythonhosted.org/packages/59/a7/63ef2fed1837d1121a894d0ce88439fe3e3b3e48c7543b2a4479eb99c2bd/pydantic_core-2.33.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:04a1a413977ab517154eebb2d326da71638271477d6ad87a769102f7c2488c56", size = 2107924 }, + { url = "https://files.pythonhosted.org/packages/04/8f/2551964ef045669801675f1cfc3b0d74147f4901c3ffa42be2ddb1f0efc4/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c8e7af2f4e0194c22b5b37205bfb293d166a7344a5b0d0eaccebc376546d77d5", size = 2063196 }, + { url = "https://files.pythonhosted.org/packages/26/bd/d9602777e77fc6dbb0c7db9ad356e9a985825547dce5ad1d30ee04903918/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:5c92edd15cd58b3c2d34873597a1e20f13094f59cf88068adb18947df5455b4e", size = 2236389 }, + { url = "https://files.pythonhosted.org/packages/42/db/0e950daa7e2230423ab342ae918a794964b053bec24ba8af013fc7c94846/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:65132b7b4a1c0beded5e057324b7e16e10910c106d43675d9bd87d4f38dde162", size = 2239223 }, + { url = "https://files.pythonhosted.org/packages/58/4d/4f937099c545a8a17eb52cb67fe0447fd9a373b348ccfa9a87f141eeb00f/pydantic_core-2.33.2-cp313-cp313-win32.whl", hash = "sha256:52fb90784e0a242bb96ec53f42196a17278855b0f31ac7c3cc6f5c1ec4811849", size = 1900473 }, + { url = "https://files.pythonhosted.org/packages/a0/75/4a0a9bac998d78d889def5e4ef2b065acba8cae8c93696906c3a91f310ca/pydantic_core-2.33.2-cp313-cp313-win_amd64.whl", hash = "sha256:c083a3bdd5a93dfe480f1125926afcdbf2917ae714bdb80b36d34318b2bec5d9", size = 1955269 }, + { url = "https://files.pythonhosted.org/packages/f9/86/1beda0576969592f1497b4ce8e7bc8cbdf614c352426271b1b10d5f0aa64/pydantic_core-2.33.2-cp313-cp313-win_arm64.whl", hash = "sha256:e80b087132752f6b3d714f041ccf74403799d3b23a72722ea2e6ba2e892555b9", size = 1893921 }, + { url = "https://files.pythonhosted.org/packages/a4/7d/e09391c2eebeab681df2b74bfe6c43422fffede8dc74187b2b0bf6fd7571/pydantic_core-2.33.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:61c18fba8e5e9db3ab908620af374db0ac1baa69f0f32df4f61ae23f15e586ac", size = 1806162 }, + { url = "https://files.pythonhosted.org/packages/f1/3d/847b6b1fed9f8ed3bb95a9ad04fbd0b212e832d4f0f50ff4d9ee5a9f15cf/pydantic_core-2.33.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95237e53bb015f67b63c91af7518a62a8660376a6a0db19b89acc77a4d6199f5", size = 1981560 }, + { url = "https://files.pythonhosted.org/packages/6f/9a/e73262f6c6656262b5fdd723ad90f518f579b7bc8622e43a942eec53c938/pydantic_core-2.33.2-cp313-cp313t-win_amd64.whl", hash = "sha256:c2fc0a768ef76c15ab9238afa6da7f69895bb5d1ee83aeea2e3509af4472d0b9", size = 1935777 }, +] + +[[package]] +name = "pydantic-settings" +version = "2.10.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic" }, + { name = "python-dotenv" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/68/85/1ea668bbab3c50071ca613c6ab30047fb36ab0da1b92fa8f17bbc38fd36c/pydantic_settings-2.10.1.tar.gz", hash = "sha256:06f0062169818d0f5524420a360d632d5857b83cffd4d42fe29597807a1614ee", size = 172583 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/58/f0/427018098906416f580e3cf1366d3b1abfb408a0652e9f31600c24a1903c/pydantic_settings-2.10.1-py3-none-any.whl", hash = "sha256:a60952460b99cf661dc25c29c0ef171721f98bfcb52ef8d9ea4c943d7c8cc796", size = 45235 }, +] + [[package]] name = "pygments" version = "2.19.1" @@ -1105,6 +2843,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/98/df/68a2b7f5fb6400c64aad82d72bcc4bc531775e62eedff993a77c780defd0/pyproj-3.7.1-cp313-cp313-win_amd64.whl", hash = "sha256:d3caac7473be22b6d6e102dde6c46de73b96bc98334e577dfaee9886f102ea2e", size = 6266573 }, ] +[[package]] +name = "pyshp" +version = "2.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/63/9f/0dd21250c60375a532c35e89fad8d5e8a3f1a2e3f7c389ccc5a60b05263e/pyshp-2.3.1.tar.gz", hash = "sha256:4caec82fd8dd096feba8217858068bacb2a3b5950f43c048c6dc32a3489d5af1", size = 1731544 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/98/2f/68116db5b36b895c0450e3072b8cb6c2fac0359279b182ea97014d3c8ac0/pyshp-2.3.1-py2.py3-none-any.whl", hash = "sha256:67024c0ccdc352ba5db777c4e968483782dfa78f8e200672a90d2d30fd8b7b49", size = 46537 }, +] + [[package]] name = "pyskein" version = "1.0" @@ -1126,6 +2873,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/30/3d/64ad57c803f1fa1e963a7946b6e0fea4a70df53c1a7fed304586539c2bac/pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820", size = 343634 }, ] +[[package]] +name = "pytest-asyncio" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d0/d4/14f53324cb1a6381bef29d698987625d80052bb33932d8e7cbf9b337b17c/pytest_asyncio-1.0.0.tar.gz", hash = "sha256:d15463d13f4456e1ead2594520216b225a16f781e144f8fdf6c5bb4667c48b3f", size = 46960 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/30/05/ce271016e351fddc8399e546f6e23761967ee09c8c568bbfbecb0c150171/pytest_asyncio-1.0.0-py3-none-any.whl", hash = "sha256:4f024da9f1ef945e680dc68610b52550e36590a67fd31bb3b4943979a1f90ef3", size = 15976 }, +] + [[package]] name = "pytest-cov" version = "6.0.0" @@ -1151,6 +2910,12 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f2/3b/b26f90f74e2986a82df6e7ac7e319b8ea7ccece1caec9f8ab6104dc70603/pytest_mock-3.14.0-py3-none-any.whl", hash = "sha256:0b72c38033392a5f4621342fe11e9219ac11ec9d375f8e2a0c164539e0d70f6f", size = 9863 }, ] +[[package]] +name = "python-baseconv" +version = "1.2.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/33/d0/9297d7d8dd74767b4d5560d834b30b2fff17d39987c23ed8656f476e0d9b/python-baseconv-1.2.2.tar.gz", hash = "sha256:0539f8bd0464013b05ad62e0a1673f0ac9086c76b43ebf9f833053527cd9931b", size = 4929 } + [[package]] name = "python-dateutil" version = "2.9.0.post0" @@ -1163,6 +2928,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892 }, ] +[[package]] +name = "python-dotenv" +version = "1.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/b0/4bc07ccd3572a2f9df7e6782f52b0c6c90dcbb803ac4a167702d7d0dfe1e/python_dotenv-1.1.1.tar.gz", hash = "sha256:a8a6399716257f45be6a007360200409fce5cda2661e3dec71d23dc15f6189ab", size = 41978 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5f/ed/539768cf28c661b5b068d66d96a2f155c4971a5d55684a514c1a0e0dec2f/python_dotenv-1.1.1-py3-none-any.whl", hash = "sha256:31f23644fe2602f88ff55e1f5c79ba497e01224ee7737937930c448e4d0e24dc", size = 20556 }, +] + [[package]] name = "pytz" version = "2025.2" @@ -1172,6 +2946,28 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00", size = 509225 }, ] +[[package]] +name = "pyunormalize" +version = "16.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b3/08/568036c725dac746ecb267bb749ef930fb7907454fe69fce83c8557287fb/pyunormalize-16.0.0.tar.gz", hash = "sha256:2e1dfbb4a118154ae26f70710426a52a364b926c9191f764601f5a8cb12761f7", size = 49968 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/39/f9/9d86e56f716e0651194a5ad58be9c146fcaf1de6901ac6f3cd3affeeb74e/pyunormalize-16.0.0-py3-none-any.whl", hash = "sha256:c647d95e5d1e2ea9a2f448d1d95d8518348df24eab5c3fd32d2b5c3300a49152", size = 49173 }, +] + +[[package]] +name = "pywin32" +version = "310" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6b/ec/4fdbe47932f671d6e348474ea35ed94227fb5df56a7c30cbbb42cd396ed0/pywin32-310-cp312-cp312-win32.whl", hash = "sha256:8a75a5cc3893e83a108c05d82198880704c44bbaee4d06e442e471d3c9ea4f3d", size = 8796239 }, + { url = "https://files.pythonhosted.org/packages/e3/e5/b0627f8bb84e06991bea89ad8153a9e50ace40b2e1195d68e9dff6b03d0f/pywin32-310-cp312-cp312-win_amd64.whl", hash = "sha256:bf5c397c9a9a19a6f62f3fb821fbf36cac08f03770056711f765ec1503972060", size = 9503839 }, + { url = "https://files.pythonhosted.org/packages/1f/32/9ccf53748df72301a89713936645a664ec001abd35ecc8578beda593d37d/pywin32-310-cp312-cp312-win_arm64.whl", hash = "sha256:2349cc906eae872d0663d4d6290d13b90621eaf78964bb1578632ff20e152966", size = 8459470 }, + { url = "https://files.pythonhosted.org/packages/1c/09/9c1b978ffc4ae53999e89c19c77ba882d9fce476729f23ef55211ea1c034/pywin32-310-cp313-cp313-win32.whl", hash = "sha256:5d241a659c496ada3253cd01cfaa779b048e90ce4b2b38cd44168ad555ce74ab", size = 8794384 }, + { url = "https://files.pythonhosted.org/packages/45/3c/b4640f740ffebadd5d34df35fecba0e1cfef8fde9f3e594df91c28ad9b50/pywin32-310-cp313-cp313-win_amd64.whl", hash = "sha256:667827eb3a90208ddbdcc9e860c81bde63a135710e21e4cb3348968e4bd5249e", size = 9503039 }, + { url = "https://files.pythonhosted.org/packages/b4/f4/f785020090fb050e7fb6d34b780f2231f302609dc964672f72bfaeb59a28/pywin32-310-cp313-cp313-win_arm64.whl", hash = "sha256:e308f831de771482b7cf692a1f308f8fca701b2d8f9dde6cc440c7da17e47b33", size = 8458152 }, +] + [[package]] name = "pyyaml" version = "6.0.2" @@ -1224,6 +3020,58 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/df/88/9db5f49ebfdd9c12365e4cac76c34ccb1a642b1c8cbab4124b3c681495de/rasterio-1.4.3-cp313-cp313-win_amd64.whl", hash = "sha256:1839960e2f3057a6daa323ccf67b330f8f2f0dbd4a50cc7031e88e649301c5c0", size = 25424949 }, ] +[[package]] +name = "referencing" +version = "0.36.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "rpds-py" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2f/db/98b5c277be99dd18bfd91dd04e1b759cad18d1a338188c936e92f921c7e2/referencing-0.36.2.tar.gz", hash = "sha256:df2e89862cd09deabbdba16944cc3f10feb6b3e6f18e902f7cc25609a34775aa", size = 74744 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c1/b1/3baf80dc6d2b7bc27a95a67752d0208e410351e3feb4eb78de5f77454d8d/referencing-0.36.2-py3-none-any.whl", hash = "sha256:e8699adbbf8b5c7de96d8ffa0eb5c158b3beafce084968e2ea8bb08c6794dcd0", size = 26775 }, +] + +[[package]] +name = "regex" +version = "2024.11.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8e/5f/bd69653fbfb76cf8604468d3b4ec4c403197144c7bfe0e6a5fc9e02a07cb/regex-2024.11.6.tar.gz", hash = "sha256:7ab159b063c52a0333c884e4679f8d7a85112ee3078fe3d9004b2dd875585519", size = 399494 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ba/30/9a87ce8336b172cc232a0db89a3af97929d06c11ceaa19d97d84fa90a8f8/regex-2024.11.6-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:52fb28f528778f184f870b7cf8f225f5eef0a8f6e3778529bdd40c7b3920796a", size = 483781 }, + { url = "https://files.pythonhosted.org/packages/01/e8/00008ad4ff4be8b1844786ba6636035f7ef926db5686e4c0f98093612add/regex-2024.11.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fdd6028445d2460f33136c55eeb1f601ab06d74cb3347132e1c24250187500d9", size = 288455 }, + { url = "https://files.pythonhosted.org/packages/60/85/cebcc0aff603ea0a201667b203f13ba75d9fc8668fab917ac5b2de3967bc/regex-2024.11.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:805e6b60c54bf766b251e94526ebad60b7de0c70f70a4e6210ee2891acb70bf2", size = 284759 }, + { url = "https://files.pythonhosted.org/packages/94/2b/701a4b0585cb05472a4da28ee28fdfe155f3638f5e1ec92306d924e5faf0/regex-2024.11.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b85c2530be953a890eaffde05485238f07029600e8f098cdf1848d414a8b45e4", size = 794976 }, + { url = "https://files.pythonhosted.org/packages/4b/bf/fa87e563bf5fee75db8915f7352e1887b1249126a1be4813837f5dbec965/regex-2024.11.6-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bb26437975da7dc36b7efad18aa9dd4ea569d2357ae6b783bf1118dabd9ea577", size = 833077 }, + { url = "https://files.pythonhosted.org/packages/a1/56/7295e6bad94b047f4d0834e4779491b81216583c00c288252ef625c01d23/regex-2024.11.6-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:abfa5080c374a76a251ba60683242bc17eeb2c9818d0d30117b4486be10c59d3", size = 823160 }, + { url = "https://files.pythonhosted.org/packages/fb/13/e3b075031a738c9598c51cfbc4c7879e26729c53aa9cca59211c44235314/regex-2024.11.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b7fa6606c2881c1db9479b0eaa11ed5dfa11c8d60a474ff0e095099f39d98e", size = 796896 }, + { url = "https://files.pythonhosted.org/packages/24/56/0b3f1b66d592be6efec23a795b37732682520b47c53da5a32c33ed7d84e3/regex-2024.11.6-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0c32f75920cf99fe6b6c539c399a4a128452eaf1af27f39bce8909c9a3fd8cbe", size = 783997 }, + { url = "https://files.pythonhosted.org/packages/f9/a1/eb378dada8b91c0e4c5f08ffb56f25fcae47bf52ad18f9b2f33b83e6d498/regex-2024.11.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:982e6d21414e78e1f51cf595d7f321dcd14de1f2881c5dc6a6e23bbbbd68435e", size = 781725 }, + { url = "https://files.pythonhosted.org/packages/83/f2/033e7dec0cfd6dda93390089864732a3409246ffe8b042e9554afa9bff4e/regex-2024.11.6-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a7c2155f790e2fb448faed6dd241386719802296ec588a8b9051c1f5c481bc29", size = 789481 }, + { url = "https://files.pythonhosted.org/packages/83/23/15d4552ea28990a74e7696780c438aadd73a20318c47e527b47a4a5a596d/regex-2024.11.6-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:149f5008d286636e48cd0b1dd65018548944e495b0265b45e1bffecce1ef7f39", size = 852896 }, + { url = "https://files.pythonhosted.org/packages/e3/39/ed4416bc90deedbfdada2568b2cb0bc1fdb98efe11f5378d9892b2a88f8f/regex-2024.11.6-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:e5364a4502efca094731680e80009632ad6624084aff9a23ce8c8c6820de3e51", size = 860138 }, + { url = "https://files.pythonhosted.org/packages/93/2d/dd56bb76bd8e95bbce684326302f287455b56242a4f9c61f1bc76e28360e/regex-2024.11.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0a86e7eeca091c09e021db8eb72d54751e527fa47b8d5787caf96d9831bd02ad", size = 787692 }, + { url = "https://files.pythonhosted.org/packages/0b/55/31877a249ab7a5156758246b9c59539abbeba22461b7d8adc9e8475ff73e/regex-2024.11.6-cp312-cp312-win32.whl", hash = "sha256:32f9a4c643baad4efa81d549c2aadefaeba12249b2adc5af541759237eee1c54", size = 262135 }, + { url = "https://files.pythonhosted.org/packages/38/ec/ad2d7de49a600cdb8dd78434a1aeffe28b9d6fc42eb36afab4a27ad23384/regex-2024.11.6-cp312-cp312-win_amd64.whl", hash = "sha256:a93c194e2df18f7d264092dc8539b8ffb86b45b899ab976aa15d48214138e81b", size = 273567 }, + { url = "https://files.pythonhosted.org/packages/90/73/bcb0e36614601016552fa9344544a3a2ae1809dc1401b100eab02e772e1f/regex-2024.11.6-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a6ba92c0bcdf96cbf43a12c717eae4bc98325ca3730f6b130ffa2e3c3c723d84", size = 483525 }, + { url = "https://files.pythonhosted.org/packages/0f/3f/f1a082a46b31e25291d830b369b6b0c5576a6f7fb89d3053a354c24b8a83/regex-2024.11.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:525eab0b789891ac3be914d36893bdf972d483fe66551f79d3e27146191a37d4", size = 288324 }, + { url = "https://files.pythonhosted.org/packages/09/c9/4e68181a4a652fb3ef5099e077faf4fd2a694ea6e0f806a7737aff9e758a/regex-2024.11.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:086a27a0b4ca227941700e0b31425e7a28ef1ae8e5e05a33826e17e47fbfdba0", size = 284617 }, + { url = "https://files.pythonhosted.org/packages/fc/fd/37868b75eaf63843165f1d2122ca6cb94bfc0271e4428cf58c0616786dce/regex-2024.11.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bde01f35767c4a7899b7eb6e823b125a64de314a8ee9791367c9a34d56af18d0", size = 795023 }, + { url = "https://files.pythonhosted.org/packages/c4/7c/d4cd9c528502a3dedb5c13c146e7a7a539a3853dc20209c8e75d9ba9d1b2/regex-2024.11.6-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b583904576650166b3d920d2bcce13971f6f9e9a396c673187f49811b2769dc7", size = 833072 }, + { url = "https://files.pythonhosted.org/packages/4f/db/46f563a08f969159c5a0f0e722260568425363bea43bb7ae370becb66a67/regex-2024.11.6-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1c4de13f06a0d54fa0d5ab1b7138bfa0d883220965a29616e3ea61b35d5f5fc7", size = 823130 }, + { url = "https://files.pythonhosted.org/packages/db/60/1eeca2074f5b87df394fccaa432ae3fc06c9c9bfa97c5051aed70e6e00c2/regex-2024.11.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3cde6e9f2580eb1665965ce9bf17ff4952f34f5b126beb509fee8f4e994f143c", size = 796857 }, + { url = "https://files.pythonhosted.org/packages/10/db/ac718a08fcee981554d2f7bb8402f1faa7e868c1345c16ab1ebec54b0d7b/regex-2024.11.6-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0d7f453dca13f40a02b79636a339c5b62b670141e63efd511d3f8f73fba162b3", size = 784006 }, + { url = "https://files.pythonhosted.org/packages/c2/41/7da3fe70216cea93144bf12da2b87367590bcf07db97604edeea55dac9ad/regex-2024.11.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:59dfe1ed21aea057a65c6b586afd2a945de04fc7db3de0a6e3ed5397ad491b07", size = 781650 }, + { url = "https://files.pythonhosted.org/packages/a7/d5/880921ee4eec393a4752e6ab9f0fe28009435417c3102fc413f3fe81c4e5/regex-2024.11.6-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b97c1e0bd37c5cd7902e65f410779d39eeda155800b65fc4d04cc432efa9bc6e", size = 789545 }, + { url = "https://files.pythonhosted.org/packages/dc/96/53770115e507081122beca8899ab7f5ae28ae790bfcc82b5e38976df6a77/regex-2024.11.6-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f9d1e379028e0fc2ae3654bac3cbbef81bf3fd571272a42d56c24007979bafb6", size = 853045 }, + { url = "https://files.pythonhosted.org/packages/31/d3/1372add5251cc2d44b451bd94f43b2ec78e15a6e82bff6a290ef9fd8f00a/regex-2024.11.6-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:13291b39131e2d002a7940fb176e120bec5145f3aeb7621be6534e46251912c4", size = 860182 }, + { url = "https://files.pythonhosted.org/packages/ed/e3/c446a64984ea9f69982ba1a69d4658d5014bc7a0ea468a07e1a1265db6e2/regex-2024.11.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4f51f88c126370dcec4908576c5a627220da6c09d0bff31cfa89f2523843316d", size = 787733 }, + { url = "https://files.pythonhosted.org/packages/2b/f1/e40c8373e3480e4f29f2692bd21b3e05f296d3afebc7e5dcf21b9756ca1c/regex-2024.11.6-cp313-cp313-win32.whl", hash = "sha256:63b13cfd72e9601125027202cad74995ab26921d8cd935c25f09c630436348ff", size = 262122 }, + { url = "https://files.pythonhosted.org/packages/45/94/bc295babb3062a731f52621cdc992d123111282e291abaf23faa413443ea/regex-2024.11.6-cp313-cp313-win_amd64.whl", hash = "sha256:2b3361af3198667e99927da8b84c1b010752fa4b1115ee30beaa332cabc3ef1a", size = 273545 }, +] + [[package]] name = "requests" version = "2.32.3" @@ -1268,6 +3116,94 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/fb/b0/2c74f302512fbd24d68fbba0ec6b650b33ef83e398daeb0a2bb1a4cd641c/rioxarray-0.18.2-py3-none-any.whl", hash = "sha256:f351c15fc682081ac2cd2c8db367ef0a7ed5acdea29b9e43a6d7bc2ebc5ec6e5", size = 61943 }, ] +[[package]] +name = "rlp" +version = "4.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "eth-utils" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1b/2d/439b0728a92964a04d9c88ea1ca9ebb128893fbbd5834faa31f987f2fd4c/rlp-4.1.0.tar.gz", hash = "sha256:be07564270a96f3e225e2c107db263de96b5bc1f27722d2855bd3459a08e95a9", size = 33429 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/99/fb/e4c0ced9893b84ac95b7181d69a9786ce5879aeb3bbbcbba80a164f85d6a/rlp-4.1.0-py3-none-any.whl", hash = "sha256:8eca394c579bad34ee0b937aecb96a57052ff3716e19c7a578883e767bc5da6f", size = 19973 }, +] + +[[package]] +name = "rpds-py" +version = "0.26.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a5/aa/4456d84bbb54adc6a916fb10c9b374f78ac840337644e4a5eda229c81275/rpds_py-0.26.0.tar.gz", hash = "sha256:20dae58a859b0906f0685642e591056f1e787f3a8b39c8e8749a45dc7d26bdb0", size = 27385 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ea/86/90eb87c6f87085868bd077c7a9938006eb1ce19ed4d06944a90d3560fce2/rpds_py-0.26.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:894514d47e012e794f1350f076c427d2347ebf82f9b958d554d12819849a369d", size = 363933 }, + { url = "https://files.pythonhosted.org/packages/63/78/4469f24d34636242c924626082b9586f064ada0b5dbb1e9d096ee7a8e0c6/rpds_py-0.26.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc921b96fa95a097add244da36a1d9e4f3039160d1d30f1b35837bf108c21136", size = 350447 }, + { url = "https://files.pythonhosted.org/packages/ad/91/c448ed45efdfdade82348d5e7995e15612754826ea640afc20915119734f/rpds_py-0.26.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e1157659470aa42a75448b6e943c895be8c70531c43cb78b9ba990778955582", size = 384711 }, + { url = "https://files.pythonhosted.org/packages/ec/43/e5c86fef4be7f49828bdd4ecc8931f0287b1152c0bb0163049b3218740e7/rpds_py-0.26.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:521ccf56f45bb3a791182dc6b88ae5f8fa079dd705ee42138c76deb1238e554e", size = 400865 }, + { url = "https://files.pythonhosted.org/packages/55/34/e00f726a4d44f22d5c5fe2e5ddd3ac3d7fd3f74a175607781fbdd06fe375/rpds_py-0.26.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9def736773fd56b305c0eef698be5192c77bfa30d55a0e5885f80126c4831a15", size = 517763 }, + { url = "https://files.pythonhosted.org/packages/52/1c/52dc20c31b147af724b16104500fba13e60123ea0334beba7b40e33354b4/rpds_py-0.26.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cdad4ea3b4513b475e027be79e5a0ceac8ee1c113a1a11e5edc3c30c29f964d8", size = 406651 }, + { url = "https://files.pythonhosted.org/packages/2e/77/87d7bfabfc4e821caa35481a2ff6ae0b73e6a391bb6b343db2c91c2b9844/rpds_py-0.26.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82b165b07f416bdccf5c84546a484cc8f15137ca38325403864bfdf2b5b72f6a", size = 386079 }, + { url = "https://files.pythonhosted.org/packages/e3/d4/7f2200c2d3ee145b65b3cddc4310d51f7da6a26634f3ac87125fd789152a/rpds_py-0.26.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d04cab0a54b9dba4d278fe955a1390da3cf71f57feb78ddc7cb67cbe0bd30323", size = 421379 }, + { url = "https://files.pythonhosted.org/packages/ae/13/9fdd428b9c820869924ab62236b8688b122baa22d23efdd1c566938a39ba/rpds_py-0.26.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:79061ba1a11b6a12743a2b0f72a46aa2758613d454aa6ba4f5a265cc48850158", size = 562033 }, + { url = "https://files.pythonhosted.org/packages/f3/e1/b69686c3bcbe775abac3a4c1c30a164a2076d28df7926041f6c0eb5e8d28/rpds_py-0.26.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:f405c93675d8d4c5ac87364bb38d06c988e11028a64b52a47158a355079661f3", size = 591639 }, + { url = "https://files.pythonhosted.org/packages/5c/c9/1e3d8c8863c84a90197ac577bbc3d796a92502124c27092413426f670990/rpds_py-0.26.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dafd4c44b74aa4bed4b250f1aed165b8ef5de743bcca3b88fc9619b6087093d2", size = 557105 }, + { url = "https://files.pythonhosted.org/packages/9f/c5/90c569649057622959f6dcc40f7b516539608a414dfd54b8d77e3b201ac0/rpds_py-0.26.0-cp312-cp312-win32.whl", hash = "sha256:3da5852aad63fa0c6f836f3359647870e21ea96cf433eb393ffa45263a170d44", size = 223272 }, + { url = "https://files.pythonhosted.org/packages/7d/16/19f5d9f2a556cfed454eebe4d354c38d51c20f3db69e7b4ce6cff904905d/rpds_py-0.26.0-cp312-cp312-win_amd64.whl", hash = "sha256:cf47cfdabc2194a669dcf7a8dbba62e37a04c5041d2125fae0233b720da6f05c", size = 234995 }, + { url = "https://files.pythonhosted.org/packages/83/f0/7935e40b529c0e752dfaa7880224771b51175fce08b41ab4a92eb2fbdc7f/rpds_py-0.26.0-cp312-cp312-win_arm64.whl", hash = "sha256:20ab1ae4fa534f73647aad289003f1104092890849e0266271351922ed5574f8", size = 223198 }, + { url = "https://files.pythonhosted.org/packages/6a/67/bb62d0109493b12b1c6ab00de7a5566aa84c0e44217c2d94bee1bd370da9/rpds_py-0.26.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:696764a5be111b036256c0b18cd29783fab22154690fc698062fc1b0084b511d", size = 363917 }, + { url = "https://files.pythonhosted.org/packages/4b/f3/34e6ae1925a5706c0f002a8d2d7f172373b855768149796af87bd65dcdb9/rpds_py-0.26.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1e6c15d2080a63aaed876e228efe4f814bc7889c63b1e112ad46fdc8b368b9e1", size = 350073 }, + { url = "https://files.pythonhosted.org/packages/75/83/1953a9d4f4e4de7fd0533733e041c28135f3c21485faaef56a8aadbd96b5/rpds_py-0.26.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:390e3170babf42462739a93321e657444f0862c6d722a291accc46f9d21ed04e", size = 384214 }, + { url = "https://files.pythonhosted.org/packages/48/0e/983ed1b792b3322ea1d065e67f4b230f3b96025f5ce3878cc40af09b7533/rpds_py-0.26.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7da84c2c74c0f5bc97d853d9e17bb83e2dcafcff0dc48286916001cc114379a1", size = 400113 }, + { url = "https://files.pythonhosted.org/packages/69/7f/36c0925fff6f660a80be259c5b4f5e53a16851f946eb080351d057698528/rpds_py-0.26.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4c5fe114a6dd480a510b6d3661d09d67d1622c4bf20660a474507aaee7eeeee9", size = 515189 }, + { url = "https://files.pythonhosted.org/packages/13/45/cbf07fc03ba7a9b54662c9badb58294ecfb24f828b9732970bd1a431ed5c/rpds_py-0.26.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3100b3090269f3a7ea727b06a6080d4eb7439dca4c0e91a07c5d133bb1727ea7", size = 406998 }, + { url = "https://files.pythonhosted.org/packages/6c/b0/8fa5e36e58657997873fd6a1cf621285ca822ca75b4b3434ead047daa307/rpds_py-0.26.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c03c9b0c64afd0320ae57de4c982801271c0c211aa2d37f3003ff5feb75bb04", size = 385903 }, + { url = "https://files.pythonhosted.org/packages/4b/f7/b25437772f9f57d7a9fbd73ed86d0dcd76b4c7c6998348c070d90f23e315/rpds_py-0.26.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5963b72ccd199ade6ee493723d18a3f21ba7d5b957017607f815788cef50eaf1", size = 419785 }, + { url = "https://files.pythonhosted.org/packages/a7/6b/63ffa55743dfcb4baf2e9e77a0b11f7f97ed96a54558fcb5717a4b2cd732/rpds_py-0.26.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:9da4e873860ad5bab3291438525cae80169daecbfafe5657f7f5fb4d6b3f96b9", size = 561329 }, + { url = "https://files.pythonhosted.org/packages/2f/07/1f4f5e2886c480a2346b1e6759c00278b8a69e697ae952d82ae2e6ee5db0/rpds_py-0.26.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:5afaddaa8e8c7f1f7b4c5c725c0070b6eed0228f705b90a1732a48e84350f4e9", size = 590875 }, + { url = "https://files.pythonhosted.org/packages/cc/bc/e6639f1b91c3a55f8c41b47d73e6307051b6e246254a827ede730624c0f8/rpds_py-0.26.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4916dc96489616a6f9667e7526af8fa693c0fdb4f3acb0e5d9f4400eb06a47ba", size = 556636 }, + { url = "https://files.pythonhosted.org/packages/05/4c/b3917c45566f9f9a209d38d9b54a1833f2bb1032a3e04c66f75726f28876/rpds_py-0.26.0-cp313-cp313-win32.whl", hash = "sha256:2a343f91b17097c546b93f7999976fd6c9d5900617aa848c81d794e062ab302b", size = 222663 }, + { url = "https://files.pythonhosted.org/packages/e0/0b/0851bdd6025775aaa2365bb8de0697ee2558184c800bfef8d7aef5ccde58/rpds_py-0.26.0-cp313-cp313-win_amd64.whl", hash = "sha256:0a0b60701f2300c81b2ac88a5fb893ccfa408e1c4a555a77f908a2596eb875a5", size = 234428 }, + { url = "https://files.pythonhosted.org/packages/ed/e8/a47c64ed53149c75fb581e14a237b7b7cd18217e969c30d474d335105622/rpds_py-0.26.0-cp313-cp313-win_arm64.whl", hash = "sha256:257d011919f133a4746958257f2c75238e3ff54255acd5e3e11f3ff41fd14256", size = 222571 }, + { url = "https://files.pythonhosted.org/packages/89/bf/3d970ba2e2bcd17d2912cb42874107390f72873e38e79267224110de5e61/rpds_py-0.26.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:529c8156d7506fba5740e05da8795688f87119cce330c244519cf706a4a3d618", size = 360475 }, + { url = "https://files.pythonhosted.org/packages/82/9f/283e7e2979fc4ec2d8ecee506d5a3675fce5ed9b4b7cb387ea5d37c2f18d/rpds_py-0.26.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:f53ec51f9d24e9638a40cabb95078ade8c99251945dad8d57bf4aabe86ecee35", size = 346692 }, + { url = "https://files.pythonhosted.org/packages/e3/03/7e50423c04d78daf391da3cc4330bdb97042fc192a58b186f2d5deb7befd/rpds_py-0.26.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ab504c4d654e4a29558eaa5bb8cea5fdc1703ea60a8099ffd9c758472cf913f", size = 379415 }, + { url = "https://files.pythonhosted.org/packages/57/00/d11ee60d4d3b16808432417951c63df803afb0e0fc672b5e8d07e9edaaae/rpds_py-0.26.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fd0641abca296bc1a00183fe44f7fced8807ed49d501f188faa642d0e4975b83", size = 391783 }, + { url = "https://files.pythonhosted.org/packages/08/b3/1069c394d9c0d6d23c5b522e1f6546b65793a22950f6e0210adcc6f97c3e/rpds_py-0.26.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:69b312fecc1d017b5327afa81d4da1480f51c68810963a7336d92203dbb3d4f1", size = 512844 }, + { url = "https://files.pythonhosted.org/packages/08/3b/c4fbf0926800ed70b2c245ceca99c49f066456755f5d6eb8863c2c51e6d0/rpds_py-0.26.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c741107203954f6fc34d3066d213d0a0c40f7bb5aafd698fb39888af277c70d8", size = 402105 }, + { url = "https://files.pythonhosted.org/packages/1c/b0/db69b52ca07413e568dae9dc674627a22297abb144c4d6022c6d78f1e5cc/rpds_py-0.26.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc3e55a7db08dc9a6ed5fb7103019d2c1a38a349ac41901f9f66d7f95750942f", size = 383440 }, + { url = "https://files.pythonhosted.org/packages/4c/e1/c65255ad5b63903e56b3bb3ff9dcc3f4f5c3badde5d08c741ee03903e951/rpds_py-0.26.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9e851920caab2dbcae311fd28f4313c6953993893eb5c1bb367ec69d9a39e7ed", size = 412759 }, + { url = "https://files.pythonhosted.org/packages/e4/22/bb731077872377a93c6e93b8a9487d0406c70208985831034ccdeed39c8e/rpds_py-0.26.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:dfbf280da5f876d0b00c81f26bedce274e72a678c28845453885a9b3c22ae632", size = 556032 }, + { url = "https://files.pythonhosted.org/packages/e0/8b/393322ce7bac5c4530fb96fc79cc9ea2f83e968ff5f6e873f905c493e1c4/rpds_py-0.26.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:1cc81d14ddfa53d7f3906694d35d54d9d3f850ef8e4e99ee68bc0d1e5fed9a9c", size = 585416 }, + { url = "https://files.pythonhosted.org/packages/49/ae/769dc372211835bf759319a7aae70525c6eb523e3371842c65b7ef41c9c6/rpds_py-0.26.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dca83c498b4650a91efcf7b88d669b170256bf8017a5db6f3e06c2bf031f57e0", size = 554049 }, + { url = "https://files.pythonhosted.org/packages/6b/f9/4c43f9cc203d6ba44ce3146246cdc38619d92c7bd7bad4946a3491bd5b70/rpds_py-0.26.0-cp313-cp313t-win32.whl", hash = "sha256:4d11382bcaf12f80b51d790dee295c56a159633a8e81e6323b16e55d81ae37e9", size = 218428 }, + { url = "https://files.pythonhosted.org/packages/7e/8b/9286b7e822036a4a977f2f1e851c7345c20528dbd56b687bb67ed68a8ede/rpds_py-0.26.0-cp313-cp313t-win_amd64.whl", hash = "sha256:ff110acded3c22c033e637dd8896e411c7d3a11289b2edf041f86663dbc791e9", size = 231524 }, + { url = "https://files.pythonhosted.org/packages/55/07/029b7c45db910c74e182de626dfdae0ad489a949d84a468465cd0ca36355/rpds_py-0.26.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:da619979df60a940cd434084355c514c25cf8eb4cf9a508510682f6c851a4f7a", size = 364292 }, + { url = "https://files.pythonhosted.org/packages/13/d1/9b3d3f986216b4d1f584878dca15ce4797aaf5d372d738974ba737bf68d6/rpds_py-0.26.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ea89a2458a1a75f87caabefe789c87539ea4e43b40f18cff526052e35bbb4fdf", size = 350334 }, + { url = "https://files.pythonhosted.org/packages/18/98/16d5e7bc9ec715fa9668731d0cf97f6b032724e61696e2db3d47aeb89214/rpds_py-0.26.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:feac1045b3327a45944e7dcbeb57530339f6b17baff154df51ef8b0da34c8c12", size = 384875 }, + { url = "https://files.pythonhosted.org/packages/f9/13/aa5e2b1ec5ab0e86a5c464d53514c0467bec6ba2507027d35fc81818358e/rpds_py-0.26.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b818a592bd69bfe437ee8368603d4a2d928c34cffcdf77c2e761a759ffd17d20", size = 399993 }, + { url = "https://files.pythonhosted.org/packages/17/03/8021810b0e97923abdbab6474c8b77c69bcb4b2c58330777df9ff69dc559/rpds_py-0.26.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1a8b0dd8648709b62d9372fc00a57466f5fdeefed666afe3fea5a6c9539a0331", size = 516683 }, + { url = "https://files.pythonhosted.org/packages/dc/b1/da8e61c87c2f3d836954239fdbbfb477bb7b54d74974d8f6fcb34342d166/rpds_py-0.26.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6d3498ad0df07d81112aa6ec6c95a7e7b1ae00929fb73e7ebee0f3faaeabad2f", size = 408825 }, + { url = "https://files.pythonhosted.org/packages/38/bc/1fc173edaaa0e52c94b02a655db20697cb5fa954ad5a8e15a2c784c5cbdd/rpds_py-0.26.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:24a4146ccb15be237fdef10f331c568e1b0e505f8c8c9ed5d67759dac58ac246", size = 387292 }, + { url = "https://files.pythonhosted.org/packages/7c/eb/3a9bb4bd90867d21916f253caf4f0d0be7098671b6715ad1cead9fe7bab9/rpds_py-0.26.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a9a63785467b2d73635957d32a4f6e73d5e4df497a16a6392fa066b753e87387", size = 420435 }, + { url = "https://files.pythonhosted.org/packages/cd/16/e066dcdb56f5632713445271a3f8d3d0b426d51ae9c0cca387799df58b02/rpds_py-0.26.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:de4ed93a8c91debfd5a047be327b7cc8b0cc6afe32a716bbbc4aedca9e2a83af", size = 562410 }, + { url = "https://files.pythonhosted.org/packages/60/22/ddbdec7eb82a0dc2e455be44c97c71c232983e21349836ce9f272e8a3c29/rpds_py-0.26.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:caf51943715b12af827696ec395bfa68f090a4c1a1d2509eb4e2cb69abbbdb33", size = 590724 }, + { url = "https://files.pythonhosted.org/packages/2c/b4/95744085e65b7187d83f2fcb0bef70716a1ea0a9e5d8f7f39a86e5d83424/rpds_py-0.26.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:4a59e5bc386de021f56337f757301b337d7ab58baa40174fb150accd480bc953", size = 558285 }, + { url = "https://files.pythonhosted.org/packages/37/37/6309a75e464d1da2559446f9c811aa4d16343cebe3dbb73701e63f760caa/rpds_py-0.26.0-cp314-cp314-win32.whl", hash = "sha256:92c8db839367ef16a662478f0a2fe13e15f2227da3c1430a782ad0f6ee009ec9", size = 223459 }, + { url = "https://files.pythonhosted.org/packages/d9/6f/8e9c11214c46098b1d1391b7e02b70bb689ab963db3b19540cba17315291/rpds_py-0.26.0-cp314-cp314-win_amd64.whl", hash = "sha256:b0afb8cdd034150d4d9f53926226ed27ad15b7f465e93d7468caaf5eafae0d37", size = 236083 }, + { url = "https://files.pythonhosted.org/packages/47/af/9c4638994dd623d51c39892edd9d08e8be8220a4b7e874fa02c2d6e91955/rpds_py-0.26.0-cp314-cp314-win_arm64.whl", hash = "sha256:ca3f059f4ba485d90c8dc75cb5ca897e15325e4e609812ce57f896607c1c0867", size = 223291 }, + { url = "https://files.pythonhosted.org/packages/4d/db/669a241144460474aab03e254326b32c42def83eb23458a10d163cb9b5ce/rpds_py-0.26.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:5afea17ab3a126006dc2f293b14ffc7ef3c85336cf451564a0515ed7648033da", size = 361445 }, + { url = "https://files.pythonhosted.org/packages/3b/2d/133f61cc5807c6c2fd086a46df0eb8f63a23f5df8306ff9f6d0fd168fecc/rpds_py-0.26.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:69f0c0a3df7fd3a7eec50a00396104bb9a843ea6d45fcc31c2d5243446ffd7a7", size = 347206 }, + { url = "https://files.pythonhosted.org/packages/05/bf/0e8fb4c05f70273469eecf82f6ccf37248558526a45321644826555db31b/rpds_py-0.26.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:801a71f70f9813e82d2513c9a96532551fce1e278ec0c64610992c49c04c2dad", size = 380330 }, + { url = "https://files.pythonhosted.org/packages/d4/a8/060d24185d8b24d3923322f8d0ede16df4ade226a74e747b8c7c978e3dd3/rpds_py-0.26.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:df52098cde6d5e02fa75c1f6244f07971773adb4a26625edd5c18fee906fa84d", size = 392254 }, + { url = "https://files.pythonhosted.org/packages/b9/7b/7c2e8a9ee3e6bc0bae26bf29f5219955ca2fbb761dca996a83f5d2f773fe/rpds_py-0.26.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9bc596b30f86dc6f0929499c9e574601679d0341a0108c25b9b358a042f51bca", size = 516094 }, + { url = "https://files.pythonhosted.org/packages/75/d6/f61cafbed8ba1499b9af9f1777a2a199cd888f74a96133d8833ce5eaa9c5/rpds_py-0.26.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9dfbe56b299cf5875b68eb6f0ebaadc9cac520a1989cac0db0765abfb3709c19", size = 402889 }, + { url = "https://files.pythonhosted.org/packages/92/19/c8ac0a8a8df2dd30cdec27f69298a5c13e9029500d6d76718130f5e5be10/rpds_py-0.26.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac64f4b2bdb4ea622175c9ab7cf09444e412e22c0e02e906978b3b488af5fde8", size = 384301 }, + { url = "https://files.pythonhosted.org/packages/41/e1/6b1859898bc292a9ce5776016c7312b672da00e25cec74d7beced1027286/rpds_py-0.26.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:181ef9b6bbf9845a264f9aa45c31836e9f3c1f13be565d0d010e964c661d1e2b", size = 412891 }, + { url = "https://files.pythonhosted.org/packages/ef/b9/ceb39af29913c07966a61367b3c08b4f71fad841e32c6b59a129d5974698/rpds_py-0.26.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:49028aa684c144ea502a8e847d23aed5e4c2ef7cadfa7d5eaafcb40864844b7a", size = 557044 }, + { url = "https://files.pythonhosted.org/packages/2f/27/35637b98380731a521f8ec4f3fd94e477964f04f6b2f8f7af8a2d889a4af/rpds_py-0.26.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:e5d524d68a474a9688336045bbf76cb0def88549c1b2ad9dbfec1fb7cfbe9170", size = 585774 }, + { url = "https://files.pythonhosted.org/packages/52/d9/3f0f105420fecd18551b678c9a6ce60bd23986098b252a56d35781b3e7e9/rpds_py-0.26.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:c1851f429b822831bd2edcbe0cfd12ee9ea77868f8d3daf267b189371671c80e", size = 554886 }, + { url = "https://files.pythonhosted.org/packages/6b/c5/347c056a90dc8dd9bc240a08c527315008e1b5042e7a4cf4ac027be9d38a/rpds_py-0.26.0-cp314-cp314t-win32.whl", hash = "sha256:7bdb17009696214c3b66bb3590c6d62e14ac5935e53e929bcdbc5a495987a84f", size = 219027 }, + { url = "https://files.pythonhosted.org/packages/75/04/5302cea1aa26d886d34cadbf2dc77d90d7737e576c0065f357b96dc7a1a6/rpds_py-0.26.0-cp314-cp314t-win_amd64.whl", hash = "sha256:f14440b9573a6f76b4ee4770c13f0b5921f71dde3b6fcb8dabbefd13b7fe05d7", size = 232821 }, +] + [[package]] name = "ruff" version = "0.11.2" @@ -1307,6 +3243,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/3a/3f/35f4041a82a68df89fe4af97c8bb44aa492dad924799cbb02078e9e303e6/s3fs-2025.3.0-py3-none-any.whl", hash = "sha256:88d803615baa04945156ca0e1498009b7acd3132c07198bd81b3e874846e0aa2", size = 30454 }, ] +[[package]] +name = "safe-pysha3" +version = "1.0.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a4/27/a942b8a95b97c5fb8d27d3b86b8d269546cf21f87d9ec6b44c3702ce67cc/safe_pysha3-1.0.5.tar.gz", hash = "sha256:88ceaad6af4b6bdecd2f54b31ad0e5e5e210d4f5ecabb1bd1fd3539ad61b7bf1", size = 827796 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bd/59/8d7f64a3eb6ebbde248cc0347a67d7825e2ba6edfa2bd180ebdfb599b3c5/safe_pysha3-1.0.5-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d137a73029c6c5a1db5791ae9fa62373827eee5226d19b79b836a6cf48b6b197", size = 190427 }, + { url = "https://files.pythonhosted.org/packages/da/6a/25e926ab9c8d714be2ee1d40f45eb425f438c968d97ede69a7cbff054094/safe_pysha3-1.0.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:9a0cb37252a8767992f354d7d2af2ef04730032927eb6af2057e71744c741287", size = 184478 }, + { url = "https://files.pythonhosted.org/packages/3a/da/7e1f252e6984f4b82393e4e083d3a48806e8a7225651559e50dec0000c68/safe_pysha3-1.0.5-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2019065f1b7d3db37cc52d091c9d5526d5d36a3e1b9efcf0b345c24e03755bff", size = 190543 }, + { url = "https://files.pythonhosted.org/packages/e6/a6/e134e353a4961090caf4f429044b845584fe11d2217960a2549943ef01ea/safe_pysha3-1.0.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa37d5d6138d5dd01d1035dba019b7525ad7c55669ded4524f589cddd13ea13b", size = 367223 }, +] + [[package]] name = "scipy" version = "1.15.2" @@ -1345,6 +3293,29 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/0a/c8/b3f566db71461cabd4b2d5b39bcc24a7e1c119535c8361f81426be39bb47/scipy-1.15.2-cp313-cp313t-win_amd64.whl", hash = "sha256:fe8a9eb875d430d81755472c5ba75e84acc980e4a8f6204d402849234d3017db", size = 40477705 }, ] +[[package]] +name = "seaborn" +version = "0.13.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "matplotlib" }, + { name = "numpy" }, + { name = "pandas" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/86/59/a451d7420a77ab0b98f7affa3a1d78a313d2f7281a57afb1a34bae8ab412/seaborn-0.13.2.tar.gz", hash = "sha256:93e60a40988f4d65e9f4885df477e2fdaff6b73a9ded434c1ab356dd57eefff7", size = 1457696 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/83/11/00d3c3dfc25ad54e731d91449895a79e4bf2384dc3ac01809010ba88f6d5/seaborn-0.13.2-py3-none-any.whl", hash = "sha256:636f8336facf092165e27924f223d3c62ca560b1f2bb5dff7ab7fad265361987", size = 294914 }, +] + +[[package]] +name = "semantic-version" +version = "2.10.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/31/f2289ce78b9b473d582568c234e104d2a342fd658cc288a7553d83bb8595/semantic_version-2.10.0.tar.gz", hash = "sha256:bdabb6d336998cbb378d4b9db3a4b56a1e3235701dc05ea2690d9a997ed5041c", size = 52289 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6a/23/8146aad7d88f4fcb3a6218f41a60f6c2d4e3a72de72da1825dc7c8f7877c/semantic_version-2.10.0-py2.py3-none-any.whl", hash = "sha256:de78a3b8e0feda74cabc54aab2da702113e33ac9d9eb9d2389bcf1f58b7d9177", size = 15552 }, +] + [[package]] name = "shapely" version = "2.0.7" @@ -1377,6 +3348,154 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050 }, ] +[[package]] +name = "sniffio" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235 }, +] + +[[package]] +name = "sortedcontainers" +version = "2.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e8/c4/ba2f8066cceb6f23394729afe52f3bf7adec04bf9ed2c820b39e19299111/sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88", size = 30594 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/32/46/9cb0e58b2deb7f82b84065f37f3bffeb12413f947f9388e4cac22c4621ce/sortedcontainers-2.4.0-py2.py3-none-any.whl", hash = "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0", size = 29575 }, +] + +[[package]] +name = "sparse" +version = "0.17.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numba" }, + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/63/74/5c674277fc3d61bd1863d233a8e1f7ddf35cb1adeeaf9973888629e7a9b1/sparse-0.17.0.tar.gz", hash = "sha256:6b1ad51a810c5be40b6f95e28513ec810fe1c785923bd83b2e4839a751df4bf7", size = 642387 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8d/fd/42a1720542199ae6ff0f9c37bbd55dd3033ddd7bbe00d68cde09d6824887/sparse-0.17.0-py2.py3-none-any.whl", hash = "sha256:1922d1d97f692b1061c4f03a1dd6ee21850aedc88e171aa845715f5069952f18", size = 259370 }, +] + +[[package]] +name = "sqlalchemy" +version = "2.0.41" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "greenlet", marker = "(python_full_version < '3.14' and platform_machine == 'AMD64') or (python_full_version < '3.14' and platform_machine == 'WIN32') or (python_full_version < '3.14' and platform_machine == 'aarch64') or (python_full_version < '3.14' and platform_machine == 'amd64') or (python_full_version < '3.14' and platform_machine == 'ppc64le') or (python_full_version < '3.14' and platform_machine == 'win32') or (python_full_version < '3.14' and platform_machine == 'x86_64')" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/63/66/45b165c595ec89aa7dcc2c1cd222ab269bc753f1fc7a1e68f8481bd957bf/sqlalchemy-2.0.41.tar.gz", hash = "sha256:edba70118c4be3c2b1f90754d308d0b79c6fe2c0fdc52d8ddf603916f83f4db9", size = 9689424 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3e/2a/f1f4e068b371154740dd10fb81afb5240d5af4aa0087b88d8b308b5429c2/sqlalchemy-2.0.41-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:81f413674d85cfd0dfcd6512e10e0f33c19c21860342a4890c3a2b59479929f9", size = 2119645 }, + { url = "https://files.pythonhosted.org/packages/9b/e8/c664a7e73d36fbfc4730f8cf2bf930444ea87270f2825efbe17bf808b998/sqlalchemy-2.0.41-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:598d9ebc1e796431bbd068e41e4de4dc34312b7aa3292571bb3674a0cb415dd1", size = 2107399 }, + { url = "https://files.pythonhosted.org/packages/5c/78/8a9cf6c5e7135540cb682128d091d6afa1b9e48bd049b0d691bf54114f70/sqlalchemy-2.0.41-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a104c5694dfd2d864a6f91b0956eb5d5883234119cb40010115fd45a16da5e70", size = 3293269 }, + { url = "https://files.pythonhosted.org/packages/3c/35/f74add3978c20de6323fb11cb5162702670cc7a9420033befb43d8d5b7a4/sqlalchemy-2.0.41-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6145afea51ff0af7f2564a05fa95eb46f542919e6523729663a5d285ecb3cf5e", size = 3303364 }, + { url = "https://files.pythonhosted.org/packages/6a/d4/c990f37f52c3f7748ebe98883e2a0f7d038108c2c5a82468d1ff3eec50b7/sqlalchemy-2.0.41-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b46fa6eae1cd1c20e6e6f44e19984d438b6b2d8616d21d783d150df714f44078", size = 3229072 }, + { url = "https://files.pythonhosted.org/packages/15/69/cab11fecc7eb64bc561011be2bd03d065b762d87add52a4ca0aca2e12904/sqlalchemy-2.0.41-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41836fe661cc98abfae476e14ba1906220f92c4e528771a8a3ae6a151242d2ae", size = 3268074 }, + { url = "https://files.pythonhosted.org/packages/5c/ca/0c19ec16858585d37767b167fc9602593f98998a68a798450558239fb04a/sqlalchemy-2.0.41-cp312-cp312-win32.whl", hash = "sha256:a8808d5cf866c781150d36a3c8eb3adccfa41a8105d031bf27e92c251e3969d6", size = 2084514 }, + { url = "https://files.pythonhosted.org/packages/7f/23/4c2833d78ff3010a4e17f984c734f52b531a8c9060a50429c9d4b0211be6/sqlalchemy-2.0.41-cp312-cp312-win_amd64.whl", hash = "sha256:5b14e97886199c1f52c14629c11d90c11fbb09e9334fa7bb5f6d068d9ced0ce0", size = 2111557 }, + { url = "https://files.pythonhosted.org/packages/d3/ad/2e1c6d4f235a97eeef52d0200d8ddda16f6c4dd70ae5ad88c46963440480/sqlalchemy-2.0.41-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4eeb195cdedaf17aab6b247894ff2734dcead6c08f748e617bfe05bd5a218443", size = 2115491 }, + { url = "https://files.pythonhosted.org/packages/cf/8d/be490e5db8400dacc89056f78a52d44b04fbf75e8439569d5b879623a53b/sqlalchemy-2.0.41-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d4ae769b9c1c7757e4ccce94b0641bc203bbdf43ba7a2413ab2523d8d047d8dc", size = 2102827 }, + { url = "https://files.pythonhosted.org/packages/a0/72/c97ad430f0b0e78efaf2791342e13ffeafcbb3c06242f01a3bb8fe44f65d/sqlalchemy-2.0.41-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a62448526dd9ed3e3beedc93df9bb6b55a436ed1474db31a2af13b313a70a7e1", size = 3225224 }, + { url = "https://files.pythonhosted.org/packages/5e/51/5ba9ea3246ea068630acf35a6ba0d181e99f1af1afd17e159eac7e8bc2b8/sqlalchemy-2.0.41-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc56c9788617b8964ad02e8fcfeed4001c1f8ba91a9e1f31483c0dffb207002a", size = 3230045 }, + { url = "https://files.pythonhosted.org/packages/78/2f/8c14443b2acea700c62f9b4a8bad9e49fc1b65cfb260edead71fd38e9f19/sqlalchemy-2.0.41-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c153265408d18de4cc5ded1941dcd8315894572cddd3c58df5d5b5705b3fa28d", size = 3159357 }, + { url = "https://files.pythonhosted.org/packages/fc/b2/43eacbf6ccc5276d76cea18cb7c3d73e294d6fb21f9ff8b4eef9b42bbfd5/sqlalchemy-2.0.41-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4f67766965996e63bb46cfbf2ce5355fc32d9dd3b8ad7e536a920ff9ee422e23", size = 3197511 }, + { url = "https://files.pythonhosted.org/packages/fa/2e/677c17c5d6a004c3c45334ab1dbe7b7deb834430b282b8a0f75ae220c8eb/sqlalchemy-2.0.41-cp313-cp313-win32.whl", hash = "sha256:bfc9064f6658a3d1cadeaa0ba07570b83ce6801a1314985bf98ec9b95d74e15f", size = 2082420 }, + { url = "https://files.pythonhosted.org/packages/e9/61/e8c1b9b6307c57157d328dd8b8348ddc4c47ffdf1279365a13b2b98b8049/sqlalchemy-2.0.41-cp313-cp313-win_amd64.whl", hash = "sha256:82ca366a844eb551daff9d2e6e7a9e5e76d2612c8564f58db6c19a726869c1df", size = 2108329 }, + { url = "https://files.pythonhosted.org/packages/1c/fc/9ba22f01b5cdacc8f5ed0d22304718d2c758fce3fd49a5372b886a86f37c/sqlalchemy-2.0.41-py3-none-any.whl", hash = "sha256:57df5dc6fdb5ed1a88a1ed2195fd31927e705cad62dedd86b46972752a80f576", size = 1911224 }, +] + +[[package]] +name = "stack-data" +version = "0.6.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "asttokens" }, + { name = "executing" }, + { name = "pure-eval" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/28/e3/55dcc2cfbc3ca9c29519eb6884dd1415ecb53b0e934862d3559ddcb7e20b/stack_data-0.6.3.tar.gz", hash = "sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9", size = 44707 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695", size = 24521 }, +] + +[[package]] +name = "tblib" +version = "3.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/54/95/4b3044ec4bf248186769629bbfb495a458deb6e4c1f9eff7f298ae1e336e/tblib-3.1.0.tar.gz", hash = "sha256:06404c2c9f07f66fee2d7d6ad43accc46f9c3361714d9b8426e7f47e595cd652", size = 30766 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/27/44/aa5c8b10b2cce7a053018e0d132bd58e27527a0243c4985383d5b6fd93e9/tblib-3.1.0-py3-none-any.whl", hash = "sha256:670bb4582578134b3d81a84afa1b016128b429f3d48e6cbbaecc9d15675e984e", size = 12552 }, +] + +[[package]] +name = "toolz" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8a/0b/d80dfa675bf592f636d1ea0b835eab4ec8df6e9415d8cfd766df54456123/toolz-1.0.0.tar.gz", hash = "sha256:2c86e3d9a04798ac556793bced838816296a2f085017664e4995cb40a1047a02", size = 66790 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/03/98/eb27cc78ad3af8e302c9d8ff4977f5026676e130d28dd7578132a457170c/toolz-1.0.0-py3-none-any.whl", hash = "sha256:292c8f1c4e7516bf9086f8850935c799a874039c8bcf959d47b600e4c44a6236", size = 56383 }, +] + +[[package]] +name = "tornado" +version = "6.5.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/51/89/c72771c81d25d53fe33e3dca61c233b665b2780f21820ba6fd2c6793c12b/tornado-6.5.1.tar.gz", hash = "sha256:84ceece391e8eb9b2b95578db65e920d2a61070260594819589609ba9bc6308c", size = 509934 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/77/89/f4532dee6843c9e0ebc4e28d4be04c67f54f60813e4bf73d595fe7567452/tornado-6.5.1-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:d50065ba7fd11d3bd41bcad0825227cc9a95154bad83239357094c36708001f7", size = 441948 }, + { url = "https://files.pythonhosted.org/packages/15/9a/557406b62cffa395d18772e0cdcf03bed2fff03b374677348eef9f6a3792/tornado-6.5.1-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:9e9ca370f717997cb85606d074b0e5b247282cf5e2e1611568b8821afe0342d6", size = 440112 }, + { url = "https://files.pythonhosted.org/packages/55/82/7721b7319013a3cf881f4dffa4f60ceff07b31b394e459984e7a36dc99ec/tornado-6.5.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b77e9dfa7ed69754a54c89d82ef746398be82f749df69c4d3abe75c4d1ff4888", size = 443672 }, + { url = "https://files.pythonhosted.org/packages/7d/42/d11c4376e7d101171b94e03cef0cbce43e823ed6567ceda571f54cf6e3ce/tornado-6.5.1-cp39-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:253b76040ee3bab8bcf7ba9feb136436a3787208717a1fb9f2c16b744fba7331", size = 443019 }, + { url = "https://files.pythonhosted.org/packages/7d/f7/0c48ba992d875521ac761e6e04b0a1750f8150ae42ea26df1852d6a98942/tornado-6.5.1-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:308473f4cc5a76227157cdf904de33ac268af770b2c5f05ca6c1161d82fdd95e", size = 443252 }, + { url = "https://files.pythonhosted.org/packages/89/46/d8d7413d11987e316df4ad42e16023cd62666a3c0dfa1518ffa30b8df06c/tornado-6.5.1-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:caec6314ce8a81cf69bd89909f4b633b9f523834dc1a352021775d45e51d9401", size = 443930 }, + { url = "https://files.pythonhosted.org/packages/78/b2/f8049221c96a06df89bed68260e8ca94beca5ea532ffc63b1175ad31f9cc/tornado-6.5.1-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:13ce6e3396c24e2808774741331638ee6c2f50b114b97a55c5b442df65fd9692", size = 443351 }, + { url = "https://files.pythonhosted.org/packages/76/ff/6a0079e65b326cc222a54720a748e04a4db246870c4da54ece4577bfa702/tornado-6.5.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:5cae6145f4cdf5ab24744526cc0f55a17d76f02c98f4cff9daa08ae9a217448a", size = 443328 }, + { url = "https://files.pythonhosted.org/packages/49/18/e3f902a1d21f14035b5bc6246a8c0f51e0eef562ace3a2cea403c1fb7021/tornado-6.5.1-cp39-abi3-win32.whl", hash = "sha256:e0a36e1bc684dca10b1aa75a31df8bdfed656831489bc1e6a6ebed05dc1ec365", size = 444396 }, + { url = "https://files.pythonhosted.org/packages/7b/09/6526e32bf1049ee7de3bebba81572673b19a2a8541f795d887e92af1a8bc/tornado-6.5.1-cp39-abi3-win_amd64.whl", hash = "sha256:908e7d64567cecd4c2b458075589a775063453aeb1d2a1853eedb806922f568b", size = 444840 }, + { url = "https://files.pythonhosted.org/packages/55/a7/535c44c7bea4578e48281d83c615219f3ab19e6abc67625ef637c73987be/tornado-6.5.1-cp39-abi3-win_arm64.whl", hash = "sha256:02420a0eb7bf617257b9935e2b754d1b63897525d8a289c9d65690d580b4dcf7", size = 443596 }, +] + +[[package]] +name = "tqdm" +version = "4.67.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a8/4b/29b4ef32e036bb34e4ab51796dd745cdba7ed47ad142a9f4a1eb8e0c744d/tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2", size = 169737 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2", size = 78540 }, +] + +[[package]] +name = "traitlets" +version = "5.14.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/eb/79/72064e6a701c2183016abbbfedaba506d81e30e232a68c9f0d6f6fcd1574/traitlets-5.14.3.tar.gz", hash = "sha256:9ed0579d3502c94b4b3732ac120375cda96f923114522847de4b3bb98b96b6b7", size = 161621 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl", hash = "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f", size = 85359 }, +] + +[[package]] +name = "trie" +version = "3.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "eth-hash" }, + { name = "eth-utils" }, + { name = "hexbytes" }, + { name = "rlp" }, + { name = "sortedcontainers" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a6/2f/5ec6be52952de47e79b1d250c00a922365a76503e3c75605c1cd890c61aa/trie-3.1.0.tar.gz", hash = "sha256:b31fd3376d6dccfe8ad13b525e233f2c268d5c48afb90a4de09672423d4b1026", size = 72800 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/85/79/e6e105236eb1bb0c3ac82c968c143de456a3192c17d60df2f8c528eb3323/trie-3.1.0-py3-none-any.whl", hash = "sha256:dfc3e6ac0e76f0efa900ec1bfd082f0f1ba87f95cbfd81cc12338b03f4c679c4", size = 38940 }, +] + [[package]] name = "typing-extensions" version = "4.13.0" @@ -1386,6 +3505,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e0/86/39b65d676ec5732de17b7e3c476e45bb80ec64eb50737a8dce1a4178aba1/typing_extensions-4.13.0-py3-none-any.whl", hash = "sha256:c8dd92cc0d6425a97c18fbb9d1954e5ff92c1ca881a309c45f06ebc0b79058e5", size = 45683 }, ] +[[package]] +name = "typing-inspection" +version = "0.4.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f8/b1/0c11f5058406b3af7609f121aaa6b609744687f1d158b3c3a5bf4cc94238/typing_inspection-0.4.1.tar.gz", hash = "sha256:6ae134cc0203c33377d43188d4064e9b357dba58cff3185f22924610e70a9d28", size = 75726 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/17/69/cd203477f944c353c31bade965f880aa1061fd6bf05ded0726ca845b6ff7/typing_inspection-0.4.1-py3-none-any.whl", hash = "sha256:389055682238f53b04f7badcb49b989835495a96700ced5dab2d8feae4b26f51", size = 14552 }, +] + [[package]] name = "typing-validation" version = "1.2.12" @@ -1413,6 +3544,12 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c8/19/4ec628951a74043532ca2cf5d97b7b14863931476d117c471e8e2b1eb39f/urllib3-2.3.0-py3-none-any.whl", hash = "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df", size = 128369 }, ] +[[package]] +name = "varint" +version = "1.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a8/fe/1ea0ba0896dfa47186692655b86db3214c4b7c9e0e76c7b1dc257d101ab1/varint-1.0.2.tar.gz", hash = "sha256:a6ecc02377ac5ee9d65a6a8ad45c9ff1dac8ccee19400a5950fb51d594214ca5", size = 1886 } + [[package]] name = "virtualenv" version = "20.29.3" @@ -1427,6 +3564,96 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c2/eb/c6db6e3001d58c6a9e67c74bb7b4206767caa3ccc28c6b9eaf4c23fb4e34/virtualenv-20.29.3-py3-none-any.whl", hash = "sha256:3e3d00f5807e83b234dfb6122bf37cfadf4be216c53a49ac059d02414f819170", size = 4301458 }, ] +[[package]] +name = "watchdog" +version = "3.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/95/a6/d6ef450393dac5734c63c40a131f66808d2e6f59f6165ab38c98fbe4e6ec/watchdog-3.0.0.tar.gz", hash = "sha256:4d98a320595da7a7c5a18fc48cb633c2e73cda78f93cac2ef42d42bf609a33f9", size = 124593 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/92/28/631872d7fbc45527037060db8c838b47a129a6c09d2297d6dddcfa283cf2/watchdog-3.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:0e06ab8858a76e1219e68c7573dfeba9dd1c0219476c5a44d5333b01d7e1743a", size = 82049 }, + { url = "https://files.pythonhosted.org/packages/c0/a2/4e3230bdc1fb878b152a2c66aa941732776f4545bd68135d490591d66713/watchdog-3.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:d00e6be486affb5781468457b21a6cbe848c33ef43f9ea4a73b4882e5f188a44", size = 82049 }, + { url = "https://files.pythonhosted.org/packages/21/72/46fd174352cd88b9157ade77e3b8835125d4b1e5186fc7f1e8c44664e029/watchdog-3.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:c07253088265c363d1ddf4b3cdb808d59a0468ecd017770ed716991620b8f77a", size = 82052 }, + { url = "https://files.pythonhosted.org/packages/74/3c/e4b77f4f069aca2b6e35925db7a1aa6cb600dcb52fc3e962284640ca37f3/watchdog-3.0.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:5113334cf8cf0ac8cd45e1f8309a603291b614191c9add34d33075727a967709", size = 82050 }, + { url = "https://files.pythonhosted.org/packages/71/3a/b12740f4f60861240d57b42a2ac6ac0a2821db506c4435f7872c1fad867d/watchdog-3.0.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:51f90f73b4697bac9c9a78394c3acbbd331ccd3655c11be1a15ae6fe289a8c83", size = 82050 }, + { url = "https://files.pythonhosted.org/packages/40/1b/4e6d3e0f587587931f590531b4ed08070d71a9efb35541d792a68d8ee593/watchdog-3.0.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:ba07e92756c97e3aca0912b5cbc4e5ad802f4557212788e72a72a47ff376950d", size = 82049 }, + { url = "https://files.pythonhosted.org/packages/2b/f0/456948b865ab259784f774154e7d65844fa9757522fdb11533fbf8ae7aca/watchdog-3.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:d429c2430c93b7903914e4db9a966c7f2b068dd2ebdd2fa9b9ce094c7d459f33", size = 82051 }, + { url = "https://files.pythonhosted.org/packages/55/0d/bfc2a0d425b12444a2dc245a934c065bbb7bd9833fff071cba79c21bb76e/watchdog-3.0.0-py3-none-win32.whl", hash = "sha256:3ed7c71a9dccfe838c2f0b6314ed0d9b22e77d268c67e015450a29036a81f60f", size = 82038 }, + { url = "https://files.pythonhosted.org/packages/9b/6e/ce8d124d03cd3f2941365d9c81d62e3afe43f2dc7e6e86274fa9c2ec2d5b/watchdog-3.0.0-py3-none-win_amd64.whl", hash = "sha256:4c9956d27be0bb08fc5f30d9d0179a855436e655f046d288e2bcc11adfae893c", size = 82040 }, + { url = "https://files.pythonhosted.org/packages/ba/0c/cd0337069c468f22ef256e768ece74c78b511092f1004ab260268e1af4a9/watchdog-3.0.0-py3-none-win_ia64.whl", hash = "sha256:5d9f3a10e02d7371cd929b5d8f11e87d4bad890212ed3901f9b4d68767bee759", size = 82040 }, +] + +[[package]] +name = "wcwidth" +version = "0.2.13" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6c/63/53559446a878410fc5a5974feb13d31d78d752eb18aeba59c7fef1af7598/wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5", size = 101301 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fd/84/fd2ba7aafacbad3c4201d395674fc6348826569da3c0937e75505ead3528/wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859", size = 34166 }, +] + +[[package]] +name = "web3" +version = "6.20.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiohttp" }, + { name = "eth-abi" }, + { name = "eth-account" }, + { name = "eth-hash", extra = ["pycryptodome"] }, + { name = "eth-typing" }, + { name = "eth-utils" }, + { name = "hexbytes" }, + { name = "jsonschema" }, + { name = "lru-dict" }, + { name = "protobuf" }, + { name = "pyunormalize" }, + { name = "pywin32", marker = "sys_platform == 'win32'" }, + { name = "requests" }, + { name = "typing-extensions" }, + { name = "websockets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/69/c1/9fc3a82718e5de39454e8b1558d4b2d41b947c860eb8f4850bebff5ee00a/web3-6.20.1.tar.gz", hash = "sha256:a29bc1863734e1c05f128ddbc56878f299ea71776806e667b581a83b5d5be0ed", size = 2302112 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ca/20/aae5c425460205b98823a5f6e7aca013d9e8ca93aedf353675d9ba4e1ba4/web3-6.20.1-py3-none-any.whl", hash = "sha256:16fe72aeb48bbd5f7e7e64b323a0d3a16522a28eb4f19ef9f9dd6ce7ee813c82", size = 1610958 }, +] + +[package.optional-dependencies] +tester = [ + { name = "eth-tester", extra = ["py-evm"] }, + { name = "py-geth" }, +] + +[[package]] +name = "websockets" +version = "15.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/21/e6/26d09fab466b7ca9c7737474c52be4f76a40301b08362eb2dbc19dcc16c1/websockets-15.0.1.tar.gz", hash = "sha256:82544de02076bafba038ce055ee6412d68da13ab47f0c60cab827346de828dee", size = 177016 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/51/6b/4545a0d843594f5d0771e86463606a3988b5a09ca5123136f8a76580dd63/websockets-15.0.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:3e90baa811a5d73f3ca0bcbf32064d663ed81318ab225ee4f427ad4e26e5aff3", size = 175437 }, + { url = "https://files.pythonhosted.org/packages/f4/71/809a0f5f6a06522af902e0f2ea2757f71ead94610010cf570ab5c98e99ed/websockets-15.0.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:592f1a9fe869c778694f0aa806ba0374e97648ab57936f092fd9d87f8bc03665", size = 173096 }, + { url = "https://files.pythonhosted.org/packages/3d/69/1a681dd6f02180916f116894181eab8b2e25b31e484c5d0eae637ec01f7c/websockets-15.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0701bc3cfcb9164d04a14b149fd74be7347a530ad3bbf15ab2c678a2cd3dd9a2", size = 173332 }, + { url = "https://files.pythonhosted.org/packages/a6/02/0073b3952f5bce97eafbb35757f8d0d54812b6174ed8dd952aa08429bcc3/websockets-15.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8b56bdcdb4505c8078cb6c7157d9811a85790f2f2b3632c7d1462ab5783d215", size = 183152 }, + { url = "https://files.pythonhosted.org/packages/74/45/c205c8480eafd114b428284840da0b1be9ffd0e4f87338dc95dc6ff961a1/websockets-15.0.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0af68c55afbd5f07986df82831c7bff04846928ea8d1fd7f30052638788bc9b5", size = 182096 }, + { url = "https://files.pythonhosted.org/packages/14/8f/aa61f528fba38578ec553c145857a181384c72b98156f858ca5c8e82d9d3/websockets-15.0.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64dee438fed052b52e4f98f76c5790513235efaa1ef7f3f2192c392cd7c91b65", size = 182523 }, + { url = "https://files.pythonhosted.org/packages/ec/6d/0267396610add5bc0d0d3e77f546d4cd287200804fe02323797de77dbce9/websockets-15.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d5f6b181bb38171a8ad1d6aa58a67a6aa9d4b38d0f8c5f496b9e42561dfc62fe", size = 182790 }, + { url = "https://files.pythonhosted.org/packages/02/05/c68c5adbf679cf610ae2f74a9b871ae84564462955d991178f95a1ddb7dd/websockets-15.0.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5d54b09eba2bada6011aea5375542a157637b91029687eb4fdb2dab11059c1b4", size = 182165 }, + { url = "https://files.pythonhosted.org/packages/29/93/bb672df7b2f5faac89761cb5fa34f5cec45a4026c383a4b5761c6cea5c16/websockets-15.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3be571a8b5afed347da347bfcf27ba12b069d9d7f42cb8c7028b5e98bbb12597", size = 182160 }, + { url = "https://files.pythonhosted.org/packages/ff/83/de1f7709376dc3ca9b7eeb4b9a07b4526b14876b6d372a4dc62312bebee0/websockets-15.0.1-cp312-cp312-win32.whl", hash = "sha256:c338ffa0520bdb12fbc527265235639fb76e7bc7faafbb93f6ba80d9c06578a9", size = 176395 }, + { url = "https://files.pythonhosted.org/packages/7d/71/abf2ebc3bbfa40f391ce1428c7168fb20582d0ff57019b69ea20fa698043/websockets-15.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:fcd5cf9e305d7b8338754470cf69cf81f420459dbae8a3b40cee57417f4614a7", size = 176841 }, + { url = "https://files.pythonhosted.org/packages/cb/9f/51f0cf64471a9d2b4d0fc6c534f323b664e7095640c34562f5182e5a7195/websockets-15.0.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ee443ef070bb3b6ed74514f5efaa37a252af57c90eb33b956d35c8e9c10a1931", size = 175440 }, + { url = "https://files.pythonhosted.org/packages/8a/05/aa116ec9943c718905997412c5989f7ed671bc0188ee2ba89520e8765d7b/websockets-15.0.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5a939de6b7b4e18ca683218320fc67ea886038265fd1ed30173f5ce3f8e85675", size = 173098 }, + { url = "https://files.pythonhosted.org/packages/ff/0b/33cef55ff24f2d92924923c99926dcce78e7bd922d649467f0eda8368923/websockets-15.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:746ee8dba912cd6fc889a8147168991d50ed70447bf18bcda7039f7d2e3d9151", size = 173329 }, + { url = "https://files.pythonhosted.org/packages/31/1d/063b25dcc01faa8fada1469bdf769de3768b7044eac9d41f734fd7b6ad6d/websockets-15.0.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:595b6c3969023ecf9041b2936ac3827e4623bfa3ccf007575f04c5a6aa318c22", size = 183111 }, + { url = "https://files.pythonhosted.org/packages/93/53/9a87ee494a51bf63e4ec9241c1ccc4f7c2f45fff85d5bde2ff74fcb68b9e/websockets-15.0.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c714d2fc58b5ca3e285461a4cc0c9a66bd0e24c5da9911e30158286c9b5be7f", size = 182054 }, + { url = "https://files.pythonhosted.org/packages/ff/b2/83a6ddf56cdcbad4e3d841fcc55d6ba7d19aeb89c50f24dd7e859ec0805f/websockets-15.0.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f3c1e2ab208db911594ae5b4f79addeb3501604a165019dd221c0bdcabe4db8", size = 182496 }, + { url = "https://files.pythonhosted.org/packages/98/41/e7038944ed0abf34c45aa4635ba28136f06052e08fc2168520bb8b25149f/websockets-15.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:229cf1d3ca6c1804400b0a9790dc66528e08a6a1feec0d5040e8b9eb14422375", size = 182829 }, + { url = "https://files.pythonhosted.org/packages/e0/17/de15b6158680c7623c6ef0db361da965ab25d813ae54fcfeae2e5b9ef910/websockets-15.0.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:756c56e867a90fb00177d530dca4b097dd753cde348448a1012ed6c5131f8b7d", size = 182217 }, + { url = "https://files.pythonhosted.org/packages/33/2b/1f168cb6041853eef0362fb9554c3824367c5560cbdaad89ac40f8c2edfc/websockets-15.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:558d023b3df0bffe50a04e710bc87742de35060580a293c2a984299ed83bc4e4", size = 182195 }, + { url = "https://files.pythonhosted.org/packages/86/eb/20b6cdf273913d0ad05a6a14aed4b9a85591c18a987a3d47f20fa13dcc47/websockets-15.0.1-cp313-cp313-win32.whl", hash = "sha256:ba9e56e8ceeeedb2e080147ba85ffcd5cd0711b89576b83784d8605a7df455fa", size = 176393 }, + { url = "https://files.pythonhosted.org/packages/1b/6c/c65773d6cab416a64d191d6ee8a8b1c68a09970ea6909d16965d26bfed1e/websockets-15.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:e09473f095a819042ecb2ab9465aee615bd9c2028e4ef7d933600a8401c79561", size = 176837 }, + { url = "https://files.pythonhosted.org/packages/fa/a8/5b41e0da817d64113292ab1f8247140aac61cbf6cfd085d6a0fa77f4984f/websockets-15.0.1-py3-none-any.whl", hash = "sha256:f7a866fbc1e97b5c617ee4116daaa09b722101d4a3c170c787450ba409f9736f", size = 169743 }, +] + [[package]] name = "wrapt" version = "1.17.2" @@ -1483,6 +3710,37 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/91/19/5dbac77ca82f009f8267d0e7cca4fd3be2a435d83ed98f6f8b9c5cb1747e/xarray-2025.3.0-py3-none-any.whl", hash = "sha256:022b7eac5ebc26d70d34211de732ec1a15127a40455f9492acdde7fbe5bbf091", size = 1270849 }, ] +[package.optional-dependencies] +complete = [ + { name = "bottleneck" }, + { name = "cartopy" }, + { name = "cftime" }, + { name = "dask", extra = ["complete"] }, + { name = "flox" }, + { name = "fsspec" }, + { name = "h5netcdf" }, + { name = "matplotlib" }, + { name = "nc-time-axis" }, + { name = "netcdf4" }, + { name = "numba" }, + { name = "numbagg" }, + { name = "opt-einsum" }, + { name = "pooch" }, + { name = "scipy" }, + { name = "seaborn" }, + { name = "sparse" }, + { name = "zarr" }, +] + +[[package]] +name = "xyzservices" +version = "2025.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/00/af/c0f7f97bb320d14c089476f487b81f733238cc5603e0914f2e409f49d589/xyzservices-2025.4.0.tar.gz", hash = "sha256:6fe764713648fac53450fbc61a3c366cb6ae5335a1b2ae0c3796b495de3709d8", size = 1134722 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d6/7d/b77455d7c7c51255b2992b429107fab811b2e36ceaf76da1e55a045dc568/xyzservices-2025.4.0-py3-none-any.whl", hash = "sha256:8d4db9a59213ccb4ce1cf70210584f30b10795bff47627cdfb862b39ff6e10c9", size = 90391 }, +] + [[package]] name = "yarl" version = "1.18.3" @@ -1531,7 +3789,7 @@ wheels = [ [[package]] name = "zarr" -version = "3.0.6" +version = "3.0.10" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "donfig" }, @@ -1540,7 +3798,16 @@ dependencies = [ { name = "packaging" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6f/bf/2820e8ad5f086ea5cd4c4d65eb5e0113f98145ddf5810cec1c75fb10fb98/zarr-3.0.6.tar.gz", hash = "sha256:6ef23c740e34917a2a1099471361537732942e49f0cabe95c9b7124cd0d6d84f", size = 240812 } +sdist = { url = "https://files.pythonhosted.org/packages/07/10/a1b6eabeb5a8681916568a7c6a7a1849c952131be127ccbd57e05d47d43e/zarr-3.0.10.tar.gz", hash = "sha256:1fd1318ade646f692d8f604be0e0ad125675a061196e612e3f7a2cfa9e957d1c", size = 263594 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/45/57/3329346940f78de49047ddcb03fdbca9e16450c3a942688bf24201a322e5/zarr-3.0.10-py3-none-any.whl", hash = "sha256:110724c045fbe4ff5509a8a2a6b6098cb244a6af43da85eaeecef9821473163f", size = 209508 }, +] + +[[package]] +name = "zict" +version = "3.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d1/ac/3c494dd7ec5122cff8252c1a209b282c0867af029f805ae9befd73ae37eb/zict-3.0.0.tar.gz", hash = "sha256:e321e263b6a97aafc0790c3cfb3c04656b7066e6738c37fffcca95d803c9fba5", size = 33238 } wheels = [ - { url = "https://files.pythonhosted.org/packages/fb/38/56a1c129577d20dc975a934ccc3f7f16276eab04ff0ffbe02ea348407a37/zarr-3.0.6-py3-none-any.whl", hash = "sha256:dba078726b6e4defb0ae6a852b7e7035ce163d89d485961681ece49191dcce82", size = 196357 }, + { url = "https://files.pythonhosted.org/packages/80/ab/11a76c1e2126084fde2639514f24e6111b789b0bfa4fc6264a8975c7e1f1/zict-3.0.0-py2.py3-none-any.whl", hash = "sha256:5796e36bd0e0cc8cf0fbc1ace6a68912611c1dbd74750a3f3026b9b9d6a327ae", size = 43332 }, ] From 74a00b4fe9c8716e1a816cb4fde7e38f6c575961 Mon Sep 17 00:00:00 2001 From: TheGreatAlgo <37487508+TheGreatAlgo@users.noreply.github.com> Date: Thu, 10 Jul 2025 09:16:32 -0400 Subject: [PATCH 6/6] fix: updated contract --- .../contracts/StacRegistry.sol | 214 ++++++++++++-- .../compiled/contracts/StacRegistry.json | 1 + dclimate_zarr_client/ipfs_retrieval.py | 168 +++++------ dclimate_zarr_client/loader.py | 120 ++++---- dclimate_zarr_client/loaders/base.py | 44 --- dclimate_zarr_client/loaders/era5.py | 46 ++- dclimate_zarr_client/registry.py | 274 +++++++++--------- tests/test_integration.py | 211 +++++++++----- 8 files changed, 633 insertions(+), 445 deletions(-) create mode 100644 dclimate_zarr_client/contracts/compiled/contracts/StacRegistry.json diff --git a/dclimate_zarr_client/contracts/StacRegistry.sol b/dclimate_zarr_client/contracts/StacRegistry.sol index 19b696b..dc4946e 100644 --- a/dclimate_zarr_client/contracts/StacRegistry.sol +++ b/dclimate_zarr_client/contracts/StacRegistry.sol @@ -2,48 +2,208 @@ pragma solidity ^0.8.20; /** - * @title StacRegistry - * @dev A simple contract to store and manage the root CID for a STAC catalog. - * The owner of the contract is the only one who can update the CID. + * @title Ownable + * @dev The Ownable contract has an owner address, and provides basic authorization control + * functions, this simplifies the implementation of "user permissions". + * For production, consider using OpenZeppelin's Ownable contract for more features. */ -contract StacRegistry { +contract Ownable { address public owner; - string public stacCid; - event CidUpdated(string newCid, address indexed updater); + event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); + + constructor() { + owner = msg.sender; + } + + modifier onlyOwner() { + require(msg.sender == owner, "Ownable: caller is not the owner"); + _; + } + + function transferOwnership(address newOwner) public virtual onlyOwner { + require(newOwner != address(0), "Ownable: new owner is the zero address"); + emit OwnershipTransferred(owner, newOwner); + owner = newOwner; + } +} + + +/** + * @title StacRegistry (Developer-Friendly Version) + * @dev A central registry for STAC data. This version is optimized for off-chain + * developer convenience, returning human-readable string arrays. + * WARNING: The getter functions are gas-intensive and should NOT be called from other contracts. + */ +contract StacRegistry is Ownable { + + enum UpdateCategory { APPEND, REPLACE } + + string public stacRootCid; + + // --- Core Data Storage --- + // collection -> dataset -> type -> cid + mapping(bytes32 => mapping(bytes32 => mapping(bytes32 => string))) private _cids; + + // --- Arrays for Human-Readable Getters --- + string[] private _collectionNames; + mapping(bytes32 => string[]) private _datasetNames; // Key: keccak256(collectionName) + mapping(bytes32 => string[]) private _typeNames; // Key: keccak256(collectionName + datasetName) + + // --- Events --- + event StacRootUpdated(string newCid, address indexed updater); + event CidUpdated(string collection, string dataset, string dataType, string newCid, UpdateCategory category, address indexed updater); + event CollectionInitialized(string collectionName, address indexed initializer); + event DatasetInitialized(string collectionName, string datasetName, address indexed initializer); + event TypeInitialized(string collectionName, string datasetName, string typeName, address indexed initializer); + + + // --- Initialization Functions (Owner Only) --- + + function initCollection(string calldata collectionName) external onlyOwner { + bytes32 collectionKey = keccak256(abi.encodePacked(collectionName)); + require(_datasetNames[collectionKey].length == 0, "Collection already exists"); + _collectionNames.push(collectionName); + emit CollectionInitialized(collectionName, msg.sender); + } + + function initDataset(string calldata collectionName, string calldata datasetName) external onlyOwner { + bytes32 collectionKey = keccak256(abi.encodePacked(collectionName)); + bytes32 typeMapKey = keccak256(abi.encodePacked(collectionName, datasetName)); + require(_typeNames[typeMapKey].length == 0, "Dataset already exists"); + + _datasetNames[collectionKey].push(datasetName); + emit DatasetInitialized(collectionName, datasetName, msg.sender); + } + + function initType( + string calldata collectionName, + string calldata datasetName, + string calldata typeName, + string calldata initialCid + ) external onlyOwner { + bytes32 collectionKey = keccak256(abi.encodePacked(collectionName)); + bytes32 datasetKey = keccak256(abi.encodePacked(datasetName)); + bytes32 typeKey = keccak256(abi.encodePacked(typeName)); + bytes32 typeMapKey = keccak256(abi.encodePacked(collectionName, datasetName)); + + require(bytes(_cids[collectionKey][datasetKey][typeKey]).length == 0, "Type already exists"); + + _cids[collectionKey][datasetKey][typeKey] = initialCid; + _typeNames[typeMapKey].push(typeName); + emit TypeInitialized(collectionName, datasetName, typeName, msg.sender); + } + + // --- Update Functions (Owner Only) --- + + function updateStacRoot(string calldata newCid) external onlyOwner { + stacRootCid = newCid; + emit StacRootUpdated(newCid, msg.sender); + } + + function updateCid(string calldata path, string calldata newCid, UpdateCategory category) external onlyOwner { + (string memory collection, string memory dataset, string memory dataType) = _parsePath(path); + + bytes32 collectionKey = keccak256(abi.encodePacked(collection)); + bytes32 datasetKey = keccak256(abi.encodePacked(dataset)); + bytes32 typeKey = keccak256(abi.encodePacked(dataType)); + + require(bytes(_cids[collectionKey][datasetKey][typeKey]).length > 0, "Path does not resolve to an existing type"); + + _cids[collectionKey][datasetKey][typeKey] = newCid; + emit CidUpdated(collection, dataset, dataType, newCid, category, msg.sender); + } + + function changeOwner(address newOwner) external onlyOwner { + transferOwnership(newOwner); + } + + // --- Read-Only Functions (Human-Readable) --- /** - * @dev Sets the initial STAC CID and the contract owner. - * @param initialCid The initial root CID of the STAC catalog. + * @notice Resolves a path string to its stored CID. */ - constructor(string memory initialCid) { - owner = msg.sender; - stacCid = initialCid; + function resolve(string calldata path) external view returns (string memory) { + (string memory collection, string memory dataset, string memory dataType) = _parsePath(path); + bytes32 collectionKey = keccak256(abi.encodePacked(collection)); + bytes32 datasetKey = keccak256(abi.encodePacked(dataset)); + bytes32 typeKey = keccak256(abi.encodePacked(dataType)); + + return _cids[collectionKey][datasetKey][typeKey]; + } + + /** + * @notice Gets all initialized collection names as readable strings. + * @dev WARNING: Do not call this from another smart contract. + */ + function getCollections() external view returns (string[] memory) { + return _collectionNames; } /** - * @dev Allows the owner to update the STAC CID. - * Emits a CidUpdated event. - * @param newCid The new root CID to store. + * @notice Gets all dataset names for a given collection as readable strings. + * @dev WARNING: Do not call this from another smart contract. */ - function updateCid(string memory newCid) public { - require(msg.sender == owner, "Only the owner can update the CID"); - stacCid = newCid; - emit CidUpdated(newCid, msg.sender); + function getDatasets(string calldata collectionName) external view returns (string[] memory) { + bytes32 collectionKey = keccak256(abi.encodePacked(collectionName)); + return _datasetNames[collectionKey]; } /** - * @dev A public view function to retrieve the current STAC CID. - * @return The current root CID string. + * @notice Gets all type names for a given dataset as readable strings. + * @dev WARNING: Do not call this from another smart contract. */ - function getCid() public view returns (string memory) { - return stacCid; + function getTypes(string calldata collectionName, string calldata datasetName) external view returns (string[] memory) { + bytes32 typeMapKey = keccak256(abi.encodePacked(collectionName, datasetName)); + return _typeNames[typeMapKey]; } - // function to change the owner of the contract - function changeOwner(address newOwner) public { - require(msg.sender == owner, "Only the owner can change ownership"); - require(newOwner != address(0), "New owner cannot be the zero address"); - owner = newOwner; + + // --- Internal Helpers --- + + function _parsePath(string calldata path) internal pure returns (string memory collection, string memory dataset, string memory dataType) { + bytes memory pathBytes = bytes(path); + uint256 firstDelimiterIndex = 0; + for (uint256 i = 0; i < pathBytes.length; i++) { + if (pathBytes[i] == '-') { + firstDelimiterIndex = i; + break; + } + } + require(firstDelimiterIndex > 0, "Invalid path format: no first '-' found."); + uint256 lastDelimiterIndex = 0; + for (uint256 i = pathBytes.length - 1; i > 0; i--) { + if (pathBytes[i] == '-') { + lastDelimiterIndex = i; + break; + } + } + require(lastDelimiterIndex > firstDelimiterIndex, "Invalid path format: no second '-' found or delimiters misplaced."); + + // Use the _slice helper function to extract the parts + collection = string(_slice(pathBytes, 0, firstDelimiterIndex)); + dataset = string(_slice(pathBytes, firstDelimiterIndex + 1, lastDelimiterIndex)); + dataType = string(_slice(pathBytes, lastDelimiterIndex + 1, pathBytes.length)); + + require(bytes(collection).length > 0, "Collection part cannot be empty"); + require(bytes(dataset).length > 0, "Dataset part cannot be empty"); + require(bytes(dataType).length > 0, "Type part cannot be empty"); + } + + /** + * @dev Slices a bytes array. + * @param data The bytes array to slice. + * @param start The starting index. + * @param end The ending index. + * @return A new bytes array containing the slice. + */ + function _slice(bytes memory data, uint256 start, uint256 end) internal pure returns (bytes memory) { + require(end >= start, "Slice: end cannot be less than start"); + uint256 len = end - start; + bytes memory result = new bytes(len); + for (uint256 i = 0; i < len; i++) { + result[i] = data[start + i]; + } + return result; } } \ No newline at end of file diff --git a/dclimate_zarr_client/contracts/compiled/contracts/StacRegistry.json b/dclimate_zarr_client/contracts/compiled/contracts/StacRegistry.json new file mode 100644 index 0000000..f944a5f --- /dev/null +++ b/dclimate_zarr_client/contracts/compiled/contracts/StacRegistry.json @@ -0,0 +1 @@ +{"compilers":[{"contractTypes":["Ownable","StacRegistry"],"name":"solidity","settings":{"optimizer":{"enabled":true,"runs":200},"outputSelection":{"StacRegistry.sol":{"":["ast"],"*":["abi","bin-runtime","devdoc","userdoc","evm.bytecode.object","evm.bytecode.sourceMap","evm.deployedBytecode.object"]}}},"version":"0.8.30+commit.73712a01"}],"contractTypes":{"Ownable":{"abi":[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"}],"contractName":"Ownable","deploymentBytecode":{"bytecode":"0x6080604052348015600e575f5ffd5b505f80546001600160a01b031916331790556101fa8061002d5f395ff3fe608060405234801561000f575f5ffd5b5060043610610034575f3560e01c80638da5cb5b14610038578063f2fde38b14610066575b5f5ffd5b5f5461004a906001600160a01b031681565b6040516001600160a01b03909116815260200160405180910390f35b610079610074366004610197565b61007b565b005b5f546001600160a01b031633146100d95760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e657260448201526064015b60405180910390fd5b6001600160a01b03811661013e5760405162461bcd60e51b815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201526564647265737360d01b60648201526084016100d0565b5f80546040516001600160a01b03808516939216917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e091a35f80546001600160a01b0319166001600160a01b0392909216919091179055565b5f602082840312156101a7575f5ffd5b81356001600160a01b03811681146101bd575f5ffd5b939250505056fea2646970667358221220a09236cd587946c1eca0e2e4927163068ff8b84fd4f0dbfc1cf8ca49a592d08e64736f6c634300081e0033"},"devdoc":{"details":"The Ownable contract has an owner address, and provides basic authorization control functions, this simplifies the implementation of \"user permissions\". For production, consider using OpenZeppelin's Ownable contract for more features.","kind":"dev","methods":{},"title":"Ownable","version":1},"methodIdentifiers":{"owner()":"0x8da5cb5b","transferOwnership(address)":"0xf2fde38b"},"runtimeBytecode":{"bytecode":"0x608060405234801561000f575f5ffd5b5060043610610034575f3560e01c80638da5cb5b14610038578063f2fde38b14610066575b5f5ffd5b5f5461004a906001600160a01b031681565b6040516001600160a01b03909116815260200160405180910390f35b610079610074366004610197565b61007b565b005b5f546001600160a01b031633146100d95760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e657260448201526064015b60405180910390fd5b6001600160a01b03811661013e5760405162461bcd60e51b815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201526564647265737360d01b60648201526084016100d0565b5f80546040516001600160a01b03808516939216917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e091a35f80546001600160a01b0319166001600160a01b0392909216919091179055565b5f602082840312156101a7575f5ffd5b81356001600160a01b03811681146101bd575f5ffd5b939250505056fea2646970667358221220a09236cd587946c1eca0e2e4927163068ff8b84fd4f0dbfc1cf8ca49a592d08e64736f6c634300081e0033"},"sourceId":"StacRegistry.sol","sourcemap":"333:554:0:-:0;;;473:49;;;;;;;;;-1:-1:-1;497:5:0;:18;;-1:-1:-1;;;;;;497:18:0;505:10;497:18;;;333:554;;;;;;","userdoc":{"kind":"user","methods":{},"version":1}},"StacRegistry":{"abi":[{"anonymous":false,"inputs":[{"indexed":false,"internalType":"string","name":"collection","type":"string"},{"indexed":false,"internalType":"string","name":"dataset","type":"string"},{"indexed":false,"internalType":"string","name":"dataType","type":"string"},{"indexed":false,"internalType":"string","name":"newCid","type":"string"},{"indexed":false,"internalType":"enum StacRegistry.UpdateCategory","name":"category","type":"uint8"},{"indexed":true,"internalType":"address","name":"updater","type":"address"}],"name":"CidUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"string","name":"collectionName","type":"string"},{"indexed":true,"internalType":"address","name":"initializer","type":"address"}],"name":"CollectionInitialized","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"string","name":"collectionName","type":"string"},{"indexed":false,"internalType":"string","name":"datasetName","type":"string"},{"indexed":true,"internalType":"address","name":"initializer","type":"address"}],"name":"DatasetInitialized","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"string","name":"newCid","type":"string"},{"indexed":true,"internalType":"address","name":"updater","type":"address"}],"name":"StacRootUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"string","name":"collectionName","type":"string"},{"indexed":false,"internalType":"string","name":"datasetName","type":"string"},{"indexed":false,"internalType":"string","name":"typeName","type":"string"},{"indexed":true,"internalType":"address","name":"initializer","type":"address"}],"name":"TypeInitialized","type":"event"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"changeOwner","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"getCollections","outputs":[{"internalType":"string[]","name":"","type":"string[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"collectionName","type":"string"}],"name":"getDatasets","outputs":[{"internalType":"string[]","name":"","type":"string[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"collectionName","type":"string"},{"internalType":"string","name":"datasetName","type":"string"}],"name":"getTypes","outputs":[{"internalType":"string[]","name":"","type":"string[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"collectionName","type":"string"}],"name":"initCollection","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"collectionName","type":"string"},{"internalType":"string","name":"datasetName","type":"string"}],"name":"initDataset","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"collectionName","type":"string"},{"internalType":"string","name":"datasetName","type":"string"},{"internalType":"string","name":"typeName","type":"string"},{"internalType":"string","name":"initialCid","type":"string"}],"name":"initType","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"path","type":"string"}],"name":"resolve","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"stacRootCid","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"path","type":"string"},{"internalType":"string","name":"newCid","type":"string"},{"internalType":"enum StacRegistry.UpdateCategory","name":"category","type":"uint8"}],"name":"updateCid","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"newCid","type":"string"}],"name":"updateStacRoot","outputs":[],"stateMutability":"nonpayable","type":"function"}],"contractName":"StacRegistry","deploymentBytecode":{"bytecode":"0x6080604052348015600e575f5ffd5b505f80546001600160a01b0319163317905561195d8061002d5f395ff3fe608060405234801561000f575f5ffd5b50600436106100cb575f3560e01c80636f0cf8cb11610088578063abbc971c11610063578063abbc971c146101a0578063e1064bba146101b3578063eff71c79146101c6578063f2fde38b146101d9575f5ffd5b80636f0cf8cb1461015b5780638da5cb5b14610163578063a6f9dae11461018d575f5ffd5b806304094107146100cf5780631da81c16146100e4578063461a4478146100f757806346e6358614610120578063607ef1631461013557806367433b1814610148575b5f5ffd5b6100e26100dd3660046112f1565b6101ec565b005b6100e26100f236600461135d565b610347565b61010a61010536600461135d565b6103c5565b60405161011791906113ca565b60405180910390f35b61012861050d565b60405161011791906113e3565b6101286101433660046112f1565b6105e1565b6100e261015636600461135d565b6106ed565b61010a610823565b5f54610175906001600160a01b031681565b6040516001600160a01b039091168152602001610117565b6100e261019b366004611446565b6108af565b6100e26101ae36600461146c565b6108e4565b6100e26101c13660046114f2565b610aae565b6101286101d436600461135d565b610ca8565b6100e26101e7366004611446565b610dae565b5f546001600160a01b0316331461021e5760405162461bcd60e51b8152600401610215906115c0565b60405180910390fd5b5f84846040516020016102329291906115f5565b6040516020818303038152906040528051906020012090505f858585856040516020016102629493929190611604565b60408051601f1981840301815291815281516020928301205f8181526005909352912054909150156102cf5760405162461bcd60e51b81526020600482015260166024820152754461746173657420616c72656164792065786973747360501b6044820152606401610215565b5f828152600460209081526040822080546001810182559083529120016102f78486836116bb565b50336001600160a01b03167f687a8a6a3df759865850822dd5a24a77d64ad29f7f0bff7908793cf5eb7e048287878787604051610337949392919061179d565b60405180910390a2505050505050565b5f546001600160a01b031633146103705760405162461bcd60e51b8152600401610215906115c0565b600161037d8284836116bb565b50336001600160a01b03167fe50e50e78a6e2a0383a03e5273dc58c6faf064c45be7342814de1d32c577c93083836040516103b99291906117ce565b60405180910390a25050565b60605f5f5f6103d48686610e95565b9250925092505f836040516020016103ec91906117e9565b6040516020818303038152906040528051906020012090505f8360405160200161041691906117e9565b6040516020818303038152906040528051906020012090505f8360405160200161044091906117e9565b60408051601f1981840301815291815281516020928301205f868152600284528281208682528452828120828252909352912080549192509061048290611637565b80601f01602080910402602001604051908101604052809291908181526020018280546104ae90611637565b80156104f95780601f106104d0576101008083540402835291602001916104f9565b820191905f5260205f20905b8154815290600101906020018083116104dc57829003601f168201915b505050505096505050505050505b92915050565b60606003805480602002602001604051908101604052809291908181526020015f905b828210156105d8578382905f5260205f2001805461054d90611637565b80601f016020809104026020016040519081016040528092919081815260200182805461057990611637565b80156105c45780601f1061059b576101008083540402835291602001916105c4565b820191905f5260205f20905b8154815290600101906020018083116105a757829003601f168201915b505050505081526020019060010190610530565b50505050905090565b60605f858585856040516020016105fb9493929190611604565b60408051601f1981840301815282825280516020918201205f8181526005835283812080548085028701850190955284865291955090929184015b828210156106de578382905f5260205f2001805461065390611637565b80601f016020809104026020016040519081016040528092919081815260200182805461067f90611637565b80156106ca5780601f106106a1576101008083540402835291602001916106ca565b820191905f5260205f20905b8154815290600101906020018083116106ad57829003601f168201915b505050505081526020019060010190610636565b50505050915050949350505050565b5f546001600160a01b031633146107165760405162461bcd60e51b8152600401610215906115c0565b5f828260405160200161072a9291906115f5565b60408051601f1981840301815291815281516020928301205f81815260049093529120549091501561079e5760405162461bcd60e51b815260206004820152601960248201527f436f6c6c656374696f6e20616c726561647920657869737473000000000000006044820152606401610215565b600380546001810182555f919091527fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b016107da8385836116bb565b50336001600160a01b03167fb9c7ca2c1d7d6ac251b4b3d037f933e5f54305b9ca8ddb8047d04e4cc6172a2784846040516108169291906117ce565b60405180910390a2505050565b6001805461083090611637565b80601f016020809104026020016040519081016040528092919081815260200182805461085c90611637565b80156108a75780601f1061087e576101008083540402835291602001916108a7565b820191905f5260205f20905b81548152906001019060200180831161088a57829003601f168201915b505050505081565b5f546001600160a01b031633146108d85760405162461bcd60e51b8152600401610215906115c0565b6108e181610dae565b50565b5f546001600160a01b0316331461090d5760405162461bcd60e51b8152600401610215906115c0565b5f5f5f61091a8888610e95565b9250925092505f8360405160200161093291906117e9565b6040516020818303038152906040528051906020012090505f8360405160200161095c91906117e9565b6040516020818303038152906040528051906020012090505f8360405160200161098691906117e9565b60408051601f1981840301815291815281516020928301205f8681526002845282812086825284528281208282529093529082208054919350906109c990611637565b905011610a2a5760405162461bcd60e51b815260206004820152602960248201527f5061746820646f6573206e6f74207265736f6c766520746f20616e206578697360448201526874696e67207479706560b81b6064820152608401610215565b5f83815260026020908152604080832085845282528083208484529091529020610a55898b836116bb565b50336001600160a01b03167fb17ac485b95246393a5eaff3393ebca1a5e17646287d2e8d4cde350be3c965d08787878d8d8d604051610a99969594939291906117ff565b60405180910390a25050505050505050505050565b5f546001600160a01b03163314610ad75760405162461bcd60e51b8152600401610215906115c0565b5f8888604051602001610aeb9291906115f5565b6040516020818303038152906040528051906020012090505f8787604051602001610b179291906115f5565b6040516020818303038152906040528051906020012090505f8686604051602001610b439291906115f5565b6040516020818303038152906040528051906020012090505f8b8b8b8b604051602001610b739493929190611604565b60408051601f1981840301815291815281516020928301205f8781526002845282812087825284528281208682529093529120805491925090610bb590611637565b159050610bfa5760405162461bcd60e51b81526020600482015260136024820152725479706520616c72656164792065786973747360681b6044820152606401610215565b5f84815260026020908152604080832086845282528083208584529091529020610c258688836116bb565b505f81815260056020908152604082208054600181018255908352912001610c4e888a836116bb565b50336001600160a01b03167fb49dd854b8a9a06ab72f50457084391db365a4e2091d03b6e37198de6e6571658d8d8d8d8d8d604051610c929695949392919061187c565b60405180910390a2505050505050505050505050565b60605f8383604051602001610cbe9291906115f5565b60408051601f1981840301815282825280516020918201205f8181526004835283812080548085028701850190955284865291955090929184015b82821015610da1578382905f5260205f20018054610d1690611637565b80601f0160208091040260200160405190810160405280929190818152602001828054610d4290611637565b8015610d8d5780601f10610d6457610100808354040283529160200191610d8d565b820191905f5260205f20905b815481529060010190602001808311610d7057829003601f168201915b505050505081526020019060010190610cf9565b5050505091505092915050565b5f546001600160a01b03163314610dd75760405162461bcd60e51b8152600401610215906115c0565b6001600160a01b038116610e3c5760405162461bcd60e51b815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201526564647265737360d01b6064820152608401610215565b5f80546040516001600160a01b03808516939216917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e091a35f80546001600160a01b0319166001600160a01b0392909216919091179055565b60608060605f85858080601f0160208091040260200160405190810160405280939291908181526020018383808284375f92018290525093945083925050505b8251811015610f1957828181518110610ef057610ef06118c4565b01602001516001600160f81b031916602d60f81b03610f1157809150610f19565b600101610ed5565b505f8111610f7a5760405162461bcd60e51b815260206004820152602860248201527f496e76616c6964207061746820666f726d61743a206e6f20666972737420272d60448201526713903337bab7321760c11b6064820152608401610215565b81515f908190610f8c906001906118ec565b90505b8015610fda57838181518110610fa757610fa76118c4565b01602001516001600160f81b031916602d60f81b03610fc857809150610fda565b80610fd2816118ff565b915050610f8f565b5081811161105a5760405162461bcd60e51b815260206004820152604160248201527f496e76616c6964207061746820666f726d61743a206e6f207365636f6e64202760448201527f2d2720666f756e64206f722064656c696d6974657273206d6973706c616365646064820152601760f91b608482015260a401610215565b611065835f84611190565b955061107c83611076846001611914565b83611190565b94506110948361108d836001611914565b8551611190565b93505f8651116110e65760405162461bcd60e51b815260206004820152601f60248201527f436f6c6c656374696f6e20706172742063616e6e6f7420626520656d707479006044820152606401610215565b5f8551116111365760405162461bcd60e51b815260206004820152601c60248201527f4461746173657420706172742063616e6e6f7420626520656d707479000000006044820152606401610215565b5f8451116111865760405162461bcd60e51b815260206004820152601960248201527f5479706520706172742063616e6e6f7420626520656d707479000000000000006044820152606401610215565b5050509250925092565b6060828210156111ee5760405162461bcd60e51b8152602060048201526024808201527f536c6963653a20656e642063616e6e6f74206265206c657373207468616e20736044820152631d185c9d60e21b6064820152608401610215565b5f6111f984846118ec565b90505f8167ffffffffffffffff81111561121557611215611623565b6040519080825280601f01601f19166020018201604052801561123f576020820181803683370190505b5090505f5b828110156112a257866112578288611914565b81518110611267576112676118c4565b602001015160f81c60f81b828281518110611284576112846118c4565b60200101906001600160f81b03191690815f1a905350600101611244565b5095945050505050565b5f5f83601f8401126112bc575f5ffd5b50813567ffffffffffffffff8111156112d3575f5ffd5b6020830191508360208285010111156112ea575f5ffd5b9250929050565b5f5f5f5f60408587031215611304575f5ffd5b843567ffffffffffffffff81111561131a575f5ffd5b611326878288016112ac565b909550935050602085013567ffffffffffffffff811115611345575f5ffd5b611351878288016112ac565b95989497509550505050565b5f5f6020838503121561136e575f5ffd5b823567ffffffffffffffff811115611384575f5ffd5b611390858286016112ac565b90969095509350505050565b5f81518084528060208401602086015e5f602082860101526020601f19601f83011685010191505092915050565b602081525f6113dc602083018461139c565b9392505050565b5f602082016020835280845180835260408501915060408160051b8601019250602086015f5b8281101561143a57603f1987860301845261142585835161139c565b94506020938401939190910190600101611409565b50929695505050505050565b5f60208284031215611456575f5ffd5b81356001600160a01b03811681146113dc575f5ffd5b5f5f5f5f5f60608688031215611480575f5ffd5b853567ffffffffffffffff811115611496575f5ffd5b6114a2888289016112ac565b909650945050602086013567ffffffffffffffff8111156114c1575f5ffd5b6114cd888289016112ac565b9094509250506040860135600281106114e4575f5ffd5b809150509295509295909350565b5f5f5f5f5f5f5f5f6080898b031215611509575f5ffd5b883567ffffffffffffffff81111561151f575f5ffd5b61152b8b828c016112ac565b909950975050602089013567ffffffffffffffff81111561154a575f5ffd5b6115568b828c016112ac565b909750955050604089013567ffffffffffffffff811115611575575f5ffd5b6115818b828c016112ac565b909550935050606089013567ffffffffffffffff8111156115a0575f5ffd5b6115ac8b828c016112ac565b999c989b5096995094979396929594505050565b6020808252818101527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572604082015260600190565b818382375f9101908152919050565b838582375f8482015f8152838582375f93019283525090949350505050565b634e487b7160e01b5f52604160045260245ffd5b600181811c9082168061164b57607f821691505b60208210810361166957634e487b7160e01b5f52602260045260245ffd5b50919050565b601f8211156116b657805f5260205f20601f840160051c810160208510156116945750805b601f840160051c820191505b818110156116b3575f81556001016116a0565b50505b505050565b67ffffffffffffffff8311156116d3576116d3611623565b6116e7836116e18354611637565b8361166f565b5f601f841160018114611718575f85156117015750838201355b5f19600387901b1c1916600186901b1783556116b3565b5f83815260208120601f198716915b828110156117475786850135825560209485019460019092019101611727565b5086821015611763575f1960f88860031b161c19848701351681555b505060018560011b0183555050505050565b81835281816020850137505f828201602090810191909152601f909101601f19169091010190565b604081525f6117b0604083018688611775565b82810360208401526117c3818587611775565b979650505050505050565b602081525f6117e1602083018486611775565b949350505050565b5f82518060208501845e5f920191825250919050565b60a081525f61181160a083018961139c565b8281036020840152611823818961139c565b90508281036040840152611837818861139c565b9050828103606084015261184c818688611775565b9150506002831061186b57634e487b7160e01b5f52602160045260245ffd5b826080830152979650505050505050565b606081525f61188f60608301888a611775565b82810360208401526118a2818789611775565b905082810360408401526118b7818587611775565b9998505050505050505050565b634e487b7160e01b5f52603260045260245ffd5b634e487b7160e01b5f52601160045260245ffd5b81810381811115610507576105076118d8565b5f8161190d5761190d6118d8565b505f190190565b80820180821115610507576105076118d856fea264697066735822122081c3321f2311eb7d3804351732662070456e8f14460096f93b4e3a8c62b06a7664736f6c634300081e0033"},"devdoc":{"details":"A central registry for STAC data. This version is optimized for off-chain developer convenience, returning human-readable string arrays. WARNING: The getter functions are gas-intensive and should NOT be called from other contracts.","kind":"dev","methods":{"getCollections()":{"details":"WARNING: Do not call this from another smart contract."},"getDatasets(string)":{"details":"WARNING: Do not call this from another smart contract."},"getTypes(string,string)":{"details":"WARNING: Do not call this from another smart contract."}},"title":"StacRegistry (Developer-Friendly Version)","version":1},"methodIdentifiers":{"changeOwner(address)":"0xa6f9dae1","getCollections()":"0x46e63586","getDatasets(string)":"0xeff71c79","getTypes(string,string)":"0x607ef163","initCollection(string)":"0x67433b18","initDataset(string,string)":"0x04094107","initType(string,string,string,string)":"0xe1064bba","owner()":"0x8da5cb5b","resolve(string)":"0x461a4478","stacRootCid()":"0x6f0cf8cb","transferOwnership(address)":"0xf2fde38b","updateCid(string,string,uint8)":"0xabbc971c","updateStacRoot(string)":"0x1da81c16"},"runtimeBytecode":{"bytecode":"0x608060405234801561000f575f5ffd5b50600436106100cb575f3560e01c80636f0cf8cb11610088578063abbc971c11610063578063abbc971c146101a0578063e1064bba146101b3578063eff71c79146101c6578063f2fde38b146101d9575f5ffd5b80636f0cf8cb1461015b5780638da5cb5b14610163578063a6f9dae11461018d575f5ffd5b806304094107146100cf5780631da81c16146100e4578063461a4478146100f757806346e6358614610120578063607ef1631461013557806367433b1814610148575b5f5ffd5b6100e26100dd3660046112f1565b6101ec565b005b6100e26100f236600461135d565b610347565b61010a61010536600461135d565b6103c5565b60405161011791906113ca565b60405180910390f35b61012861050d565b60405161011791906113e3565b6101286101433660046112f1565b6105e1565b6100e261015636600461135d565b6106ed565b61010a610823565b5f54610175906001600160a01b031681565b6040516001600160a01b039091168152602001610117565b6100e261019b366004611446565b6108af565b6100e26101ae36600461146c565b6108e4565b6100e26101c13660046114f2565b610aae565b6101286101d436600461135d565b610ca8565b6100e26101e7366004611446565b610dae565b5f546001600160a01b0316331461021e5760405162461bcd60e51b8152600401610215906115c0565b60405180910390fd5b5f84846040516020016102329291906115f5565b6040516020818303038152906040528051906020012090505f858585856040516020016102629493929190611604565b60408051601f1981840301815291815281516020928301205f8181526005909352912054909150156102cf5760405162461bcd60e51b81526020600482015260166024820152754461746173657420616c72656164792065786973747360501b6044820152606401610215565b5f828152600460209081526040822080546001810182559083529120016102f78486836116bb565b50336001600160a01b03167f687a8a6a3df759865850822dd5a24a77d64ad29f7f0bff7908793cf5eb7e048287878787604051610337949392919061179d565b60405180910390a2505050505050565b5f546001600160a01b031633146103705760405162461bcd60e51b8152600401610215906115c0565b600161037d8284836116bb565b50336001600160a01b03167fe50e50e78a6e2a0383a03e5273dc58c6faf064c45be7342814de1d32c577c93083836040516103b99291906117ce565b60405180910390a25050565b60605f5f5f6103d48686610e95565b9250925092505f836040516020016103ec91906117e9565b6040516020818303038152906040528051906020012090505f8360405160200161041691906117e9565b6040516020818303038152906040528051906020012090505f8360405160200161044091906117e9565b60408051601f1981840301815291815281516020928301205f868152600284528281208682528452828120828252909352912080549192509061048290611637565b80601f01602080910402602001604051908101604052809291908181526020018280546104ae90611637565b80156104f95780601f106104d0576101008083540402835291602001916104f9565b820191905f5260205f20905b8154815290600101906020018083116104dc57829003601f168201915b505050505096505050505050505b92915050565b60606003805480602002602001604051908101604052809291908181526020015f905b828210156105d8578382905f5260205f2001805461054d90611637565b80601f016020809104026020016040519081016040528092919081815260200182805461057990611637565b80156105c45780601f1061059b576101008083540402835291602001916105c4565b820191905f5260205f20905b8154815290600101906020018083116105a757829003601f168201915b505050505081526020019060010190610530565b50505050905090565b60605f858585856040516020016105fb9493929190611604565b60408051601f1981840301815282825280516020918201205f8181526005835283812080548085028701850190955284865291955090929184015b828210156106de578382905f5260205f2001805461065390611637565b80601f016020809104026020016040519081016040528092919081815260200182805461067f90611637565b80156106ca5780601f106106a1576101008083540402835291602001916106ca565b820191905f5260205f20905b8154815290600101906020018083116106ad57829003601f168201915b505050505081526020019060010190610636565b50505050915050949350505050565b5f546001600160a01b031633146107165760405162461bcd60e51b8152600401610215906115c0565b5f828260405160200161072a9291906115f5565b60408051601f1981840301815291815281516020928301205f81815260049093529120549091501561079e5760405162461bcd60e51b815260206004820152601960248201527f436f6c6c656374696f6e20616c726561647920657869737473000000000000006044820152606401610215565b600380546001810182555f919091527fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b016107da8385836116bb565b50336001600160a01b03167fb9c7ca2c1d7d6ac251b4b3d037f933e5f54305b9ca8ddb8047d04e4cc6172a2784846040516108169291906117ce565b60405180910390a2505050565b6001805461083090611637565b80601f016020809104026020016040519081016040528092919081815260200182805461085c90611637565b80156108a75780601f1061087e576101008083540402835291602001916108a7565b820191905f5260205f20905b81548152906001019060200180831161088a57829003601f168201915b505050505081565b5f546001600160a01b031633146108d85760405162461bcd60e51b8152600401610215906115c0565b6108e181610dae565b50565b5f546001600160a01b0316331461090d5760405162461bcd60e51b8152600401610215906115c0565b5f5f5f61091a8888610e95565b9250925092505f8360405160200161093291906117e9565b6040516020818303038152906040528051906020012090505f8360405160200161095c91906117e9565b6040516020818303038152906040528051906020012090505f8360405160200161098691906117e9565b60408051601f1981840301815291815281516020928301205f8681526002845282812086825284528281208282529093529082208054919350906109c990611637565b905011610a2a5760405162461bcd60e51b815260206004820152602960248201527f5061746820646f6573206e6f74207265736f6c766520746f20616e206578697360448201526874696e67207479706560b81b6064820152608401610215565b5f83815260026020908152604080832085845282528083208484529091529020610a55898b836116bb565b50336001600160a01b03167fb17ac485b95246393a5eaff3393ebca1a5e17646287d2e8d4cde350be3c965d08787878d8d8d604051610a99969594939291906117ff565b60405180910390a25050505050505050505050565b5f546001600160a01b03163314610ad75760405162461bcd60e51b8152600401610215906115c0565b5f8888604051602001610aeb9291906115f5565b6040516020818303038152906040528051906020012090505f8787604051602001610b179291906115f5565b6040516020818303038152906040528051906020012090505f8686604051602001610b439291906115f5565b6040516020818303038152906040528051906020012090505f8b8b8b8b604051602001610b739493929190611604565b60408051601f1981840301815291815281516020928301205f8781526002845282812087825284528281208682529093529120805491925090610bb590611637565b159050610bfa5760405162461bcd60e51b81526020600482015260136024820152725479706520616c72656164792065786973747360681b6044820152606401610215565b5f84815260026020908152604080832086845282528083208584529091529020610c258688836116bb565b505f81815260056020908152604082208054600181018255908352912001610c4e888a836116bb565b50336001600160a01b03167fb49dd854b8a9a06ab72f50457084391db365a4e2091d03b6e37198de6e6571658d8d8d8d8d8d604051610c929695949392919061187c565b60405180910390a2505050505050505050505050565b60605f8383604051602001610cbe9291906115f5565b60408051601f1981840301815282825280516020918201205f8181526004835283812080548085028701850190955284865291955090929184015b82821015610da1578382905f5260205f20018054610d1690611637565b80601f0160208091040260200160405190810160405280929190818152602001828054610d4290611637565b8015610d8d5780601f10610d6457610100808354040283529160200191610d8d565b820191905f5260205f20905b815481529060010190602001808311610d7057829003601f168201915b505050505081526020019060010190610cf9565b5050505091505092915050565b5f546001600160a01b03163314610dd75760405162461bcd60e51b8152600401610215906115c0565b6001600160a01b038116610e3c5760405162461bcd60e51b815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201526564647265737360d01b6064820152608401610215565b5f80546040516001600160a01b03808516939216917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e091a35f80546001600160a01b0319166001600160a01b0392909216919091179055565b60608060605f85858080601f0160208091040260200160405190810160405280939291908181526020018383808284375f92018290525093945083925050505b8251811015610f1957828181518110610ef057610ef06118c4565b01602001516001600160f81b031916602d60f81b03610f1157809150610f19565b600101610ed5565b505f8111610f7a5760405162461bcd60e51b815260206004820152602860248201527f496e76616c6964207061746820666f726d61743a206e6f20666972737420272d60448201526713903337bab7321760c11b6064820152608401610215565b81515f908190610f8c906001906118ec565b90505b8015610fda57838181518110610fa757610fa76118c4565b01602001516001600160f81b031916602d60f81b03610fc857809150610fda565b80610fd2816118ff565b915050610f8f565b5081811161105a5760405162461bcd60e51b815260206004820152604160248201527f496e76616c6964207061746820666f726d61743a206e6f207365636f6e64202760448201527f2d2720666f756e64206f722064656c696d6974657273206d6973706c616365646064820152601760f91b608482015260a401610215565b611065835f84611190565b955061107c83611076846001611914565b83611190565b94506110948361108d836001611914565b8551611190565b93505f8651116110e65760405162461bcd60e51b815260206004820152601f60248201527f436f6c6c656374696f6e20706172742063616e6e6f7420626520656d707479006044820152606401610215565b5f8551116111365760405162461bcd60e51b815260206004820152601c60248201527f4461746173657420706172742063616e6e6f7420626520656d707479000000006044820152606401610215565b5f8451116111865760405162461bcd60e51b815260206004820152601960248201527f5479706520706172742063616e6e6f7420626520656d707479000000000000006044820152606401610215565b5050509250925092565b6060828210156111ee5760405162461bcd60e51b8152602060048201526024808201527f536c6963653a20656e642063616e6e6f74206265206c657373207468616e20736044820152631d185c9d60e21b6064820152608401610215565b5f6111f984846118ec565b90505f8167ffffffffffffffff81111561121557611215611623565b6040519080825280601f01601f19166020018201604052801561123f576020820181803683370190505b5090505f5b828110156112a257866112578288611914565b81518110611267576112676118c4565b602001015160f81c60f81b828281518110611284576112846118c4565b60200101906001600160f81b03191690815f1a905350600101611244565b5095945050505050565b5f5f83601f8401126112bc575f5ffd5b50813567ffffffffffffffff8111156112d3575f5ffd5b6020830191508360208285010111156112ea575f5ffd5b9250929050565b5f5f5f5f60408587031215611304575f5ffd5b843567ffffffffffffffff81111561131a575f5ffd5b611326878288016112ac565b909550935050602085013567ffffffffffffffff811115611345575f5ffd5b611351878288016112ac565b95989497509550505050565b5f5f6020838503121561136e575f5ffd5b823567ffffffffffffffff811115611384575f5ffd5b611390858286016112ac565b90969095509350505050565b5f81518084528060208401602086015e5f602082860101526020601f19601f83011685010191505092915050565b602081525f6113dc602083018461139c565b9392505050565b5f602082016020835280845180835260408501915060408160051b8601019250602086015f5b8281101561143a57603f1987860301845261142585835161139c565b94506020938401939190910190600101611409565b50929695505050505050565b5f60208284031215611456575f5ffd5b81356001600160a01b03811681146113dc575f5ffd5b5f5f5f5f5f60608688031215611480575f5ffd5b853567ffffffffffffffff811115611496575f5ffd5b6114a2888289016112ac565b909650945050602086013567ffffffffffffffff8111156114c1575f5ffd5b6114cd888289016112ac565b9094509250506040860135600281106114e4575f5ffd5b809150509295509295909350565b5f5f5f5f5f5f5f5f6080898b031215611509575f5ffd5b883567ffffffffffffffff81111561151f575f5ffd5b61152b8b828c016112ac565b909950975050602089013567ffffffffffffffff81111561154a575f5ffd5b6115568b828c016112ac565b909750955050604089013567ffffffffffffffff811115611575575f5ffd5b6115818b828c016112ac565b909550935050606089013567ffffffffffffffff8111156115a0575f5ffd5b6115ac8b828c016112ac565b999c989b5096995094979396929594505050565b6020808252818101527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572604082015260600190565b818382375f9101908152919050565b838582375f8482015f8152838582375f93019283525090949350505050565b634e487b7160e01b5f52604160045260245ffd5b600181811c9082168061164b57607f821691505b60208210810361166957634e487b7160e01b5f52602260045260245ffd5b50919050565b601f8211156116b657805f5260205f20601f840160051c810160208510156116945750805b601f840160051c820191505b818110156116b3575f81556001016116a0565b50505b505050565b67ffffffffffffffff8311156116d3576116d3611623565b6116e7836116e18354611637565b8361166f565b5f601f841160018114611718575f85156117015750838201355b5f19600387901b1c1916600186901b1783556116b3565b5f83815260208120601f198716915b828110156117475786850135825560209485019460019092019101611727565b5086821015611763575f1960f88860031b161c19848701351681555b505060018560011b0183555050505050565b81835281816020850137505f828201602090810191909152601f909101601f19169091010190565b604081525f6117b0604083018688611775565b82810360208401526117c3818587611775565b979650505050505050565b602081525f6117e1602083018486611775565b949350505050565b5f82518060208501845e5f920191825250919050565b60a081525f61181160a083018961139c565b8281036020840152611823818961139c565b90508281036040840152611837818861139c565b9050828103606084015261184c818688611775565b9150506002831061186b57634e487b7160e01b5f52602160045260245ffd5b826080830152979650505050505050565b606081525f61188f60608301888a611775565b82810360208401526118a2818789611775565b905082810360408401526118b7818587611775565b9998505050505050505050565b634e487b7160e01b5f52603260045260245ffd5b634e487b7160e01b5f52601160045260245ffd5b81810381811115610507576105076118d8565b5f8161190d5761190d6118d8565b505f190190565b80820180821115610507576105076118d856fea264697066735822122081c3321f2311eb7d3804351732662070456e8f14460096f93b4e3a8c62b06a7664736f6c634300081e0033"},"sourceId":"StacRegistry.sol","sourcemap":"1196:7559:0:-:0;;;;;;;;;;;;-1:-1:-1;497:5:0;:18;;-1:-1:-1;;;;;;497:18:0;505:10;497:18;;;1196:7559;;;;;;","userdoc":{"kind":"user","methods":{"getCollections()":{"notice":"Gets all initialized collection names as readable strings."},"getDatasets(string)":{"notice":"Gets all dataset names for a given collection as readable strings."},"getTypes(string,string)":{"notice":"Gets all type names for a given dataset as readable strings."},"resolve(string)":{"notice":"Resolves a path string to its stored CID."}},"version":1}}},"manifest":"ethpm/3","sources":{"StacRegistry.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.20;\n\n/**\n * @title Ownable\n * @dev The Ownable contract has an owner address, and provides basic authorization control\n * functions, this simplifies the implementation of \"user permissions\".\n * For production, consider using OpenZeppelin's Ownable contract for more features.\n */\ncontract Ownable {\n address public owner;\n\n event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);\n\n constructor() {\n owner = msg.sender;\n }\n\n modifier onlyOwner() {\n require(msg.sender == owner, \"Ownable: caller is not the owner\");\n _;\n }\n\n function transferOwnership(address newOwner) public virtual onlyOwner {\n require(newOwner != address(0), \"Ownable: new owner is the zero address\");\n emit OwnershipTransferred(owner, newOwner);\n owner = newOwner;\n }\n}\n\n\n/**\n * @title StacRegistry (Developer-Friendly Version)\n * @dev A central registry for STAC data. This version is optimized for off-chain\n * developer convenience, returning human-readable string arrays.\n * WARNING: The getter functions are gas-intensive and should NOT be called from other contracts.\n */\ncontract StacRegistry is Ownable {\n\n enum UpdateCategory { APPEND, REPLACE }\n\n string public stacRootCid;\n\n // --- Core Data Storage ---\n // collection -> dataset -> type -> cid\n mapping(bytes32 => mapping(bytes32 => mapping(bytes32 => string))) private _cids;\n\n // --- Arrays for Human-Readable Getters ---\n string[] private _collectionNames;\n mapping(bytes32 => string[]) private _datasetNames; // Key: keccak256(collectionName)\n mapping(bytes32 => string[]) private _typeNames; // Key: keccak256(collectionName + datasetName)\n\n // --- Events ---\n event StacRootUpdated(string newCid, address indexed updater);\n event CidUpdated(string collection, string dataset, string dataType, string newCid, UpdateCategory category, address indexed updater);\n event CollectionInitialized(string collectionName, address indexed initializer);\n event DatasetInitialized(string collectionName, string datasetName, address indexed initializer);\n event TypeInitialized(string collectionName, string datasetName, string typeName, address indexed initializer);\n\n\n // --- Initialization Functions (Owner Only) ---\n\n function initCollection(string calldata collectionName) external onlyOwner {\n bytes32 collectionKey = keccak256(abi.encodePacked(collectionName));\n require(_datasetNames[collectionKey].length == 0, \"Collection already exists\");\n _collectionNames.push(collectionName);\n emit CollectionInitialized(collectionName, msg.sender);\n }\n\n function initDataset(string calldata collectionName, string calldata datasetName) external onlyOwner {\n bytes32 collectionKey = keccak256(abi.encodePacked(collectionName));\n bytes32 typeMapKey = keccak256(abi.encodePacked(collectionName, datasetName));\n require(_typeNames[typeMapKey].length == 0, \"Dataset already exists\");\n \n _datasetNames[collectionKey].push(datasetName);\n emit DatasetInitialized(collectionName, datasetName, msg.sender);\n }\n\n function initType(\n string calldata collectionName,\n string calldata datasetName,\n string calldata typeName,\n string calldata initialCid\n ) external onlyOwner {\n bytes32 collectionKey = keccak256(abi.encodePacked(collectionName));\n bytes32 datasetKey = keccak256(abi.encodePacked(datasetName));\n bytes32 typeKey = keccak256(abi.encodePacked(typeName));\n bytes32 typeMapKey = keccak256(abi.encodePacked(collectionName, datasetName));\n\n require(bytes(_cids[collectionKey][datasetKey][typeKey]).length == 0, \"Type already exists\");\n\n _cids[collectionKey][datasetKey][typeKey] = initialCid;\n _typeNames[typeMapKey].push(typeName);\n emit TypeInitialized(collectionName, datasetName, typeName, msg.sender);\n }\n\n // --- Update Functions (Owner Only) ---\n\n function updateStacRoot(string calldata newCid) external onlyOwner {\n stacRootCid = newCid;\n emit StacRootUpdated(newCid, msg.sender);\n }\n\n function updateCid(string calldata path, string calldata newCid, UpdateCategory category) external onlyOwner {\n (string memory collection, string memory dataset, string memory dataType) = _parsePath(path);\n \n bytes32 collectionKey = keccak256(abi.encodePacked(collection));\n bytes32 datasetKey = keccak256(abi.encodePacked(dataset));\n bytes32 typeKey = keccak256(abi.encodePacked(dataType));\n\n require(bytes(_cids[collectionKey][datasetKey][typeKey]).length > 0, \"Path does not resolve to an existing type\");\n\n _cids[collectionKey][datasetKey][typeKey] = newCid;\n emit CidUpdated(collection, dataset, dataType, newCid, category, msg.sender);\n }\n \n function changeOwner(address newOwner) external onlyOwner {\n transferOwnership(newOwner);\n }\n\n // --- Read-Only Functions (Human-Readable) ---\n\n /**\n * @notice Resolves a path string to its stored CID.\n */\n function resolve(string calldata path) external view returns (string memory) {\n (string memory collection, string memory dataset, string memory dataType) = _parsePath(path);\n bytes32 collectionKey = keccak256(abi.encodePacked(collection));\n bytes32 datasetKey = keccak256(abi.encodePacked(dataset));\n bytes32 typeKey = keccak256(abi.encodePacked(dataType));\n\n return _cids[collectionKey][datasetKey][typeKey];\n }\n \n /**\n * @notice Gets all initialized collection names as readable strings.\n * @dev WARNING: Do not call this from another smart contract.\n */\n function getCollections() external view returns (string[] memory) {\n return _collectionNames;\n }\n\n /**\n * @notice Gets all dataset names for a given collection as readable strings.\n * @dev WARNING: Do not call this from another smart contract.\n */\n function getDatasets(string calldata collectionName) external view returns (string[] memory) {\n bytes32 collectionKey = keccak256(abi.encodePacked(collectionName));\n return _datasetNames[collectionKey];\n }\n\n /**\n * @notice Gets all type names for a given dataset as readable strings.\n * @dev WARNING: Do not call this from another smart contract.\n */\n function getTypes(string calldata collectionName, string calldata datasetName) external view returns (string[] memory) {\n bytes32 typeMapKey = keccak256(abi.encodePacked(collectionName, datasetName));\n return _typeNames[typeMapKey];\n }\n\n\n // --- Internal Helpers ---\n\n function _parsePath(string calldata path) internal pure returns (string memory collection, string memory dataset, string memory dataType) {\n bytes memory pathBytes = bytes(path);\n uint256 firstDelimiterIndex = 0;\n for (uint256 i = 0; i < pathBytes.length; i++) {\n if (pathBytes[i] == '-') {\n firstDelimiterIndex = i;\n break;\n }\n }\n require(firstDelimiterIndex > 0, \"Invalid path format: no first '-' found.\");\n uint256 lastDelimiterIndex = 0;\n for (uint256 i = pathBytes.length - 1; i > 0; i--) {\n if (pathBytes[i] == '-') {\n lastDelimiterIndex = i;\n break;\n }\n }\n require(lastDelimiterIndex > firstDelimiterIndex, \"Invalid path format: no second '-' found or delimiters misplaced.\");\n \n // Use the _slice helper function to extract the parts\n collection = string(_slice(pathBytes, 0, firstDelimiterIndex));\n dataset = string(_slice(pathBytes, firstDelimiterIndex + 1, lastDelimiterIndex));\n dataType = string(_slice(pathBytes, lastDelimiterIndex + 1, pathBytes.length));\n\n require(bytes(collection).length > 0, \"Collection part cannot be empty\");\n require(bytes(dataset).length > 0, \"Dataset part cannot be empty\");\n require(bytes(dataType).length > 0, \"Type part cannot be empty\");\n }\n\n /**\n * @dev Slices a bytes array.\n * @param data The bytes array to slice.\n * @param start The starting index.\n * @param end The ending index.\n * @return A new bytes array containing the slice.\n */\n function _slice(bytes memory data, uint256 start, uint256 end) internal pure returns (bytes memory) {\n require(end >= start, \"Slice: end cannot be less than start\");\n uint256 len = end - start;\n bytes memory result = new bytes(len);\n for (uint256 i = 0; i < len; i++) {\n result[i] = data[start + i];\n }\n return result;\n }\n}\n","urls":[]}}} \ No newline at end of file diff --git a/dclimate_zarr_client/ipfs_retrieval.py b/dclimate_zarr_client/ipfs_retrieval.py index bc30fda..656998a 100644 --- a/dclimate_zarr_client/ipfs_retrieval.py +++ b/dclimate_zarr_client/ipfs_retrieval.py @@ -31,94 +31,94 @@ _stac_hamt_cid_cache: typing.Dict[str, str] = {} -async def get_dataset_stac( - stac_root_cid: str, - collection: str, - dataset: str, - gateway_uri_stem: str, -) -> dict: - """ - Traverses a STAC catalog on IPFS starting from a root CID to find and - return a specific dataset's STAC Item JSON. - - This function uses KuboCAS for all IPFS interactions. - - Args: - stac_root_cid (str): The root CID of the STAC catalog. - collection (str): The ID or title of the target collection. - dataset (str): The ID of the target dataset (STAC Item). - gateway_uri_stem (str): The base URL for the IPFS gateway. - - Returns: - dict: The STAC Item JSON for the requested dataset. - - Raises: - StacCatalogError: If the catalog structure is invalid or a network error occurs. - DatasetNotFoundError: If the collection or dataset cannot be found. - """ - logger.info( - f"Searching STAC catalog for dataset '{dataset}' in collection '{collection}'" - ) - async with KuboCAS(gateway_base_url=gateway_uri_stem) as kubo_cas: - try: - # 1. Fetch the root catalog - logger.debug(f"Fetching root catalog from CID: {stac_root_cid}") - root_catalog_bytes = await kubo_cas.load(CID.decode(stac_root_cid)) - root_catalog = json.loads(root_catalog_bytes) - - if not isinstance(root_catalog, dict) or root_catalog.get("type") != "Catalog": - raise StacCatalogError("Invalid root catalog format.") - - # 2. Find the target collection's CID - collection_cid_str = None - for link in root_catalog.get("links", []): - # Match by collection ID or title for flexibility - if link.get("rel") == "child" and ( - link.get("title") == collection or link.get("id") == collection - ): - href_obj = link.get("href") - if isinstance(href_obj, dict) and "/" in href_obj: - collection_cid_str = href_obj["/"] - break +# async def get_dataset_stac( +# stac_root_cid: str, +# collection: str, +# dataset: str, +# gateway_uri_stem: str, +# ) -> dict: +# """ +# Traverses a STAC catalog on IPFS starting from a root CID to find and +# return a specific dataset's STAC Item JSON. + +# This function uses KuboCAS for all IPFS interactions. + +# Args: +# stac_root_cid (str): The root CID of the STAC catalog. +# collection (str): The ID or title of the target collection. +# dataset (str): The ID of the target dataset (STAC Item). +# gateway_uri_stem (str): The base URL for the IPFS gateway. + +# Returns: +# dict: The STAC Item JSON for the requested dataset. + +# Raises: +# StacCatalogError: If the catalog structure is invalid or a network error occurs. +# DatasetNotFoundError: If the collection or dataset cannot be found. +# """ +# logger.info( +# f"Searching STAC catalog for dataset '{dataset}' in collection '{collection}'" +# ) +# async with KuboCAS(gateway_base_url=gateway_uri_stem) as kubo_cas: +# try: +# # 1. Fetch the root catalog +# logger.debug(f"Fetching root catalog from CID: {stac_root_cid}") +# root_catalog_bytes = await kubo_cas.load(CID.decode(stac_root_cid)) +# root_catalog = json.loads(root_catalog_bytes) + +# if not isinstance(root_catalog, dict) or root_catalog.get("type") != "Catalog": +# raise StacCatalogError("Invalid root catalog format.") + +# # 2. Find the target collection's CID +# collection_cid_str = None +# for link in root_catalog.get("links", []): +# # Match by collection ID or title for flexibility +# if link.get("rel") == "child" and ( +# link.get("title") == collection or link.get("id") == collection +# ): +# href_obj = link.get("href") +# if isinstance(href_obj, dict) and "/" in href_obj: +# collection_cid_str = href_obj["/"] +# break - if not collection_cid_str: - raise DatasetNotFoundError(f"Collection '{collection}' not found in root catalog.") - - # 3. Fetch the collection - logger.debug(f"Fetching collection '{collection}' from CID: {collection_cid_str}") - collection_bytes = await kubo_cas.load(CID.decode(collection_cid_str)) - collection_json = json.loads(collection_bytes) - - if not isinstance(collection_json, dict) or collection_json.get("type") != "Collection": - raise StacCatalogError(f"Invalid format for collection '{collection}'.") - - # 4. Find and return the target dataset (item) or collection - for item_link in collection_json.get("links", []): - if item_link.get("rel") == "item" or item_link.get("rel") == "child": - item_href_obj = item_link.get("href") - item_cid_str = None - if isinstance(item_href_obj, dict) and "/" in item_href_obj: - item_cid_str = item_href_obj["/"] +# if not collection_cid_str: +# raise DatasetNotFoundError(f"Collection '{collection}' not found in root catalog.") + +# # 3. Fetch the collection +# logger.debug(f"Fetching collection '{collection}' from CID: {collection_cid_str}") +# collection_bytes = await kubo_cas.load(CID.decode(collection_cid_str)) +# collection_json = json.loads(collection_bytes) + +# if not isinstance(collection_json, dict) or collection_json.get("type") != "Collection": +# raise StacCatalogError(f"Invalid format for collection '{collection}'.") + +# # 4. Find and return the target dataset (item) or collection +# for item_link in collection_json.get("links", []): +# if item_link.get("rel") == "item" or item_link.get("rel") == "child": +# item_href_obj = item_link.get("href") +# item_cid_str = None +# if isinstance(item_href_obj, dict) and "/" in item_href_obj: +# item_cid_str = item_href_obj["/"] - if not item_cid_str: - continue +# if not item_cid_str: +# continue - # Fetch the item to check its ID - item_bytes = await kubo_cas.load(CID.decode(item_cid_str)) - item_json = json.loads(item_bytes) +# # Fetch the item to check its ID +# item_bytes = await kubo_cas.load(CID.decode(item_cid_str)) +# item_json = json.loads(item_bytes) - if item_json.get("id") == dataset: - logger.info(f"Found matching STAC Item for dataset '{dataset}'.") - return item_json # Return the full STAC item - - # If the loop completes, the dataset was not found in the collection - raise DatasetNotFoundError(f"Dataset '{dataset}' not found in collection '{collection}'.") - - except Exception as e: - # Catch potential errors from KuboCAS (e.g., network issues) or JSON parsing - # and wrap them in a more specific error. - logger.error(f"Failed during STAC traversal: {e}", exc_info=True) - raise StacCatalogError(f"An error occurred during STAC traversal: {e}") from e +# if item_json.get("id") == dataset: +# logger.info(f"Found matching STAC Item for dataset '{dataset}'.") +# return item_json # Return the full STAC item + +# # If the loop completes, the dataset was not found in the collection +# raise DatasetNotFoundError(f"Dataset '{dataset}' not found in collection '{collection}'.") + +# except Exception as e: +# # Catch potential errors from KuboCAS (e.g., network issues) or JSON parsing +# # and wrap them in a more specific error. +# logger.error(f"Failed during STAC traversal: {e}", exc_info=True) +# raise StacCatalogError(f"An error occurred during STAC traversal: {e}") from e # --- IPFSStore Configuration --- # async def _get_ipfs_store( diff --git a/dclimate_zarr_client/loader.py b/dclimate_zarr_client/loader.py index 9632290..d316fd4 100644 --- a/dclimate_zarr_client/loader.py +++ b/dclimate_zarr_client/loader.py @@ -4,7 +4,7 @@ from .loaders.era5 import ERA5Loader from .geotemporal_data import GeotemporalData import xarray as xr -from .registry import get_cid_from_registry +# from .registry import get_cid_from_registry from py_hamt import KuboCAS from multiformats import CID import json @@ -77,63 +77,63 @@ async def _load_stac_json_from_cid(cid: str) -> dict: raise # Get all datasets in the catalog -async def get_all_datasets(collection: str, gateway_url: str = DCLIMATE_GATEWAY_URL) -> list: - """ - Fetches all datasets available in a specific collection from dClimate. - - Args: - collection (str): The name of the data collection (e.g., "era5"). - gateway_url (str): The IPFS gateway URL to use for fetching data. - - Returns: - list: A list of dataset names available in the specified collection. - """ - stac_root_cid = get_cid_from_registry() - - stac_json = await _load_stac_json_from_cid(stac_root_cid) - if not stac_json: - raise ValueError(f"No STAC JSON found for collection: {collection}") +# async def get_all_datasets(collection: str, gateway_url: str = DCLIMATE_GATEWAY_URL) -> list: +# """ +# Fetches all datasets available in a specific collection from dClimate. + +# Args: +# collection (str): The name of the data collection (e.g., "era5"). +# gateway_url (str): The IPFS gateway URL to use for fetching data. + +# Returns: +# list: A list of dataset names available in the specified collection. +# """ +# stac_root_cid = get_cid_from_registry() + +# stac_json = await _load_stac_json_from_cid(stac_root_cid) +# if not stac_json: +# raise ValueError(f"No STAC JSON found for collection: {collection}") - collection_cid = None - - # Capitalize all letters in collection - collection = collection.upper() - - # The first links that are of rel child are all the collections - for link in stac_json.get("links", []): - if link.get("rel") == "child" and link.get("title") == collection: - collection_cid = link.get("href")["/"] - break - - if not collection_cid: - raise ValueError(f"No datasets found for collection: {collection}") - - # Get the stac for the collection - collection_stac = await _load_stac_json_from_cid(collection_cid) - if not collection_stac: - raise ValueError(f"No STAC JSON found for collection CID: {collection_cid}") - # Extract dataset names from the collection STAC - datasets = [] - for item in collection_stac.get("links", []): - if item.get("rel") == "child": - datasets.append(item.get("title")) - if item.get("rel") == "item": - datasets.append(item.get("title")) - return datasets - -async def get_all_collections(gateway_url: str = DCLIMATE_GATEWAY_URL) -> list: - """ - Fetches all collections available in the dClimate catalog. - """ - stac_root_cid = get_cid_from_registry() - - stac_json = await _load_stac_json_from_cid(stac_root_cid) - if not stac_json: - raise ValueError(f"No STAC JSON found for collection: {collection}") - - collections = [] - for link in stac_json.get("links", []): - if link.get("rel") == "child": - collections.append(link.get("title")) - - return collections \ No newline at end of file +# collection_cid = None + +# # Capitalize all letters in collection +# collection = collection.upper() + +# # The first links that are of rel child are all the collections +# for link in stac_json.get("links", []): +# if link.get("rel") == "child" and link.get("title") == collection: +# collection_cid = link.get("href")["/"] +# break + +# if not collection_cid: +# raise ValueError(f"No datasets found for collection: {collection}") + +# # Get the stac for the collection +# collection_stac = await _load_stac_json_from_cid(collection_cid) +# if not collection_stac: +# raise ValueError(f"No STAC JSON found for collection CID: {collection_cid}") +# # Extract dataset names from the collection STAC +# datasets = [] +# for item in collection_stac.get("links", []): +# if item.get("rel") == "child": +# datasets.append(item.get("title")) +# if item.get("rel") == "item": +# datasets.append(item.get("title")) +# return datasets + +# async def get_all_collections(gateway_url: str = DCLIMATE_GATEWAY_URL) -> list: +# """ +# Fetches all collections available in the dClimate catalog. +# """ +# stac_root_cid = get_cid_from_registry() + +# stac_json = await _load_stac_json_from_cid(stac_root_cid) +# if not stac_json: +# raise ValueError(f"No STAC JSON found for collection: {collection}") + +# collections = [] +# for link in stac_json.get("links", []): +# if link.get("rel") == "child": +# collections.append(link.get("title")) + +# return collections \ No newline at end of file diff --git a/dclimate_zarr_client/loaders/base.py b/dclimate_zarr_client/loaders/base.py index c00f445..c2f99c7 100644 --- a/dclimate_zarr_client/loaders/base.py +++ b/dclimate_zarr_client/loaders/base.py @@ -5,55 +5,11 @@ from multiformats import CID import json -STAC_REGISTRY_ABI = """ -[ - { - "inputs": [{"internalType": "string", "name": "initialCid", "type": "string"}], - "stateMutability": "nonpayable", - "type": "constructor" - }, - { - "anonymous": false, - "inputs": [ - {"indexed": false, "internalType": "string", "name": "newCid", "type": "string"}, - {"indexed": true, "internalType": "address", "name": "updater", "type": "address"} - ], - "name": "CidUpdated", - "type": "event" - }, - { - "inputs": [], - "name": "getCid", - "outputs": [{"internalType": "string", "name": "", "type": "string"}], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [{"internalType": "address", "name": "newOwner", "type": "address"}], - "name": "changeOwner", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - } -] -""" - class BaseLoader(ABC): def __init__(self, gateway_url: str): self.gateway_url = gateway_url - async def _load_stac_json_from_cid(self, cid: str) -> dict: - """Loads a STAC JSON from a given CID.""" - async with KuboCAS(gateway_base_url=self.gateway_url) as kubo_cas: - try: - item_bytes = await kubo_cas.load(CID.decode(cid)) - item = json.loads(item_bytes) - return item - except Exception as e: - print(f"Error loading STAC JSON from CID {cid}: {e}") - raise - async def _load_zarr_from_cid(self, cid: str) -> xr.Dataset: """Loads a sharded Zarr store from a given CID.""" async with KuboCAS(gateway_base_url=self.gateway_url) as kubo_cas: diff --git a/dclimate_zarr_client/loaders/era5.py b/dclimate_zarr_client/loaders/era5.py index 1731e93..6ff98bf 100644 --- a/dclimate_zarr_client/loaders/era5.py +++ b/dclimate_zarr_client/loaders/era5.py @@ -1,46 +1,40 @@ import xarray as xr from .base import BaseLoader -from dclimate_zarr_client.ipfs_retrieval import get_dataset_stac +# from dclimate_zarr_client.ipfs_retrieval import get_dataset_stac import numpy as np import pandas as pd -from dclimate_zarr_client.registry import get_cid_from_registry +from dclimate_zarr_client.registry import resolve_path class ERA5Loader(BaseLoader): - collection = "ERA5" + collection = "era5" # Helps get the finalized or unfinalized dataset CID - async def _get_dataset_type_cid(self, stac: dict, type: str) -> str: - links = stac.get("links", []) - dataset_cid = None - for link in links: - if link.get("rel") == "item" and link.get("title") == type: - dataset_cid = link.get("href")["/"] - break - finalized_stac_item = await self._load_stac_json_from_cid(dataset_cid) - finalized_stac_cid = finalized_stac_item.get("assets", {}).get("sharded-zarr", {}).get("href", None) - return finalized_stac_cid + # async def _get_dataset_type_cid(self, stac: dict, type: str) -> str: + # links = stac.get("links", []) + # dataset_cid = None + # for link in links: + # if link.get("rel") == "item" and link.get("title") == type: + # dataset_cid = link.get("href")["/"] + # break + # finalized_stac_item = await self._load_stac_json_from_cid(dataset_cid) + # finalized_stac_cid = finalized_stac_item.get("assets", {}).get("sharded-zarr", {}).get("href", None) + # return finalized_stac_cid async def load(self, dataset: str, options: dict) -> xr.Dataset: + finalized_only = options.get('finalized_only', False) - root_cid = get_cid_from_registry() - - # # 1. --- Get STAC metadata for the dataset collection --- - dataset_stac = await get_dataset_stac( - stac_root_cid=root_cid, - collection=self.collection, - dataset=dataset, - gateway_uri_stem=self.gateway_url - ) - - # # 2. --- Lazily Open Finalized Dataset --- - finalized_stac_cid = await self._get_dataset_type_cid(dataset_stac, "finalized") + finalized_stac_cid = resolve_path(f"{self.collection}-{dataset}-finalized") + + # # # 2. --- Lazily Open Finalized Dataset --- ds_finalized = await self._load_zarr_from_cid(finalized_stac_cid) if finalized_only: return ds_finalized # # 2. --- Lazily Open Unfinalized Dataset --- - unfinalized_stac_cid = await self._get_dataset_type_cid(dataset_stac, "non-finalized") + # unfinalized_stac_cid = await self._get_dataset_type_cid(dataset_stac, "non-finalized") + + unfinalized_stac_cid = resolve_path(f"{self.collection}-{dataset}-non_finalized") ds_unfinalized = await self._load_zarr_from_cid(unfinalized_stac_cid) # 3. --- Perform Lazy Slicing and Concatenation --- diff --git a/dclimate_zarr_client/registry.py b/dclimate_zarr_client/registry.py index d214b3f..1118e6f 100644 --- a/dclimate_zarr_client/registry.py +++ b/dclimate_zarr_client/registry.py @@ -1,178 +1,186 @@ import os +import json from pathlib import Path from eth_account import Account from ape import accounts, Project, networks from ape.exceptions import AccountsError -from ape_accounts import import_account_from_mnemonic from web3 import Web3 +from typing import List -# The path to the embedded Ape project +# --- Configuration --- + +# Path to the directory containing the compiled contract artifacts CONTRACTS_PROJECT_PATH = Path(__file__).parent / "contracts" -STAC_REGISTRY_CONTRACT_ADDRESS = os.environ.get("STAC_REGISTRY_CONTRACT_ADDRESS", "0xabe7441E21bDb6cCf4E517E0072c5E962F5f0B2d") + +# Load environment variables +STAC_REGISTRY_CONTRACT_ADDRESS = os.environ.get("STAC_REGISTRY_CONTRACT_ADDRESS", "0xb54A652489b864638d02C508A2d5Bc14FbFA8df8") RPC_URL = os.environ.get("RPC_URL", "https://sepolia.base.org") -STAC_REGISTRY_ABI = """ -[ - { - "inputs": [{"internalType": "string", "name": "initialCid", "type": "string"}], - "stateMutability": "nonpayable", - "type": "constructor" - }, - { - "anonymous": false, - "inputs": [ - {"indexed": false, "internalType": "string", "name": "newCid", "type": "string"}, - {"indexed": true, "internalType": "address", "name": "updater", "type": "address"} - ], - "name": "CidUpdated", - "type": "event" - }, - { - "inputs": [], - "name": "getCid", - "outputs": [{"internalType": "string", "name": "", "type": "string"}], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [{"internalType": "address", "name": "newOwner", "type": "address"}], - "name": "changeOwner", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [{"internalType": "string", "name": "newCid", "type": "string"}], - "name": "updateCid", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - } -] -""" +MNEMONIC_PATH = os.environ.get("MNEMONIC_PATH", "mnemonic.txt") + +# --- Helper Functions --- def _get_web3_instance() -> Web3: """Initializes and returns a Web3 instance.""" return Web3(Web3.HTTPProvider(RPC_URL)) -def _load_account_from_mnemonic_ape(mnemonic_path: str): - """Loads a deployer/owner account from a mnemonic file.""" - mnemonic_file = Path(mnemonic_path) +def _get_contract_abi() -> list: + """Loads the contract ABI from the project's build artifacts.""" + abi_path = CONTRACTS_PROJECT_PATH / "compiled" / "contracts" / "StacRegistry.json" + if not abi_path.is_file(): + raise FileNotFoundError(f"ABI file not found at: {abi_path}. Please compile your contract.") + with open(abi_path) as f: + contract_json = json.load(f) + return contract_json['contractTypes']['StacRegistry']['abi'] + +def _load_owner_account(): + """Loads the owner account from a mnemonic file for web3.py usage.""" + mnemonic_file = Path(MNEMONIC_PATH) if not mnemonic_file.is_file(): - raise FileNotFoundError(f"Mnemonic file not found at: {mnemonic_path}") + raise FileNotFoundError(f"Mnemonic file not found at: {MNEMONIC_PATH}") - # Use a unique alias to avoid collisions if multiple mnemonics are used + Account.enable_unaudited_hdwallet_features() + mnemonic = mnemonic_file.read_text().strip() + return Account.from_mnemonic(mnemonic) + +def _load_deployer_account_ape(): + """Loads a deployer account from a mnemonic for Ape usage.""" + mnemonic_file = Path(MNEMONIC_PATH) alias = f"etl_deployer_{mnemonic_file.stem}" - try: - # Try to load if it already exists in Ape's accounts return accounts.load(alias) - except KeyError: - print(f"Account '{alias}' not found. Importing from mnemonic...") - return import_account_from_mnemonic( - alias=alias, # Note the parameter name change to 'account_alias' + except AccountsError: + print(f"Ape account '{alias}' not found. Importing from mnemonic...") + return accounts.import_account_from_mnemonic( + account_alias=alias, mnemonic=mnemonic_file.read_text().strip(), - passphrase=os.environ.get("APE_ACCOUNT_PASSPHRASE", "test") # Provide a default + passphrase=os.environ.get("APE_ACCOUNT_PASSPHRASE", "") ) -def _load_account_from_mnemonic(mnemonic_path: str): - """Loads an account from a mnemonic file and returns the account object.""" - mnemonic_file = Path(mnemonic_path) - if not mnemonic_file.is_file(): - raise FileNotFoundError(f"Mnemonic file not found at: {mnemonic_path}") +def _execute_transaction(function_call): + """Builds, signs, and sends a transaction for the given function call.""" + w3 = _get_web3_instance() + owner_account = _load_owner_account() - Account.enable_unaudited_hdwallet_features() - mnemonic = mnemonic_file.read_text().strip() - return Account.from_mnemonic(mnemonic) + tx = function_call.build_transaction({ + 'chainId': w3.eth.chain_id, + 'from': owner_account.address, + 'nonce': w3.eth.get_transaction_count(owner_account.address), + 'gas': 5000000, # Increased default gas limit for more complex transactions + 'gasPrice': w3.eth.gas_price, + }) + + signed_tx = w3.eth.account.sign_transaction(tx, private_key=owner_account.key) + tx_hash = w3.eth.send_raw_transaction(signed_tx.rawTransaction) + receipt = w3.eth.wait_for_transaction_receipt(tx_hash) + + print(f"✓ Transaction successful! Hash: {receipt.transactionHash.hex()}") + return receipt.transactionHash.hex() -def deploy_registry(initial_cid: str, mnemonic_path: str, network: str = "base:sepolia") -> str: - """ - Deploys the StacRegistry contract programmatically. - Args: - initial_cid (str): The initial root CID of the STAC catalog. - mnemonic_path (str): The path to the mnemonic.txt file for the deployer account. - network (str): The network to deploy on (e.g., 'base:sepolia'). +# --- Deployment Function --- - Returns: - str: The address of the newly deployed contract. - """ +def deploy_registry(network: str = "base:sepolia") -> str: + """Deploys the StacRegistry contract using Ape.""" project = Project(path=CONTRACTS_PROJECT_PATH) - deployer = _load_account_from_mnemonic_ape(mnemonic_path) + deployer = _load_deployer_account_ape() with networks.parse_network_choice(network) as provider: print(f"Using network: {provider.name}") print(f"Deployer address: {deployer.address}") - + StacRegistry = project.StacRegistry - - print(f"Deploying StacRegistry with initial CID: {initial_cid}") - contract_instance = deployer.deploy(StacRegistry, initial_cid) - - print(f"✓ Deployment successful!") + + print("Deploying StacRegistry...") + contract_instance = deployer.deploy(StacRegistry) + + print(f"✓ Deployment successful! Contract Address: {contract_instance.address}") return contract_instance.address -def _get_web3_instance() -> Web3: - """Initializes and returns a Web3 instance.""" - return Web3(Web3.HTTPProvider(RPC_URL)) +# --- Read-Only (Getter) Functions --- -def _load_account_from_mnemonic(mnemonic_path: str): - """Loads an account from a mnemonic file and returns the account object.""" - mnemonic_file = Path(mnemonic_path) - if not mnemonic_file.is_file(): - raise FileNotFoundError(f"Mnemonic file not found at: {mnemonic_path}") - - Account.enable_unaudited_hdwallet_features() - mnemonic = mnemonic_file.read_text().strip() - return Account.from_mnemonic(mnemonic) +def get_stac_root_cid(contract_address: str = STAC_REGISTRY_CONTRACT_ADDRESS) -> str: + """Retrieves the top-level STAC Root CID.""" + w3 = _get_web3_instance() + contract = w3.eth.contract(address=contract_address, abi=_get_contract_abi()) + cid = contract.functions.stacRootCid().call() + print(f"STAC Root CID: {cid}") + return cid -def update_registry(contract_address: str, new_cid: str, mnemonic_path: str) -> str: - """ - Updates the STAC CID in an existing StacRegistry contract using web3.py. - """ +def resolve_path(path: str, contract_address: str = STAC_REGISTRY_CONTRACT_ADDRESS) -> str: + """Resolves a 'collection-dataset-type' path to its CID.""" w3 = _get_web3_instance() - owner_account = _load_account_from_mnemonic(mnemonic_path) - - contract = w3.eth.contract(address=contract_address, abi=STAC_REGISTRY_ABI) - - print(f"Using network: {RPC_URL}") - print(f"Owner address: {owner_account.address}") - print(f"Updating STAC CID on contract {contract_address} to: {new_cid}") + contract = w3.eth.contract(address=contract_address, abi=_get_contract_abi()) + cid = contract.functions.resolve(path).call() + print(f"CID for path '{path}': {cid}") + return cid - # 1. Build the transaction - nonce = w3.eth.get_transaction_count(owner_account.address) - tx = contract.functions.updateCid(new_cid).build_transaction({ - 'chainId': w3.eth.chain_id, - 'from': owner_account.address, - 'nonce': nonce, - 'gas': 200000, # You might need to adjust gas estimation - 'gasPrice': w3.eth.gas_price, - }) +def get_collections(contract_address: str = STAC_REGISTRY_CONTRACT_ADDRESS) -> List[str]: + """Retrieves the list of all collection names.""" + w3 = _get_web3_instance() + contract = w3.eth.contract(address=contract_address, abi=_get_contract_abi()) + collections = contract.functions.getCollections().call() + print(f"Available Collections: {collections}") + return collections - # 2. Sign the transaction - signed_tx = w3.eth.account.sign_transaction(tx, private_key=owner_account.key) +def get_datasets(collection_name: str, contract_address: str = STAC_REGISTRY_CONTRACT_ADDRESS) -> List[str]: + """Retrieves the list of all dataset names for a given collection.""" + w3 = _get_web3_instance() + contract = w3.eth.contract(address=contract_address, abi=_get_contract_abi()) + datasets = contract.functions.getDatasets(collection_name).call() + print(f"Available Datasets in '{collection_name}': {datasets}") + return datasets - # 3. Send the raw transaction - tx_hash = w3.eth.send_raw_transaction(signed_tx.rawTransaction) - - # 4. Wait for the transaction receipt (optional, but good practice) - receipt = w3.eth.wait_for_transaction_receipt(tx_hash) - - print("✓ Update successful!") - print(f"Transaction hash: {receipt.transactionHash.hex()}") - - return receipt.transactionHash.hex() +def get_types(collection_name: str, dataset_name: str, contract_address: str = STAC_REGISTRY_CONTRACT_ADDRESS) -> List[str]: + """Retrieves the list of all type names for a given dataset.""" + w3 = _get_web3_instance() + contract = w3.eth.contract(address=contract_address, abi=_get_contract_abi()) + types = contract.functions.getTypes(collection_name, dataset_name).call() + print(f"Available Types in '{collection_name}/{dataset_name}': {types}") + return types + + +# --- State-Changing (Update/Init) Functions --- -def get_cid_from_registry() -> str: +def update_stac_root_cid(new_cid: str, contract_address: str = STAC_REGISTRY_CONTRACT_ADDRESS) -> str: + """Updates the top-level STAC Root CID.""" + print(f"Updating STAC Root CID to: {new_cid}") + w3 = _get_web3_instance() + contract = w3.eth.contract(address=contract_address, abi=_get_contract_abi()) + function_call = contract.functions.updateStacRoot(new_cid) + return _execute_transaction(function_call) + +def update_path_cid(path: str, new_cid: str, category: int = 1, contract_address: str = STAC_REGISTRY_CONTRACT_ADDRESS) -> str: """ - Retrieves the current STAC CID from the StacRegistry contract. - (This function already uses web3.py correctly) + Updates the CID for a specific 'collection-dataset-type' path. + Category: 0 for APPEND, 1 for REPLACE. """ + print(f"Updating CID for path '{path}' to: {new_cid}") + w3 = _get_web3_instance() + contract = w3.eth.contract(address=contract_address, abi=_get_contract_abi()) + function_call = contract.functions.updateCid(path, new_cid, category) + return _execute_transaction(function_call) + +def init_collection(collection_name: str, contract_address: str = STAC_REGISTRY_CONTRACT_ADDRESS) -> str: + """Initializes a new collection.""" + print(f"Initializing collection: {collection_name}") + w3 = _get_web3_instance() + contract = w3.eth.contract(address=contract_address, abi=_get_contract_abi()) + function_call = contract.functions.initCollection(collection_name) + return _execute_transaction(function_call) + +def init_dataset(collection_name: str, dataset_name: str, contract_address: str = STAC_REGISTRY_CONTRACT_ADDRESS) -> str: + """Initializes a new dataset within a collection.""" + print(f"Initializing dataset '{dataset_name}' in collection '{collection_name}'") + w3 = _get_web3_instance() + contract = w3.eth.contract(address=contract_address, abi=_get_contract_abi()) + function_call = contract.functions.initDataset(collection_name, dataset_name) + return _execute_transaction(function_call) + +def init_type(collection_name: str, dataset_name: str, type_name: str, initial_cid: str, contract_address: str = STAC_REGISTRY_CONTRACT_ADDRESS) -> str: + """Initializes a new type for a dataset with an initial CID.""" + print(f"Initializing type '{type_name}' for '{collection_name}/{dataset_name}' with CID: {initial_cid}") w3 = _get_web3_instance() - contract = w3.eth.contract( - address=STAC_REGISTRY_CONTRACT_ADDRESS, - abi=STAC_REGISTRY_ABI - ) - stac_cid = contract.functions.getCid().call() - print(f"Current STAC CID from registry: {stac_cid}") - return stac_cid + contract = w3.eth.contract(address=contract_address, abi=_get_contract_abi()) + function_call = contract.functions.initType(collection_name, dataset_name, type_name, initial_cid) + return _execute_transaction(function_call) \ No newline at end of file diff --git a/tests/test_integration.py b/tests/test_integration.py index 23a1b34..b937468 100644 --- a/tests/test_integration.py +++ b/tests/test_integration.py @@ -1,103 +1,172 @@ import pytest -import xarray as xr -import numpy as np -import pandas as pd -from unittest.mock import patch from pathlib import Path - +import time +from web3 import Web3 +import os +# Assuming your updated registry functions are in this module from dclimate_zarr_client import registry, loader -import xarray as xr -import numpy as np -import pandas as pd -from py_hamt import KuboCAS, ShardedZarrStore # Mark all tests in this file as asyncio pytestmark = pytest.mark.asyncio +RPC_URL = os.environ.get("RPC_URL", "https://sepolia.base.org") + +@pytest.fixture(scope="module") +def w3(): + """Provides a Web3 instance connected to a local test network.""" + return Web3(Web3.HTTPProvider(RPC_URL)) + +@pytest.fixture(scope="module") +def contract_address(w3): + """Deploys the contract and returns its address.""" + contract_address = "0xb54A652489b864638d02C508A2d5Bc14FbFA8df8" + return contract_address @pytest.fixture(scope="module") def mnemonic(tmp_path_factory): """ - Fixture to create a temporary mnemonic file for testing. - - IMPORTANT: You must manually create a `tests/mnemonic.txt` file containing - a seed phrase for a wallet funded with Base Goerli ETH. - This test will fail if the account has no funds. + Fixture to provide the path to the mnemonic file for testing. + + IMPORTANT: You must manually create a `mnemonic.txt` file in your project's + root directory containing a seed phrase for a wallet funded with testnet ETH + (e.g., Base Sepolia ETH). These tests will fail if the account has no funds. """ - # This fixture now points to a user-created mnemonic file - # for the live testnet. mnemonic_path = Path("mnemonic.txt") if not mnemonic_path.exists(): pytest.fail( "A `mnemonic.txt` file is required for live testnet tests. " - "Please create it and fund the associated account with Base Goerli ETH." + "Please create it and fund the associated account with testnet ETH." ) return str(mnemonic_path) -# This test is now parameterized to run ONLY on the Base Goerli testnet. +# This test is now parameterized to run ONLY on the Base Sepolia testnet. # NOTE: This will be slower and will consume real testnet ETH. -@pytest.mark.parametrize("network", ["base:sepolia"]) -async def test_deployment_and_update_on_testnet(mnemonic, network): - """ - Tests the full contract deployment and update lifecycle on the Base Goerli testnet. - """ - print(f"\n--- Running Integration Test on {network} ---") - - # 1. Deploy the contract - initial_cid = "baguqehra7gqmpt6j4rfhekln7pel4log7p3qu6iike2qgj3ypl74g7aj4isa" - contract_address = registry.deploy_registry( - initial_cid=initial_cid, - mnemonic_path=mnemonic, - network=network - ) +# @pytest.mark.parametrize("network", ["base:sepolia"]) +# async def test_full_lifecycle_on_testnet(mnemonic, network): +# """ +# Tests the full contract deployment and hierarchical initialization on the testnet. +# """ +# print(f"\n--- Running Full Integration Test on {network} ---") + +# w3 = registry._get_web3_instance() +# assert w3.is_connected(), "Web3 is not connected. Check your RPC URL and network configuration." + +# # 1. Deploy the contract +# # The new contract doesn't require an initial CID at deployment. +# print("Step 1: Deploying StacSuperRegistry...") +# # contract_address = registry.deploy_registry(network=network) +# contract_address = "0xb54A652489b864638d02C508A2d5Bc14FbFA8df8" +# assert isinstance(contract_address, str) +# assert contract_address.startswith("0x") +# print(f"✅ Deployment successful: {contract_address}") + +# time.sleep(10) + + +# # --- 2. Initialize the full data path --- + +# # Define the data structure +# collection = "era5" +# dataset = "2m_temperature" +# dtype = "non_finalized" +# cid = "bafyr4ibj3bfl5oo7bf6gagzr2g33jlnf23mq2xo632mbl6ytfry7jbuepy" +# full_path = f"{collection}-{dataset}-{dtype}" + +# # Initialize the Collection +# # print(f"\nStep 2: Initializing Collection '{collection}'...") +# # tx_hash_collection = registry.init_collection(contract_address, collection) +# # assert tx_hash_collection.startswith("0x") +# # print(f"✅ Collection initialized. Tx: {tx_hash_collection}") + +# # w3.eth.wait_for_transaction_receipt(tx_hash_collection, timeout=120) # 120-second timeout + +# # time.sleep(10) + +# # Initialize the Dataset +# # print(f"\nStep 3: Initializing Dataset '{dataset}'...") +# # tx_hash_dataset = registry.init_dataset(contract_address, collection, dataset) +# # assert tx_hash_dataset.startswith("0x") +# # print(f"✅ Dataset initialized. Tx: {tx_hash_dataset}") + +# # w3.eth.wait_for_transaction_receipt(tx_hash_dataset, timeout=120) # 120-second timeout +# # time.sleep(10) + + +# # Initialize the Type and set its CID +# print(f"\nStep 4: Initializing Type '{dtype}' with its CID...") +# tx_hash_type = registry.init_type(contract_address, collection, dataset, dtype, cid) +# assert tx_hash_type.startswith("0x") +# print(f"✅ Type initialized. Tx: {tx_hash_type}") + +# w3.eth.wait_for_transaction_receipt(tx_hash_type, timeout=120) # 120-second timeout +# time.sleep(10) + +# # --- 3. Verify the data --- + +# print(f"\nStep 5: Verifying the stored CID for path '{full_path}'...") +# # Use the resolve function to get the CID for the full path +# retrieved_cid = registry.resolve_path(contract_address, full_path) + +# assert retrieved_cid == cid +# print(f"✅ Verification successful! Retrieved CID matches expected CID.") + +# # Optional: Verify getter functions +# print("\nStep 6: Verifying getter functions...") +# collections = registry.get_collections(contract_address) +# datasets = registry.get_datasets(contract_address, collection) +# types = registry.get_types(contract_address, collection, dataset) + +# assert collection in collections +# assert dataset in datasets +# assert dtype in types +# print("✅ Getter functions returned expected values.") + +# def test_update_path_cid(w3, contract_address): +# """ +# Tests that the CID for a given path can be successfully updated. +# """ +# # --- 1. Setup: Define test data and initialize the path --- +# collection = "era5" +# dataset = "2m_temperature" +# dtype = "finalized" +# initial_cid = "baguqehradygn3nlscqe4fnuszz6yhsnsgdtmxv5trose44iw44dfa73axbfq" +# updated_cid = "bafyr4icrox4pxashkfmbyztn7jhp6zjlpj3bufg5ggsjux74zr7ocnqdpu" +# path = f"{collection}-{dataset}-{dtype}" + +# # --- 2. Action: Update the CID --- +# print("\n--- Test Action: Updating CID ---") +# # Using category 1 for REPLACE +# tx_hash_update = registry.update_path_cid(contract_address, path, updated_cid, category=1) +# w3.eth.wait_for_transaction_receipt(tx_hash_update) +# print(f"✅ CID update transaction sent. Tx: {tx_hash_update}") + +# time.sleep(5) + +# # --- 3. Assertion: Verify the CID was updated --- +# print("\n--- Test Assertion: Verifying Update ---") +# resolved_updated_cid = registry.resolve_path(contract_address, path) - assert isinstance(contract_address, str) - assert contract_address.startswith("0x") - print(f"✅ Deployment successful: {contract_address}") - - # 2. Update the contract - new_cid = "bafybeihweq2w7v7nnsywn2vqn33y3o6q52rl7mvk2idxmozm2v2c6a2d5y" - tx_hash = registry.update_registry( - contract_address=contract_address, - new_cid=new_cid, - mnemonic_path=mnemonic, - ) +# assert resolved_updated_cid == updated_cid, "The resolved CID should match the updated CID" +# assert resolved_updated_cid != initial_cid, "The resolved CID should no longer be the initial CID" - assert isinstance(tx_hash, str) - assert tx_hash.startswith("0x") - print(f"✅ Update successful: {tx_hash}") - - -def create_dummy_dataset(): - """Helper to create a simple xarray.Dataset for mocking.""" - times = pd.date_range("2024-01-01", periods=10, freq="h") - lats = np.arange(40, 42, 1) - lons = np.arange(-80, -78, 1) - temp = np.random.rand(len(times), len(lats), len(lons)) - return xr.Dataset( - {"2m_temperature": (("time", "latitude", "longitude"), temp)}, - coords={"time": times, "latitude": lats, "longitude": lons}, - ) +# print(f"✅ CID for '{path}' successfully updated to: {resolved_updated_cid}") -async def test_era5_loader_with_mocks(): +async def test_era5_loader(): """ This test remains unchanged. It tests the Python logic of the data loader by mocking blockchain and IPFS calls. It is fast and does not depend on any network connection, which is a testing best practice. """ - all_datasets = await loader.get_all_collections() - print(f"All : {all_datasets}") - # era5_ds = await loader.get_geo_temporal_dataset( - # collection="era5", - # dataset="2m_temperature", - # options={'finalized_only': False} - # ) - # # Download data from latitude 0 and longitude 0 at 2023-01-01T00:00:00 - # data = era5_ds.sel( - # latitude=0, longitude=0, time="2023-01-01T00:00:00" - # ).load() # Load the data into memory - # print(f"Data at (0, 0) on 2023-01-01T00:00:00: {data['2m_temperature'].values}") + era5_ds = await loader.dclimate_dataset_loader( + collection="era5", + dataset="2m_temperature", + options={'finalized_only': False} + ) + data = era5_ds.sel( + latitude=0, longitude=0, time="2023-01-01T00:00:00" + ).load() # Load the data into memory + assert data['2m_temperature'].values == 300.3167419433594 \ No newline at end of file