From d89e95db55bfd1e5162e61c5074b0d7e9afbfa1d Mon Sep 17 00:00:00 2001 From: Lukas Bindreiter Date: Mon, 27 Jul 2026 10:38:13 +0200 Subject: [PATCH 1/4] Update ruff --- .pre-commit-config.yaml | 24 - README.md | 17 +- prek.toml | 45 ++ pyproject.toml | 1 + tilebox-datasets/README.md | 17 +- .../tilebox/datasets/aio/dataset.py | 4 +- tilebox-datasets/tilebox/datasets/service.py | 5 +- .../tilebox/datasets/sync/dataset.py | 4 +- tilebox-grpc/README.md | 5 +- tilebox-grpc/tests/aio/test_channel.py | 2 +- tilebox-storage/README.md | 4 +- tilebox-storage/tilebox/storage/aio.py | 2 +- tilebox-workflows/README.md | 5 +- .../tilebox/workflows/jobs/client.py | 2 +- .../tilebox/workflows/runner/executor.py | 2 +- .../tilebox/workflows/runner/task_runner.py | 2 +- uv.lock | 446 +++++++++--------- 17 files changed, 300 insertions(+), 287 deletions(-) delete mode 100644 .pre-commit-config.yaml create mode 100644 prek.toml diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml deleted file mode 100644 index d267c91..0000000 --- a/.pre-commit-config.yaml +++ /dev/null @@ -1,24 +0,0 @@ -repos: - - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v6.0.0 - hooks: - - id: check-yaml - - id: end-of-file-fixer - - repo: https://github.com/tsvikas/sync-with-uv - rev: v0.5.0 - hooks: - - id: sync-with-uv - - repo: https://github.com/charliermarsh/ruff-pre-commit - rev: v0.15.22 - hooks: - - id: ruff-check - args: [--fix, --exit-non-zero-on-fix] - - id: ruff-format - - repo: local - hooks: - - id: ty - name: ty-check - entry: uv run ty check - language: python - types: [python] - pass_filenames: true diff --git a/README.md b/README.md index 2869404..f811d21 100644 --- a/README.md +++ b/README.md @@ -65,15 +65,15 @@ sentinel2_msi = client.dataset("open_data.copernicus.sentinel2_msi") collections = sentinel2_msi.collections() print(collections) -area_of_interest = shape({ - "type": "Polygon", # coords in lon, lat - "coordinates": [[[-5, 50], [-5, 56], [-11, 56], [-11, 50], [-5, 50]]]} +area_of_interest = shape( + { + "type": "Polygon", # coords in lon, lat + "coordinates": [[[-5, 50], [-5, 56], [-11, 56], [-11, 50], [-5, 50]]], + } ) s2a_l1c = sentinel2_msi.collection("S2A_S2MSI1C") results = s2a_l1c.query( - temporal_extent=("2025-03-01", "2025-06-01"), - spatial_extent=area_of_interest, - show_progress=True + temporal_extent=("2025-03-01", "2025-06-01"), spatial_extent=area_of_interest, show_progress=True ) print(f"Found {results.sizes['time']} datapoints") # Found 979 datapoints ``` @@ -85,9 +85,10 @@ A parallel processing engine to simplify the creation of dynamic tasks that can ```python from tilebox.workflows import Client, Task + class MyFirstTask(Task): - def execute(self): - print("Hello World from my first Tilebox task!") + def execute(self): + print("Hello World from my first Tilebox task!") # create your API key at https://console.tilebox.com diff --git a/prek.toml b/prek.toml new file mode 100644 index 0000000..1d6a559 --- /dev/null +++ b/prek.toml @@ -0,0 +1,45 @@ +# Configuration file for `prek`, a git hook framework written in Rust. +# See https://prek.j178.dev for more information. +#:schema https://www.schemastore.org/prek.json + +[[repos]] +repo = "https://github.com/pre-commit/pre-commit-hooks" +rev = "v6.0.0" +hooks = [ + { id = "check-yaml" }, + { id = "end-of-file-fixer" } +] + +[[repos]] +repo = "https://github.com/tsvikas/sync-with-uv" +rev = "v0.6.0" +hooks = [ + { id = "sync-with-uv" } +] + +[[repos]] +repo = "https://github.com/charliermarsh/ruff-pre-commit" +rev = "v0.16.0" +hooks = [ + { + id = "ruff-check", + args = [ + "--fix", + "--exit-non-zero-on-fix" + ] + }, + { id = "ruff-format" } +] + +[[repos]] +repo = "local" +hooks = [ + { + id = "ty", + name = "ty-check", + entry = "uv run ty check", + language = "python", + types = ["python"], + pass_filenames = true + } +] diff --git a/pyproject.toml b/pyproject.toml index 140f83a..1068e66 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -94,6 +94,7 @@ ignore = [ "PLR2004", # magic-value-comparison: sometimes comparison with constants (e.g. 0) makes sense "TRY003", # raise-vanilla-args: exceptions like this make sense in python "TRY400", # error-instead-of-exception: logger.error is ok with loguru + "CPY001", # missing-copyright-notice: we don't add copyright notices to our source files # disabled because of formatter "E501", # line-too-long -> formatter takes care of this "ISC001", # single-line-implicit-str-concatenation -> formatter takes care of this diff --git a/tilebox-datasets/README.md b/tilebox-datasets/README.md index b4da531..78cb19d 100644 --- a/tilebox-datasets/README.md +++ b/tilebox-datasets/README.md @@ -65,10 +65,7 @@ Query data: ```python s2a_l1c = sentinel2_msi.collection("S2A_S2MSI1C") -results = s2a_l1c.query( - temporal_extent=("2025-03-01", "2025-06-01"), - show_progress=True -) +results = s2a_l1c.query(temporal_extent=("2025-03-01", "2025-06-01"), show_progress=True) print(f"Found {results.sizes['time']} datapoints") # Found 220542 datapoints ``` @@ -77,15 +74,15 @@ Spatio-temporal queries: ```python from shapely.geometry import shape -area_of_interest = shape({ - "type": "Polygon", # coords in lon, lat - "coordinates": [[[-5, 50], [-5, 56], [-11, 56], [-11, 50], [-5, 50]]]} +area_of_interest = shape( + { + "type": "Polygon", # coords in lon, lat + "coordinates": [[[-5, 50], [-5, 56], [-11, 56], [-11, 50], [-5, 50]]], + } ) s2a_l1c = sentinel2_msi.collection("S2A_S2MSI1C") results = s2a_l1c.query( - temporal_extent=("2025-03-01", "2025-06-01"), - spatial_extent=area_of_interest, - show_progress=True + temporal_extent=("2025-03-01", "2025-06-01"), spatial_extent=area_of_interest, show_progress=True ) print(f"Found {results.sizes['time']} datapoints") # Found 979 datapoints ``` diff --git a/tilebox-datasets/tilebox/datasets/aio/dataset.py b/tilebox-datasets/tilebox/datasets/aio/dataset.py index 9706c73..062ee42 100644 --- a/tilebox-datasets/tilebox/datasets/aio/dataset.py +++ b/tilebox-datasets/tilebox/datasets/aio/dataset.py @@ -598,7 +598,7 @@ async def delete(self, datapoints: DatapointIDs, *, show_progress: bool | Progre return num_deleted -async def _query_page( # noqa: PLR0913 +async def _query_page( # noqa: PLR0913, PLR0917 service: TileboxDatasetService, dataset_id: UUID, collection_ids: list[UUID] | None, @@ -610,7 +610,7 @@ async def _query_page( # noqa: PLR0913 return await service.query(dataset_id, collection_ids or [], filters, skip_data, query_page) -async def _iter_query_pages( # noqa: PLR0913 +async def _iter_query_pages( # noqa: PLR0913, PLR0917 service: TileboxDatasetService, dataset_id: UUID, collection_ids: list[UUID] | None, diff --git a/tilebox-datasets/tilebox/datasets/service.py b/tilebox-datasets/tilebox/datasets/service.py index fe79e4f..7773830 100644 --- a/tilebox-datasets/tilebox/datasets/service.py +++ b/tilebox-datasets/tilebox/datasets/service.py @@ -320,7 +320,7 @@ def _environment_info() -> str: annotation=FieldAnnotation( description="A universally unique identifier (UUID) that uniquely identifies each data point, automatically generated by Tilebox.", example_value="4e8a2836-72f8-4ac2-a9e9-cbe3492ef60c", - source_json_pointer="/properties/tilebox_id", + source_json_pointer="/properties/tilebox:id", ), ) @@ -334,8 +334,7 @@ def _environment_info() -> str: annotation=FieldAnnotation( description="The time the data point was ingested into the Tilebox API, automatically generated by Tilebox.", example_value="2022-10-17T14:35:28Z", - source_json_pointer="/properties/created", - json_schema_ref=("https://schemas.stacspec.org/v1.1.0/item-spec/json-schema/datetime.json#/properties/created"), + source_json_pointer="/properties/tilebox:ingestion_time", ), ) diff --git a/tilebox-datasets/tilebox/datasets/sync/dataset.py b/tilebox-datasets/tilebox/datasets/sync/dataset.py index 7f95fa6..85bf87f 100644 --- a/tilebox-datasets/tilebox/datasets/sync/dataset.py +++ b/tilebox-datasets/tilebox/datasets/sync/dataset.py @@ -590,7 +590,7 @@ def delete(self, datapoints: DatapointIDs, *, show_progress: bool | ProgressCall return num_deleted -def _query_page( # noqa: PLR0913 +def _query_page( # noqa: PLR0913, PLR0917 service: TileboxDatasetService, dataset_id: UUID, collection_ids: list[UUID] | None, @@ -602,7 +602,7 @@ def _query_page( # noqa: PLR0913 return service.query(dataset_id, collection_ids or [], filters, skip_data, query_page).get() -def _iter_query_pages( # noqa: PLR0913 +def _iter_query_pages( # noqa: PLR0913, PLR0917 service: TileboxDatasetService, dataset_id: UUID, collection_ids: list[UUID] | None, diff --git a/tilebox-grpc/README.md b/tilebox-grpc/README.md index dbe1331..c34c268 100644 --- a/tilebox-grpc/README.md +++ b/tilebox-grpc/README.md @@ -46,10 +46,7 @@ Open a gRPC channel: ```python from _tilebox.grpc.channel import open_channel -channel = open_channel( - "https://api.tilebox.com", - auth_token="YOUR_TILEBOX_API_KEY" -) +channel = open_channel("https://api.tilebox.com", auth_token="YOUR_TILEBOX_API_KEY") ``` ## License diff --git a/tilebox-grpc/tests/aio/test_channel.py b/tilebox-grpc/tests/aio/test_channel.py index 518374c..6a04259 100644 --- a/tilebox-grpc/tests/aio/test_channel.py +++ b/tilebox-grpc/tests/aio/test_channel.py @@ -7,7 +7,7 @@ @pytest.mark.asyncio @pytest.mark.parametrize("req_metadata", [None, [("some-other", "header")]]) -async def test_auth_interceptor(req_metadata: None | list[tuple[str, str]]) -> None: +async def test_auth_interceptor(req_metadata: list[tuple[str, str]] | None) -> None: """Test that the auth interceptor adds the auth token as metadata to every gRPC request""" interceptor = _AuthMetadataInterceptor("very-secret") diff --git a/tilebox-storage/README.md b/tilebox-storage/README.md index 2d2ec3a..fd689ea 100644 --- a/tilebox-storage/README.md +++ b/tilebox-storage/README.md @@ -73,9 +73,7 @@ from tilebox.storage import CopernicusStorageClient # https://documentation.dataspace.copernicus.eu/APIs/S3.html # to learn how to get your access key and secret access key storage_client = CopernicusStorageClient( - access_key="YOUR_ACCESS_KEY", - secret_access_key="YOUR_SECRET_ACCESS_KEY", - cache_directory=Path("./data") + access_key="YOUR_ACCESS_KEY", secret_access_key="YOUR_SECRET_ACCESS_KEY", cache_directory=Path("./data") ) downloaded_data = storage_client.download(s2_granule) diff --git a/tilebox-storage/tilebox/storage/aio.py b/tilebox-storage/tilebox/storage/aio.py index 229638b..c2c5741 100644 --- a/tilebox-storage/tilebox/storage/aio.py +++ b/tilebox-storage/tilebox/storage/aio.py @@ -322,7 +322,7 @@ async def list_object_paths(store: ObjectStore, prefix: str) -> list[str]: return sorted(str(ObjectPath(obj["path"]).relative_to(prefix_path)) for obj in objects) -async def download_objects( # noqa: PLR0913 +async def download_objects( # noqa: PLR0913, PLR0917 store: ObjectStore, prefix: str, objects: list[str], diff --git a/tilebox-workflows/README.md b/tilebox-workflows/README.md index 8687b8d..7f2e491 100644 --- a/tilebox-workflows/README.md +++ b/tilebox-workflows/README.md @@ -46,9 +46,10 @@ Create a task: ```python from tilebox.workflows import Task + class MyFirstTask(Task): - def execute(self): - print("Hello World from my first Tilebox task!") + def execute(self): + print("Hello World from my first Tilebox task!") ``` Submit a job diff --git a/tilebox-workflows/tilebox/workflows/jobs/client.py b/tilebox-workflows/tilebox/workflows/jobs/client.py index 3d0c02b..3d675a6 100644 --- a/tilebox-workflows/tilebox/workflows/jobs/client.py +++ b/tilebox-workflows/tilebox/workflows/jobs/client.py @@ -209,7 +209,7 @@ def visualize(self, job: JobIDLike, direction: str = "down", layout: str = "dagr """ return self._service.visualize(_to_uuid(job), direction, layout, sketchy) - def query( # noqa: PLR0913 + def query( # noqa: PLR0913, PLR0917 self, temporal_extent: "TimeIntervalLike | IDIntervalLike", automation_ids: UUID | list[UUID] | None = None, diff --git a/tilebox-workflows/tilebox/workflows/runner/executor.py b/tilebox-workflows/tilebox/workflows/runner/executor.py index 163f74d..72cab8b 100644 --- a/tilebox-workflows/tilebox/workflows/runner/executor.py +++ b/tilebox-workflows/tilebox/workflows/runner/executor.py @@ -33,7 +33,7 @@ class TaskExecutor: - def __init__( # noqa: PLR0913 + def __init__( # noqa: PLR0913, PLR0917 self, runner: Runner, cache: JobCache, diff --git a/tilebox-workflows/tilebox/workflows/runner/task_runner.py b/tilebox-workflows/tilebox/workflows/runner/task_runner.py index 1bcfb9d..6f5894c 100644 --- a/tilebox-workflows/tilebox/workflows/runner/task_runner.py +++ b/tilebox-workflows/tilebox/workflows/runner/task_runner.py @@ -403,7 +403,7 @@ def _active(self, task: Task) -> Iterator[None]: class TaskRunner: - def __init__( # noqa: PLR0913 + def __init__( # noqa: PLR0913, PLR0917 self, service: TaskService, cluster: str, diff --git a/uv.lock b/uv.lock index 9a066ee..ea5d705 100644 --- a/uv.lock +++ b/uv.lock @@ -101,30 +101,30 @@ wheels = [ [[package]] name = "boto3" -version = "1.43.51" +version = "1.43.56" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "botocore" }, { name = "jmespath" }, { name = "s3transfer" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a2/4f/b93620c09e81b8dd28558e73862d7b61f938c5a9db9366fab4b0bb53512f/boto3-1.43.51.tar.gz", hash = "sha256:b5a416cc703db73b69b22bef563c89c1fb14a4b10a93628d3c7abc4dd1aaf979", size = 112649, upload-time = "2026-07-17T19:33:23.358Z" } +sdist = { url = "https://files.pythonhosted.org/packages/53/05/23e1aa8c9e4b0399a61e7fd65c4f9cc0625121f24760e37471f776404abb/boto3-1.43.56.tar.gz", hash = "sha256:57c90df9fb026f2e6ae22530861198130203733c5c9ec4e5cca3a4037f5a8db4", size = 112673, upload-time = "2026-07-24T19:31:48.606Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/85/7e/86204db9235f08e051a83e039ec5af209211e3ac51ed3644c4350c150b72/boto3-1.43.51-py3-none-any.whl", hash = "sha256:a97057bb609fd38c80448db6a93db770786775dabd651401debf09816d4553d7", size = 140030, upload-time = "2026-07-17T19:33:21.381Z" }, + { url = "https://files.pythonhosted.org/packages/b8/57/3a960c9f581c00f2a591901b46e035ff79ab3956d16607f12306b3b8d483/boto3-1.43.56-py3-none-any.whl", hash = "sha256:feb699d4ab241ef5c1b80bb58277be2aaad365cd4b672d7817e0bc59ee45131b", size = 140026, upload-time = "2026-07-24T19:31:47.155Z" }, ] [[package]] name = "boto3-stubs" -version = "1.43.51" +version = "1.43.56" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "botocore-stubs" }, { name = "types-s3transfer" }, { name = "typing-extensions", marker = "python_full_version < '3.12'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2e/cb/052936e4efb898ba165333864bfa0c1f99b4431b48562f8b07863d53a64e/boto3_stubs-1.43.51.tar.gz", hash = "sha256:56177d149f918ef2caf38176b61e286400448152c70f08a50616c596e8a851db", size = 103200, upload-time = "2026-07-17T20:29:43.733Z" } +sdist = { url = "https://files.pythonhosted.org/packages/fb/bc/1f2beda73136f950fc8b5596633803a82b482d793a4a85bdde926bc818b9/boto3_stubs-1.43.56.tar.gz", hash = "sha256:fe3d8049eb74dd7756dadd42f5a73b0f85ec3ed15f75b6e583f893f4563a0d2f", size = 103223, upload-time = "2026-07-24T20:10:25.771Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/19/8e/f2266a4a4a480883500e4d31badc621524125a1124b8d7595a3b4d83227a/boto3_stubs-1.43.51-py3-none-any.whl", hash = "sha256:dc01e52a142c023f59cad0a68cc5f149c5c64b2d9ca6af1b0cbb7e08f405de18", size = 70961, upload-time = "2026-07-17T20:29:40.693Z" }, + { url = "https://files.pythonhosted.org/packages/a0/62/2ba9e5d2378be5b3e1cfc8b444ee9a1b5d826493feb15df15cf47eae5c69/boto3_stubs-1.43.56-py3-none-any.whl", hash = "sha256:8d3529082b3330e8718986a6a05cc0cd8319f8fddd10104eb7624e1c94b20fd2", size = 70962, upload-time = "2026-07-24T20:10:21.149Z" }, ] [package.optional-dependencies] @@ -140,16 +140,16 @@ essential = [ [[package]] name = "botocore" -version = "1.43.51" +version = "1.43.56" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "jmespath" }, { name = "python-dateutil" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e6/f5/5a6b4fe037ba689fd4a9eaf31af8b681e4c1eb82debc470d18dec99eac62/botocore-1.43.51.tar.gz", hash = "sha256:e0e5e88585fdb01dcb6b533ac2dd3f18d5d45092a14ccbfd330e3576a4152128", size = 15713861, upload-time = "2026-07-17T19:33:12.101Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b9/cc/7f84a5d3071fe878380e9f610ab36ca87b8cbbc4aa81ba2727f90e1f3ea3/botocore-1.43.56.tar.gz", hash = "sha256:6c01f85f0ff9863076f4c761e74ee3aa96c5ccc1ad09fc1efd62ef8f2d22bf57", size = 15733117, upload-time = "2026-07-24T19:31:38.125Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/38/ca/9080b2f261ad9209e4d790b4282951df46274f786edb5c416a27fb18f4c6/botocore-1.43.51-py3-none-any.whl", hash = "sha256:7c2c538c932bddc95834e177ce6f91dcc388c6a7934b4f8d0db13caa30e3e543", size = 15399252, upload-time = "2026-07-17T19:33:08.808Z" }, + { url = "https://files.pythonhosted.org/packages/5c/cd/86fe9e659e9699f62f8dd5ecd8c6725474334b23cab8aa71d82b5f56f1a4/botocore-1.43.56-py3-none-any.whl", hash = "sha256:aafc741f1b10f6fd63253eaf6ea029680c1ff436d87e1b8969d62aefa0c76976", size = 15418773, upload-time = "2026-07-24T19:31:34.758Z" }, ] [[package]] @@ -223,11 +223,11 @@ wheels = [ [[package]] name = "certifi" -version = "2026.6.17" +version = "2026.7.22" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c9/c7/424b75da314c1045981bd9777432fad05a9e0c69daa4ed7e308bbaffe405/certifi-2026.6.17.tar.gz", hash = "sha256:024c88eeec92ca068db80f02b8b07c9cef7b9fe261d1d535abfd5abd6f6af432", size = 134594, upload-time = "2026-06-17T10:31:07.894Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a3/c2/24167ea9858356b47a87a50d39908bfdb72ceeefe0041586e704e5376b3a/certifi-2026.7.22.tar.gz", hash = "sha256:741e2c3b351ddf169a738da9f2c048608ff7f2c5cc02f1ebc6b118bb090d5d55", size = 138112, upload-time = "2026-07-22T03:35:12.644Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ef/2f/c5464532e965badff2f4c4c1a3a83f5697f0d7c407ed0cda44aaa99bb451/certifi-2026.6.17-py3-none-any.whl", hash = "sha256:2227dcbaafe0d2f59279d1762ddddc37783ed4354594f194ffc31d20f41fc3db", size = 133289, upload-time = "2026-06-17T10:31:06.348Z" }, + { url = "https://files.pythonhosted.org/packages/0b/a7/71ac2cff56fec219ed242bb11b8efb69fcc4bec75db06fb7bfe35de520e6/certifi-2026.7.22-py3-none-any.whl", hash = "sha256:62f22742b58a1a33014a2b6b706588a8d7e2a88ae7bd1a6ebe8c992928483775", size = 136983, upload-time = "2026-07-22T03:35:11.276Z" }, ] [[package]] @@ -673,40 +673,40 @@ wheels = [ [[package]] name = "cython" -version = "3.2.8" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b6/6b/80101e02ebacaf9232ecf32bf6a788d36b27d820ee02434746252569ef98/cython-3.2.8.tar.gz", hash = "sha256:f4f23a56b25221a06f91817fe8f3114ab8b48a4fac73187dbb64bc2c4a87961f", size = 3290300, upload-time = "2026-06-30T07:41:57.874Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a3/49/36c8c805aa9c039292102d5e56e92d23cd600e241f4e49976b89fe79f9ed/cython-3.2.8-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:51a1d2df1545c480f377f1bb2aea514e28956c87908f351bedc3772b3737c598", size = 2999561, upload-time = "2026-06-30T07:42:08.211Z" }, - { url = "https://files.pythonhosted.org/packages/a4/c5/278be136629062d7ca4fe84309b6078aee1b74c3d3eee1fc73f94d1e217b/cython-3.2.8-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4f1bbec7a7bd2c3836314e84f68e3f9a4c20e11cd164a01990a9ca34f595d5dd", size = 3302878, upload-time = "2026-06-30T07:42:09.932Z" }, - { url = "https://files.pythonhosted.org/packages/ab/4b/ce156ca3790a10cdf05380ad1b2a55eb1961745b403dafd959f4ba3488cd/cython-3.2.8-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:eb25b2afedc6a3585c5ae50db79ba0c3d6277ee8e1d6ce0223528db2c4981d9b", size = 3455745, upload-time = "2026-06-30T07:42:12.012Z" }, - { url = "https://files.pythonhosted.org/packages/e5/c5/e57dc4188cc89b1914b456bc7c41488d9a2c5c787c2e32e2ba9d62cf362e/cython-3.2.8-cp310-cp310-win_amd64.whl", hash = "sha256:ad9a80e5dda71cf30b9c9577902f0c080aa14a215d7c178d669cda21cf5ab801", size = 2784066, upload-time = "2026-06-30T07:42:13.759Z" }, - { url = "https://files.pythonhosted.org/packages/25/44/379c4f16d6b9e920ca8d00dc3bb96ea3f4fb8b73ed8b99c136a7667e2605/cython-3.2.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a7761e1a97081b707a9ea4fb7c8ecdeddcc6b5ae00dda0f0b7bbf933c1b599f9", size = 2977552, upload-time = "2026-06-30T07:42:15.673Z" }, - { url = "https://files.pythonhosted.org/packages/b6/9e/957e489fd7da81771407907502a78efa5989473fbc5ca25ec099adac8754/cython-3.2.8-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:84def174fbf8886fb026e04716908276c074d8c231e66db803abd04afcdcb998", size = 3304081, upload-time = "2026-06-30T07:42:17.463Z" }, - { url = "https://files.pythonhosted.org/packages/1d/f2/03c283e0baf610a29b148ac3e9e85a8d15c31703cb492f9f41b634604096/cython-3.2.8-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a134a19bba6ce7b1dd21dedc5cb2d4ea39a4177e42e535e333786eb9d0ba61a7", size = 3457242, upload-time = "2026-06-30T07:42:19.406Z" }, - { url = "https://files.pythonhosted.org/packages/72/ce/1c18ba78abb311d2042a5510b0ac5d47f1a196a2af0cd184d0b1a8907a8a/cython-3.2.8-cp311-cp311-win_amd64.whl", hash = "sha256:6c9bf89ac1d43a9e1c3bfee61e7b4dd8d1ef7a610001aa7779f0cb824bad8ae7", size = 2787555, upload-time = "2026-06-30T07:42:21.518Z" }, - { url = "https://files.pythonhosted.org/packages/ed/c4/47e0bcfc15b36b1c5cbde5235c60bf88df552ab216ddb836d7f816386ae6/cython-3.2.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f2547a31fbd3b1610a8859a16edee2a141f7781691cb98a2c6fd54870c5f7541", size = 2995546, upload-time = "2026-06-30T07:42:23.618Z" }, - { url = "https://files.pythonhosted.org/packages/4a/f4/bc5830abeb57a7c7498cd9a0f2df953fd9fc7f33e3f5352c9824802b83bb/cython-3.2.8-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ab1fe11ebd61e497a848622cdd157a4324ac06e7935b1219715408844e15dd13", size = 3179546, upload-time = "2026-06-30T07:42:25.566Z" }, - { url = "https://files.pythonhosted.org/packages/89/38/a70e879ea52debac11d2810e066a5a2cb16e71229edae303f024506bc142/cython-3.2.8-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f3bce1f079734753649f8a3d3a95832297207feb120d0ef4fd5db4c813cc6c04", size = 3351714, upload-time = "2026-06-30T07:42:27.513Z" }, - { url = "https://files.pythonhosted.org/packages/45/f1/f071c5e7050a7924ffad9822558c74d489afe4764f7486cb68555a509219/cython-3.2.8-cp312-cp312-win_amd64.whl", hash = "sha256:8297efe129e6421c34ddbeb09ed5627ef6c0fc4868bf7f9bdf6c147f595ccaed", size = 2774196, upload-time = "2026-06-30T07:42:29.47Z" }, - { url = "https://files.pythonhosted.org/packages/dc/31/9487462239ccd47feb70fc023b2f416cee6cf30fe79d15f6a1eda59ea107/cython-3.2.8-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0d78205f525287de94effa2f2fab6b9edafdab44ef8c58ecf52fcb1013225d68", size = 2984097, upload-time = "2026-06-30T07:42:31.335Z" }, - { url = "https://files.pythonhosted.org/packages/68/68/4305333cd2a27fcf4769c79941772f067007671e90caeb5e7af33db45387/cython-3.2.8-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:474d9c5378032c0237b70c7af4d2122eb00719fec2cab296ddc9cf2907d55d58", size = 3181866, upload-time = "2026-06-30T07:42:33.331Z" }, - { url = "https://files.pythonhosted.org/packages/ff/3f/ce32b14ee64c5f5923fcd207a82b86c2531fae1dc3a964a32cfd7bc72ecf/cython-3.2.8-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e2221be06b6aa92486d7c3644c18cb3643b3e794600f0d6c8555a523e959a069", size = 3355829, upload-time = "2026-06-30T07:42:35.394Z" }, - { url = "https://files.pythonhosted.org/packages/9c/a5/2510e88149abb9966f35b0e775c2549a0a71b39550457f9de6f1b239869e/cython-3.2.8-cp313-cp313-win_amd64.whl", hash = "sha256:0961ba91f59492d1bef974cc6b45993c743162776188bcb79e029b442d5d4fcc", size = 2770884, upload-time = "2026-06-30T07:42:37.315Z" }, - { url = "https://files.pythonhosted.org/packages/ab/46/b491e2eebf00864421fe7b4c2c7d3c2fdd12d16ef8b76c42abd4e571d86b/cython-3.2.8-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1c7f4143d0f9fb7b26444e00ebf7c8845f585d56f37b73bd3fb3dac269af8732", size = 3012195, upload-time = "2026-06-30T07:42:39.299Z" }, - { url = "https://files.pythonhosted.org/packages/60/c7/c98115431c92007606efed4220a69ff02ae5a3cbbfc82605ffb6eb85a56b/cython-3.2.8-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6f788a2fec204bdd9291d5d542a4856db101dba59f8d83789d3f1eb2895971a0", size = 3227582, upload-time = "2026-06-30T07:42:41.252Z" }, - { url = "https://files.pythonhosted.org/packages/a6/3c/e8675aeb18bf9029d861473fe49f9d612bd738faa3942928c8cba7a40239/cython-3.2.8-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1c3a33c2ff250d0277d0ee8b080ba0a0391a00e48af4a96fb09dbebbd1ac4b0e", size = 3368421, upload-time = "2026-06-30T07:42:43.198Z" }, - { url = "https://files.pythonhosted.org/packages/27/f7/4edbf4ca4832e2c5ba5f8a09ab91f347a591d9b00dbf8eec3edce95c7c3f/cython-3.2.8-cp314-cp314-win_amd64.whl", hash = "sha256:89b0fdc2ca0b502afedc4dd4ddbc4f9cb5a135245afacf9483e556e8ad3ada3b", size = 2807626, upload-time = "2026-06-30T07:42:45.2Z" }, - { url = "https://files.pythonhosted.org/packages/92/a2/0f2eaa5076bcaef52567471a54ce02ffd70007bf8688cd054f7aab9bc3b8/cython-3.2.8-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:127bc4039be48c6eebe7f1d68c33d23eb3a0c5ae95e1d730bdd048837751438b", size = 2892527, upload-time = "2026-06-30T07:42:55.45Z" }, - { url = "https://files.pythonhosted.org/packages/a4/08/b5488aef44662e48ac09b42d4cb398207f591c770797036fb1d6fbeb7a52/cython-3.2.8-cp39-abi3-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:e480ae9f195cd29e5ce334e3d434c83dbac0783c0cc88f2407e31ba997724192", size = 3220335, upload-time = "2026-06-30T07:42:57.403Z" }, - { url = "https://files.pythonhosted.org/packages/7c/96/d04a3621045e9fe9c7c5e406a688ee3d6e04a65f545ea7c622ead4b4afd8/cython-3.2.8-cp39-abi3-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3142407b9d63f233c766e17000e2aac782411bf0409b9adc97cb7c320aecd199", size = 2876481, upload-time = "2026-06-30T07:42:59.406Z" }, - { url = "https://files.pythonhosted.org/packages/71/9a/daa259b638c5eabb8a8c36f203b85b01b5362101ff8fc4ec6ad592d34bb3/cython-3.2.8-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:41c118fd91d320cd72af26e29232ff3f1a0a170c47d477c9a176d766067a4718", size = 2999974, upload-time = "2026-06-30T07:43:01.29Z" }, - { url = "https://files.pythonhosted.org/packages/21/de/71cc5b4be5ee4d34c3302b6e7272189106a4072e9890d284d05239d2b645/cython-3.2.8-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:6335c6e8737a39734e20d25f89f425bf3274c104fc7efc05aacc7ed1a4858c9d", size = 2897932, upload-time = "2026-06-30T07:43:03.606Z" }, - { url = "https://files.pythonhosted.org/packages/5d/0c/da68b9d3056e90b2060970b50d575cd7fcd1c778e8f23cc467f346e1d471/cython-3.2.8-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:1034facc082cb882e1e5beb61e136ae8e282df2eafa11e9771e9b0a15c860801", size = 3235980, upload-time = "2026-06-30T07:43:05.486Z" }, - { url = "https://files.pythonhosted.org/packages/36/0b/d88bc50e66fd1f1160dd2677d9af18273a2fb2f102c086d21f64a5a9c78b/cython-3.2.8-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:8896ff6b133f346ebcf22aa23706c4031e8d9d5ae184433dfc67dc1053318b69", size = 3118504, upload-time = "2026-06-30T07:43:07.485Z" }, - { url = "https://files.pythonhosted.org/packages/db/de/511c4364808b3b4036d051a0301b0b142a3ddf8a319bfdbbd474fcfdc879/cython-3.2.8-cp39-abi3-win32.whl", hash = "sha256:3fd6464433d925cba66ae31bf5780c8a469a06da1d109180cffb39ee3c88ae20", size = 2435866, upload-time = "2026-06-30T07:43:09.367Z" }, - { url = "https://files.pythonhosted.org/packages/18/4f/911b2b2a0a02be15829ccbf0c906029a318efbf53d9f8e021e438261c206/cython-3.2.8-cp39-abi3-win_arm64.whl", hash = "sha256:4e9447d9b652396a285cdfbd4f9f0721842c63c6df281720e87dcc4b9ea65af5", size = 2457829, upload-time = "2026-06-30T07:43:11.645Z" }, - { url = "https://files.pythonhosted.org/packages/c4/19/31aa63ab719b2e1eea5f200a8a54e2591dc1966a6b75e3de30ef8be9bc2c/cython-3.2.8-py3-none-any.whl", hash = "sha256:f635e113677666de13a2ec2979e9b1d5b90617cdfd1a691d3559be81e2dd6cb9", size = 1258688, upload-time = "2026-06-30T07:41:55.624Z" }, +version = "3.2.9" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/de/db48b8870e766cfea809986cc50c1e986c663a9ab7bafd0ac1a2512c4a26/cython-3.2.9.tar.gz", hash = "sha256:d249c9022ab13286b17bd66f30609e800c5f95efeecb06168990c7a66cecde6c", size = 3293493, upload-time = "2026-07-24T06:21:21.867Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/27/29/bc7e088201af74eacbb5799542d0f09d6e5e139cc55f19fd54c409f58109/cython-3.2.9-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2751b7c0cb13135aadcc1cc8fe223be98d458cd1132611d31675e768a5e4a2e9", size = 3000461, upload-time = "2026-07-24T06:21:37.078Z" }, + { url = "https://files.pythonhosted.org/packages/81/e8/ac8266fa88b3a80009a2fa55b2729be1518c05d182888223f95b8e2a2b8e/cython-3.2.9-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ccb0bc6b8437cfcb04459a02bc5ecc58bf161ea11659438945ab5a1392ee39fe", size = 3306728, upload-time = "2026-07-24T06:21:39.011Z" }, + { url = "https://files.pythonhosted.org/packages/df/9b/b4ff8cdff357ba0f41791e044a2a95898d0768b20e9247823decf5eabb5e/cython-3.2.9-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1c2974742433be5c36ae1df1e761ce0063753fc2702316d5a3062b0a4e94a1df", size = 3458732, upload-time = "2026-07-24T06:21:41.034Z" }, + { url = "https://files.pythonhosted.org/packages/f3/eb/06ba035f8b845d2d7d67f533a58ebd885ceab1e48b8cc442f093ecf59201/cython-3.2.9-cp310-cp310-win_amd64.whl", hash = "sha256:a3fc783d12202d1b064b6f125772d85f00e36e62eee6b2e415f56d8fd2d2e7b2", size = 2785625, upload-time = "2026-07-24T06:21:42.969Z" }, + { url = "https://files.pythonhosted.org/packages/fc/97/2f477d30fc6ca0ff333552233aa6dee0e57323a55913f84f24da9cac26fc/cython-3.2.9-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e8ec2d5a7b798c84d6779e7ca5318fd928b8fe9dd021106d714dc2e77cdc7555", size = 2992696, upload-time = "2026-07-24T06:21:44.931Z" }, + { url = "https://files.pythonhosted.org/packages/0a/bf/0f280f7960c129957d2ce650e7fd7dec8e706c26d61a428e93927f4c9904/cython-3.2.9-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cac792b0bde1c86d8f832e513cd061b7e682181cc5a6d843d487e6c8ae9d5fcb", size = 3307656, upload-time = "2026-07-24T06:21:46.649Z" }, + { url = "https://files.pythonhosted.org/packages/ac/cd/6d7c35a1065f1a07c9fa5ec784ee562f32f36a8fd05748e5f10694887eb7/cython-3.2.9-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8c843dd85857cd0d0da055489f448d032866120cfb094ce16205a1ff54d06353", size = 3459259, upload-time = "2026-07-24T06:21:48.881Z" }, + { url = "https://files.pythonhosted.org/packages/9f/f6/c1ad54ec35fcd5c5a5808d8b5b6319d5ae96acc63f7b5c1f7812f8cc3f71/cython-3.2.9-cp311-cp311-win_amd64.whl", hash = "sha256:efd54fe07f808e7e82f6a04f370457ca02e770c948f0e2f83345ccc7ec8bb829", size = 2789027, upload-time = "2026-07-24T06:21:50.551Z" }, + { url = "https://files.pythonhosted.org/packages/fd/37/c74d842306c8fe381c415b37460d5e3086a820fac72b8ff5cb48513ccfcd/cython-3.2.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:114b2dee0fa1daa48a59574d848da0ff1b6bdb725a755e9b92fad14962e1ff8d", size = 3009571, upload-time = "2026-07-24T06:21:52.534Z" }, + { url = "https://files.pythonhosted.org/packages/15/c0/f7c42b161edd585e3ae556fd62a2c72cd80a6ed527a9907f0c5c6fb060de/cython-3.2.9-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a5cd9c5f138cb052130b40ad3b6976d2180c35348410995812678f4636bd8f94", size = 3183562, upload-time = "2026-07-24T06:21:54.62Z" }, + { url = "https://files.pythonhosted.org/packages/56/1b/c04520ac7f3157aa12a69b632c16261170dac9fab6c48608cc004b8f1b17/cython-3.2.9-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:23e80bc885c599e72072e18d0746df82d394b73100c1e153cda7359e6e59fe09", size = 3354811, upload-time = "2026-07-24T06:21:56.72Z" }, + { url = "https://files.pythonhosted.org/packages/0b/b5/4ce33235a25b19fcd51dc639f0f403b783a3b7f9b1934eade0d993fbe029/cython-3.2.9-cp312-cp312-win_amd64.whl", hash = "sha256:4b1fd5a9c03f72a18618668a8e90d569442ed742f910e3ad003dcc9348e9598b", size = 2778077, upload-time = "2026-07-24T06:21:58.7Z" }, + { url = "https://files.pythonhosted.org/packages/83/fa/7ffbaab88558e28411715ad35d913cac7d64ee965344c185325da84c4309/cython-3.2.9-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:44b7fc417933d65bf31bce00337ff318efa3ea59daed21d10fe842d41e657c08", size = 2998816, upload-time = "2026-07-24T06:22:00.822Z" }, + { url = "https://files.pythonhosted.org/packages/04/69/feed68904f389452494d9e0861c24228c0bda656f9fbd32f9e5b525b2ef4/cython-3.2.9-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:41637c644d7224d2d3170ee312077c2173693f0d4a0da1c82a0cffa3680a42dc", size = 3185802, upload-time = "2026-07-24T06:22:02.831Z" }, + { url = "https://files.pythonhosted.org/packages/f0/0d/a7f094343e5c4925efac60e6dbdcf8602042e7f54c83ae14ccbc8033d2d9/cython-3.2.9-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f815cd3387bd88ca9eeabc357ce4bdf884eef0ab2949db0fbd5e0b69fe5ee422", size = 3360405, upload-time = "2026-07-24T06:22:04.645Z" }, + { url = "https://files.pythonhosted.org/packages/3f/ba/a5c25226e29cb4f3a2c97d2b29de10e37c8a6b67b3c2f2a8b16ba713b218/cython-3.2.9-cp313-cp313-win_amd64.whl", hash = "sha256:ddb43637b7749b88644df4319e1c36f767e7fb71a92bb9942558c8de2f1cc5f4", size = 2774714, upload-time = "2026-07-24T06:22:06.953Z" }, + { url = "https://files.pythonhosted.org/packages/3c/69/e5969fa87c7b8833f62fe790e1617a1391112fa55fe12079c41a4553119c/cython-3.2.9-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:63a42e131112072f912b66734088abc946c6bc42cb7454461a6dfdd2d09d3bae", size = 3013040, upload-time = "2026-07-24T06:22:08.9Z" }, + { url = "https://files.pythonhosted.org/packages/b4/98/8eaceeec00d4f9cc75e3106397c005952e0d710332c4966215b8a110a522/cython-3.2.9-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f25b402a6f1eed34af2af57603a9aadcf594dacaced1655e925eb317fba3f337", size = 3231531, upload-time = "2026-07-24T06:22:11.028Z" }, + { url = "https://files.pythonhosted.org/packages/94/a4/1e03a0c115afa9de7b06891721d6489d6a05f78934a6897a0f5a409ee2f5/cython-3.2.9-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d3cba9c4770cf76370fead8d2ae71bc474f3ef67ae4119e455d025726bcdc1c", size = 3372818, upload-time = "2026-07-24T06:22:12.97Z" }, + { url = "https://files.pythonhosted.org/packages/1b/04/b5c4d76723824d84e3761718c51964f7829335ee1c7c16b69f11b111adf0/cython-3.2.9-cp314-cp314-win_amd64.whl", hash = "sha256:56d95c0674c25f281c6ae8f1d17bd425d6c2818bb304ff781831bb5d00d04b0b", size = 2812223, upload-time = "2026-07-24T06:22:14.9Z" }, + { url = "https://files.pythonhosted.org/packages/0a/4a/342312c5fe021c8e0c386e1915d138e0902c48ae179b0374ab04773a8831/cython-3.2.9-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:944dc8747f640b3527649c566a5fc75ee0c15e80642ea2fdae4fe6378e1a9d4a", size = 2899729, upload-time = "2026-07-24T06:22:24.877Z" }, + { url = "https://files.pythonhosted.org/packages/f0/62/ea919ee426cb4d435ec8155e1ee6bcbb46b20d8f070527191b59769d4e7f/cython-3.2.9-cp39-abi3-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:4b871ad97dd7fb1cbf56f6238c54423febd310afc1d9d9bc70c69c89b7ce57fc", size = 3226650, upload-time = "2026-07-24T06:22:26.947Z" }, + { url = "https://files.pythonhosted.org/packages/18/02/057b4f63e2ced8c3cf217c4e9fb544bfe48145f493347c7ca3f51607526c/cython-3.2.9-cp39-abi3-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e9b6ebc6c74b4318eaa4e51e520dc8b95ebc7b262953c3ecb24131104681f14e", size = 2881919, upload-time = "2026-07-24T06:22:29.318Z" }, + { url = "https://files.pythonhosted.org/packages/64/e4/e158793ee3de7e4417ba17e7ff1015d6e2cf557cb485ad270b2446c9d1c7/cython-3.2.9-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:92989da161a7d18a7ad4baebc49289b2b77556d5a94916f90140ba26aecf6892", size = 3004702, upload-time = "2026-07-24T06:22:31.535Z" }, + { url = "https://files.pythonhosted.org/packages/f9/c0/d5bbbd743ab4feddb24a7e823b34c3ec4ebab91ff503d16743a4e7ce106b/cython-3.2.9-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:e75ec625d8f8781ced690b7a2f5c2d138067711cf24bb8fb68c872c30c2fefe5", size = 2902695, upload-time = "2026-07-24T06:22:33.525Z" }, + { url = "https://files.pythonhosted.org/packages/d6/2f/80850817395985259f135baa510d9186d3a325df81cd1862060bba977029/cython-3.2.9-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:2b1756ddc3bc0cd4341a515fc420c3e25e13c249f5537159b3fb0bff8d19e55c", size = 3241554, upload-time = "2026-07-24T06:22:35.667Z" }, + { url = "https://files.pythonhosted.org/packages/4c/ce/6be776814f6cb81751f3da737ed537385738148e1ea99f89fb4637799198/cython-3.2.9-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:7d41baea51ea00f9237f75af498577827493bca5e9b45bbd4e351543727e589a", size = 3124337, upload-time = "2026-07-24T06:22:37.868Z" }, + { url = "https://files.pythonhosted.org/packages/e2/48/27c948cbbfe6050994e67a497ae530955dcda7e79084319321c49969e0fd/cython-3.2.9-cp39-abi3-win32.whl", hash = "sha256:61d4abbf84f77c8d19361d05d9f51d65d8d95e74f736eae55fa1aed8a1430469", size = 2435609, upload-time = "2026-07-24T06:22:40.005Z" }, + { url = "https://files.pythonhosted.org/packages/17/ef/cf0e1bd7542296f1752be63b027f90271448d8c8062eac66d8e44a79b883/cython-3.2.9-cp39-abi3-win_arm64.whl", hash = "sha256:57a6a78d14f7dd7d6062d9bca694e2a8c1c14113b6ceceea076abcd1161fdc5a", size = 2458025, upload-time = "2026-07-24T06:22:41.973Z" }, + { url = "https://files.pythonhosted.org/packages/00/ec/e61deec9bcfbb0e1b36f8b5ba75cb44644419b4bfd0fdd666bffd21d9579/cython-3.2.9-py3-none-any.whl", hash = "sha256:a2b0e87f6b80790c929308ca0831d686f7a180feab684fe8cd4a4380bd96aaca", size = 1259272, upload-time = "2026-07-24T06:21:18.95Z" }, ] [[package]] @@ -759,7 +759,7 @@ wheels = [ [[package]] name = "google-api-core" -version = "2.32.0" +version = "2.33.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "google-auth" }, @@ -768,22 +768,22 @@ dependencies = [ { name = "protobuf" }, { name = "requests" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/03/33/00277be1305fd68355d08197f05e22db259c0cff49a10c8590a1869ade9b/google_api_core-2.32.0.tar.gz", hash = "sha256:2b33aad226b19272458c46abfe5c5a38d9531ece0c44502129a1463ce83674ac", size = 177659, upload-time = "2026-07-16T20:36:07.717Z" } +sdist = { url = "https://files.pythonhosted.org/packages/87/62/8fb1fb647d2788c950d69d6a769cd9d55c918ac1fc57be2f90b7e4029787/google_api_core-2.33.0.tar.gz", hash = "sha256:3a36bcc3e319783f4c97da41f6f45ea6ffcaa55848e341de16e09cb70243c2bb", size = 181607, upload-time = "2026-07-22T16:28:28.027Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/81/44/5018c5ac1526c98169db98d87a6ff7d5508f5246621c3ee1a046fdd5e0a6/google_api_core-2.32.0-py3-none-any.whl", hash = "sha256:ae1f0d58a6c8869350bf469f8eb3092e7f8c494a942d9525494afb6c162b0904", size = 174198, upload-time = "2026-07-16T20:35:41.865Z" }, + { url = "https://files.pythonhosted.org/packages/89/31/5056a347bb934ea04583c8b27916ef1501729c72638629545bce26ff4223/google_api_core-2.33.0-py3-none-any.whl", hash = "sha256:a2e22a0c1d0f03eafff1858b38cf46f832d5902b0c052235bf0ab8402929fbdc", size = 176462, upload-time = "2026-07-22T16:28:22.447Z" }, ] [[package]] name = "google-auth" -version = "2.56.0" +version = "2.56.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cryptography" }, { name = "pyasn1-modules" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/58/66/b4ba60005743e01933e22b4f62313e063f7460458b7d8a358427b4930013/google_auth-2.56.0.tar.gz", hash = "sha256:f90fa030b569a92654b9d690665a073841df33d57487be53db583a9a0867a553", size = 364629, upload-time = "2026-07-13T19:09:57.143Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c7/33/dbc946a407401b975f0719658f18e664ece2109f79ffd1ff3bf226c205f4/google_auth-2.56.2.tar.gz", hash = "sha256:e28f103ca8091fb7012b99c44243d7366c29863713b8e34a220c3322b7a07051", size = 365820, upload-time = "2026-07-21T21:53:28.188Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a8/7d/cd3e187f14ce832e419e70709bfcc40cb0dc11517d5d03c9d3919bcc3101/google_auth-2.56.0-py3-none-any.whl", hash = "sha256:6e88c10217e07a92bfd01cac8ee99e32ccfb08414c3102e6c5b8d58f37a0d1e0", size = 257976, upload-time = "2026-07-13T19:09:42.685Z" }, + { url = "https://files.pythonhosted.org/packages/88/63/50636aae68c9bf17c891c7eb18b49baa9bd6b31d2a97b8de4813a9fc8d1c/google_auth-2.56.2-py3-none-any.whl", hash = "sha256:c8270ea95b2697b74e3d8438ae9c5b898e38b623b915c7b5c5635921e7de68a6", size = 258588, upload-time = "2026-07-21T21:53:26.399Z" }, ] [[package]] @@ -961,71 +961,75 @@ wheels = [ [[package]] name = "hypothesis" -version = "6.157.0" +version = "6.161.6" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, { name = "sortedcontainers" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/bf/ea/3d93589be7784d396a5a4d6aca6eb26783dfe9f40813a1f82ef71d7f6df7/hypothesis-6.157.0.tar.gz", hash = "sha256:5e4cd0af9261b06fc79e8aabe7d840384b3c24eaceae7e7e25ee3800a6d6ac58", size = 477477, upload-time = "2026-07-19T08:02:21.964Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a6/f6/867b57c8eb2005cf801bb03b0158f0524cccf57868a271eb9c0132082bf7/hypothesis-6.157.0-cp310-abi3-macosx_10_12_x86_64.whl", hash = "sha256:c958d78cfe93e00fc410d8dadfafac379bff3633348e87a4c5a5af47a0ef0873", size = 749334, upload-time = "2026-07-19T08:01:44.006Z" }, - { url = "https://files.pythonhosted.org/packages/db/a8/c0bae5c4332cd9183f133d4234fba49befd7285946fbfba2096c50fa91fe/hypothesis-6.157.0-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:6ffb3948acffacd2fe9cd56b5b259f48002ed97d8addc2087db55d2e76ab8183", size = 743963, upload-time = "2026-07-19T08:02:20.403Z" }, - { url = "https://files.pythonhosted.org/packages/1a/f4/c19460524a08fc5a78e90a2fc7b472e1d3c23fbeb71b6a6302cc0025f49f/hypothesis-6.157.0-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4764ea1cb63e8d75b0b3889da30640466425b5a2fcaec803952b3fb13e56515a", size = 1071472, upload-time = "2026-07-19T08:01:48.132Z" }, - { url = "https://files.pythonhosted.org/packages/3c/c2/64ed0b609f9d90865373fb067f11a8444977b2b4547b42ff7f500758bdcf/hypothesis-6.157.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:116734981c3c59e06e6ed1446b054fa92db999cbdbc354c3335c593ce917ebb9", size = 1122954, upload-time = "2026-07-19T08:01:54.528Z" }, - { url = "https://files.pythonhosted.org/packages/e0/5a/a6cf428f2b0e4f67f0e7e48f8a72eb4aad4780a736d01887c69bc565dbaa/hypothesis-6.157.0-cp310-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4b93a357d1ff0dae972e8accbfe15cd91c1eeb8fa766a2a1c361b56bd0414f80", size = 1112453, upload-time = "2026-07-19T08:01:27.225Z" }, - { url = "https://files.pythonhosted.org/packages/e4/bd/0d9661dce553d5977c6e9f9ed46c06ab89fc64d471892362ce87cc02eddb/hypothesis-6.157.0-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:e8778a10749cfd3cd236224dc78b354b14970a07cd5630345269ffbf8830db9a", size = 1246171, upload-time = "2026-07-19T08:01:07.635Z" }, - { url = "https://files.pythonhosted.org/packages/74/81/f50fd9e502c11780a250df8cf6f9f3b0cef47792226f5138f6e55647967b/hypothesis-6.157.0-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b718c800bfd1fbec5a11ac1e56c28a5115a31af499ebb6acfc6e13922a6dd0d4", size = 1289928, upload-time = "2026-07-19T08:01:58.976Z" }, - { url = "https://files.pythonhosted.org/packages/78/02/6c8fbe77ea67b31b777402ee28ba783e831a5724d098eb167905f6b882c1/hypothesis-6.157.0-cp310-abi3-win32.whl", hash = "sha256:17e9264974ffdeaa95e48caa36db418d49bb1f0fa164be616adea64f5547d900", size = 636444, upload-time = "2026-07-19T08:01:12.732Z" }, - { url = "https://files.pythonhosted.org/packages/b8/06/8e3262f176cea35bffcdfbd6b2a2d80b1719f6d16d1516f3ba197dfbc054/hypothesis-6.157.0-cp310-abi3-win_amd64.whl", hash = "sha256:c8ac434ead091077b1fc9ca69ed7162cd644e42bf75e59a11ae35258e8922225", size = 642232, upload-time = "2026-07-19T08:01:42.615Z" }, - { url = "https://files.pythonhosted.org/packages/ce/72/f94108a7979b40cf8407d3fe2e6e0b58ba0c177cf91271dc16ccc2288782/hypothesis-6.157.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:5dd5b530a847cfecd7dc7ec97d0776e2744e0abab16bb8ed94a100a463209003", size = 750108, upload-time = "2026-07-19T08:02:05.784Z" }, - { url = "https://files.pythonhosted.org/packages/7a/9f/78bdaa3225afcdf28284060ca5c792ec84fae0aba50b56dfe32f8806a081/hypothesis-6.157.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:37953dfffdf4c5147d92ce2269aed9f66123edc09e9de3356b6e68cc2ae5d70f", size = 744871, upload-time = "2026-07-19T08:01:41.098Z" }, - { url = "https://files.pythonhosted.org/packages/d8/8b/8a29e1bed1eca444e34358b18d5dbadcf9fc9e443cb58f30c262b2e9ecca/hypothesis-6.157.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:96abb36b598b1b9a427716e28ddccb4b88fd4be462e92d23c3c12e1f91ee3efd", size = 1072230, upload-time = "2026-07-19T08:01:04.779Z" }, - { url = "https://files.pythonhosted.org/packages/74/2a/5a649ae28d8e9a50e7d7f0fde2598c4f3743b19384d3db56e79da2e7c4e1/hypothesis-6.157.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db9124511b4bde227e927d2e3eb43a2df4416930c47874126a8d71ea13e3fe2b", size = 1123534, upload-time = "2026-07-19T08:01:19.024Z" }, - { url = "https://files.pythonhosted.org/packages/79/4e/52e91c9259e4388a9169d384d61487e313aa0a63ae0ce62425f28fc018ab/hypothesis-6.157.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b7b53bf761fac490c758db65062072b2239b70e4f3ac772439aa73186171ffd7", size = 1247522, upload-time = "2026-07-19T08:01:28.592Z" }, - { url = "https://files.pythonhosted.org/packages/c9/f4/1bc00b8a92778de9db7a99624029956bbb164e3f522367d3ea89e76daf5a/hypothesis-6.157.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:dc4cfa9feb2cb7d3ff327b2298711e5b61fbdf1f95617344ae6de8ef25563c88", size = 1290555, upload-time = "2026-07-19T08:01:20.352Z" }, - { url = "https://files.pythonhosted.org/packages/af/01/b43d52732a700ab75da571496ead3e1c93caa2e610c9ee366152e04a2fae/hypothesis-6.157.0-cp310-cp310-win_amd64.whl", hash = "sha256:89c0c6e3426eb4ebe721c80edf19285e8979a119e2b77971c5d8e959efeddbd9", size = 642220, upload-time = "2026-07-19T08:01:16.454Z" }, - { url = "https://files.pythonhosted.org/packages/f6/14/a3feb8afbbe4b3541bd186f09b40c73b63c6f102a8311fbf0533aa1952e2/hypothesis-6.157.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:d73b30b5d5904493cf6c2fd22ed202e9f05537c9da8e8f1c985c52abe118d35d", size = 749800, upload-time = "2026-07-19T08:02:15.275Z" }, - { url = "https://files.pythonhosted.org/packages/30/83/de9743d1dc15286c33e63a59606c1d28d34454d249f8a911d58be1b3fe79/hypothesis-6.157.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:856ee6862ba535820117d3f042ccdc078eee30915d5a79af94fb97ac604cf7aa", size = 744584, upload-time = "2026-07-19T08:01:23.116Z" }, - { url = "https://files.pythonhosted.org/packages/0c/ce/9d1ecbbbd36f3fdba1f9bc6090709e3043a062199855ddaeaec2d1072a34/hypothesis-6.157.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc9c695dab6573d27c0f1482937b2df963c24e08a75f6cdc9eb3aa32efb183bf", size = 1072117, upload-time = "2026-07-19T08:01:15.101Z" }, - { url = "https://files.pythonhosted.org/packages/23/91/c0d92b7de95b69e5495d58ce8c2c61af264ef93f4b534ffee5e2a0879a77/hypothesis-6.157.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89d41263f048b05e3723c66d10ce7e9ff7e73931dadf5e7cfab0dade18c1abd1", size = 1123313, upload-time = "2026-07-19T08:02:04.254Z" }, - { url = "https://files.pythonhosted.org/packages/43/8f/b7a10b1db66413a1f02fe7d8dc229c00320cd363af2fe809b7896d00ccdf/hypothesis-6.157.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:26d7f7f95b3d93b7930f9e0a13ae93bc75832c97eea7b76c966aa8799ba0f2ab", size = 1247043, upload-time = "2026-07-19T08:02:13.759Z" }, - { url = "https://files.pythonhosted.org/packages/9c/65/9572b024f8268ed80ededbfa7b5f99343ecee0889b539ce8d8dd3e00b60f/hypothesis-6.157.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:00701c60761391eb4b11ed469a265ea2957fe49bcdc3b4920ce527589cef11e9", size = 1290299, upload-time = "2026-07-19T08:01:01.891Z" }, - { url = "https://files.pythonhosted.org/packages/e6/5d/46abbd1e91acd5dde5d5e625e7559d1267ce39ec4d38a55730e023189e48/hypothesis-6.157.0-cp311-cp311-win_amd64.whl", hash = "sha256:f2e2e18aa5c8bf8e8046e4bef5cc6c58bf72a0a57e15d75abf519060ea37cdec", size = 642037, upload-time = "2026-07-19T08:02:08.872Z" }, - { url = "https://files.pythonhosted.org/packages/61/19/56e49a7ed09464a2d3509b3da01c230c52ee2b69f13f9a2ca66fbc2d3c11/hypothesis-6.157.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:cc840c8878e80f3a2a3bc273a2a16027925585a19ce8ef0e21d43900c855b72f", size = 750201, upload-time = "2026-07-19T08:00:58.693Z" }, - { url = "https://files.pythonhosted.org/packages/1b/cc/40efc46193f1a72256e6d39836aa5b292def386aaf8b7c0994adfaf2e55d/hypothesis-6.157.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c13eeeb7ac9c493e3985d5190ffda8779d9f50cdd7f5ca2cb13bb55c4f885cef", size = 742734, upload-time = "2026-07-19T08:01:13.906Z" }, - { url = "https://files.pythonhosted.org/packages/20/be/38d2d92ad140af39760a441b308bdca77f65e2a6a71c86acc1870f281e85/hypothesis-6.157.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99e5c77e29000033412ef56ffe6900d1cb396c60860b69794d35d535350d3151", size = 1071062, upload-time = "2026-07-19T08:01:32.847Z" }, - { url = "https://files.pythonhosted.org/packages/9e/da/1e2c7301bff26fbcf8734ab371f19342c55079e04c1d537badc27fb24af2/hypothesis-6.157.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b84a473dc19e5bdd26fff44c08b33ef9c342a59da90466cdf31400e812e2feae", size = 1122279, upload-time = "2026-07-19T08:01:11.537Z" }, - { url = "https://files.pythonhosted.org/packages/2f/a9/1e7defd8efa72ac78058290cde879c28bc28f960ec65819026dcda0bc449/hypothesis-6.157.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:78efc2441fded9c12de7db6d38b64af2173af49c0f09d09acdb0ad37028f6044", size = 1245362, upload-time = "2026-07-19T08:01:08.948Z" }, - { url = "https://files.pythonhosted.org/packages/a3/d0/6e00036831757d5f013ec1414684d4beaea34ee55dc6b68f8a77504e88dc/hypothesis-6.157.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1f961c45fb9908966bef523d42b5c018896cfcb1dc4faee70cdcb03c55de0f14", size = 1288907, upload-time = "2026-07-19T08:01:17.808Z" }, - { url = "https://files.pythonhosted.org/packages/3a/44/7159788216d566d00bc957e823bf910d692fcfc849a9e8ca91c50ea2c3e2/hypothesis-6.157.0-cp312-cp312-win_amd64.whl", hash = "sha256:848f5c0349a2629f13c40493f0c1ad380e92a31edb7b76646ba58b1268f29d7c", size = 639556, upload-time = "2026-07-19T08:01:57.562Z" }, - { url = "https://files.pythonhosted.org/packages/a6/4b/5c17c9f2b15ca4390a4527dd335296bcc816ae7aa5df0a7d12643e1fb9c2/hypothesis-6.157.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:84a93415a8d0c69ef7d37548c653cd1f4cbc861457f29ca5ecfeb9ab21955195", size = 750572, upload-time = "2026-07-19T08:01:30.186Z" }, - { url = "https://files.pythonhosted.org/packages/2c/3b/ceb0b139193d300f5eed81a3171073af912918141ab96a58471e63701702/hypothesis-6.157.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cea91fe827de004963e611697c52ee2f766cc4366183a822d9ed1470c2bdc0a9", size = 743060, upload-time = "2026-07-19T08:01:36.923Z" }, - { url = "https://files.pythonhosted.org/packages/83/46/396e0691b1e0efa1a38fb98b70551938ddf91e3962de0df3240f7f71ac90/hypothesis-6.157.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:feb9af5c6ea644b136a871bab2a43930836445cf1feae29c9e05ae0173439668", size = 1071269, upload-time = "2026-07-19T08:02:01.002Z" }, - { url = "https://files.pythonhosted.org/packages/d1/d3/52735aa71749924577907717fd02aa08601b3c2f0a769ba07c5a4cfddf11/hypothesis-6.157.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce0e0bfe1e9c849d96850d23eb5cf02d80d278d2a5130cabe7815c13c8aea15e", size = 1122486, upload-time = "2026-07-19T08:01:31.456Z" }, - { url = "https://files.pythonhosted.org/packages/30/7a/49adf3ee9acc0ff70079ed0cab48505d560363f475bda63b80ea5bcb2d6c/hypothesis-6.157.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ae9064b021a0571514db40f2beb99a31c8d18a1fd12f0ee30cb7e72c102483e4", size = 1245730, upload-time = "2026-07-19T08:01:06.31Z" }, - { url = "https://files.pythonhosted.org/packages/0d/91/70bbb1f785701d1443afedf880e6cb5a5707e89f0050c02ea4046c1ff1c3/hypothesis-6.157.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:84b664f0500911713f564ec826fe29ba883a635921babc2683a4fd12cb567988", size = 1289249, upload-time = "2026-07-19T08:02:18.462Z" }, - { url = "https://files.pythonhosted.org/packages/a6/d6/f45b02bc3d63590cd5ed486a091ea5e65ce4ca339be7e8d37b78903136f8/hypothesis-6.157.0-cp313-cp313-win_amd64.whl", hash = "sha256:2fabcd93730584a28b0324da308122f9cb6f35d2d540d223d5561649d71b5392", size = 639770, upload-time = "2026-07-19T08:01:46.781Z" }, - { url = "https://files.pythonhosted.org/packages/d8/a3/170b589ee0e07c1dbf7dbfff68ec55138fb67585d924967654c47aaf0586/hypothesis-6.157.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:c3084b75511d50a912eb597ab5d1d19a12734a38155c93b061a6beead84a55cd", size = 750646, upload-time = "2026-07-19T08:01:38.359Z" }, - { url = "https://files.pythonhosted.org/packages/0d/05/63a3261d9609807a382206d15bd4ae8022abb33abc0a90b7d3fe1a2d232e/hypothesis-6.157.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:46519de255107a12213470aebea23fb457acec1157ca9aafc2ce4278d073d61e", size = 743180, upload-time = "2026-07-19T08:01:49.814Z" }, - { url = "https://files.pythonhosted.org/packages/21/0e/c8a350907bf330b2fb0ab3029ad26ccd914fe2e5cc1e3cf527a88a562e00/hypothesis-6.157.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:702c39588097cdaacbbd9b3c7c26abc2321a91f023163af364698d79d57adcd2", size = 1071546, upload-time = "2026-07-19T08:01:10.034Z" }, - { url = "https://files.pythonhosted.org/packages/1c/2c/eaafbf8e0cf473a68d22ee0962acaab932663c1ff4075e15e9d24bdf2eae/hypothesis-6.157.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2f1f48231ba294c0eff490403ff1580c5ba8534679ee5462171f7c23a7096592", size = 1122801, upload-time = "2026-07-19T08:01:35.619Z" }, - { url = "https://files.pythonhosted.org/packages/12/60/e0faf53a9d0b89eb3a8fe541305f41125be4283c5647c7943b27c6c705a2/hypothesis-6.157.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fcc1cf1d961e9fa4043e5938ebee8b81de559c5253253ef42a523bb73f9a6be4", size = 1246079, upload-time = "2026-07-19T08:02:07.38Z" }, - { url = "https://files.pythonhosted.org/packages/cc/6e/a322bc611d3bb725cc06cb97a829dffe7be7a0499667b2b153ddb8f7152b/hypothesis-6.157.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:48a75902f2a5b6fbf8b3e22e5d1c9c099ecb1accb1b32860dbc07ef8b66d5cc5", size = 1289525, upload-time = "2026-07-19T08:02:02.717Z" }, - { url = "https://files.pythonhosted.org/packages/cf/c4/4a11576b60ec964e92c4968c944270cccd171619a599ca410ea01d19b3aa/hypothesis-6.157.0-cp314-cp314-pyemscripten_2026_0_wasm32.whl", hash = "sha256:a48197661d22f0a6d6fad9ae43fc630901c996e7914e8a22eb0fa3c041295b0c", size = 587912, upload-time = "2026-07-19T08:02:12.195Z" }, - { url = "https://files.pythonhosted.org/packages/fe/94/8a39d37b7523fc838e634f300140b98973d8033ad1c65ff98c6305d0c5bd/hypothesis-6.157.0-cp314-cp314-win_amd64.whl", hash = "sha256:2d8d7c38f06cd43f68eca7532fc55171b265c3b55cf29317e7d330fbdfdb19ab", size = 639589, upload-time = "2026-07-19T08:01:45.423Z" }, - { url = "https://files.pythonhosted.org/packages/d0/99/dc5fd3e30467b4f3c326b5f94b59d955f769a89f71aadbcfcde22170e942/hypothesis-6.157.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:a32ae18769c095331eedd1921e999075660631d532370497116dc5dc1975024a", size = 748530, upload-time = "2026-07-19T08:01:34.253Z" }, - { url = "https://files.pythonhosted.org/packages/26/9e/a88f7a5f968930ef4348b17b19c37194a4d8246c3f9385f0a991f19bc30b/hypothesis-6.157.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:d6947a4978496aeafc94815b16643a3aa753e12da090ccee65bf2f72c29b0b96", size = 741820, upload-time = "2026-07-19T08:01:21.885Z" }, - { url = "https://files.pythonhosted.org/packages/ae/30/9d9dcb36512f9a724c95a56694272dbf82d7b51ec56722e993d92f88f986/hypothesis-6.157.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c32d9da2d7f52d6cd04cfaaab6e32793c99525f07c5cb44a04c3895df73506b", size = 1070720, upload-time = "2026-07-19T08:01:39.651Z" }, - { url = "https://files.pythonhosted.org/packages/f4/87/9fc11ce9545315e489ef6694226b88bc606561223f987fd57ae66efbd0e8/hypothesis-6.157.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9404b73097ef8deb2f0a72d7a8c38c15c86f4aa3d2fcad89540e17c33eb6259", size = 1121766, upload-time = "2026-07-19T08:01:00.31Z" }, - { url = "https://files.pythonhosted.org/packages/09/f2/847a0fe262deab8077f17dd87448a3fc14f30c353b61831eb482acf64672/hypothesis-6.157.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8b97ebad2018f39287b5c8fdfd66d4372c005bc914fe2db6c283facbac7ddb81", size = 1244522, upload-time = "2026-07-19T08:02:10.713Z" }, - { url = "https://files.pythonhosted.org/packages/6e/ba/fe9e0db0c0dafb186ded1caf0d5dc3ac9f4cc98de20bad1e677ab52b8103/hypothesis-6.157.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cb4670b6f9a2c863025d9c4a8e7307bf083bce391e37dcb8857d9ec1a422ae5e", size = 1288305, upload-time = "2026-07-19T08:01:52.896Z" }, - { url = "https://files.pythonhosted.org/packages/2e/c1/1198203c07514252da690baecc97ff3bea9cea8029495c2a345c563114d9/hypothesis-6.157.0-cp314-cp314t-win_amd64.whl", hash = "sha256:42861f32c1ca6fb999528a5feb553a967fc6b18c39411a224002cdf0d6e91364", size = 639666, upload-time = "2026-07-19T08:01:26.051Z" }, - { url = "https://files.pythonhosted.org/packages/74/fc/44c892815ccfe5fb5df7e416a2ab8c2de82f4f138e73ade15ca34288b3a7/hypothesis-6.157.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:aabe6c3f74bac72be3908581be071a90c21fb2a6d99a264363710783cfd072c5", size = 750516, upload-time = "2026-07-19T08:02:16.765Z" }, - { url = "https://files.pythonhosted.org/packages/d9/26/a13043a9955b64cb59545a52223a68f34cd5001975c6d6be806e13d55e10/hypothesis-6.157.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:5ffb93da23a43edd894b4cbec3af5f8975cc53def3df345d4ca9f5549939e2d7", size = 745302, upload-time = "2026-07-19T08:01:51.289Z" }, - { url = "https://files.pythonhosted.org/packages/f9/e0/17260e2eb155f7984caa54385f98641d6cd46d376e27e21735e28660f77c/hypothesis-6.157.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0bff02bff2b8cb0d464cfbe62aacca26838155de3ac8f8c6afd697d1866722e8", size = 1072390, upload-time = "2026-07-19T08:01:56.04Z" }, - { url = "https://files.pythonhosted.org/packages/fb/a5/18ac2c2ed6dcb833a52839fa287357cea2ca7463dab023d157b8a3112ecb/hypothesis-6.157.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:19d9bc11c4e56ac0b3709e78589d9d94156c1649fe371849e02940d474a71755", size = 1123954, upload-time = "2026-07-19T08:01:24.538Z" }, - { url = "https://files.pythonhosted.org/packages/20/45/7631fc1cfe2636c49275cee5579eb8084841d41abbfb6b8cadc4fcc4908b/hypothesis-6.157.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:c0f75a017cc914f8aa4ae11c210a6de8592501e34a0697a24c349069c16b33dd", size = 642614, upload-time = "2026-07-19T08:01:03.522Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/2c/2a/f082741a0aa60e2729c8afed2f0c8717aa74648b1ca15ff553516548d262/hypothesis-6.161.6.tar.gz", hash = "sha256:e276f5705cb929ff059785db8fd3a5074c9081529944833a0a4b4f6b9b9303dd", size = 487491, upload-time = "2026-07-27T05:33:24.859Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6a/59/0b6670cdeea0de95d9b80a22877e8ac9fdc78d48f92fc4bd774f14528017/hypothesis-6.161.6-cp310-abi3-macosx_10_12_x86_64.whl", hash = "sha256:a7933cf957fa95ed311be348beb6ebab9a80e4fbcd665bdfdfc82d28703967d8", size = 767824, upload-time = "2026-07-27T05:32:09.953Z" }, + { url = "https://files.pythonhosted.org/packages/e0/ed/b74f9035406917eebf0bbac4fd075478457fd63e9a610cda7ac5c41ae24f/hypothesis-6.161.6-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:39564c86891a731872d5a2fffa76788ee24cf57dc713d8d140382487f4afc4f4", size = 763367, upload-time = "2026-07-27T05:32:07.164Z" }, + { url = "https://files.pythonhosted.org/packages/94/76/9b406c3c0fc4596ce46d87e5f7e9dc0751550af3425287472e1decff024b/hypothesis-6.161.6-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:428951eb9e755d5d061fe110313cd6c625184cb8b1cbe2cd781b117aaf02d3a7", size = 1092657, upload-time = "2026-07-27T05:32:04.657Z" }, + { url = "https://files.pythonhosted.org/packages/5e/96/d10f48cc40b9a6091d460c9a7597f6e2e28a7eab1e794feffdd5d5ee5df1/hypothesis-6.161.6-cp310-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c0eee4d6609b0245fb6343c600794387d780d41f298b2120869930adb10b22bf", size = 1121225, upload-time = "2026-07-27T05:32:39.111Z" }, + { url = "https://files.pythonhosted.org/packages/d6/6f/d79b488b8e5f25f01a66ae91d3bb7d50df988eddb5a9cfd34656f946888f/hypothesis-6.161.6-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bedf9019cac5272b0c08542e56a124841121368d084cb6f840d0d97fd2dd73c0", size = 1142152, upload-time = "2026-07-27T05:32:24.288Z" }, + { url = "https://files.pythonhosted.org/packages/50/ed/fe23766d0f884582e8b4e55cfd58ac5560f4df11ca8b8daaa2ac271060e8/hypothesis-6.161.6-cp310-abi3-manylinux_2_31_riscv64.whl", hash = "sha256:f94c1189c8b4e42a3fa28e89df4fa5b5dfe9289e7aa50381443e459ac003d8ea", size = 1097466, upload-time = "2026-07-27T05:32:15.201Z" }, + { url = "https://files.pythonhosted.org/packages/72/6a/f05dc0b558a05cc5da691a54a75870232425e16b57ec7e7f095387af9668/hypothesis-6.161.6-cp310-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3ce10197c57f7efd7bf49509d24a4154f2a0e01a9758b57cef1523e289c4858d", size = 1134196, upload-time = "2026-07-27T05:32:44.109Z" }, + { url = "https://files.pythonhosted.org/packages/3f/b0/f43c5f503b5c73776428a37e90c90b5b0a607f275a18b2822ce52618ffdf/hypothesis-6.161.6-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:c40f0aed8e8037a5ea4fa9486ea98c0ec3a007419bd7b3dbe14712a3632e028b", size = 1266502, upload-time = "2026-07-27T05:32:42.527Z" }, + { url = "https://files.pythonhosted.org/packages/f4/2a/bb563f9718ac5bf98ef6dbb566fb1d5fae9fd23829ed320fc4c84c4b5985/hypothesis-6.161.6-cp310-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:fc96ba21133e95a01ab56486b91f83f1e5609ffe55f17618d732cbb29dcb8863", size = 1394242, upload-time = "2026-07-27T05:32:13.435Z" }, + { url = "https://files.pythonhosted.org/packages/f7/26/6dd5a85e9d3556074ebb3988f5bddb3bf52532b2a2bd10ea2a8421a0f852/hypothesis-6.161.6-cp310-abi3-musllinux_1_2_riscv64.whl", hash = "sha256:4fad1cb8f49b453890dd5768e14c79e9f6cc8079f1f474e3b9027956985ab2c9", size = 1267017, upload-time = "2026-07-27T05:32:11.746Z" }, + { url = "https://files.pythonhosted.org/packages/26/34/a6cde6774c815bcc98648a17f1ed461345212bcee4492d8c6b0ee72282cf/hypothesis-6.161.6-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:7478ba09770f49c160ffbd22b4bbf7d886ac309653fa7116b5edd65e53536b8d", size = 1309114, upload-time = "2026-07-27T05:32:25.889Z" }, + { url = "https://files.pythonhosted.org/packages/f5/b2/4178fe71ee42c383ab669f345e1841a7b880a8fe84e498e78cffdaac87d3/hypothesis-6.161.6-cp310-abi3-win32.whl", hash = "sha256:7e4cc1073644a1b468f46252cc4f135eb51c59d79dbc08acfcd6b4ac290a9ffc", size = 653673, upload-time = "2026-07-27T05:32:01.864Z" }, + { url = "https://files.pythonhosted.org/packages/48/fe/2d91b2e7e06e9737450d7e3fcf09d7efa0c66e57767babc0016ed56cd174/hypothesis-6.161.6-cp310-abi3-win_amd64.whl", hash = "sha256:4a40098bc9c863c2013efb7f9b02f22721e971f795305f24934874a3135e77ef", size = 659826, upload-time = "2026-07-27T05:32:16.609Z" }, + { url = "https://files.pythonhosted.org/packages/a2/67/a0fb537be61560bf26d4faecc9450eb65b54e2dd97b78dcba27d715725a0/hypothesis-6.161.6-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:ce243a3caa26284247eee61992e0e07f85a466fc6f5ac8e72992ba910612dbc2", size = 768537, upload-time = "2026-07-27T05:32:56.462Z" }, + { url = "https://files.pythonhosted.org/packages/c4/a0/73d0229035534c5d27486309bca91513e302072593f9dda82595b40a6c16/hypothesis-6.161.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:635f0286e7fc771e58d82d38280dcedf2d0cd41f353f49cd74be04367683b9d6", size = 764266, upload-time = "2026-07-27T05:32:49.712Z" }, + { url = "https://files.pythonhosted.org/packages/32/79/4374d2948c568f1594b0e7f42d0d69e516aeac53f89560525ae895e3a21d/hypothesis-6.161.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b199468cb6a1afe74bfad8c0f0580ced611341052df478e7d6db1128bce4b07f", size = 1093131, upload-time = "2026-07-27T05:31:56.382Z" }, + { url = "https://files.pythonhosted.org/packages/4a/ce/b5f849f4fe433eb55c8d6195fa566973c489b0deec06f5f37a9f20cba868/hypothesis-6.161.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d585eb08b1319776093cde6a7bc30ce7ee9675001b105e6f4e99f2850ed4620", size = 1142671, upload-time = "2026-07-27T05:32:21.293Z" }, + { url = "https://files.pythonhosted.org/packages/16/5a/c575a65a61639fcbaabbf31f26826cb7cc0228653a1d1c06382784e2761e/hypothesis-6.161.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:42c70d23e59dc4748cb2f8aee131f979bf16e9612b2ee48f7e8fb51c70f4a948", size = 1267068, upload-time = "2026-07-27T05:33:04.635Z" }, + { url = "https://files.pythonhosted.org/packages/78/9d/86bdcb868a742376ef602997947efd3c7774f9d760d1f8f3654363f6c652/hypothesis-6.161.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7fbed7de4873833d4a51fbe437dc3edc5f2a76c48666485432233864a530c549", size = 1309437, upload-time = "2026-07-27T05:31:57.653Z" }, + { url = "https://files.pythonhosted.org/packages/b8/f9/ac1993ca99c61505f895877f46d6fea233acb0cbb79ab9324491ac278613/hypothesis-6.161.6-cp310-cp310-win_amd64.whl", hash = "sha256:e8c9de18a5dc42bef1b7c3c65e4f72a5a9c3604a438c721d25c802ec2ded06ac", size = 659673, upload-time = "2026-07-27T05:33:06.487Z" }, + { url = "https://files.pythonhosted.org/packages/0a/78/78d6743e3a4c425412986e1c2595a1fb6f3024cfa61afdd77385d357ee12/hypothesis-6.161.6-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:959a0db6f42fe01faf393646876ce43f736fb3c0162dbfafd4f4de21bd4aa6c8", size = 768298, upload-time = "2026-07-27T05:32:59.534Z" }, + { url = "https://files.pythonhosted.org/packages/f7/b6/5f05dbc91523f4e6f8b0da75960318faf8287b38298af2b62f1bc5772fcb/hypothesis-6.161.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:081a740171bf5bb3cacf2ee2f895803a3d0e6a9709722e0681ed919c724aaf6c", size = 764079, upload-time = "2026-07-27T05:32:22.777Z" }, + { url = "https://files.pythonhosted.org/packages/e0/43/c9027d8029162069afec7fbdef240b6770f7ed7e086a771ba2b8a5c103d6/hypothesis-6.161.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:359eb0d8bd7c6d2a63ac765fc0b6e0d73bfad9a06aef98ad0f7b2619c17ee8fe", size = 1092970, upload-time = "2026-07-27T05:32:29.116Z" }, + { url = "https://files.pythonhosted.org/packages/04/86/67bf5f444896334dae1b3d52203f573864f2cf7c55400a33b5da3b51e589/hypothesis-6.161.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6bbd0a83fc79cb317230bc9122755035a64505071d1e0599e9dfd6371e4ebdb3", size = 1142431, upload-time = "2026-07-27T05:32:18.082Z" }, + { url = "https://files.pythonhosted.org/packages/ed/a8/b86778a6945c69c067a46329d1680aecf3c0eb85e00be82444319e2500bd/hypothesis-6.161.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b76dd9bfca6ac3baed9996b20a12c84058f7c17e5802636939c13ed9b3664f4d", size = 1266744, upload-time = "2026-07-27T05:31:46.283Z" }, + { url = "https://files.pythonhosted.org/packages/d8/b3/3b8a6eb0e78a52efc902a4927d7207d2f9478b70658ad4e38f861d1441df/hypothesis-6.161.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:2af997b980ddad5cb8a6d13bd48fcae35f4211e339ff5bb48abe2754d75918b8", size = 1309396, upload-time = "2026-07-27T05:32:53.204Z" }, + { url = "https://files.pythonhosted.org/packages/ee/3d/89a5dc081cdbb6966aa14c8320d74bf2fdce6ac52f27a5c62c2645c762dd/hypothesis-6.161.6-cp311-cp311-win_amd64.whl", hash = "sha256:b9321091368ff8e7955f2178b9df3bf2f1e4dd8d360c173a9414c9d27ed697f4", size = 659521, upload-time = "2026-07-27T05:31:55.117Z" }, + { url = "https://files.pythonhosted.org/packages/eb/f8/bafca1b91c225d7905d6d2d20882f79591925288c5ca0c6aed4c23e0c421/hypothesis-6.161.6-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:96c325113320f3fc50ed889a20114b9e317145a66c19544dbd071ba173328690", size = 769412, upload-time = "2026-07-27T05:32:03.332Z" }, + { url = "https://files.pythonhosted.org/packages/9b/2f/89c48ddfb0bf79cd47ec94d30673ebed554c946816f407bd3c79321e2397/hypothesis-6.161.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ff4e786eb37ef81dd6f78f0238ec787217dbcdb00ccc565c14eee22c676677ba", size = 760979, upload-time = "2026-07-27T05:32:35.558Z" }, + { url = "https://files.pythonhosted.org/packages/00/23/f2fee896448d5d480d3de7d94f8aad2c1c8a0961575d054547f7f81d681d/hypothesis-6.161.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:36bd6e336f4a0b259311860306674e83175b56650bd1a54aaa6bb1c878accf03", size = 1091422, upload-time = "2026-07-27T05:31:59.292Z" }, + { url = "https://files.pythonhosted.org/packages/97/92/1f5ac80a195a14837a041ef7232a9760fca52720edea27f543e17a38c9eb/hypothesis-6.161.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce64858eaef339a487c4e7ac1cb8898412a36d06a7872bc44af239a7ab5c2ffd", size = 1141456, upload-time = "2026-07-27T05:33:11.802Z" }, + { url = "https://files.pythonhosted.org/packages/98/38/6c4025549e071e315ab97175a159b5e261afec1cca7d9d4f9cb1c32ef120/hypothesis-6.161.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0323be2fca0754dff630fe1d719d5876c93c5e3be9678ae6fe8557bf689b5868", size = 1264180, upload-time = "2026-07-27T05:31:50.92Z" }, + { url = "https://files.pythonhosted.org/packages/80/37/b31de9ba2fc908529b18a1e386d8167a39883fcab938107b3ee2de02872b/hypothesis-6.161.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:18fff0ca1d5dded13c56f430d377bc6585327bb4b987fcf8e37676633569b48c", size = 1308460, upload-time = "2026-07-27T05:32:45.915Z" }, + { url = "https://files.pythonhosted.org/packages/aa/b3/795860dc3817f4cf0bbbcd4ecfba307a77dcc0e85e2d601765364f4989bb/hypothesis-6.161.6-cp312-cp312-win_amd64.whl", hash = "sha256:16da70ffe8507307bd8cea7f2ecdcb7bda1d1980412213ad55571044ba53300f", size = 656950, upload-time = "2026-07-27T05:32:05.965Z" }, + { url = "https://files.pythonhosted.org/packages/09/65/143d649a444037afec0b52aac0b85c16ac0ca99c454f6f8bcbe7202180f1/hypothesis-6.161.6-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:53e279433bba79a30ec299d281572ca1533309986ed7e1992f9b1c300cbc9173", size = 769306, upload-time = "2026-07-27T05:31:52.397Z" }, + { url = "https://files.pythonhosted.org/packages/6a/cf/7ec64020028051d76160d1e23ec4e2d00ca7772bb387c0a85dc399f38b5b/hypothesis-6.161.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:17bec7179accef79e7bfaed5c68e312a8b440aaf8423e9a67289f8804517ec32", size = 760943, upload-time = "2026-07-27T05:32:57.945Z" }, + { url = "https://files.pythonhosted.org/packages/de/27/2a79199aa9714f786ad9a522dfffb022e8571339fe392f0af5337f40e4f1/hypothesis-6.161.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:badae92f9fe82a882d0e33f48079fdab760fb1c16ac0be282234c0eb89de9f18", size = 1091338, upload-time = "2026-07-27T05:33:22.896Z" }, + { url = "https://files.pythonhosted.org/packages/ec/e1/5db65f078b00472f69df015c4ebefd5c78da70e633b0563a1ccec39443fa/hypothesis-6.161.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b54632d679dc00079ee4aa458131ed58776abb98d96ef27919777ac06b363944", size = 1141284, upload-time = "2026-07-27T05:32:30.926Z" }, + { url = "https://files.pythonhosted.org/packages/78/e6/05f320f26ff4dd3994e2afed775d0399b348999d554b142ccfaa72ff1d60/hypothesis-6.161.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:fdad7dfa87d62ffbbeb89197cfd734285275576f8d8106bcd72696446202c0fc", size = 1264201, upload-time = "2026-07-27T05:31:48.378Z" }, + { url = "https://files.pythonhosted.org/packages/51/9b/7826008f4e0baff43da6f38d897c5a8c6e16dafed4f12cee52ead0e2f936/hypothesis-6.161.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a06a6d62efec24bd196ff47dc4dd95c0714e23e870b7f9e2ef2f4429525760ee", size = 1308183, upload-time = "2026-07-27T05:32:32.625Z" }, + { url = "https://files.pythonhosted.org/packages/44/47/0e2a6179a3c2173acf15de897f6b144d0e22657fbbf38e561a7f0f236ce5/hypothesis-6.161.6-cp313-cp313-win_amd64.whl", hash = "sha256:662221ad5961cf746626ca024b1b52474f2debf892bbac2af978aad4f5e2bfbf", size = 656916, upload-time = "2026-07-27T05:32:34.077Z" }, + { url = "https://files.pythonhosted.org/packages/74/32/1b7c11d7b293928078aba04a363f6d51c530d89b470c146b1040ae2f94aa/hypothesis-6.161.6-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:ecf98cf996b82ca46efa3eebe151c6721c1109df50d34aeb73ad8477d7b85363", size = 769519, upload-time = "2026-07-27T05:32:40.75Z" }, + { url = "https://files.pythonhosted.org/packages/e8/c4/fcaac023585da73fca407932bd11afeaeb0bfd1da174da8ec4225761c8e5/hypothesis-6.161.6-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:fc11a56f2b4931ecf25ce578c535c9f6448ed60f112dce9eae09fb0b0d655db1", size = 761082, upload-time = "2026-07-27T05:33:17.442Z" }, + { url = "https://files.pythonhosted.org/packages/ef/41/563549e4f82c360973a38909018bb4b8e46465bb00d4b5b686509df01d8d/hypothesis-6.161.6-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:16c2af82180b08fa843b699db7f2b7d0ba9b62fd1e21fb8dd646bb96a16fd352", size = 1091840, upload-time = "2026-07-27T05:32:47.818Z" }, + { url = "https://files.pythonhosted.org/packages/aa/2a/46b5f0a3b46959ced231ff78ea941483f2f571473d6f249c6438967510df/hypothesis-6.161.6-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:332e8a7b99fa69a2675a726f5136e68545b9e84e0ff744679b14accf62ed2b5d", size = 1141464, upload-time = "2026-07-27T05:33:10.127Z" }, + { url = "https://files.pythonhosted.org/packages/33/25/77c3052c0fcedd2a19bffd7b2b44ea93dac73bd62f1d8b03420d3ac55ee2/hypothesis-6.161.6-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:160f49f1b7e54e50a3ab67e9772b15df5085ec0f8e624e9acaf66dbaf8422f6e", size = 1264629, upload-time = "2026-07-27T05:33:20.984Z" }, + { url = "https://files.pythonhosted.org/packages/7c/8a/06a54d1be5468089c9688a6f7e3f3e9c65eb24d0046896d9646cfa4ebce9/hypothesis-6.161.6-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7712075d545604a728007ea085fa10acd785713d6e54c8702dbc0b5bed6d2175", size = 1308485, upload-time = "2026-07-27T05:33:08.227Z" }, + { url = "https://files.pythonhosted.org/packages/d3/8e/8e4688ee36c16502aa09b3d2d771f7413b9d9124448c3483b996228fa110/hypothesis-6.161.6-cp314-cp314-pyemscripten_2026_0_wasm32.whl", hash = "sha256:8da289ff744e6225feb222f6d8ece08d77324cbabe2357ab7067632c0c06a359", size = 601010, upload-time = "2026-07-27T05:33:13.62Z" }, + { url = "https://files.pythonhosted.org/packages/2d/2f/1f8451e2f1c18f34a5fad71610887eb81451d09658555c2de1470689ca47/hypothesis-6.161.6-cp314-cp314-win_amd64.whl", hash = "sha256:61ae780388292510ea303f0d79154a4623e8e0c00c49ba981329ec111800bd7c", size = 656829, upload-time = "2026-07-27T05:33:15.325Z" }, + { url = "https://files.pythonhosted.org/packages/86/ef/9da7f06a693f0fb0c51c51e843c37210492a03c330699f000d007f505452/hypothesis-6.161.6-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:6fc0cd5490f69202a8608aa3c3085078c598117049672c6173c4eb8cdfff8b26", size = 768102, upload-time = "2026-07-27T05:31:53.708Z" }, + { url = "https://files.pythonhosted.org/packages/ef/80/c26463bbacbe3caa1567a3121be328ee2ae16a91b5cded6f79edbe3e192c/hypothesis-6.161.6-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:e692d51542664b3d431fe835ada7d37630764e7542150bfa987346b03b3b00a1", size = 759547, upload-time = "2026-07-27T05:31:49.746Z" }, + { url = "https://files.pythonhosted.org/packages/46/06/4c926dc8bc4d7c6df7a0321a291dcfcc214665f34a9a073165e9fdbadf75/hypothesis-6.161.6-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:225b51e0ea315cb70eaee0e0d6506bc5452f1b3f08f48c48953dd2afea006491", size = 1090434, upload-time = "2026-07-27T05:33:02.965Z" }, + { url = "https://files.pythonhosted.org/packages/1c/3c/cc7d2f675b7c44179ba3d343b5fa6b07627365bdc2f0d8c014be5448b85a/hypothesis-6.161.6-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2efac2635c2a690b2afe84e94fceed8c4d3c219fa806340e73fc8f26233dc801", size = 1140375, upload-time = "2026-07-27T05:32:19.866Z" }, + { url = "https://files.pythonhosted.org/packages/8c/f4/662cb8b5ae2a5f92c12892f3654a2c59ce73c25adbacddc6876336c33876/hypothesis-6.161.6-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ac13ee5963380f63f9ffe2de47b7b489a09f8a1e2701e9c816476ff52405ed71", size = 1262843, upload-time = "2026-07-27T05:32:27.546Z" }, + { url = "https://files.pythonhosted.org/packages/7b/49/ee44c0aec2ae4f2e3024c44893b6fa2761cacd4d3b08a3223ea1a1ba8779/hypothesis-6.161.6-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:47ff47cff1fc4b704b805c255bc7c3c64e2c3f7eebdc1bc4c4155df8621cbab3", size = 1307243, upload-time = "2026-07-27T05:32:00.696Z" }, + { url = "https://files.pythonhosted.org/packages/96/4b/725d3eef7be39fb2957eab63ded9c2d338b3ffb52140ec77c449f193fa41/hypothesis-6.161.6-cp314-cp314t-win_amd64.whl", hash = "sha256:981fa76521dd82a05e8749182404f675d3e0ad0b84566c558c8c45d7a8fb70fc", size = 656980, upload-time = "2026-07-27T05:33:01.104Z" }, + { url = "https://files.pythonhosted.org/packages/79/9d/ab53e1be2074ccf42bea03eb97a8ed7e50da392a7ab841e294d9894877b5/hypothesis-6.161.6-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:3a890919b3c93aab5d7fbc945d25e16100bd3cf58acfba54664f4f7b2023f4c1", size = 769226, upload-time = "2026-07-27T05:32:08.384Z" }, + { url = "https://files.pythonhosted.org/packages/6b/75/8a08176ef2a44d012490e31315cdad34884c5f157d65e38da9336ca43284/hypothesis-6.161.6-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:f8e3d3c0820b8a71a4804f92e3d2667db2d013c6285e467ac0e098927243e14a", size = 765073, upload-time = "2026-07-27T05:33:19.285Z" }, + { url = "https://files.pythonhosted.org/packages/ae/19/38c24c5011a3825ee27124e742c681ade72cfdbb4500c0afa1e9e1fc485b/hypothesis-6.161.6-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e5eb2a65d803da165ca5faa01ca07d2c47aa41e0a4de7d14b3c74f478f38241a", size = 1093943, upload-time = "2026-07-27T05:32:51.38Z" }, + { url = "https://files.pythonhosted.org/packages/25/72/918c964e22d94c78fc0594a06d8077a469e28dcc2f5ede45a703df72f36d/hypothesis-6.161.6-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b2a58004ed367961bfce1703bee591221d94e28ad8ac15a57f0eecabbe659ac", size = 1143728, upload-time = "2026-07-27T05:32:37.309Z" }, + { url = "https://files.pythonhosted.org/packages/dd/0c/395637e3bcb374f1b513000d2de2690236acbeb2baf33b4b8446155072a5/hypothesis-6.161.6-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:91bccc94514b865604079a2f5187f02a8e6e855c78ae2127b7fe2e7929a47949", size = 660621, upload-time = "2026-07-27T05:32:54.84Z" }, ] [[package]] @@ -2027,7 +2031,7 @@ wheels = [ [[package]] name = "pandas" -version = "3.0.3" +version = "3.0.5" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.14' and sys_platform == 'win32'", @@ -2049,55 +2053,49 @@ dependencies = [ { name = "python-dateutil" }, { name = "tzdata", marker = "sys_platform == 'emscripten' or sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f8/87/4341c6252d1c47b08768c3d25ac487362bf403f0313ddae4a2a26c9b1b4c/pandas-3.0.3.tar.gz", hash = "sha256:696a4a00a2a2a35d4e5deb3fc946641b96c944f02230e4f76137fe35d806c4fc", size = 4651414, upload-time = "2026-05-11T18:54:29.21Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/42/16/b5c76b838fd9bf6ce84d3a53346b8874ec05c5f0040d75ef2c320100cd2a/pandas-3.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:455f6f8139d4282188f526868dbc3c828470e88a3d9d59a891bd46a455f21b98", size = 10338495, upload-time = "2026-05-11T18:52:11.558Z" }, - { url = "https://files.pythonhosted.org/packages/5a/b0/a4ffc4ae74d2d822200dcc46898987d8eb6032d1e2b219cae39da6f5cbcc/pandas-3.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4e15135e2ee5df1063313e2425ceef8ac0f4ae775893815b0923651b806a5639", size = 9938250, upload-time = "2026-05-11T18:52:17.005Z" }, - { url = "https://files.pythonhosted.org/packages/2e/b2/3323601a52caee42c019e370090ca4544b241437240ca04f786cce82b0cf/pandas-3.0.3-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:05f1f1752b8533ea03f7f39a9c15b1a058d067bb48f4748948e7a8691e0510f2", size = 10770558, upload-time = "2026-05-11T18:52:19.865Z" }, - { url = "https://files.pythonhosted.org/packages/32/f1/bbecd2f867b97abebe0f9b53d750f862251b40337e061b36676ded3d920f/pandas-3.0.3-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8a1e45c80cceb3b4a21bc5939d52e8cbd8d9b7305309219d59e9754d9ce09e27", size = 11274611, upload-time = "2026-05-11T18:52:22.622Z" }, - { url = "https://files.pythonhosted.org/packages/7f/4f/eafabf2d5fae5adf143b4d18d3706c5efdc368a7c4eb1ee8a3eddabbd0f6/pandas-3.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:14da8316da4d0c5a77618425996bfb1248ca87fc2c1486e6fde4652bd18b5824", size = 11784670, upload-time = "2026-05-11T18:52:25.4Z" }, - { url = "https://files.pythonhosted.org/packages/49/44/1eb20389301b57b19cc099a1c2f662501f72f08a65f912d05822613c1532/pandas-3.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a55066a0505dae0ba2b50a46637db34b46f9094c65c5d4800794ef6335010938", size = 12353708, upload-time = "2026-05-11T18:52:28.139Z" }, - { url = "https://files.pythonhosted.org/packages/eb/62/c321f13b5ba1819fc8dca456c7fce578da2dcfecff1abbf0eaddf8406c0f/pandas-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:6674ab18ad8c57802867264b00e15e7bb904700cdd9046e3b2fa1fce237439ea", size = 9907609, upload-time = "2026-05-11T18:52:30.982Z" }, - { url = "https://files.pythonhosted.org/packages/53/85/1b7f563ebc6357c27233a02a96b589bcce1fa9c6eb89fb4f0e56421d277e/pandas-3.0.3-cp311-cp311-win_arm64.whl", hash = "sha256:5cc09a68b3120e0f54870dede8287a7bb1fa463907e4fcec1ea77cab6179bf7a", size = 9165596, upload-time = "2026-05-11T18:52:33.334Z" }, - { url = "https://files.pythonhosted.org/packages/24/f1/392f8c5bfc16f66a0d2d41561c01627c228fe7ed2a0d056ef11315042570/pandas-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fed2ff7fd9779120e388e285fc029bd5cf9490cdd2e4166a9ee22c0e49a9ab09", size = 10357846, upload-time = "2026-05-11T18:52:36.143Z" }, - { url = "https://files.pythonhosted.org/packages/cf/3d/b16412745651e855f357e5e66930248688378853a6e2698a214e331fba1f/pandas-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b168fc218fd80a6cbdbdbc1a97ddc7889ed057d7eb45f50d866ceab5f39904c4", size = 9899550, upload-time = "2026-05-11T18:52:38.976Z" }, - { url = "https://files.pythonhosted.org/packages/31/a8/fa2535168fffcedf67f4f6de28d2dd903a747ca7c8ea6989451aaeb3a92f/pandas-3.0.3-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0383c72c75cdcca61a9e116e611143902dbfd08bff356829c2f6d1cf40a9ca8c", size = 10412965, upload-time = "2026-05-11T18:52:41.915Z" }, - { url = "https://files.pythonhosted.org/packages/65/b6/09b01cdbc15224e2850365192d17b7bdebb8bdbd8780ed221fcdf0d9a515/pandas-3.0.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6dc0b3fd2169c9157deed50b4d519553a3655c8c6a96027136d654592be973a9", size = 10894600, upload-time = "2026-05-11T18:52:45.02Z" }, - { url = "https://files.pythonhosted.org/packages/c9/a4/2eb28f2fccb4ced4a2c79ab2a5dee9ade1ebf44922ebad6fea158c9f95d4/pandas-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7e65d5407dc0b394f509699650e4a2ec01c0514f21850f453fa60f3be79a5dbf", size = 11422824, upload-time = "2026-05-11T18:52:48.058Z" }, - { url = "https://files.pythonhosted.org/packages/f8/45/830bb57f533a4604b355e07edcb8ea18cf88b5f94e5fca92f27052d7c597/pandas-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f8894dc474d648fe7b6ff0ca9b0bd73950d19952bc1a6534540762c5d79d305c", size = 11950889, upload-time = "2026-05-11T18:52:50.905Z" }, - { url = "https://files.pythonhosted.org/packages/b9/c5/fc1b368f303087d20e8c9bf3d6ceb186263cfac0ade735cd938538bea839/pandas-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:c7be265b62cef88e253a941e4698604973736dcfe242fdb5198f0f7bc473cdcc", size = 9755463, upload-time = "2026-05-11T18:52:53.386Z" }, - { url = "https://files.pythonhosted.org/packages/86/bd/fda8f9705b1b09c6ebe14bfc0fa0e4ec8584d54ea673628f157ff55131af/pandas-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:557409bc4178e70ee8d9ddb494798e51ebf6ea59330f6be22c51bab2a7db6c49", size = 9066158, upload-time = "2026-05-11T18:52:56.038Z" }, - { url = "https://files.pythonhosted.org/packages/c5/90/62d8302883c44308c477e222c3daf7c813a34c8e96985882fbd53d964352/pandas-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:67b3b64c11910cfa29f4e94a14d3bff9ee693b6fc76055e7cad549cee0aec5fa", size = 10331071, upload-time = "2026-05-11T18:52:58.838Z" }, - { url = "https://files.pythonhosted.org/packages/7f/ae/6a6493c783a101f165e4356953ba3c74d6f77f0042fa7d753da9dfbb640c/pandas-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:39436b377d56d2a2e52d0395bdbee171f01068e99af5250509aceeb929f765c7", size = 9875690, upload-time = "2026-05-11T18:53:01.431Z" }, - { url = "https://files.pythonhosted.org/packages/62/7c/5df8e9f56c69a2769fbe9382a5ef8f2658c007e376434e1e2cbb57ad895f/pandas-3.0.3-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d4be06d68f9ddcfc645b87534911da79a8fbffc7573c80e0edcf42a5020624d8", size = 10381634, upload-time = "2026-05-11T18:53:04.393Z" }, - { url = "https://files.pythonhosted.org/packages/99/68/1237369725aa617bb358263d535803e3053fdbc593513ec5ed9c9896b5b6/pandas-3.0.3-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a4eeb6830daf35a71cc09649bd823e2b542dac246cdee9614c6e4bd65028cd6a", size = 10891243, upload-time = "2026-05-11T18:53:07.643Z" }, - { url = "https://files.pythonhosted.org/packages/25/93/77d108e8af7222b4a503ebde0e30215b1c2e4f8e53a526431890f22d5586/pandas-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1928e07221f82db493cd4af1e23c1bfca524a19a4699887975bff68f49a72bfb", size = 11388659, upload-time = "2026-05-11T18:53:10.634Z" }, - { url = "https://files.pythonhosted.org/packages/d0/bd/eff5b4399f332ac386c853f6cd2bd3fa2ca0061b9f36ecd9c4d7c4265649/pandas-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:51b1fe551acb77dac643c6fda86084d8d446c10fe64b06a9cc29c4cc8540e7f2", size = 11942880, upload-time = "2026-05-11T18:53:13.536Z" }, - { url = "https://files.pythonhosted.org/packages/2c/20/559ace4200982c3887d0b86bfd0d856a2143ef8ddab63cc07934951a964c/pandas-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:a82d532a3351d435432cd913edbccaf8b8e01d4dd0e5ced5a8d2e8ecd94c7e44", size = 9757091, upload-time = "2026-05-11T18:53:16.306Z" }, - { url = "https://files.pythonhosted.org/packages/3a/66/69055a09fe200f29f922a3eeec4804611900b95f52d932ece3393c3c0c19/pandas-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:275c14e0fce14a2ec20eee474aecd305478ea3c1e6f6a9d8fe219a165542717e", size = 9057282, upload-time = "2026-05-11T18:53:18.768Z" }, - { url = "https://files.pythonhosted.org/packages/57/0e/efe801b0e6811e8e650cd21b7f2608e30f08a7067e2bf6e8752b0d56ee3c/pandas-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:46997386d528eb40376ecd6b033cf4a8a1e5282580f68f43de875b78cba2199d", size = 10767016, upload-time = "2026-05-11T18:53:21.227Z" }, - { url = "https://files.pythonhosted.org/packages/ea/dc/eb55135a1d5f0f0519f28da1f609a206d2cad1f9c35c32d51e38dd7261ae/pandas-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:261e308dfb22448384b7580cf719d2f998fe2966c92893c3e77d14008af1f066", size = 10420210, upload-time = "2026-05-11T18:53:23.982Z" }, - { url = "https://files.pythonhosted.org/packages/c6/3e/b1d5d955ce33ffecb407465a60bc32769d74fcf68224b7ae67ae11d4dea4/pandas-3.0.3-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dd1a5d1def6a46002e964510bdc67c368aa0951df5d1d9f8365336f5a1f490cd", size = 10336126, upload-time = "2026-05-11T18:53:26.731Z" }, - { url = "https://files.pythonhosted.org/packages/f5/76/a01261711ab60a22d71b862f0de20e4c504bf80457270ad8cb42110f6abc/pandas-3.0.3-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d72828c20c6d6e83e1e22a6a3b47b326b71664112fa9705dcbccfd7a39b62085", size = 10728051, upload-time = "2026-05-11T18:53:29.125Z" }, - { url = "https://files.pythonhosted.org/packages/e9/21/ea191195e587b18cf682e97f433f81b2d0fbe341380e80a3e0d6e4403c8e/pandas-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:d26cbe1fcfc12e8fd900e2454163e466b2d3af84f7c75481df7683ffc073d870", size = 11350796, upload-time = "2026-05-11T18:53:32.056Z" }, - { url = "https://files.pythonhosted.org/packages/64/69/f0eaaf54939f0e8c6768fd06be9af2cef9b36048b96dfb9e1b2c685a807e/pandas-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:3e91cec1879ada0624fc3dc9953c5cbd60208e59c0db28f540c5d6d47502422f", size = 11799741, upload-time = "2026-05-11T18:53:34.985Z" }, - { url = "https://files.pythonhosted.org/packages/45/a4/865e0e510cae5fc2194de4db28be638952de942571ba9125934fd9c01d47/pandas-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:08d789b41f87e0905880e293cedf6197ce71fe67cc081358b1e148a491b9bd13", size = 10499958, upload-time = "2026-05-11T18:53:37.857Z" }, - { url = "https://files.pythonhosted.org/packages/86/54/effdcc3c0ff7a08037889200e148ebe94c16c4f653be078c7b3675955df1/pandas-3.0.3-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:3650109c0f22879df8bd6179ab9ee3d7f1d1d4e7e0094a3f0032d9f51e2e64ac", size = 10336065, upload-time = "2026-05-11T18:53:41.099Z" }, - { url = "https://files.pythonhosted.org/packages/68/10/bf2d6738d72748b961a3751ab89522d58c54efc36a8e1a12161216cd45cf/pandas-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:bab900348131a7db1f69a7309ef141fd5680f1487094193bcbbb61791573bf8f", size = 9926101, upload-time = "2026-05-11T18:53:43.515Z" }, - { url = "https://files.pythonhosted.org/packages/ae/e9/e35cf11c8a136e757b956f5f0efdcaa50aecde85ea055f1898dfc68262f3/pandas-3.0.3-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ba7e08b9ac1d54569cd1e256e3668975ed624d6826f7b68df0342b012007bddb", size = 10457553, upload-time = "2026-05-11T18:53:46.394Z" }, - { url = "https://files.pythonhosted.org/packages/58/3b/1cdec6772bdbaf7b25dab360c59f03cadf05492dd724c6540af905389b07/pandas-3.0.3-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d71c63ae4ebdbf70209742096f1fc46a83a0613c99d4b23766cced9ff8cd62a", size = 10914065, upload-time = "2026-05-11T18:53:49.134Z" }, - { url = "https://files.pythonhosted.org/packages/c4/c2/1ef644445fcd72e3627bceec77e3560636f87ddce4ed841afe76b83b5bf9/pandas-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e3a2ec42c98ffa2565a67e08e218d06d72576d758d90facb7c00805194d8f360", size = 11459188, upload-time = "2026-05-11T18:53:52.527Z" }, - { url = "https://files.pythonhosted.org/packages/7e/49/4d8d4f42cbc9c4adc7a1870f269c02cbd6cd40d059622c06fb298addcbad/pandas-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:335f62418ed562cfc3c49e9e196375c28b729dcef8543abf4f9438e381bf3c76", size = 11982966, upload-time = "2026-05-11T18:53:55.043Z" }, - { url = "https://files.pythonhosted.org/packages/38/55/792619469bab9882d8bbd5865d45a72f6478762d04a9af4bf0d08c503e95/pandas-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:3c20a521bbb85902f79f7270c80a59e1b5452d96d170c034f207181870f97ac5", size = 9876755, upload-time = "2026-05-11T18:53:58.067Z" }, - { url = "https://files.pythonhosted.org/packages/2a/af/33c469653b0ba03b50c3a98192d4c07f0c75c66b263ceb097fce0ee97d31/pandas-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:a2d2dff8a04f3917b55ab3910c32990f8ddf7eceba114947838cefa976a68977", size = 9198658, upload-time = "2026-05-11T18:54:00.733Z" }, - { url = "https://files.pythonhosted.org/packages/a2/fa/b8c257bd76b8bd060c3a9151c1fca05e9b9c5e3af5d0f549c0356f6d143d/pandas-3.0.3-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:0d589105b3c14645af1738ff279b2995102d8f7a03b0a66dc8d95550eb513e04", size = 10787242, upload-time = "2026-05-11T18:54:03.564Z" }, - { url = "https://files.pythonhosted.org/packages/54/eb/f19206ffb0bf1919002969aa448b4702c6594845156a6f8050674855aac3/pandas-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:13fc1e853d9e04743d11ba75a985ccbc2a317fe07d8af61e445a6fd24dacd6a6", size = 10436369, upload-time = "2026-05-11T18:54:06.311Z" }, - { url = "https://files.pythonhosted.org/packages/fd/24/c7c39fb4fe22b71a0c2d78bf0c585c600092d85f94f086d2b3b2f6ca27e2/pandas-3.0.3-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:819959dab7bbd0049c15623fbac4e29a191b9528160a61fb1032242d8ced2d9c", size = 10358306, upload-time = "2026-05-11T18:54:09.085Z" }, - { url = "https://files.pythonhosted.org/packages/16/ec/dd2a9eb7fa1204df88c0864164e35b228ac581062ac612ba0a67fd812e4c/pandas-3.0.3-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:60ae316d3fd75d1858d450d0db0103ea2be3e7d4a95ec2f064f7e2ae63f7b028", size = 10758394, upload-time = "2026-05-11T18:54:11.956Z" }, - { url = "https://files.pythonhosted.org/packages/95/6e/00c61ea8e85b4f6d8d35e11852a1a4998fc7fafc91c6a602d1cc9c972d64/pandas-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:bd3a518890b400d32f9023722dc9a9a5c969f00b415419a3c06c043f09bb5d7d", size = 11375717, upload-time = "2026-05-11T18:54:14.539Z" }, - { url = "https://files.pythonhosted.org/packages/31/89/8fc1c268969fac43688d65fd92e67df24bd128d53cb4d2eee534cd307399/pandas-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:9c39be2d709d01fa972a0cabc522389fceca4f3969332ba25a7d6c5802cf976a", size = 11828897, upload-time = "2026-05-11T18:54:17.146Z" }, - { url = "https://files.pythonhosted.org/packages/56/3b/e7d20dea247a3e6dc0bd8a6953854afbedc03951def4e7371e05e7263e25/pandas-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4db8c527972a821cf5286b40ccc57642a39bc62e62022b42f99f8a67fca8c3a1", size = 10900855, upload-time = "2026-05-11T18:54:19.72Z" }, - { url = "https://files.pythonhosted.org/packages/0f/54/68a0978d1ef8502b8492099beaa6e7a0c1b32e3b5d4f677f5810cb08711c/pandas-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:b2c95f8bfc1ee412bf482605d7bfd30c12d1d26bd59fdd91efeef1d4718decb1", size = 9466464, upload-time = "2026-05-11T18:54:22.754Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/be/4f/5f3422a2afec5ffc46308b79e53291365a93748b498ac2e58bead0197916/pandas-3.0.5.tar.gz", hash = "sha256:dca3734d6ab7c906e6730f0788b0a1dbb9f2467731f9711f77995c8e9d62d712", size = 4658219, upload-time = "2026-07-22T22:19:28.819Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/48/ef/f1fd7431d635bf20015489bf0bd69c17fff1018de773540f651455a3916b/pandas-3.0.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2946e77e4a53cd248cbde631a12f0e51c8324ce354c3eba4d20147c1ad6f4282", size = 10397178, upload-time = "2026-07-22T22:17:48.274Z" }, + { url = "https://files.pythonhosted.org/packages/31/b4/0eafac990a431561187694126de01f9b12559549b4d86360c0c4bd870fde/pandas-3.0.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:71ecc8fb7ed1a7aa4392316b5309a6347e8e7f832f38fd897846b3a1457a9298", size = 9990736, upload-time = "2026-07-22T22:17:52.388Z" }, + { url = "https://files.pythonhosted.org/packages/de/21/359880af3ea9b7cb23bea5b51e8e70ef3866c03be09da9a2787e18e330a8/pandas-3.0.5-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b173f5951ff6b8b0ec7675e20dff3c97b7e7a57dfcce387c2d7c5afe87cb7899", size = 10814438, upload-time = "2026-07-22T22:17:54.708Z" }, + { url = "https://files.pythonhosted.org/packages/d1/50/d6cc4d7e508bbccf5d6027314a8312bc7ac73d0ec7f195f53838daafab40/pandas-3.0.5-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2c0cf1dd9b55a22d105fc46c1b489af3bd42264fcba7c66297bf47a9a1d9c78a", size = 11323634, upload-time = "2026-07-22T22:17:56.858Z" }, + { url = "https://files.pythonhosted.org/packages/70/2b/d5f0a8c90dd0ae04e64ba53b871afb796ec026b615086d382ddc2ade729b/pandas-3.0.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0fac0010c75e4efb6b99e249c183a8993ce0dc95c240f9b120a5e67c727b7928", size = 11850860, upload-time = "2026-07-22T22:17:59.1Z" }, + { url = "https://files.pythonhosted.org/packages/5c/30/183aec2e19adf778a98d29b5729a0a68f4cc4ebf9b9c3b70d0297355bcb1/pandas-3.0.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:08d24fe11a17dc33bd6e937dc9c665f9cba08fbdc9f657f405713515febe300d", size = 12411100, upload-time = "2026-07-22T22:18:01.485Z" }, + { url = "https://files.pythonhosted.org/packages/fa/9a/31f4983f191af51ab2a8f2d0c7b33dff3a84da26533f982fff02c2f9e28b/pandas-3.0.5-cp311-cp311-win_amd64.whl", hash = "sha256:b1261758dfb6cf12c3cff8300e21cefad30e7ec709abb4c24ac7318e6a52462a", size = 9968804, upload-time = "2026-07-22T22:18:03.903Z" }, + { url = "https://files.pythonhosted.org/packages/49/97/7886c89a39045c69ad82cbceaf3343810480c8ef49a216319ce8183860a6/pandas-3.0.5-cp311-cp311-win_arm64.whl", hash = "sha256:679f4e85b30ddb1515458ab1e788d3e260eae369b1f78da7a3aa4cac8ebf4a2a", size = 9205447, upload-time = "2026-07-22T22:18:06.134Z" }, + { url = "https://files.pythonhosted.org/packages/1c/54/1dc810ea558d1320b597aa140a514f2fdf1d2ea09c38cf556f13ea712ec9/pandas-3.0.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fa290c16964d4963fbfbc358928239cf3bd755b20e988ce944877def2f44471d", size = 10411717, upload-time = "2026-07-22T22:18:08.307Z" }, + { url = "https://files.pythonhosted.org/packages/68/56/fbe81c09195924d8b7b8d4461a20458fe80a6a5ed6b24f0314da684277e1/pandas-3.0.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c2e26bb46934b8a2ca0c3de1d3d606fc5f6746584791b2db264d58cf370e08dc", size = 9957095, upload-time = "2026-07-22T22:18:10.6Z" }, + { url = "https://files.pythonhosted.org/packages/e0/51/fac252f4a913ed5eabf3c11b880a9e8d5a6c10f0b2129d0462212d238b4d/pandas-3.0.5-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:73fa87b08a7ef706f8aafda39ddaccf2a99047bea62d8c88a0361bcafb2237bc", size = 10485458, upload-time = "2026-07-22T22:18:12.834Z" }, + { url = "https://files.pythonhosted.org/packages/12/98/e976540c1addf70442be7842a18cf70884a964abbf69442504f4d2939989/pandas-3.0.5-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d373ce03ffd84010ed9839fa73672a9c8256990532e158440c0085db7d914b34", size = 10998091, upload-time = "2026-07-22T22:18:15.209Z" }, + { url = "https://files.pythonhosted.org/packages/a4/8c/1f29b5be8d3fc47dd7567eb167fabba2085879b31e0287ce7cba6d3d2ff4/pandas-3.0.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2a29c53d85ea98c5e792c59ef82ee9fbe6ca902c0d0adb6b23f45ef894cd7bf6", size = 11499501, upload-time = "2026-07-22T22:18:17.689Z" }, + { url = "https://files.pythonhosted.org/packages/9d/e2/bd9c98ad2df7b38bde002adde4cdf353519da51881634323b126c55997f9/pandas-3.0.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a5ad3b02ed6bc7d7ae9b70804b2c6aa31827489d150f8e623ce82491b82085d7", size = 12060559, upload-time = "2026-07-22T22:18:20.147Z" }, + { url = "https://files.pythonhosted.org/packages/f3/9a/ffbd852d58bd74a617fe2f8ee6a58a96982271ce41cf981eab22190b4a4b/pandas-3.0.5-cp312-cp312-pyemscripten_2024_0_wasm32.whl", hash = "sha256:b2acb4650527eec6822c3dadb2b771277b65e7dae7a267d4bccf65fd1bb3fbce", size = 7197652, upload-time = "2026-07-22T22:18:22.502Z" }, + { url = "https://files.pythonhosted.org/packages/70/b5/d2d3e9ae73362ba4229651b0ee1455cf78073a1ce585f6ff693782ce263e/pandas-3.0.5-cp312-cp312-win_amd64.whl", hash = "sha256:80a611068e8a3ac23f7398c6c14eb46dc974e5cc9997f653e2dcfd1da74edd41", size = 9831691, upload-time = "2026-07-22T22:18:24.534Z" }, + { url = "https://files.pythonhosted.org/packages/52/51/dea1e89d6a6796b9c43f85a09b484ee03edb8a4c4842e73e200a8c11301c/pandas-3.0.5-cp312-cp312-win_arm64.whl", hash = "sha256:25ff585b972a18ef1fe9ffa3ac6544d9950508aa76832e5147640b6022821e49", size = 9105796, upload-time = "2026-07-22T22:18:27.064Z" }, + { url = "https://files.pythonhosted.org/packages/bf/09/7b95c4a0025227d6f118c4039b423412ac6a982db02864166185d812fbc7/pandas-3.0.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c1c05a767fe8e5b4fe9e1c29806829c582052eaedb9120a3da83ba3f69e24a5b", size = 10385742, upload-time = "2026-07-22T22:18:29.346Z" }, + { url = "https://files.pythonhosted.org/packages/8d/0c/dc78fd8c4da477b4b5e8ad37295af352190d21ef63a9ee1bc071753074cc/pandas-3.0.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b86765f268b56f7e665b93bce9d5df69dee7f99e595cf8fb839483ab315942a3", size = 9932067, upload-time = "2026-07-22T22:18:31.833Z" }, + { url = "https://files.pythonhosted.org/packages/3e/71/3592c055cf44df9808550f9368ceda80ff2b224d355ef73fe251dcda1802/pandas-3.0.5-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c597ecf5616b5c420372c1d4d4c00dbbfba7398bea857dcc984347e1ea48417b", size = 10466756, upload-time = "2026-07-22T22:18:34.195Z" }, + { url = "https://files.pythonhosted.org/packages/e3/70/4363150359f95b4cb4bcbb34ca23572bb5495749a621a8f3d5a1ddfd293c/pandas-3.0.5-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4b11c36e218331d0387cbe3a0a5f75162357a1d92d57b2b08a336ff94b19b2be", size = 10938525, upload-time = "2026-07-22T22:18:36.81Z" }, + { url = "https://files.pythonhosted.org/packages/f7/d0/317e7a0c67c0e69fa905a0161409397a7dc2d46ff611f6ca4803352c042b/pandas-3.0.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cf52e1f61d229496da17dc7ab54acdee627357e7008fd4fecba3d0ba2937fa58", size = 11489303, upload-time = "2026-07-22T22:18:39.287Z" }, + { url = "https://files.pythonhosted.org/packages/f1/8d/36dade89b49e4f9d5cbdbe863772581f98c0c6d78fc39ad4c557f6f2e17e/pandas-3.0.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:db172144bb56422bd157812f3b021eacc255451470b31e2c633c349490a1cfee", size = 11989004, upload-time = "2026-07-22T22:18:42.208Z" }, + { url = "https://files.pythonhosted.org/packages/9c/ba/18c4ec8a746e177da05a9e7a7963781d8ea195780724f854601b6ebd6b78/pandas-3.0.5-cp313-cp313-win_amd64.whl", hash = "sha256:0d298e951f23016ce4699951d044ae6418dbc91bf68cefca0f77666fcbb4e5c6", size = 9826896, upload-time = "2026-07-22T22:18:44.539Z" }, + { url = "https://files.pythonhosted.org/packages/de/ec/28a57266b753799a87b8bc79e7887ac6fd981b8c6d2978a0b7e7b6bd708c/pandas-3.0.5-cp313-cp313-win_arm64.whl", hash = "sha256:66266d3442a5e8b3c90274c2b8b230bee42dd1c286bc822cc2f9f2c7e12b883e", size = 9094790, upload-time = "2026-07-22T22:18:47.468Z" }, + { url = "https://files.pythonhosted.org/packages/51/2f/cf6aae281264f4463f0875bcbb15fd2bb6d291cc535187dad1732475e4a9/pandas-3.0.5-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:2f264fc46911cc8131a7322a16199bbf8e353d27c10bb211f5bd0c814324dc36", size = 10390034, upload-time = "2026-07-22T22:18:49.818Z" }, + { url = "https://files.pythonhosted.org/packages/06/ec/5189518c7a7659c4bdcc6b1eb32c46c6f3c86b0661ffd84143d1112c7732/pandas-3.0.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:53730687fcd161883b24e10411c06d6a4c0f2275d2faf3bb2bc25deb4ba8007c", size = 9980065, upload-time = "2026-07-22T22:18:52.249Z" }, + { url = "https://files.pythonhosted.org/packages/ea/f1/598503ce8d7e3c35601e0747ba288c7864baae66380725bc12f13f884dfe/pandas-3.0.5-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:960d3ebcf249f75206899fcd2c6de53f736b7265759ced0d3e559df0b8b709b0", size = 10545532, upload-time = "2026-07-22T22:18:54.813Z" }, + { url = "https://files.pythonhosted.org/packages/fa/de/ceae2adf7034e07e9910299fe412e1819c4f0dd520700a888bcb03625448/pandas-3.0.5-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e94c2c5ca43bd3ca32bf64d32308887b65e5f9bfd8023ea52755107a999f93b", size = 10963120, upload-time = "2026-07-22T22:18:57.42Z" }, + { url = "https://files.pythonhosted.org/packages/66/25/86e0f4451874eb79e688deeebe3c451fec4557f8952005818d800ee8ac7e/pandas-3.0.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e819dd5f62966b481a8cb649d3299ebd886a1ea91ed5a99bf7ce77c98d18ab94", size = 11563178, upload-time = "2026-07-22T22:18:59.729Z" }, + { url = "https://files.pythonhosted.org/packages/f3/45/8643daa3b4147e433adfcccefdd0380d3aad79d86b15d8999730fe1944d5/pandas-3.0.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:3c5ed2e7c06e91d340dfd091d7934f9bc82e4a36b95f647f090b9d1c9ac649da", size = 12028708, upload-time = "2026-07-22T22:19:02.164Z" }, + { url = "https://files.pythonhosted.org/packages/96/58/ad979ae617615576e8aafd569c9d4b62f1191d896e38f51d66ba06f3b89a/pandas-3.0.5-cp314-cp314-win_amd64.whl", hash = "sha256:cd8f7c6dc98527058ee6264219343f5392240a6f1bfa654fc5d79023020d0c92", size = 9951806, upload-time = "2026-07-22T22:19:04.596Z" }, + { url = "https://files.pythonhosted.org/packages/69/32/7ac03886b304049a9d2625ee88f59af760d8a93bd30ed9239bce7b9869a8/pandas-3.0.5-cp314-cp314-win_arm64.whl", hash = "sha256:5183427f5a8156d480f30333777bc978be93650a49a7c01db26adffe95b31e85", size = 9238297, upload-time = "2026-07-22T22:19:06.836Z" }, + { url = "https://files.pythonhosted.org/packages/be/ed/1d1f2ee5547d5167face2376d11c8b2a4c7bfff5a416ee7a9046891fab1e/pandas-3.0.5-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:303da736987d481074ca720ada325f8bd80c64ebc2d45ed79b29df3aaa4a26ca", size = 10849690, upload-time = "2026-07-22T22:19:09.391Z" }, + { url = "https://files.pythonhosted.org/packages/57/55/17e17152e98fbb0c4b1e562bc65387a2f20a80db0f4a86bf8d3a0e4248d4/pandas-3.0.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:3b2801bbb049d0136f6c213eae02b5fca969384fc2064dd728d8620552aa49da", size = 10509945, upload-time = "2026-07-22T22:19:11.773Z" }, + { url = "https://files.pythonhosted.org/packages/88/90/817d44dbf83facf9556f33576d9af0a241981e7bb5c00606c0bcb5df8dda/pandas-3.0.5-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cce3a9d11d2b1f82c69a27ec1f4948a170e2c403c4bbfa8cca62e3fdebe2ef3a", size = 10392197, upload-time = "2026-07-22T22:19:14.024Z" }, + { url = "https://files.pythonhosted.org/packages/f1/da/889f00c0a6f5aa1545add70abbf01502dff87ab577adb855bd631c54d2f2/pandas-3.0.5-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ef01af4d8dc6cd2c8d6c7736f149574ef93fe043811eeb5e445f2647154b5040", size = 10862726, upload-time = "2026-07-22T22:19:16.351Z" }, + { url = "https://files.pythonhosted.org/packages/bc/98/f1e934fb3c98fce859c6147c6785816c7b5b9ab7821115c5d8c4de9842b9/pandas-3.0.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e2759e890db96dfcffdbd9b86c3c2cb6afaf58def482820317e06163ec1066cd", size = 11414864, upload-time = "2026-07-22T22:19:18.981Z" }, + { url = "https://files.pythonhosted.org/packages/fe/be/d448af7d657d82e1888dd8551f79c6d6fb161080b5b9752d84d910ec2319/pandas-3.0.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:b58b1b39d46a5862e3fb18f50d1a201398619d16a0f9f73f57eea5583cf0e63c", size = 11925105, upload-time = "2026-07-22T22:19:21.515Z" }, + { url = "https://files.pythonhosted.org/packages/29/c1/ccb4238212c8c4f496c584f3044d94e0c030ed8e1d68999db46c91c2242f/pandas-3.0.5-cp314-cp314t-win_amd64.whl", hash = "sha256:1c10461f6eeb35d8f05b6184c65c8b9991663b66c46b1d559b682cb34ae7c6ea", size = 10387612, upload-time = "2026-07-22T22:19:24.257Z" }, + { url = "https://files.pythonhosted.org/packages/d2/cf/6a51b2c38980e04c279fd2fa908a1b0982064e860444acfca4ec2e2c8359/pandas-3.0.5-cp314-cp314t-win_arm64.whl", hash = "sha256:3c5015fd1730fbf883647e88068176c839c102cea883ba1769a6f4593bfc1f8c", size = 9509776, upload-time = "2026-07-22T22:19:26.694Z" }, ] [[package]] @@ -2141,26 +2139,26 @@ wheels = [ [[package]] name = "prek" -version = "0.4.10" +version = "0.4.11" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7c/54/edc21e275f9fa3540d4d98cf349c2de11621d6729cc401bb7aedf563609e/prek-0.4.10.tar.gz", hash = "sha256:db3122f4e780eb4587635e6a83df881caf2dbb1eb7799d1cca51158216d6f33b", size = 502565, upload-time = "2026-07-16T10:13:00.788Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c4/1a/73b6dae5ce7e997cb35a69bfe1d25a798e85fa3d2eabf95324563f461b30/prek-0.4.11.tar.gz", hash = "sha256:4a14cb9bbae850605ae3904fbdbb12f0e00c12455efaa2266da8fb8e5c0350d7", size = 516254, upload-time = "2026-07-24T17:05:35.107Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/db/e7/5a63528ba7b95b64f38db3e253aed49ee8e5e8ba16589889d2b7f809edb7/prek-0.4.10-py3-none-linux_armv6l.whl", hash = "sha256:023f302741d79301346c3088ba43a9592aff0ecdbe5ddc3019fa9b1183319c5e", size = 5694609, upload-time = "2026-07-16T10:12:26.352Z" }, - { url = "https://files.pythonhosted.org/packages/0b/ef/ee9e6bf9a5ce242e9e4e66ac4e2e9042a0f6fd9f367cee18ad404456e93d/prek-0.4.10-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:72adc707e16f97564bbae08d22b222ac3bb2491f8fbfb5a0754f80d472c28a71", size = 6044037, upload-time = "2026-07-16T10:12:28.539Z" }, - { url = "https://files.pythonhosted.org/packages/68/7e/da08cc39e5348ccb9234e63a21ee56861f72e8497d6a78f0db1ccae6515d/prek-0.4.10-py3-none-macosx_11_0_arm64.whl", hash = "sha256:04c9321957e1b32e1fc7cf60bb4f90bba3761f8659d5551ed04f96e25596de49", size = 5535983, upload-time = "2026-07-16T10:12:30.691Z" }, - { url = "https://files.pythonhosted.org/packages/30/c6/0486a35bb687a9beac7a5810bd1104c6da56d469b30b1eeaeefd03c99da2/prek-0.4.10-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:e66ccf6c5e4ebadd05cd98cb338d7f553e4d27aa243cf91279c5a569b3cdccc7", size = 5862085, upload-time = "2026-07-16T10:12:33.042Z" }, - { url = "https://files.pythonhosted.org/packages/52/39/277fe17ae1f121e532e3942456f5a6d01ddacfbc550e481dcb359be7a1b0/prek-0.4.10-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:63f9061d75a50ef0ca92c4b596ad352937a845df80758244950e513b27e9e18f", size = 5605697, upload-time = "2026-07-16T10:12:35.498Z" }, - { url = "https://files.pythonhosted.org/packages/ad/a1/08354af3e000f2656fad086690d834eab6c04631ff41313a219ea6232199/prek-0.4.10-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c2ff7110e4bfaafbbab13c2893a337081aca61ed797f14b6b224d2ea9741eef", size = 6034111, upload-time = "2026-07-16T10:12:37.545Z" }, - { url = "https://files.pythonhosted.org/packages/e4/74/4702396c8d486132e5ce009ab56a0b37f50cb6866830d371f2617b7bdfdc/prek-0.4.10-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2b696a05542e79aa27bcce68d1792e77f4fe6f9c6b012b34d74d62f964f3c72d", size = 6787203, upload-time = "2026-07-16T10:12:40.031Z" }, - { url = "https://files.pythonhosted.org/packages/90/29/b5d5d6fb87ebd64b37471e3e79761de9983f85e14d69c522efe7af6620ce/prek-0.4.10-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:431b44d6054e72815b4b05e1173596dfd02a7f7461211d40a2e3117e414642ad", size = 6261333, upload-time = "2026-07-16T10:12:42.216Z" }, - { url = "https://files.pythonhosted.org/packages/94/d6/54ba696d19f7efdc184093353cce713a850aef9c3556e23faeecafa22e94/prek-0.4.10-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:ccbd2b4fd1df790087ba18b4506f680471922a5f13714f19801568434a040dee", size = 5867761, upload-time = "2026-07-16T10:12:44.329Z" }, - { url = "https://files.pythonhosted.org/packages/be/7d/3975098aa2baaabfc10f99f9fcf78045c4f10851beed8e9812b6a2688eab/prek-0.4.10-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:479e7480b447191aa5c6ed67e80f081d0f5ee4e878b140f4d2cee44165395f1c", size = 5714412, upload-time = "2026-07-16T10:12:46.297Z" }, - { url = "https://files.pythonhosted.org/packages/97/c0/3e0aac190fe95fdef98526343559b61d4d9fd54444c8c9137ba02412afe1/prek-0.4.10-py3-none-musllinux_1_1_armv7l.whl", hash = "sha256:0bb7451025cbd2b68e480a13cf665d7a5c87c8b87bf18549a78985c17df817ed", size = 5578145, upload-time = "2026-07-16T10:12:48.261Z" }, - { url = "https://files.pythonhosted.org/packages/d7/44/7b26035534204b8b8a9d5e625479201e616413d287262f557cb32e1f8d77/prek-0.4.10-py3-none-musllinux_1_1_i686.whl", hash = "sha256:4fb047e5776676805794574b2d7b178cb3ab536793aadf172419fcda56b34a57", size = 5889245, upload-time = "2026-07-16T10:12:50.818Z" }, - { url = "https://files.pythonhosted.org/packages/7e/6c/178a9d768876b4211a1bf63907fe308ae02d173639bcf41cea3c5eed35c1/prek-0.4.10-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:08318818d19caf79643babb89f872c92fda134a622b4731df1d6ed61e29d2d26", size = 6372849, upload-time = "2026-07-16T10:12:52.952Z" }, - { url = "https://files.pythonhosted.org/packages/4d/84/d5f5ac8193602883f9dd1d675d9d4084e34fbe3ed2ef50a0c336d8a53d8f/prek-0.4.10-py3-none-win32.whl", hash = "sha256:092872714dcde480a662bbdd98b980b248c2d3e10543d4d53a3a58cc9e5b35b0", size = 5413005, upload-time = "2026-07-16T10:12:55.113Z" }, - { url = "https://files.pythonhosted.org/packages/41/63/9e648fda10bc02c9b6ba305f93b6a6e4fd37d23d13a269a9d2d6bb44eaa1/prek-0.4.10-py3-none-win_amd64.whl", hash = "sha256:3d323a18d0f8c50e474a8fa29fb93bd2db680116d8afb19b76e72ad4667f58e6", size = 5799075, upload-time = "2026-07-16T10:12:56.963Z" }, - { url = "https://files.pythonhosted.org/packages/22/74/b34d8c80cec8dccc7b922c75b9dca62b18b603b5ed2eea93c9d7c2928d2d/prek-0.4.10-py3-none-win_arm64.whl", hash = "sha256:5e93865ef96756c4a26f37ece04ad514abbc19ae6a23ed1a507b6314e6a0d2fb", size = 5563955, upload-time = "2026-07-16T10:12:59.07Z" }, + { url = "https://files.pythonhosted.org/packages/8e/d7/a00b2de492a80e99b1698e72c1d196ac3ed544dc7ee0ada261ac066e78e0/prek-0.4.11-py3-none-linux_armv6l.whl", hash = "sha256:3830cb7cc47e837888b8b464ecb21355a69235cfacef0fd89101e17f09345d63", size = 5770511, upload-time = "2026-07-24T17:05:12.829Z" }, + { url = "https://files.pythonhosted.org/packages/c1/1e/f97c74defcd5d5645888cb99fcdd9b8b48cc7247cf31e64414d106d56d66/prek-0.4.11-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:45facadf9c2332b28e6ab2744312ae0275a93ab5a437da8bcc53a8c7260cb4b0", size = 6118049, upload-time = "2026-07-24T17:05:14.451Z" }, + { url = "https://files.pythonhosted.org/packages/d9/92/8367d26421ee6fe6019a63928fb0ed31179cd0d6199879c524f89ef4c95c/prek-0.4.11-py3-none-macosx_11_0_arm64.whl", hash = "sha256:2f8a194b1d00d24dff8baff691c99e2668339da2da3482b4ee99c1a0f2409378", size = 5601478, upload-time = "2026-07-24T17:05:15.81Z" }, + { url = "https://files.pythonhosted.org/packages/e1/6f/7617c9b87afaadede4167720aae7ef12d7e51167db903bc8fbac0cadef7b/prek-0.4.11-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:21d93e6d76bf3d7a9bb70c6ac86ef372adaee50068c45749e6b9e1c2ac4ec939", size = 5932071, upload-time = "2026-07-24T17:05:17.217Z" }, + { url = "https://files.pythonhosted.org/packages/ef/41/6796a4011b04212333259064aa885a71d03d9581ba9bff52db3ce58d1f06/prek-0.4.11-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7fb07cde2d2156efa6980122b3f13dc88f20c84b933c6205675d0f1e7be2cde8", size = 5677617, upload-time = "2026-07-24T17:05:18.658Z" }, + { url = "https://files.pythonhosted.org/packages/03/5f/3e339901f8460b6073313619b4fd9bf4135e48430695c53640b83ace88ea/prek-0.4.11-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6b7f5e446d2aca739bd18b380578e9c78bb4c086299abc3e167d51c47840c56f", size = 6106370, upload-time = "2026-07-24T17:05:20.219Z" }, + { url = "https://files.pythonhosted.org/packages/8e/4e/94e24b5c1910ec15692ecaf33d0d8ef0d02a02a8b5e40d3c976396880cba/prek-0.4.11-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b7059d640e595d098600e2d97af961f46292b4112670edbe632de84f6828389e", size = 6884342, upload-time = "2026-07-24T17:05:21.587Z" }, + { url = "https://files.pythonhosted.org/packages/a2/7c/fc0daa033dcafe74990c00af2da1e16922790b9c1b8da7e8eebf1123838b/prek-0.4.11-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85a4df33998fcac878bce3b2c624e1caeb9609f3d562c640702c1234ed815daf", size = 6331365, upload-time = "2026-07-24T17:05:22.87Z" }, + { url = "https://files.pythonhosted.org/packages/d7/36/49f152b8f539930e9685cff0509695ce4054abcecc22f4c16c4e1e5c23d0/prek-0.4.11-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:866991f387527c5f880ce2cc3ddea9b33cc5b986881f8c7f92524cf0969c1350", size = 5939075, upload-time = "2026-07-24T17:05:24.249Z" }, + { url = "https://files.pythonhosted.org/packages/4f/fe/7b097af9161edae7ddedcc9f5cda4a5f31346a492ce3f92583d96c46f628/prek-0.4.11-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:247e5d8740e137ebdf24fa96f01a95b2d2f1892e956a1ab00c7b1474a59ebcc0", size = 5799029, upload-time = "2026-07-24T17:05:25.652Z" }, + { url = "https://files.pythonhosted.org/packages/49/63/dc955ff99e1002d3cd375b21467c87046bc41deddfce86d898240757b151/prek-0.4.11-py3-none-musllinux_1_1_armv7l.whl", hash = "sha256:603ba9f2fd9d666dddb3ae190a25a5c55091b843cde90ed52e0a1116f50d4062", size = 5651211, upload-time = "2026-07-24T17:05:27.027Z" }, + { url = "https://files.pythonhosted.org/packages/8f/b8/bf2139ec25eefb5afef43afac7620c5181f2c2661f220598beacb1771207/prek-0.4.11-py3-none-musllinux_1_1_i686.whl", hash = "sha256:f2682ece3c5fc7201106c4fdd84b0587ccea4b8a2ecefa3e94d3074d2841f1df", size = 5954784, upload-time = "2026-07-24T17:05:28.391Z" }, + { url = "https://files.pythonhosted.org/packages/fc/29/3fe5990aee1bd7c4d50a03358ad867c5e912d9510aafb83a359c7347e5fa/prek-0.4.11-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:22721d30394192931fecc80d6fc6dd47e8ddf8db8b9693805aba8ec0f50087ec", size = 6448916, upload-time = "2026-07-24T17:05:29.837Z" }, + { url = "https://files.pythonhosted.org/packages/0f/fb/abddacf43738302242ecd237236c7c80cd7c1d27545e16e803770aee76e9/prek-0.4.11-py3-none-win32.whl", hash = "sha256:8b093e7624522146049e994d5cf283d01d71632b638620b79ae0f96afeaa2624", size = 5483539, upload-time = "2026-07-24T17:05:31.228Z" }, + { url = "https://files.pythonhosted.org/packages/00/1e/c293f7a15cb93963c4be36a02e144b93f43906bc02644a3f04e5708e7453/prek-0.4.11-py3-none-win_amd64.whl", hash = "sha256:5a3d7c80b970b456e5f1bcec8382008ee1ae6a3f324a6b9bb4ff7e666ab0f3c4", size = 5861119, upload-time = "2026-07-24T17:05:32.479Z" }, + { url = "https://files.pythonhosted.org/packages/cd/0c/05fe6eb9d6a54d0e02dfa8cc5ad6f86869bf953419ec15909a32466a28ee/prek-0.4.11-py3-none-win_arm64.whl", hash = "sha256:e7b0df37ce05e45a14a9da39ab104691474d72f139bf4f6c860f754763a322cb", size = 5626386, upload-time = "2026-07-24T17:05:33.813Z" }, ] [[package]] @@ -2174,26 +2172,26 @@ sdist = { url = "https://files.pythonhosted.org/packages/cf/9c/fb5d48abfe5d791cd [[package]] name = "prompt-toolkit" -version = "3.0.52" +version = "3.0.53" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "wcwidth" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a1/96/06e01a7b38dce6fe1db213e061a4602dd6032a8a97ef6c1a862537732421/prompt_toolkit-3.0.52.tar.gz", hash = "sha256:28cde192929c8e7321de85de1ddbe736f1375148b02f2e17edd840042b1be855", size = 434198, upload-time = "2025-08-27T15:24:02.057Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/ea/39b988c938f75cb75d7045b5c69f8bfed47ee2152c8837fb403de29d6fb8/prompt_toolkit-3.0.53.tar.gz", hash = "sha256:9ec8a0ad96d5c56148b3f914aa79c1564c3fde5d2e6b876e7bc327e353cf8fa6", size = 435492, upload-time = "2026-07-26T20:56:14.758Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl", hash = "sha256:9aac639a3bbd33284347de5ad8d68ecc044b91a762dc39b7c21095fcd6a19955", size = 391431, upload-time = "2025-08-27T15:23:59.498Z" }, + { url = "https://files.pythonhosted.org/packages/54/6f/84908cad2d6aa5144abcf7b42709fe4fdb459bc640ec7ac5786e7693dabc/prompt_toolkit-3.0.53-py3-none-any.whl", hash = "sha256:01c0891d7f9237d5e339f7d3e42cdae80b7534abb1c7c0e3352efba6231492f2", size = 392288, upload-time = "2026-07-26T20:56:12.512Z" }, ] [[package]] name = "proto-plus" -version = "1.28.1" +version = "1.28.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "protobuf" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/87/44/767757fd2cdd4a60d7e4440d9f7b491d6131103d313638d2c03e06c268fb/proto_plus-1.28.1.tar.gz", hash = "sha256:832e68e7fe064cf90ab153b6e5eb935b27891bb89aaeb68b115e9b702f6cb168", size = 57166, upload-time = "2026-07-08T17:04:02.367Z" } +sdist = { url = "https://files.pythonhosted.org/packages/73/3e/29e0d6a2c5adde6ab5772253fd16ab346324026b89a66e354689c86d0584/proto_plus-1.28.2.tar.gz", hash = "sha256:26d843eb99c1e32fdf1d20ff0faae56607f7748fe774acf9ecd5cfe6c6472501", size = 58063, upload-time = "2026-07-22T16:28:29.119Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6a/34/2f2b57dbfd145b995a29847a16b0903fce5ef6ad3c7aad740a609c5d3678/proto_plus-1.28.1-py3-none-any.whl", hash = "sha256:6660f5f1970874bdcfc3088b435188a36a37bd3596668f7d726417c4ae8cfbed", size = 50408, upload-time = "2026-07-08T17:03:34.532Z" }, + { url = "https://files.pythonhosted.org/packages/9d/84/4e9a53a062d4073c74897a6bd20fff74d55307341b3e85c081002462b3ef/proto_plus-1.28.2-py3-none-any.whl", hash = "sha256:b874236fcac2358f601e4330bcb76cb8b89c851303ccf4078408b3d4774d1c52", size = 50693, upload-time = "2026-07-22T16:28:24.059Z" }, ] [[package]] @@ -2515,11 +2513,11 @@ wheels = [ [[package]] name = "pytz" -version = "2026.2" +version = "2026.3.post1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ff/46/dd499ec9038423421951e4fad73051febaa13d2df82b4064f87af8b8c0c3/pytz-2026.2.tar.gz", hash = "sha256:0e60b47b29f21574376f218fe21abc009894a2321ea16c6754f3cad6eb7cdd6a", size = 320861, upload-time = "2026-05-04T01:35:29.667Z" } +sdist = { url = "https://files.pythonhosted.org/packages/fb/48/fb042503b6ca6cd271261dc559fd6432f7d8c713153e9ec5c591af4dfc1c/pytz-2026.3.post1.tar.gz", hash = "sha256:2211d3fcf9a797d3405cac96ac7f61d80e6a644f72a3309607282fe8a2010c5d", size = 319745, upload-time = "2026-07-25T15:12:07.385Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ec/dd/96da98f892250475bdf2328112d7468abdd4acc7b902b6af23f4ed958ea0/pytz-2026.2-py2.py3-none-any.whl", hash = "sha256:04156e608bee23d3792fd45c94ae47fae1036688e75032eea2e3bf0323d1f126", size = 510141, upload-time = "2026-05-04T01:35:27.408Z" }, + { url = "https://files.pythonhosted.org/packages/0f/7b/39c34ca613b0b198cb866466651b26b045e2009864c5183c979a3b83f383/pytz-2026.3.post1-py2.py3-none-any.whl", hash = "sha256:dd95840dd199baea12d9cc096a1d452caa6596a1c1e4b5f3dbd1541855d5e815", size = 508283, upload-time = "2026-07-25T15:12:05.782Z" }, ] [[package]] @@ -2702,39 +2700,39 @@ wheels = [ [[package]] name = "ruff" -version = "0.15.22" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/3a/06/ae069393fc66e8ff33036d4b368003833bf6e88ccf182e17e7a2f1c754fd/ruff-0.15.22.tar.gz", hash = "sha256:3f15175b1fb580126f58285a5dae6b2ea89000136d980c64499211f116b54809", size = 4785063, upload-time = "2026-07-16T15:14:13.244Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/23/18/ee54b7ae1e121be7a28ea6da4b67564ebb0530e183a54415ab7e3bcd2c4e/ruff-0.15.22-py3-none-linux_armv6l.whl", hash = "sha256:44423e73493737f5e7c5b41d475483898ff37afcdae38bc3da5085e29af1c2d8", size = 10781258, upload-time = "2026-07-16T15:13:19.452Z" }, - { url = "https://files.pythonhosted.org/packages/2f/d2/2520cb14761ddbeaf57642a76942fc36adcbdbe53b4532241995f6fc485c/ruff-0.15.22-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b82c6482946e9eda7ff2e091d25b8bad3f718684e1916d41bd56873cee05b697", size = 10999477, upload-time = "2026-07-16T15:13:23.318Z" }, - { url = "https://files.pythonhosted.org/packages/c9/10/74e53572aa758dfaa678c2a2646b5c5515d884b7ca56be4d2ce03ca4b560/ruff-0.15.22-py3-none-macosx_11_0_arm64.whl", hash = "sha256:11c1c715af53a09f714e011106bffc419751ec8232fcb5da42173284ea3fec6f", size = 10466716, upload-time = "2026-07-16T15:13:26.162Z" }, - { url = "https://files.pythonhosted.org/packages/1e/cc/44eaaf0844e028182f2d0a8f2190d0f359159aed0a9e5ab861d892f1ae2a/ruff-0.15.22-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:742a29cf29bddb7c8327895d6a10e0e6c5b38a96dd407af9b5d0857f809c0576", size = 10892644, upload-time = "2026-07-16T15:13:29.229Z" }, - { url = "https://files.pythonhosted.org/packages/9f/21/8edf559014d2b0f82beea19cfb713993ad802ccda16868769979c6090a84/ruff-0.15.22-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:72af58b951b0ae395935ae79763dc349bc0eb706319d28f7a33ad2cfb3cfc178", size = 10576719, upload-time = "2026-07-16T15:13:32.35Z" }, - { url = "https://files.pythonhosted.org/packages/bf/1e/3a13abd392a3b50b62e5938a831f9ab6e588358cacad5c18545b716d2182/ruff-0.15.22-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:62d425005c1835eb24e2ee4161cb90e8db263415f4a71c8c72c33abaa6c0c224", size = 11376494, upload-time = "2026-07-16T15:13:35.958Z" }, - { url = "https://files.pythonhosted.org/packages/bf/3e/422d3d95bcf04dd78e1aeac22184d4f9a8fb2c01865d39d44618484a0317/ruff-0.15.22-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e8b9b3f8779a4f08c969defc3c8c35abffaa757e601ed5ae66d6d1db6519969a", size = 12208370, upload-time = "2026-07-16T15:13:39.185Z" }, - { url = "https://files.pythonhosted.org/packages/1e/91/5d065a0e0a02bf4813f5119ad278462eed081d2b832eb7c021ade0ec9e65/ruff-0.15.22-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1e0dd1b2e4d3d585f897a0d137cbf4eaf6223bef4e8ce34d6bb12556c5f9249e", size = 11581098, upload-time = "2026-07-16T15:13:42.132Z" }, - { url = "https://files.pythonhosted.org/packages/f6/f9/a0d4871d12fae702eb1f41b686caf05f1f8b124dc6db6f784f53d74918fa/ruff-0.15.22-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:365523eb91d9224e1bcb03b022fbf0facb8f9e23792a2c53d9d4b3924bdbdebb", size = 11399422, upload-time = "2026-07-16T15:13:45.2Z" }, - { url = "https://files.pythonhosted.org/packages/18/80/c843a5176cddbceb0b7e8dd41cf9993490796c1c469348d384f5a5c13c56/ruff-0.15.22-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:fabfd168afdf29fee5be98b831efa9683c94d7c5a3b58b9ce5a2e38444589a74", size = 11381683, upload-time = "2026-07-16T15:13:48.46Z" }, - { url = "https://files.pythonhosted.org/packages/d4/00/8485de0ae92239438a36cfc51350db9b9e85c9ebdfaea91b18e422706662/ruff-0.15.22-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:225dbf095a87f1d9f90f5fd7924d2613ee452a75a4308c63a8f50f761787aa7c", size = 10850295, upload-time = "2026-07-16T15:13:51.655Z" }, - { url = "https://files.pythonhosted.org/packages/fa/91/24977ec2ec72eaf15e4394ace2959fdff2dd1e14f03e005e838023407169/ruff-0.15.22-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:1877d63b9d24ed278744f1523fd11b85540566d54641f97c566d7d9dc5ca5296", size = 10579640, upload-time = "2026-07-16T15:13:54.79Z" }, - { url = "https://files.pythonhosted.org/packages/9c/47/9b51216951974df1f263ac19da550d34252e0ed7218c25f10c5ef9ed7517/ruff-0.15.22-py3-none-musllinux_1_2_i686.whl", hash = "sha256:a1606c510bd7215680d32efab38965f7cdec3ef69f5170a3f4791404ffdd5262", size = 11105077, upload-time = "2026-07-16T15:13:57.915Z" }, - { url = "https://files.pythonhosted.org/packages/c2/47/20e9d4a3b8016778acea5fc32bb50d35d207500a17ddb529ffa6996feef8/ruff-0.15.22-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:630479b18625f5ffc373f77603a22a9f8ac0acd7ff0501178b5db28ec71e9c64", size = 11490980, upload-time = "2026-07-16T15:14:01.032Z" }, - { url = "https://files.pythonhosted.org/packages/4d/76/3f72d8fc38c1cb77b38c56a70da9d0c17700cc1cc50f9649c9d3c8f5ba71/ruff-0.15.22-py3-none-win32.whl", hash = "sha256:e5ba0e4a13fd14abbed2a77b517a3911290c6c6c59ef67784328d1668fab76cf", size = 10789165, upload-time = "2026-07-16T15:14:04.16Z" }, - { url = "https://files.pythonhosted.org/packages/cb/46/4965251734c2b6fcdca1b1b187d20bcac3af0ee5b083b89c910bb961ce3a/ruff-0.15.22-py3-none-win_amd64.whl", hash = "sha256:9be63ba1eb936acd2d1342fb8337c356353706fce233b2a15a09a97037e6acde", size = 11938297, upload-time = "2026-07-16T15:14:07.316Z" }, - { url = "https://files.pythonhosted.org/packages/57/c9/e69b1ff4c8b69093ef08b8919ab767af0569666865b39c30a8795d88d3c6/ruff-0.15.22-py3-none-win_arm64.whl", hash = "sha256:e1168075b72158510839f250027659cdd78476f40507dd517892304c41318661", size = 11298172, upload-time = "2026-07-16T15:14:10.51Z" }, +version = "0.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4d/94/1e5e4967626faf12fa56999cd6222dff6992ceb086ad7945756baf70c7a7/ruff-0.16.0.tar.gz", hash = "sha256:e460aafd5495ec89efaa6ced2e4a9a581116451e1c88b9d37ef497e0f8e93982", size = 4790557, upload-time = "2026-07-23T19:11:30.981Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4b/81/1c8818fee7ce1a04cd7d1b3172e0a8f8e4f1dc4feb7fc390e16daa8af323/ruff-0.16.0-py3-none-linux_armv6l.whl", hash = "sha256:e5115729eb08c585e5121978ba5d5b60caeae394ce21b9fb5e6cd33a1c6c9b1e", size = 10754633, upload-time = "2026-07-23T19:10:46.415Z" }, + { url = "https://files.pythonhosted.org/packages/23/df/beaf59c09d68db84304d555f188b276a77132a5d5b0b67a5c762aa143628/ruff-0.16.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:3c954b1d580bfa035b41654f7858cc7e71d5fc3ac5b723dd62bd9133830ed522", size = 10969164, upload-time = "2026-07-23T19:10:50.271Z" }, + { url = "https://files.pythonhosted.org/packages/42/ce/741cd197496a1abbf51352710fd15ed995d2a2be87189c1da26a450d6e83/ruff-0.16.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:e01c21d10eb1b29f47b7454e1f4056db9a3f0260c646aa88457c610291db9f81", size = 10488846, upload-time = "2026-07-23T19:10:52.639Z" }, + { url = "https://files.pythonhosted.org/packages/52/2a/a2db8e88cade358f5cdcb05674a917751074109315d014eb6352d9a893f7/ruff-0.16.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6e364e5ed22ed8dc05082fd78e35308618260907ac2d3c1d637b2e682415b6c9", size = 10889729, upload-time = "2026-07-23T19:10:54.89Z" }, + { url = "https://files.pythonhosted.org/packages/42/65/62a771694ebd63029dc953e27dbad40e1588bd4860ff9fe881018fddaa49/ruff-0.16.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d327b8fc113a1d4421a04f3839d3752057c8dd1ee320223a6f3f52d04ada462a", size = 10568275, upload-time = "2026-07-23T19:10:56.993Z" }, + { url = "https://files.pythonhosted.org/packages/3f/e2/ced249fe8af5f086c5c58cc21cc3356d50f32f7401c5df87050c999620a7/ruff-0.16.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9b50c55e263103586b3dcf5f73d479eb8cb5fdb6098fec59a62891dab653717", size = 11385112, upload-time = "2026-07-23T19:10:59.615Z" }, + { url = "https://files.pythonhosted.org/packages/87/0b/05154977a8fd69eeb6c103271f55403bfd8711f5c0f8ed07489d95a504e7/ruff-0.16.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0ff4a79ce3ec0172f3241943835de1c4cb4e2dcd07f0f8c2d02603dbbbee4b17", size = 12207008, upload-time = "2026-07-23T19:11:02.154Z" }, + { url = "https://files.pythonhosted.org/packages/fb/29/98225831a3a1eab0e02f4acc6ca6559a98611dcc68b6965ff4b7234627c1/ruff-0.16.0-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e95c448fca1fb2a18372a9440926c5a6ee789639bb975c72e7ae6d0b04218ab4", size = 11650842, upload-time = "2026-07-23T19:11:04.557Z" }, + { url = "https://files.pythonhosted.org/packages/91/66/6bd3cf90500653d55dc0ffc8507aa8300bd49d0214b2e8cb4d3fef2943ba/ruff-0.16.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f11a8d11010301d0a398a2fdef67691feca7294da6aef55e2150e8fa2cd520b", size = 11400718, upload-time = "2026-07-23T19:11:09.233Z" }, + { url = "https://files.pythonhosted.org/packages/8e/a2/a54eb4eae05d66364050a5d3b8a9c5ef88196531b3cbe7109d873f87f819/ruff-0.16.0-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:48044c678e9cb8698246c99b14aaccfa6601dea7379eb48a6f8f73f7a6d86cd0", size = 11426177, upload-time = "2026-07-23T19:11:11.994Z" }, + { url = "https://files.pythonhosted.org/packages/1a/be/16e3eea4b2a478a496919f5e36f17c4559e54620bd3bbac5d6affa068006/ruff-0.16.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:7aa0959bad8eb8bef50340154fc9b58678dae31fa4293afa38b44b6e552c0213", size = 10856126, upload-time = "2026-07-23T19:11:14.221Z" }, + { url = "https://files.pythonhosted.org/packages/a2/84/252eb8b868a16eec7257c14f504f77537e734b2d69c762e639e588e304a3/ruff-0.16.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:28ea2b7df8ebf7f9da6b7d47b230ab48f387c0a29be3b474c4d0740e197bb9af", size = 10571208, upload-time = "2026-07-23T19:11:16.378Z" }, + { url = "https://files.pythonhosted.org/packages/21/09/817a482f542f7570cbb4554b26e896610c7114f539b1d9e2d2145bf6bef6/ruff-0.16.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:33a3dfac8c35f81498dea9181bccc2f4c4bc8f1521a1dd9406e77643e0f0fb09", size = 11063329, upload-time = "2026-07-23T19:11:19.173Z" }, + { url = "https://files.pythonhosted.org/packages/2e/23/9403c180ca1cb9b1f7335f5c3e5305c09d49ea5b345196682a36028bde4a/ruff-0.16.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:a5237a0bda500d30d81b8e07a6973a5cbc772864cbf746ae2f4e8a2e01c9f4ed", size = 11489751, upload-time = "2026-07-23T19:11:21.74Z" }, + { url = "https://files.pythonhosted.org/packages/b2/1d/1b2ef7bcde851c78d7f17f1cca13fd6dc695fc4b3d6197941e72cae5b132/ruff-0.16.0-py3-none-win32.whl", hash = "sha256:7fab76fa065c873f41ff744347c6e77bcc3dfec4bcc754dc26b63d23c0f7f5fb", size = 10785885, upload-time = "2026-07-23T19:11:23.947Z" }, + { url = "https://files.pythonhosted.org/packages/b2/a3/d5e4ef7a56be3f928ffb90b94c25ba7d3cb9c7fe0736aeaaedf361770712/ruff-0.16.0-py3-none-win_amd64.whl", hash = "sha256:429c117f022bf481fabd9d551e7a3952b24c65e6ef44337ea09d90bebef14472", size = 11923141, upload-time = "2026-07-23T19:11:26.409Z" }, + { url = "https://files.pythonhosted.org/packages/cb/9a/8415f2657cbe200f41a4531ccededf135505a92d4a012229121f885b26f9/ruff-0.16.0-py3-none-win_arm64.whl", hash = "sha256:14296fedcd2705c77ab8235439278bbb38f285cf7da5528b00b3e330c3d4872d", size = 11273407, upload-time = "2026-07-23T19:11:28.705Z" }, ] [[package]] name = "s3transfer" -version = "0.19.1" +version = "0.19.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "botocore" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/65/da/4bef7ce7bb989b222aa4785a413896dbec53306dfc59c6ce7d16a7ffbd6a/s3transfer-0.19.1.tar.gz", hash = "sha256:d3d6371dc3f1e5c5427b2b457bcf13bcf87bec334c95aed18642eae61f6926f3", size = 165354, upload-time = "2026-07-10T19:32:04.849Z" } +sdist = { url = "https://files.pythonhosted.org/packages/76/43/35e4d8aa320bffe8287fe8f65f578fa2d2db0a64212f0e710dce58267854/s3transfer-0.19.2.tar.gz", hash = "sha256:ba0309fd86be3c27dbf78cdd813c13c5e1df16e5874b99d2535ebbdfb9892993", size = 165592, upload-time = "2026-07-22T19:30:44.432Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/24/23/e84c64ad0e8bc59cd1b2ef98def848deff0ef3456c542afe74d51e9e8c85/s3transfer-0.19.1-py3-none-any.whl", hash = "sha256:d5fd7005ee39307455ad5f310b5ea67f4b1960d7fed5b3671ee50c249de675de", size = 90072, upload-time = "2026-07-10T19:32:03.673Z" }, + { url = "https://files.pythonhosted.org/packages/bc/e7/5c595c75e9f41a44f30e526eda465ea0b4eec93470e074e4a111b253f13a/s3transfer-0.19.2-py3-none-any.whl", hash = "sha256:d8168eccca828cbb2cd573675333f3bddd254313a9c42494b84c76b539e8ba25", size = 90216, upload-time = "2026-07-22T19:30:43.251Z" }, ] [[package]] @@ -2857,7 +2855,7 @@ dependencies = [ { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, { name = "numpy", version = "2.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, { name = "pandas", version = "2.3.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "pandas", version = "3.0.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "pandas", version = "3.0.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "promise" }, { name = "shapely" }, { name = "tilebox-grpc" }, @@ -3136,14 +3134,14 @@ wheels = [ [[package]] name = "tqdm" -version = "4.69.0" +version = "4.69.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/8c/69/40407dfc835517f058b603dbf37a6df094d8582b015a51eddc988febbcb7/tqdm-4.69.0.tar.gz", hash = "sha256:700c5e85dcd5f009dd6222588a29180a193a748247a5d855b4d67db93d79a53b", size = 792569, upload-time = "2026-07-17T18:09:06.2Z" } +sdist = { url = "https://files.pythonhosted.org/packages/dd/84/da0e5038228fa34dfd77c5026b173ed035d2a3ba31f1077590c013de2bff/tqdm-4.69.1.tar.gz", hash = "sha256:2be21080a0ce17e902c2f1baeb6a74bf551b67bbdfa4bc0109fad471d0b4cb0d", size = 793046, upload-time = "2026-07-24T14:22:02.08Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/fe/21/99a0cdaf54eb35e77623c41b5a2c9472ee4404bba687052791fe2aba6773/tqdm-4.69.0-py3-none-any.whl", hash = "sha256:9979978912be667a6ef21fd5d8abf54e324e63d82f7f43c360792ebc2bc4e622", size = 676680, upload-time = "2026-07-17T18:09:04.172Z" }, + { url = "https://files.pythonhosted.org/packages/01/50/5817619a0fca56aff06383dbfde7ae017b3ca383915b3f1e4713164273cf/tqdm-4.69.1-py3-none-any.whl", hash = "sha256:0a654b96f7a2660cceb615b56f307ec2bef96c515409014a429a561981ab52b4", size = 675452, upload-time = "2026-07-24T14:22:00.048Z" }, ] [[package]] @@ -3235,25 +3233,25 @@ wheels = [ [[package]] name = "urllib3-future" -version = "2.23.900" +version = "2.24.900" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "h11" }, { name = "jh2" }, { name = "qh3", marker = "(python_full_version < '3.12' and platform_machine == 'AMD64' and platform_python_implementation == 'PyPy' and sys_platform == 'darwin') or (python_full_version < '3.12' and platform_machine == 'ARM64' and platform_python_implementation == 'PyPy' and sys_platform == 'darwin') or (python_full_version < '3.12' and platform_machine == 'aarch64' and platform_python_implementation == 'PyPy' and sys_platform == 'darwin') or (python_full_version < '3.12' and platform_machine == 'arm64' and platform_python_implementation == 'PyPy' and sys_platform == 'darwin') or (python_full_version < '3.12' and platform_machine == 'armv7l' and platform_python_implementation == 'PyPy' and sys_platform == 'darwin') or (python_full_version < '3.12' and platform_machine == 'i686' and platform_python_implementation == 'PyPy' and sys_platform == 'darwin') or (python_full_version < '3.12' and platform_machine == 'ppc64' and platform_python_implementation == 'PyPy' and sys_platform == 'darwin') or (python_full_version < '3.12' and platform_machine == 'ppc64le' and platform_python_implementation == 'PyPy' and sys_platform == 'darwin') or (python_full_version < '3.12' and platform_machine == 'riscv64' and platform_python_implementation == 'PyPy' and sys_platform == 'darwin') or (python_full_version < '3.12' and platform_machine == 'riscv64gc' and platform_python_implementation == 'PyPy' and sys_platform == 'darwin') or (python_full_version < '3.12' and platform_machine == 's390x' and platform_python_implementation == 'PyPy' and sys_platform == 'darwin') or (python_full_version < '3.12' and platform_machine == 'x86' and platform_python_implementation == 'PyPy' and sys_platform == 'darwin') or (python_full_version < '3.12' and platform_machine == 'x86_64' and platform_python_implementation == 'PyPy' and sys_platform == 'darwin') or (python_full_version < '3.12' and platform_machine == 'AMD64' and platform_python_implementation == 'PyPy' and sys_platform == 'linux') or (python_full_version < '3.12' and platform_machine == 'ARM64' and platform_python_implementation == 'PyPy' and sys_platform == 'linux') or (python_full_version < '3.12' and platform_machine == 'aarch64' and platform_python_implementation == 'PyPy' and sys_platform == 'linux') or (python_full_version < '3.12' and platform_machine == 'arm64' and platform_python_implementation == 'PyPy' and sys_platform == 'linux') or (python_full_version < '3.12' and platform_machine == 'armv7l' and platform_python_implementation == 'PyPy' and sys_platform == 'linux') or (python_full_version < '3.12' and platform_machine == 'i686' and platform_python_implementation == 'PyPy' and sys_platform == 'linux') or (python_full_version < '3.12' and platform_machine == 'ppc64' and platform_python_implementation == 'PyPy' and sys_platform == 'linux') or (python_full_version < '3.12' and platform_machine == 'ppc64le' and platform_python_implementation == 'PyPy' and sys_platform == 'linux') or (python_full_version < '3.12' and platform_machine == 'riscv64' and platform_python_implementation == 'PyPy' and sys_platform == 'linux') or (python_full_version < '3.12' and platform_machine == 'riscv64gc' and platform_python_implementation == 'PyPy' and sys_platform == 'linux') or (python_full_version < '3.12' and platform_machine == 's390x' and platform_python_implementation == 'PyPy' and sys_platform == 'linux') or (python_full_version < '3.12' and platform_machine == 'x86' and platform_python_implementation == 'PyPy' and sys_platform == 'linux') or (python_full_version < '3.12' and platform_machine == 'x86_64' and platform_python_implementation == 'PyPy' and sys_platform == 'linux') or (python_full_version < '3.12' and platform_machine == 'AMD64' and platform_python_implementation == 'PyPy' and sys_platform == 'win32') or (python_full_version < '3.12' and platform_machine == 'ARM64' and platform_python_implementation == 'PyPy' and sys_platform == 'win32') or (python_full_version < '3.12' and platform_machine == 'aarch64' and platform_python_implementation == 'PyPy' and sys_platform == 'win32') or (python_full_version < '3.12' and platform_machine == 'arm64' and platform_python_implementation == 'PyPy' and sys_platform == 'win32') or (python_full_version < '3.12' and platform_machine == 'armv7l' and platform_python_implementation == 'PyPy' and sys_platform == 'win32') or (python_full_version < '3.12' and platform_machine == 'i686' and platform_python_implementation == 'PyPy' and sys_platform == 'win32') or (python_full_version < '3.12' and platform_machine == 'ppc64' and platform_python_implementation == 'PyPy' and sys_platform == 'win32') or (python_full_version < '3.12' and platform_machine == 'ppc64le' and platform_python_implementation == 'PyPy' and sys_platform == 'win32') or (python_full_version < '3.12' and platform_machine == 'riscv64' and platform_python_implementation == 'PyPy' and sys_platform == 'win32') or (python_full_version < '3.12' and platform_machine == 'riscv64gc' and platform_python_implementation == 'PyPy' and sys_platform == 'win32') or (python_full_version < '3.12' and platform_machine == 's390x' and platform_python_implementation == 'PyPy' and sys_platform == 'win32') or (python_full_version < '3.12' and platform_machine == 'x86' and platform_python_implementation == 'PyPy' and sys_platform == 'win32') or (python_full_version < '3.12' and platform_machine == 'x86_64' and platform_python_implementation == 'PyPy' and sys_platform == 'win32') or (platform_machine == 'AMD64' and platform_python_implementation == 'CPython' and sys_platform == 'darwin') or (platform_machine == 'ARM64' and platform_python_implementation == 'CPython' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and platform_python_implementation == 'CPython' and sys_platform == 'darwin') or (platform_machine == 'arm64' and platform_python_implementation == 'CPython' and sys_platform == 'darwin') or (platform_machine == 'armv7l' and platform_python_implementation == 'CPython' and sys_platform == 'darwin') or (platform_machine == 'i686' and platform_python_implementation == 'CPython' and sys_platform == 'darwin') or (platform_machine == 'ppc64' and platform_python_implementation == 'CPython' and sys_platform == 'darwin') or (platform_machine == 'ppc64le' and platform_python_implementation == 'CPython' and sys_platform == 'darwin') or (platform_machine == 'riscv64' and platform_python_implementation == 'CPython' and sys_platform == 'darwin') or (platform_machine == 'riscv64gc' and platform_python_implementation == 'CPython' and sys_platform == 'darwin') or (platform_machine == 's390x' and platform_python_implementation == 'CPython' and sys_platform == 'darwin') or (platform_machine == 'x86' and platform_python_implementation == 'CPython' and sys_platform == 'darwin') or (platform_machine == 'x86_64' and platform_python_implementation == 'CPython' and sys_platform == 'darwin') or (platform_machine == 'AMD64' and platform_python_implementation == 'CPython' and sys_platform == 'linux') or (platform_machine == 'ARM64' and platform_python_implementation == 'CPython' and sys_platform == 'linux') or (platform_machine == 'aarch64' and platform_python_implementation == 'CPython' and sys_platform == 'linux') or (platform_machine == 'arm64' and platform_python_implementation == 'CPython' and sys_platform == 'linux') or (platform_machine == 'armv7l' and platform_python_implementation == 'CPython' and sys_platform == 'linux') or (platform_machine == 'i686' and platform_python_implementation == 'CPython' and sys_platform == 'linux') or (platform_machine == 'ppc64' and platform_python_implementation == 'CPython' and sys_platform == 'linux') or (platform_machine == 'ppc64le' and platform_python_implementation == 'CPython' and sys_platform == 'linux') or (platform_machine == 'riscv64' and platform_python_implementation == 'CPython' and sys_platform == 'linux') or (platform_machine == 'riscv64gc' and platform_python_implementation == 'CPython' and sys_platform == 'linux') or (platform_machine == 's390x' and platform_python_implementation == 'CPython' and sys_platform == 'linux') or (platform_machine == 'x86' and platform_python_implementation == 'CPython' and sys_platform == 'linux') or (platform_machine == 'x86_64' and platform_python_implementation == 'CPython' and sys_platform == 'linux') or (platform_machine == 'AMD64' and platform_python_implementation == 'CPython' and sys_platform == 'win32') or (platform_machine == 'ARM64' and platform_python_implementation == 'CPython' and sys_platform == 'win32') or (platform_machine == 'aarch64' and platform_python_implementation == 'CPython' and sys_platform == 'win32') or (platform_machine == 'arm64' and platform_python_implementation == 'CPython' and sys_platform == 'win32') or (platform_machine == 'armv7l' and platform_python_implementation == 'CPython' and sys_platform == 'win32') or (platform_machine == 'i686' and platform_python_implementation == 'CPython' and sys_platform == 'win32') or (platform_machine == 'ppc64' and platform_python_implementation == 'CPython' and sys_platform == 'win32') or (platform_machine == 'ppc64le' and platform_python_implementation == 'CPython' and sys_platform == 'win32') or (platform_machine == 'riscv64' and platform_python_implementation == 'CPython' and sys_platform == 'win32') or (platform_machine == 'riscv64gc' and platform_python_implementation == 'CPython' and sys_platform == 'win32') or (platform_machine == 's390x' and platform_python_implementation == 'CPython' and sys_platform == 'win32') or (platform_machine == 'x86' and platform_python_implementation == 'CPython' and sys_platform == 'win32') or (platform_machine == 'x86_64' and platform_python_implementation == 'CPython' and sys_platform == 'win32')" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/43/e0/b8d8e0a919fe2a0674a3193b42de9c56816341b85d69b5ef6af58a72710b/urllib3_future-2.23.900.tar.gz", hash = "sha256:f488f22983f96cd8c733640b47e6a32def462d33c59bcc4bea6d97a8568b70c8", size = 1321689, upload-time = "2026-07-19T08:40:16.762Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ed/9f/590bfa6575e8872829dc65d02c887d0599b3897912479e5ca198660a8c1e/urllib3_future-2.24.900.tar.gz", hash = "sha256:05c2e9d09293ab29a154fed8f5b60644c7cdaffe4ae2587a07b1c7f00f23a7fb", size = 1370419, upload-time = "2026-07-27T06:16:52.572Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6f/d8/6deac579e5b9f658b4221ac0b732517df2225fff49630dc4aed6f5b105c1/urllib3_future-2.23.900-py3-none-any.whl", hash = "sha256:4303dc9a5d8c4c810f3fcd3b13bd62d69c3a4f63bb7e62089410907c34aeaf6b", size = 786724, upload-time = "2026-07-19T08:40:15.081Z" }, + { url = "https://files.pythonhosted.org/packages/5d/b6/1a7cf7971c979e56ecff5eb76cf8d748d567ef5f7a4fa39fb87adac922e4/urllib3_future-2.24.900-py3-none-any.whl", hash = "sha256:a4fe06b59b0c3ce2fffa821c3de72eaf59b1e61619becc255dee18919ee25260", size = 809879, upload-time = "2026-07-27T06:16:50.77Z" }, ] [[package]] name = "wassima" -version = "2.1.2" +version = "2.1.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d1/9f/be43f3a97d27bd3bcdd572024ed082b55f486e0541834061da6acf9c77c7/wassima-2.1.2.tar.gz", hash = "sha256:f74b5441151728c54118ece6d747cfe92b2c595a3d062a1955f42a2bc894cd10", size = 139636, upload-time = "2026-07-07T14:01:57.921Z" } +sdist = { url = "https://files.pythonhosted.org/packages/53/a1/714674b53d3a57013730187e027e291c652a25db053e02236798bec49d61/wassima-2.1.3.tar.gz", hash = "sha256:fcd6c38be0f909c393da35cb2a993101fcdcff673b8fa8d5da228f73b630d0d0", size = 140075, upload-time = "2026-07-24T18:36:21.235Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d7/94/5ccf60801c011725de5deaa4efc0db9a3719c56c77aac8716ce2def3d790/wassima-2.1.2-py3-none-any.whl", hash = "sha256:9a5779e8ade39960973c6cc17e095cc998d6345ab3dbdd1ec2d7fde8c356fb82", size = 130729, upload-time = "2026-07-07T14:01:56.343Z" }, + { url = "https://files.pythonhosted.org/packages/af/a2/4c8cd0dddfe40a779739afab2f09e84286bea37cdb73c7b1c12b4990ff12/wassima-2.1.3-py3-none-any.whl", hash = "sha256:f8251c23720dd092117a554b3012756caee3feed6d0122c2458a2aca5d7d92c5", size = 131416, upload-time = "2026-07-24T18:36:19.36Z" }, ] [[package]] @@ -3420,7 +3418,7 @@ dependencies = [ { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, { name = "numpy", version = "2.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, { name = "packaging" }, - { name = "pandas", version = "3.0.3", source = { registry = "https://pypi.org/simple" } }, + { name = "pandas", version = "3.0.5", source = { registry = "https://pypi.org/simple" } }, ] sdist = { url = "https://files.pythonhosted.org/packages/ea/96/3f7bdd00e505ec3698903415b30135024703e017b28c6b61f98da3193b0d/xarray-2026.7.0.tar.gz", hash = "sha256:361b495928fdbf5b58d0969bb6775339019da5e93ca74d61ddf4eb5edd6ce604", size = 3145348, upload-time = "2026-07-09T17:38:26.515Z" } wheels = [ From ea0f76717faf38bb5906f3cbab4f1122ea1812d9 Mon Sep 17 00:00:00 2001 From: Lukas Bindreiter Date: Mon, 27 Jul 2026 15:37:03 +0200 Subject: [PATCH 2/4] filter expression in dataset query for custom queryables --- CHANGELOG.md | 5 + .../tests/data/test_data_access.py | 68 ++++ tilebox-datasets/tests/data/test_datasets.py | 6 +- .../tests/query/test_expression.py | 160 +++++++++ .../tests/query/test_time_interval.py | 25 +- tilebox-datasets/tests/test_timeseries.py | 35 +- tilebox-datasets/tilebox/datasets/__init__.py | 7 +- .../tilebox/datasets/aio/dataset.py | 22 +- .../tilebox/datasets/data/data_access.py | 40 ++- .../tilebox/datasets/message_pool.py | 26 +- .../tilebox/datasets/query/__init__.py | 11 +- .../tilebox/datasets/query/expression.py | 327 ++++++++++++++++++ .../tilebox/datasets/query/time_interval.py | 6 +- .../tilebox/datasets/sync/dataset.py | 20 +- 14 files changed, 733 insertions(+), 25 deletions(-) create mode 100644 tilebox-datasets/tests/query/test_expression.py create mode 100644 tilebox-datasets/tilebox/datasets/query/expression.py diff --git a/CHANGELOG.md b/CHANGELOG.md index e60d2d4..446bec4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- `tilebox-datasets`: Added typed server-side dataset query filters with comparison, logical, and null-check + expressions through `field()` and the `filter` query argument. + ## [0.56.0] - 2026-07-16 ### Added diff --git a/tilebox-datasets/tests/data/test_data_access.py b/tilebox-datasets/tests/data/test_data_access.py index 773a313..afd4fad 100644 --- a/tilebox-datasets/tests/data/test_data_access.py +++ b/tilebox-datasets/tests/data/test_data_access.py @@ -1,8 +1,15 @@ +from datetime import datetime, timedelta, timezone +from uuid import uuid4 + +import pytest from hypothesis import given from shapely import Geometry from tests.data.data_access import query_filters, spatial_filter_likes, spatial_filters from tilebox.datasets.data.data_access import QueryFilters, SpatialFilter, SpatialFilterDict +from tilebox.datasets.datasets.v1 import data_access_pb2 +from tilebox.datasets.query import TimeInterval, field +from tilebox.datasets.query.id_interval import IDInterval @given(spatial_filters()) @@ -33,3 +40,64 @@ def test_parse_spatial_filter_like(spatial_filter_like: Geometry | SpatialFilter @given(query_filters()) def test_query_filters_to_message_and_back(q: QueryFilters) -> None: assert QueryFilters.from_message(q.to_message()) == q + + +def test_query_filters_expression_to_message_and_back() -> None: + query_filter = QueryFilters( + temporal_extent=TimeInterval( + datetime(2026, 7, 27, tzinfo=timezone.utc), + datetime(2026, 7, 28, tzinfo=timezone.utc), + ), + filter=(field("cloud_cover") < 20) | field("cloud_cover").is_null(), + ) + + message = query_filter.to_message() + assert len(message.expressions) == 1 + assert QueryFilters.from_message(message) == query_filter + + +def test_multiple_wire_expressions_are_combined_with_and() -> None: + query_filter = QueryFilters( + TimeInterval( + datetime.now(tz=timezone.utc), + datetime.now(tz=timezone.utc) + timedelta(days=1), + ) + ) + message = query_filter.to_message() + message.expressions.extend( + [ + (field("cloud_cover") < 20).to_message(), + (field("quality") >= 80).to_message(), + ] + ) + + round_tripped = QueryFilters.from_message(message).to_message() + assert len(round_tripped.expressions) == 1 + assert round_tripped.expressions[0].logical.operator == data_access_pb2.LOGICAL_OPERATOR_AND + assert len(round_tripped.expressions[0].logical.operands) == 2 + + +def test_query_filters_reject_invalid_filter() -> None: + with pytest.raises(TypeError, match="Expected a query expression"): + QueryFilters( + TimeInterval( + datetime(2026, 7, 27, tzinfo=timezone.utc), + datetime(2026, 7, 28, tzinfo=timezone.utc), + ), + filter="quality > 80", # type: ignore[arg-type] + ) + + +def test_query_filters_reject_invalid_interval_variants() -> None: + with pytest.raises(ValueError, match="exactly one time or datapoint interval"): + QueryFilters.from_message(data_access_pb2.QueryFilters()) + + message = QueryFilters( + TimeInterval( + datetime(2026, 7, 27, tzinfo=timezone.utc), + datetime(2026, 7, 28, tzinfo=timezone.utc), + ) + ).to_message() + message.datapoint_interval.CopyFrom(IDInterval(uuid4(), uuid4(), False, False).to_message()) + with pytest.raises(ValueError, match="exactly one time or datapoint interval"): + QueryFilters.from_message(message) diff --git a/tilebox-datasets/tests/data/test_datasets.py b/tilebox-datasets/tests/data/test_datasets.py index 79f9b8d..64171e4 100644 --- a/tilebox-datasets/tests/data/test_datasets.py +++ b/tilebox-datasets/tests/data/test_datasets.py @@ -117,10 +117,10 @@ def test_create_and_update_dataset_include_field_annotations(operation: str) -> annotations = {field.descriptor.name: field.annotation for field in request.type.fields} assert annotations["time"].source_json_pointer == "/properties/datetime" assert annotations["time"].json_schema_ref.endswith("datetime.json#/properties/datetime") - assert annotations["id"].source_json_pointer == "/properties/tilebox_id" + assert annotations["id"].source_json_pointer == "/properties/tilebox:id" assert not annotations["id"].HasField("json_schema_ref") - assert annotations["ingestion_time"].source_json_pointer == "/properties/created" - assert annotations["ingestion_time"].json_schema_ref.endswith("datetime.json#/properties/created") + assert annotations["ingestion_time"].source_json_pointer == "/properties/tilebox:ingestion_time" + assert not annotations["ingestion_time"].HasField("json_schema_ref") assert annotations["geometry"].source_json_pointer == "/geometry" assert annotations["geometry"].json_schema_ref == "https://geojson.org/schema/Geometry.json" assert all( diff --git a/tilebox-datasets/tests/query/test_expression.py b/tilebox-datasets/tests/query/test_expression.py new file mode 100644 index 0000000..726e9a4 --- /dev/null +++ b/tilebox-datasets/tests/query/test_expression.py @@ -0,0 +1,160 @@ +from datetime import datetime, timedelta, timezone +from enum import Enum, IntEnum + +import numpy as np +import pytest + +from tilebox.datasets.datasets.v1 import data_access_pb2 +from tilebox.datasets.query import Expression, field + + +class Quality(Enum): + GOOD = "good" + + +class NumericQuality(IntEnum): + GOOD = 1 + + +@pytest.mark.parametrize( + ("value", "kind", "expected"), + [ + (False, "bool_value", False), + (0, "int64_value", 0), + (np.uint64(2**63), "uint64_value", 2**63), + (0.0, "double_value", 0.0), + ("", "string_value", ""), + (b"", "bytes_value", b""), + (Quality.GOOD, "enum_name", "GOOD"), + (NumericQuality.GOOD, "enum_name", "GOOD"), + ], +) +def test_query_value_types(value: object, kind: str, expected: object) -> None: + message = (field("quality") == value).to_message().comparison.value + assert message.HasField(kind) + assert getattr(message, kind) == expected + + +@pytest.mark.parametrize( + "value", + [ + datetime(2026, 7, 27, 12, 30, tzinfo=timezone.utc), + timedelta(days=-1, microseconds=123), + np.datetime64("2026-07-27T12:30:00.123456789"), + np.timedelta64(-123456789, "ns"), + ], +) +def test_well_known_query_values_round_trip(value: object) -> None: + expression = field("value") == value + assert Expression.from_message(expression.to_message()) == expression + + +@pytest.mark.parametrize( + ("value", "seconds", "nanos"), + [ + (timedelta(microseconds=-1), 0, -1000), + (np.timedelta64(-123456789, "ns"), 0, -123456789), + (timedelta(seconds=-1, microseconds=-1), -1, -1000), + ], +) +def test_negative_duration_values(value: object, seconds: int, nanos: int) -> None: + duration = (field("value") == value).to_message().comparison.value.duration_value + assert (duration.seconds, duration.nanos) == (seconds, nanos) + + +@pytest.mark.parametrize( + "value", + [ + np.bool_(True), + np.int64(3), + np.uint64(2**63), + np.float64(3.5), + np.datetime64("2026-07-27T12:30:00.123456789"), + np.timedelta64(-123456789, "ns"), + ], +) +def test_numpy_query_values_preserve_type_when_reflected(value: object) -> None: + field_first = (field("value") == value).to_message().comparison.value + value_first = (value == field("value")).to_message().comparison.value + assert value_first == field_first + + +def test_logical_expression_shape_and_round_trip() -> None: + expression = ( + (field("cloud_cover") < 20) + & (field("quality") >= 80) + & ((field("platform") == "sentinel-2") | field("platform").is_null()) + & field("title").is_not_null() + ) + + message = expression.to_message() + assert message.logical.operator == data_access_pb2.LOGICAL_OPERATOR_AND + assert len(message.logical.operands) == 4 + assert message.logical.operands[2].logical.operator == data_access_pb2.LOGICAL_OPERATOR_OR + assert message.logical.operands[3].logical.operator == data_access_pb2.LOGICAL_OPERATOR_NOT + assert Expression.from_message(message) == expression + + +def test_query_expression_cannot_be_used_as_boolean() -> None: + expression = field("quality") == 1 + with pytest.raises(TypeError, match=r"Use &, \|, and ~"): + bool(expression) + + with pytest.raises(TypeError, match="chained comparisons"): + _ = 0 < field("quality") < 10 + + +@pytest.mark.parametrize("value", [None, float("nan"), float("inf"), [], object()]) +def test_invalid_query_values(value: object) -> None: + with pytest.raises((TypeError, ValueError)): + _ = field("quality") == value + + +def test_query_duration_rejects_values_outside_protobuf_range() -> None: + with pytest.raises(OverflowError, match="outside the supported protobuf range"): + _ = field("duration") == timedelta.max + + +def test_invalid_query_operators() -> None: + with pytest.raises(TypeError, match="Boolean query values only support"): + _ = field("enabled") < True + + with pytest.raises(TypeError, match="Expected a query expression"): + (field("quality") == 1) & 2 # type: ignore[operator] + + +def test_invalid_expression_message() -> None: + with pytest.raises(ValueError, match="exactly one expression node"): + Expression.from_message(data_access_pb2.FilterExpression()) + + malformed_not = data_access_pb2.FilterExpression( + logical=data_access_pb2.LogicalExpression(operator=data_access_pb2.LOGICAL_OPERATOR_NOT) + ) + with pytest.raises(ValueError, match="NOT requires exactly one operand"): + Expression.from_message(malformed_not) + + +@pytest.mark.parametrize( + "message", + [ + data_access_pb2.FilterExpression( + comparison=data_access_pb2.FieldComparison( + field_name="quality", + operator=data_access_pb2.FIELD_COMPARISON_OPERATOR_LESS_THAN, + value=data_access_pb2.FieldQueryValue(bool_value=True), + ) + ), + data_access_pb2.FilterExpression( + comparison=data_access_pb2.FieldComparison( + field_name="quality", + operator=data_access_pb2.FIELD_COMPARISON_OPERATOR_EQUAL, + value=data_access_pb2.FieldQueryValue(double_value=float("nan")), + ) + ), + ], +) +def test_expression_message_cannot_bypass_comparison_validation( + message: data_access_pb2.FilterExpression, +) -> None: + with pytest.raises((TypeError, ValueError)): + Expression.from_message(message) diff --git a/tilebox-datasets/tests/query/test_time_interval.py b/tilebox-datasets/tests/query/test_time_interval.py index 258b514..1de996d 100644 --- a/tilebox-datasets/tests/query/test_time_interval.py +++ b/tilebox-datasets/tests/query/test_time_interval.py @@ -1,5 +1,6 @@ -from datetime import datetime, timezone +from datetime import datetime, timedelta, timezone +import pytest from hypothesis import given from hypothesis.strategies import datetimes from pandas.core.tools.datetimes import DatetimeScalar @@ -10,6 +11,8 @@ TimeInterval, _convert_to_datetime, datetime_to_timestamp, + duration_to_timedelta, + timedelta_to_duration, timestamp_to_datetime, ) @@ -75,8 +78,24 @@ def test_time_interval_to_message_and_back(interval: TimeInterval) -> None: @given(datetimes()) def test_datetime_to_timestamp_and_back(dt: datetime) -> None: """Make sure converting a datetime to a protobuf timestamp and then back again ends up with the same timestamp.""" - # we always convert to UTC when converting to protobuf, so we loose the information of which timezone it was before - assert timestamp_to_datetime(datetime_to_timestamp(dt)) == dt.astimezone(timezone.utc) + # Naive datetimes are interpreted as UTC; timezone-aware datetimes are converted to UTC. + expected = dt.replace(tzinfo=timezone.utc) if dt.tzinfo is None else dt.astimezone(timezone.utc) + assert timestamp_to_datetime(datetime_to_timestamp(dt)) == expected + + +@pytest.mark.parametrize( + "value", + [ + timedelta(microseconds=-1), + timedelta(seconds=-1, microseconds=-1), + timedelta(days=-1, microseconds=123), + timedelta(seconds=1, microseconds=123), + ], +) +def test_timedelta_to_duration_and_back(value: timedelta) -> None: + duration = timedelta_to_duration(value) + assert duration_to_timedelta(duration) == value + assert duration.seconds == 0 or duration.nanos == 0 or (duration.seconds > 0) == (duration.nanos > 0) @given(datetime_scalars()) diff --git a/tilebox-datasets/tests/test_timeseries.py b/tilebox-datasets/tests/test_timeseries.py index e1b874e..f5df6c3 100644 --- a/tilebox-datasets/tests/test_timeseries.py +++ b/tilebox-datasets/tests/test_timeseries.py @@ -15,7 +15,7 @@ from tests.data.datapoint import example_datapoints, paginated_query_results from tests.data.datasets import example_dataset_type, example_dataset_type_url from tests.example_dataset.example_dataset_pb2 import ExampleDatapoint -from tilebox.datasets import CollectionClient, DatasetClient +from tilebox.datasets import CollectionClient, DatasetClient, field from tilebox.datasets.data.collection import Collection, CollectionInfo from tilebox.datasets.data.datapoint import AnyMessage, QueryResultPage from tilebox.datasets.data.datasets import Dataset @@ -260,6 +260,39 @@ def test_timeseries_dataset_query_multiple_collections(pages: list[QueryResultPa ] +@settings(max_examples=1) +@given(pages=paginated_query_results()) +def test_timeseries_dataset_query_filter(pages: list[QueryResultPage]) -> None: + dataset, service = _mocked_dataset() + service.get_collections.return_value = Promise.resolve([]) + service.query.side_effect = [Promise.resolve(page) for page in pages] + interval = TimeInterval(datetime.now(), datetime.now() + timedelta(days=1)) + + dataset.query( + temporal_extent=interval, + filter=(field("quality") >= 80) | field("quality").is_null(), + ) + + filters = service.query.call_args_list[0][0][2].to_message() + assert len(filters.expressions) == 1 + assert filters.expressions[0].logical.operands[0].comparison.field_name == "quality" + + +def test_timeseries_dataset_query_rejects_invalid_filter_before_request() -> None: + dataset, service = _mocked_dataset() + interval = TimeInterval(datetime.now(), datetime.now() + timedelta(days=1)) + + with pytest.raises(TypeError, match="Expected a query expression"): + dataset.query( + collections=["collection"], + temporal_extent=interval, + filter="quality > 80", # type: ignore[arg-type] + ) + + service.get_collections.assert_not_called() + service.query.assert_not_called() + + @patch("tilebox.datasets.sync.pagination.tqdm") @patch("tilebox.datasets.progress.tqdm") @settings(deadline=1000, max_examples=3) # increase deadline to 1s to not timeout because of the progress bar diff --git a/tilebox-datasets/tilebox/datasets/__init__.py b/tilebox-datasets/tilebox/datasets/__init__.py index ee5d8c1..7ee27a0 100644 --- a/tilebox-datasets/tilebox/datasets/__init__.py +++ b/tilebox-datasets/tilebox/datasets/__init__.py @@ -6,10 +6,11 @@ if TYPE_CHECKING: from tilebox.datasets.aio.timeseries import TimeseriesCollection, TimeseriesDataset + from tilebox.datasets.query import field from tilebox.datasets.sync.client import Client from tilebox.datasets.sync.dataset import CollectionClient, DatasetClient -__all__ = ["Client", "CollectionClient", "DatasetClient", "TimeseriesCollection", "TimeseriesDataset"] +__all__ = ["Client", "CollectionClient", "DatasetClient", "TimeseriesCollection", "TimeseriesDataset", "field"] def __getattr__(name: str) -> Any: @@ -29,6 +30,10 @@ def __getattr__(name: str) -> Any: from tilebox.datasets.sync.dataset import DatasetClient # noqa: PLC0415 value = DatasetClient + case "field": + from tilebox.datasets.query import field # noqa: PLC0415 + + value = field case "TimeseriesCollection": from tilebox.datasets.aio.timeseries import TimeseriesCollection # noqa: PLC0415 diff --git a/tilebox-datasets/tilebox/datasets/aio/dataset.py b/tilebox-datasets/tilebox/datasets/aio/dataset.py index 062ee42..83be95f 100644 --- a/tilebox-datasets/tilebox/datasets/aio/dataset.py +++ b/tilebox-datasets/tilebox/datasets/aio/dataset.py @@ -26,6 +26,7 @@ marshal_messages, to_messages, ) +from tilebox.datasets.query.expression import Expression from tilebox.datasets.query.id_interval import IDInterval, IDIntervalLike from tilebox.datasets.query.pagination import Pagination from tilebox.datasets.query.time_interval import TimeInterval, TimeIntervalLike @@ -178,11 +179,12 @@ async def find( converter.convert(data) return converter.finalize("time", skip_empty_fields=skip_data).isel(time=0) - async def query( + async def query( # noqa: PLR0913 self, *, - collections: "list[str] | list[UUID] | list[Collection] | list[CollectionInfo] | list[CollectionClient] | dict[str, CollectionClient] | None", + collections: "list[str] | list[UUID] | list[Collection] | list[CollectionInfo] | list[CollectionClient] | dict[str, CollectionClient] | None" = None, temporal_extent: TimeIntervalLike, + filter: Expression | None = None, # noqa: A002 spatial_extent: SpatialFilterLike | None = None, skip_data: bool = False, show_progress: bool | ProgressCallback = False, @@ -194,6 +196,7 @@ async def query( collections: The collections to query in. Supports collection names, ids or collection objects. If not specified, all collections in the dataset are queried. temporal_extent: The temporal extent to query data for. (Required) + filter: An expression over queryable dataset fields. (Optional) spatial_extent: The spatial extent to query data in. (Optional) skip_data: Whether to skip the actual data of the datapoint. If True, only datapoint metadata is returned. @@ -203,6 +206,8 @@ async def query( Returns: Matching datapoints in the given temporal and spatial extent as an xarray dataset. """ + if filter is not None and not isinstance(filter, Expression): + raise TypeError(f"Expected a query expression, got {type(filter).__name__}") if temporal_extent is None: raise ValueError("A temporal_extent for your query must be specified") @@ -214,6 +219,7 @@ async def query( temporal_extent, spatial_extent, skip_data, + filter_expression=filter, dataset_name=self.name, show_progress=show_progress, ) @@ -434,6 +440,7 @@ async def query( self, *, temporal_extent: TimeIntervalLike, + filter: Expression | None = None, # noqa: A002 spatial_extent: SpatialFilterLike | None = None, skip_data: bool = False, show_progress: bool | ProgressCallback = False, @@ -453,6 +460,7 @@ async def query( first and last value in the array and the end time inclusive - xr.Dataset: [ds.time[0], ds.time[-1]] -> Construct a TimeInterval with start and end time set to the first and last value in the time coordinate of the dataset and the end time inclusive + filter: An expression over queryable dataset fields. (Optional) spatial_extent: The spatial extent to query data in. (Optional) Expected to be either a shapely geometry, or a dict with the following keys: - geometry: The geometry to query by. Must be a shapely.Polygon, shapely.MultiPolygon or shapely.Point. @@ -469,6 +477,8 @@ async def query( Returns: Matching datapoints in the given temporal and spatial extent as an xarray dataset """ + if filter is not None and not isinstance(filter, Expression): + raise TypeError(f"Expected a query expression, got {type(filter).__name__}") if temporal_extent is None: raise ValueError("A temporal_extent for your query must be specified") @@ -479,6 +489,7 @@ async def query( temporal_extent, spatial_extent, skip_data, + filter_expression=filter, dataset_name=self._dataset.name, show_progress=show_progress, ) @@ -618,12 +629,17 @@ async def _iter_query_pages( # noqa: PLR0913, PLR0917 spatial_extent: SpatialFilterLike | None = None, skip_data: bool = False, *, + filter_expression: Expression | None = None, dataset_name: str, show_progress: bool | ProgressCallback = False, page_size: int | None = None, ) -> AsyncIterator[QueryResultPage]: time_interval = TimeInterval.parse(temporal_extent) - filters = QueryFilters(time_interval, SpatialFilter.parse(spatial_extent) if spatial_extent else None) + filters = QueryFilters( + time_interval, + SpatialFilter.parse(spatial_extent) if spatial_extent else None, + filter_expression, + ) request = partial(_query_page, service, dataset_id, collection_ids, filters, skip_data) diff --git a/tilebox-datasets/tilebox/datasets/data/data_access.py b/tilebox-datasets/tilebox/datasets/data/data_access.py index 8d93d20..b081340 100644 --- a/tilebox-datasets/tilebox/datasets/data/data_access.py +++ b/tilebox-datasets/tilebox/datasets/data/data_access.py @@ -8,6 +8,7 @@ from typing_extensions import NotRequired from tilebox.datasets.datasets.v1 import data_access_pb2, well_known_types_pb2 +from tilebox.datasets.query.expression import Expression from tilebox.datasets.query.id_interval import IDInterval from tilebox.datasets.query.time_interval import TimeInterval @@ -106,31 +107,52 @@ def parse(cls, spatial_filter_like: SpatialFilterLike) -> "SpatialFilter": class QueryFilters: temporal_extent: TimeInterval | IDInterval spatial_extent: SpatialFilter | None = None + filter: Expression | None = None + + def __post_init__(self) -> None: + if self.filter is not None and not isinstance(self.filter, Expression): + raise TypeError(f"Expected a query expression, got {type(self.filter).__name__}") @classmethod def from_message(cls, filters: data_access_pb2.QueryFilters) -> "QueryFilters": - temporal_extent: TimeInterval | IDInterval | None = None - if filters.HasField("time_interval"): + interval_variants = [name for name in ("time_interval", "datapoint_interval") if filters.HasField(name)] + if len(interval_variants) != 1: + raise ValueError("Invalid filter: exactly one time or datapoint interval must be set") + + if interval_variants[0] == "time_interval": temporal_extent = TimeInterval.from_message(filters.time_interval) - if filters.HasField("datapoint_interval"): + else: temporal_extent = IDInterval.from_message(filters.datapoint_interval) - if temporal_extent is None: - raise ValueError("Invalid filter: time or datapoint interval must be set") - spatial_extent = None if filters.HasField("spatial_extent"): spatial_extent = SpatialFilter.from_message(filters.spatial_extent) - return QueryFilters(temporal_extent=temporal_extent, spatial_extent=spatial_extent) + expressions = tuple(Expression.from_message(expression) for expression in filters.expressions) + filter_expression = None + if expressions: + filter_expression = expressions[0] + for expression in expressions[1:]: + filter_expression &= expression + + return QueryFilters( + temporal_extent=temporal_extent, + spatial_extent=spatial_extent, + filter=filter_expression, + ) def to_message(self) -> data_access_pb2.QueryFilters: spatial_extent = self.spatial_extent.to_message() if self.spatial_extent else None + expressions = [self.filter.to_message()] if self.filter is not None else [] if isinstance(self.temporal_extent, TimeInterval): return data_access_pb2.QueryFilters( - time_interval=self.temporal_extent.to_message(), spatial_extent=spatial_extent + time_interval=self.temporal_extent.to_message(), + spatial_extent=spatial_extent, + expressions=expressions, ) return data_access_pb2.QueryFilters( - datapoint_interval=self.temporal_extent.to_message(), spatial_extent=spatial_extent + datapoint_interval=self.temporal_extent.to_message(), + spatial_extent=spatial_extent, + expressions=expressions, ) diff --git a/tilebox-datasets/tilebox/datasets/message_pool.py b/tilebox-datasets/tilebox/datasets/message_pool.py index bfd5ffd..875d612 100644 --- a/tilebox-datasets/tilebox/datasets/message_pool.py +++ b/tilebox-datasets/tilebox/datasets/message_pool.py @@ -4,10 +4,34 @@ from google.protobuf.message_factory import GetMessageClass, GetMessages from tilebox.datasets.data.datasets import AnnotatedType +from tilebox.datasets.datasets.stac.v1 import ( + asset_metadata_pb2, + asset_pb2, + authentication_pb2, + core_pb2, + processing_pb2, + product_pb2, + sar_pb2, + satellite_pb2, + storage_pb2, +) from tilebox.datasets.datasets.v1 import well_known_types_pb2 # make sure all the well known types are imported, and therefore available in the global protobuf message pool -__all__ = ["duration_pb2", "timestamp_pb2", "well_known_types_pb2"] # this is here so ruff doesn't remove the imports +__all__ = [ # this is here so ruff doesn't remove the imports + "asset_metadata_pb2", + "asset_pb2", + "authentication_pb2", + "core_pb2", + "duration_pb2", + "processing_pb2", + "product_pb2", + "sar_pb2", + "satellite_pb2", + "storage_pb2", + "timestamp_pb2", + "well_known_types_pb2", +] def register_once(message_type: AnnotatedType) -> type: diff --git a/tilebox-datasets/tilebox/datasets/query/__init__.py b/tilebox-datasets/tilebox/datasets/query/__init__.py index c77fae8..1946ec5 100644 --- a/tilebox-datasets/tilebox/datasets/query/__init__.py +++ b/tilebox-datasets/tilebox/datasets/query/__init__.py @@ -1,3 +1,4 @@ +from tilebox.datasets.query.expression import Expression, Field, field from tilebox.datasets.query.time_interval import ( TimeInterval, TimeIntervalLike, @@ -5,4 +6,12 @@ timestamp_to_datetime, ) -__all__ = ["TimeInterval", "TimeIntervalLike", "datetime_to_timestamp", "timestamp_to_datetime"] +__all__ = [ + "Expression", + "Field", + "TimeInterval", + "TimeIntervalLike", + "datetime_to_timestamp", + "field", + "timestamp_to_datetime", +] diff --git a/tilebox-datasets/tilebox/datasets/query/expression.py b/tilebox-datasets/tilebox/datasets/query/expression.py new file mode 100644 index 0000000..af6b4f5 --- /dev/null +++ b/tilebox-datasets/tilebox/datasets/query/expression.py @@ -0,0 +1,327 @@ +from dataclasses import dataclass +from datetime import datetime, timedelta +from enum import Enum +from math import isfinite +from typing import TYPE_CHECKING, ClassVar, NoReturn, TypeAlias + +from google.protobuf.duration_pb2 import Duration + +from tilebox.datasets.datasets.v1 import data_access_pb2 +from tilebox.datasets.query.time_interval import datetime_to_timestamp, timedelta_to_duration + +if TYPE_CHECKING: + import numpy as np + + _QueryScalar: TypeAlias = bool | int | float | str | bytes | datetime | timedelta | Enum | np.generic +else: + _QueryScalar: TypeAlias = object + +_INT64_MIN = -(2**63) +_INT64_MAX = 2**63 - 1 +_UINT64_MAX = 2**64 - 1 +_DURATION_MAX_SECONDS = 315_576_000_000 + + +class Expression: + """A server-side filter expression over queryable dataset fields. + + Expressions use SQL- and CQL2-compatible three-valued Boolean logic. A comparison with a missing or null field + evaluates to unknown, and a datapoint matches only when the complete expression evaluates to true. + """ + + def __and__(self, other: "Expression") -> "Expression": + return _combine(data_access_pb2.LOGICAL_OPERATOR_AND, self, other) + + def __or__(self, other: "Expression") -> "Expression": + return _combine(data_access_pb2.LOGICAL_OPERATOR_OR, self, other) + + def __invert__(self) -> "Expression": + return _Logical(data_access_pb2.LOGICAL_OPERATOR_NOT, (self,)) + + def __bool__(self) -> NoReturn: + raise TypeError( + "Query expressions cannot be evaluated as Python booleans. " + "Use &, |, and ~; chained comparisons are unsupported." + ) + + def to_message(self) -> data_access_pb2.FilterExpression: + """Convert this expression to its protobuf representation.""" + # Each concrete expression node overrides this method with its protobuf representation. + raise NotImplementedError + + @classmethod + def from_message(cls, message: data_access_pb2.FilterExpression) -> "Expression": + """Construct an expression from its protobuf representation.""" + variants = [name for name in ("logical", "comparison", "is_null") if message.HasField(name)] + if len(variants) != 1: + raise ValueError("A filter expression must contain exactly one expression node") + + variant = variants[0] + if variant == "comparison": + comparison = message.comparison + if not comparison.HasField("value"): + raise ValueError("A field comparison must contain a value") + if comparison.operator not in _COMPARISON_OPERATORS: + raise ValueError(f"Unknown field comparison operator: {comparison.operator}") + query_value = _QueryValue.from_message(comparison.value) + _validate_comparison(comparison.operator, query_value) + return _Comparison( + comparison.field_name, + comparison.operator, + query_value, + ) + if variant == "is_null": + return _NullCheck(message.is_null.field_name) + + logical = message.logical + if logical.operator not in _LOGICAL_OPERATORS: + raise ValueError(f"Unknown logical operator: {logical.operator}") + operands = tuple(cls.from_message(operand) for operand in logical.operands) + if logical.operator == data_access_pb2.LOGICAL_OPERATOR_NOT: + if len(operands) != 1: + raise ValueError("NOT requires exactly one operand") + elif len(operands) < 2: + raise ValueError("AND and OR require at least two operands") + return _Logical(logical.operator, operands) + + +@dataclass(frozen=True, slots=True) +class Field: + """A reference to a queryable dataset field.""" + + __array_priority__: ClassVar[float] = 1000 + + name: str + + __hash__ = None + + def __eq__(self, value: object) -> Expression: # type: ignore[override] + return self._comparison(data_access_pb2.FIELD_COMPARISON_OPERATOR_EQUAL, value) + + def __ne__(self, value: object) -> Expression: # type: ignore[override] + return self._comparison(data_access_pb2.FIELD_COMPARISON_OPERATOR_NOT_EQUAL, value) + + def __lt__(self, value: _QueryScalar) -> Expression: + return self._comparison(data_access_pb2.FIELD_COMPARISON_OPERATOR_LESS_THAN, value) + + def __le__(self, value: _QueryScalar) -> Expression: + return self._comparison(data_access_pb2.FIELD_COMPARISON_OPERATOR_LESS_THAN_OR_EQUAL, value) + + def __gt__(self, value: _QueryScalar) -> Expression: + return self._comparison(data_access_pb2.FIELD_COMPARISON_OPERATOR_GREATER_THAN, value) + + def __ge__(self, value: _QueryScalar) -> Expression: + return self._comparison(data_access_pb2.FIELD_COMPARISON_OPERATOR_GREATER_THAN_OR_EQUAL, value) + + def is_null(self) -> Expression: + """Match datapoints where this field is missing or explicitly null.""" + return _NullCheck(self.name) + + def is_not_null(self) -> Expression: + """Match datapoints where this field is present and non-null.""" + return ~self.is_null() + + def _comparison(self, operator: data_access_pb2.FieldComparisonOperator, value: object) -> Expression: + query_value = _QueryValue.parse(value) + _validate_comparison(operator, query_value) + return _Comparison(self.name, operator, query_value) + + +def field(name: str) -> Field: + """Reference a queryable dataset field in a filter expression.""" + return Field(name) + + +@dataclass(frozen=True, slots=True) +class _QueryValue: + kind: str + value: bool | int | float | str | bytes | tuple[int, int] + + @classmethod + def parse(cls, value: object) -> "_QueryValue": # noqa: C901, PLR0911, PLR0912 + import numpy as np # noqa: PLC0415 + + if isinstance(value, Enum): + return cls("enum_name", value.name) + if isinstance(value, bool | np.bool_): + return cls("bool_value", bool(value)) + if isinstance(value, np.datetime64): + if np.isnat(value): + raise ValueError("NaT is not a valid query value") + return cls("timestamp_value", _nanoseconds_to_parts(int(value.astype("datetime64[ns]").astype(np.int64)))) + if isinstance(value, np.timedelta64): + if np.isnat(value): + raise ValueError("NaT is not a valid query value") + duration = Duration() + duration.FromNanoseconds(int(value.astype("timedelta64[ns]").astype(np.int64))) + _validate_duration(duration.seconds, duration.nanos) + return cls("duration_value", (duration.seconds, duration.nanos)) + if isinstance(value, datetime): + timestamp = datetime_to_timestamp(value) + return cls("timestamp_value", (timestamp.seconds, timestamp.nanos)) + if isinstance(value, timedelta): + duration = timedelta_to_duration(value) + _validate_duration(duration.seconds, duration.nanos) + return cls("duration_value", (duration.seconds, duration.nanos)) + if isinstance(value, np.unsignedinteger): + integer = int(value) + if integer > _UINT64_MAX: + raise OverflowError("Unsigned query integer does not fit in uint64") + return cls("uint64_value", integer) + if isinstance(value, int | np.signedinteger): + integer = int(value) + if not _INT64_MIN <= integer <= _INT64_MAX: + raise OverflowError("Query integer does not fit in int64") + return cls("int64_value", integer) + if isinstance(value, float | np.floating): + number = float(value) + if not isfinite(number): + raise ValueError("Query float values must be finite") + return cls("double_value", number) + if isinstance(value, str | np.str_): + return cls("string_value", str(value)) + if isinstance(value, bytes | np.bytes_): + return cls("bytes_value", bytes(value)) + if value is None: + raise TypeError("None is not a query value; use field(...).is_null()") + raise TypeError(f"Unsupported query value type: {type(value).__name__}") + + @classmethod + def from_message(cls, message: data_access_pb2.FieldQueryValue) -> "_QueryValue": + variants = [name for name in _QUERY_VALUE_KINDS if message.HasField(name)] + if len(variants) != 1: + raise ValueError("A field query value must contain exactly one typed literal") + kind = variants[0] + value = getattr(message, kind) + if kind in {"timestamp_value", "duration_value"}: + value = (value.seconds, value.nanos) + if kind == "duration_value": + _validate_duration(*value) + return cls(kind, value) + + def to_message(self) -> data_access_pb2.FieldQueryValue: + if self.kind == "timestamp_value": + if not isinstance(self.value, tuple): + raise TypeError("Invalid timestamp query value") + seconds, nanos = self.value + return data_access_pb2.FieldQueryValue( + timestamp_value={"seconds": seconds, "nanos": nanos}, + ) + if self.kind == "duration_value": + if not isinstance(self.value, tuple): + raise TypeError("Invalid duration query value") + seconds, nanos = self.value + _validate_duration(seconds, nanos) + return data_access_pb2.FieldQueryValue( + duration_value={"seconds": seconds, "nanos": nanos}, + ) + message = data_access_pb2.FieldQueryValue() + setattr(message, self.kind, self.value) + return message + + +@dataclass(frozen=True, slots=True) +class _Comparison(Expression): + field_name: str + operator: data_access_pb2.FieldComparisonOperator + value: _QueryValue + + def to_message(self) -> data_access_pb2.FilterExpression: + return data_access_pb2.FilterExpression( + comparison=data_access_pb2.FieldComparison( + field_name=self.field_name, + operator=self.operator, + value=self.value.to_message(), + ) + ) + + +@dataclass(frozen=True, slots=True) +class _NullCheck(Expression): + field_name: str + + def to_message(self) -> data_access_pb2.FilterExpression: + return data_access_pb2.FilterExpression( + is_null=data_access_pb2.FieldNullCheck(field_name=self.field_name), + ) + + +@dataclass(frozen=True, slots=True) +class _Logical(Expression): + operator: data_access_pb2.LogicalOperator + operands: tuple[Expression, ...] + + def to_message(self) -> data_access_pb2.FilterExpression: + return data_access_pb2.FilterExpression( + logical=data_access_pb2.LogicalExpression( + operator=self.operator, + operands=[operand.to_message() for operand in self.operands], + ) + ) + + +def _combine(operator: data_access_pb2.LogicalOperator, left: Expression, right: Expression) -> Expression: + if not isinstance(right, Expression): + raise TypeError(f"Expected a query expression, got {type(right).__name__}") + operands: tuple[Expression, ...] = () + for expression in (left, right): + if isinstance(expression, _Logical) and expression.operator == operator: + operands += expression.operands + else: + operands += (expression,) + return _Logical(operator, operands) + + +def _nanoseconds_to_parts(nanoseconds: int) -> tuple[int, int]: + return divmod(nanoseconds, 1_000_000_000) + + +def _validate_comparison( + operator: data_access_pb2.FieldComparisonOperator, + value: _QueryValue, +) -> None: + if value.kind == "bool_value" and operator not in { + data_access_pb2.FIELD_COMPARISON_OPERATOR_EQUAL, + data_access_pb2.FIELD_COMPARISON_OPERATOR_NOT_EQUAL, + }: + raise TypeError("Boolean query values only support == and !=") + if value.kind == "double_value": + if not isinstance(value.value, float): + raise TypeError("Invalid double query value") + if not isfinite(value.value): + raise ValueError("Query float values must be finite") + + +def _validate_duration(seconds: int, nanos: int) -> None: + if not -_DURATION_MAX_SECONDS <= seconds <= _DURATION_MAX_SECONDS: + raise OverflowError("Query duration is outside the supported protobuf range") + if not -999_999_999 <= nanos <= 999_999_999: + raise ValueError("Query duration nanoseconds are outside the supported protobuf range") + if seconds > 0 > nanos or seconds < 0 < nanos: + raise ValueError("Query duration seconds and nanoseconds must have the same sign") + + +_COMPARISON_OPERATORS = { + data_access_pb2.FIELD_COMPARISON_OPERATOR_EQUAL, + data_access_pb2.FIELD_COMPARISON_OPERATOR_NOT_EQUAL, + data_access_pb2.FIELD_COMPARISON_OPERATOR_LESS_THAN, + data_access_pb2.FIELD_COMPARISON_OPERATOR_LESS_THAN_OR_EQUAL, + data_access_pb2.FIELD_COMPARISON_OPERATOR_GREATER_THAN, + data_access_pb2.FIELD_COMPARISON_OPERATOR_GREATER_THAN_OR_EQUAL, +} +_LOGICAL_OPERATORS = { + data_access_pb2.LOGICAL_OPERATOR_AND, + data_access_pb2.LOGICAL_OPERATOR_OR, + data_access_pb2.LOGICAL_OPERATOR_NOT, +} +_QUERY_VALUE_KINDS = ( + "bool_value", + "int64_value", + "uint64_value", + "double_value", + "string_value", + "timestamp_value", + "duration_value", + "enum_name", + "bytes_value", +) diff --git a/tilebox-datasets/tilebox/datasets/query/time_interval.py b/tilebox-datasets/tilebox/datasets/query/time_interval.py index 1d12bc4..df66a7b 100644 --- a/tilebox-datasets/tilebox/datasets/query/time_interval.py +++ b/tilebox-datasets/tilebox/datasets/query/time_interval.py @@ -217,6 +217,8 @@ def timestamp_to_datetime(timestamp: Timestamp) -> datetime: def datetime_to_timestamp(dt: datetime) -> Timestamp: """Convert a datetime object to a protobuf timestamp.""" + if dt.tzinfo is None: + dt = dt.replace(tzinfo=timezone.utc) # manual epoch offset calculation to avoid rounding errors and support negative timestamps (before 1970) offset_us = datetime_to_us(dt.astimezone(timezone.utc)) seconds, us = divmod(offset_us, 10**6) @@ -238,7 +240,9 @@ def us_to_datetime(us: int) -> datetime: def timedelta_to_duration(td: timedelta) -> Duration: """Convert a timedelta to a duration protobuf message.""" - return Duration(seconds=int(td.total_seconds()), nanos=int(td.microseconds * 1000)) + duration = Duration() + duration.FromTimedelta(td) + return duration def duration_to_timedelta(duration: Duration) -> timedelta: diff --git a/tilebox-datasets/tilebox/datasets/sync/dataset.py b/tilebox-datasets/tilebox/datasets/sync/dataset.py index 85bf87f..ea86271 100644 --- a/tilebox-datasets/tilebox/datasets/sync/dataset.py +++ b/tilebox-datasets/tilebox/datasets/sync/dataset.py @@ -24,6 +24,7 @@ marshal_messages, to_messages, ) +from tilebox.datasets.query.expression import Expression from tilebox.datasets.query.id_interval import IDInterval, IDIntervalLike from tilebox.datasets.query.pagination import Pagination from tilebox.datasets.query.time_interval import TimeInterval, TimeIntervalLike @@ -179,11 +180,12 @@ def find( converter.convert(data) return converter.finalize("time", skip_empty_fields=skip_data).isel(time=0) - def query( + def query( # noqa: PLR0913 self, *, collections: "list[str] | list[UUID] | list[Collection] | list[CollectionInfo] | list[CollectionClient] | dict[str, CollectionClient] | None" = None, temporal_extent: TimeIntervalLike, + filter: Expression | None = None, # noqa: A002 spatial_extent: SpatialFilterLike | None = None, skip_data: bool = False, show_progress: bool | ProgressCallback = False, @@ -195,6 +197,7 @@ def query( collections: The collections to query in. Supports collection names, ids or collection objects. If not specified, all collections in the dataset are queried. temporal_extent: The temporal extent to query data for. (Required) + filter: An expression over queryable dataset fields. (Optional) spatial_extent: The spatial extent to query data in. (Optional) skip_data: Whether to skip the actual data of the datapoint. If True, only datapoint metadata is returned. @@ -204,6 +207,8 @@ def query( Returns: Matching datapoints in the given temporal and spatial extent as an xarray dataset. """ + if filter is not None and not isinstance(filter, Expression): + raise TypeError(f"Expected a query expression, got {type(filter).__name__}") if temporal_extent is None: raise ValueError("A temporal_extent for your query must be specified") @@ -215,6 +220,7 @@ def query( temporal_extent, spatial_extent, skip_data, + filter_expression=filter, dataset_name=self.name, show_progress=show_progress, ) @@ -427,6 +433,7 @@ def query( self, *, temporal_extent: TimeIntervalLike, + filter: Expression | None = None, # noqa: A002 spatial_extent: SpatialFilterLike | None = None, skip_data: bool = False, show_progress: bool | ProgressCallback = False, @@ -446,6 +453,7 @@ def query( first and last value in the array and the end time inclusive - xr.Dataset: [ds.time[0], ds.time[-1]] -> Construct a TimeInterval with start and end time set to the first and last value in the time coordinate of the dataset and the end time inclusive + filter: An expression over queryable dataset fields. (Optional) spatial_extent: The spatial extent to query data in. (Optional) Expected to be either a shapely geometry, or a dict with the following keys: - geometry: The geometry to query by. Must be a shapely.Polygon, shapely.MultiPolygon or shapely.Point. @@ -462,6 +470,8 @@ def query( Returns: Matching datapoints in the given temporal and spatial extent as an xarray dataset """ + if filter is not None and not isinstance(filter, Expression): + raise TypeError(f"Expected a query expression, got {type(filter).__name__}") if temporal_extent is None: raise ValueError("A temporal_extent for your query must be specified") @@ -472,6 +482,7 @@ def query( temporal_extent, spatial_extent, skip_data, + filter_expression=filter, dataset_name=self._dataset.name, show_progress=show_progress, ) @@ -610,12 +621,17 @@ def _iter_query_pages( # noqa: PLR0913, PLR0917 spatial_extent: SpatialFilterLike | None = None, skip_data: bool = False, *, + filter_expression: Expression | None = None, dataset_name: str, show_progress: bool | ProgressCallback = False, page_size: int | None = None, ) -> Iterator[QueryResultPage]: time_interval = TimeInterval.parse(temporal_extent) - filters = QueryFilters(time_interval, SpatialFilter.parse(spatial_extent) if spatial_extent else None) + filters = QueryFilters( + time_interval, + SpatialFilter.parse(spatial_extent) if spatial_extent else None, + filter_expression, + ) request = partial(_query_page, service, dataset_id, collection_ids, filters, skip_data) From 70996aa00b8f735fe68d92b852146d8301ae5c20 Mon Sep 17 00:00:00 2001 From: Lukas Bindreiter Date: Mon, 27 Jul 2026 17:15:38 +0200 Subject: [PATCH 3/4] Preserve unsupported protobuf message fields, including STAC well-known types, as object-valued xarray variables --- CHANGELOG.md | 5 +++ .../test_protobuf_xarray.py | 43 +++++++++++++++++++ .../protobuf_conversion/field_types.py | 5 +-- 3 files changed, 49 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 446bec4..52ca834 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `tilebox-datasets`: Added typed server-side dataset query filters with comparison, logical, and null-check expressions through `field()` and the `filter` query argument. +### Fixed + +- `tilebox-datasets`: Preserve unsupported protobuf message fields, including STAC well-known types, as object-valued + xarray variables instead of failing dataset queries. + ## [0.56.0] - 2026-07-16 ### Added diff --git a/tilebox-datasets/tests/protobuf_conversion/test_protobuf_xarray.py b/tilebox-datasets/tests/protobuf_conversion/test_protobuf_xarray.py index 56020fa..fd5896e 100644 --- a/tilebox-datasets/tests/protobuf_conversion/test_protobuf_xarray.py +++ b/tilebox-datasets/tests/protobuf_conversion/test_protobuf_xarray.py @@ -2,6 +2,9 @@ import pandas as pd import pytest +from google.protobuf import descriptor_pb2 +from google.protobuf.descriptor_pool import Default +from google.protobuf.message_factory import GetMessageClass from hypothesis import given, settings from hypothesis.strategies import lists from numpy.testing import assert_array_almost_equal, assert_array_equal @@ -11,6 +14,7 @@ from tests.data.datapoint import example_datapoints from tests.example_dataset.example_dataset_pb2 import ExampleDatapoint +from tilebox.datasets.datasets.stac.v1.asset_pb2 import Assets from tilebox.datasets.protobuf_conversion.protobuf_xarray import MessageToXarrayConverter from tilebox.datasets.query.time_interval import timestamp_to_datetime, us_to_datetime @@ -99,6 +103,45 @@ def test_convert_datapoint(datapoint: ExampleDatapoint) -> None: # noqa: PLR091 assert isinstance(dataset.some_repeated_geometry[i].item(), Polygon | MultiPolygon) +def test_convert_unknown_message_fields_as_objects() -> None: + file_descriptor = descriptor_pb2.FileDescriptorProto( + name="tests/protobuf_conversion/stac_datapoint.proto", + package="tests.protobuf_conversion", + dependency=["datasets/stac/v1/asset.proto"], + ) + message_descriptor = file_descriptor.message_type.add(name="StacDatapoint") + message_descriptor.field.add( + name="assets", + number=1, + label=descriptor_pb2.FieldDescriptorProto.LABEL_OPTIONAL, + type=descriptor_pb2.FieldDescriptorProto.TYPE_MESSAGE, + type_name=".datasets.stac.v1.Assets", + ) + message_descriptor.field.add( + name="related_assets", + number=2, + label=descriptor_pb2.FieldDescriptorProto.LABEL_REPEATED, + type=descriptor_pb2.FieldDescriptorProto.TYPE_MESSAGE, + type_name=".datasets.stac.v1.Assets", + ) + descriptor = Default().AddSerializedFile(file_descriptor.SerializeToString()) + message_type = GetMessageClass(descriptor.message_types_by_name["StacDatapoint"]) + + assets = Assets() + related_assets = Assets() + messages = [message_type(), message_type(assets=assets, related_assets=[related_assets])] + + converter = MessageToXarrayConverter() + converter.convert_all(messages) + dataset = converter.finalize("time") + + assert dataset.assets.dtype == object + assert dataset.assets[0].item() is None + assert dataset.assets[1].item() == assets + assert dataset.related_assets.dtype == object + assert dataset.related_assets[1, 0].item() == related_assets + + @given(lists(example_datapoints(generated_fields=True, missing_fields=True), min_size=5, max_size=30)) def test_convert_datapoints(datapoints: list[ExampleDatapoint]) -> None: # noqa: C901, PLR0912 converter = MessageToXarrayConverter() diff --git a/tilebox-datasets/tilebox/datasets/protobuf_conversion/field_types.py b/tilebox-datasets/tilebox/datasets/protobuf_conversion/field_types.py index 22d92fd..649bc54 100644 --- a/tilebox-datasets/tilebox/datasets/protobuf_conversion/field_types.py +++ b/tilebox-datasets/tilebox/datasets/protobuf_conversion/field_types.py @@ -257,10 +257,7 @@ def infer_field_type(field: FieldDescriptor) -> ProtobufFieldType: if field.message_type is None: raise ValueError("Expected message type for field but got None") message_name = field.message_type.full_name - if message_name not in _MESSAGE_NAMES_TO_FIELDS: - raise ValueError(f"Unsupported message type {message_name}") - - return _MESSAGE_NAMES_TO_FIELDS[message_name] + return _MESSAGE_NAMES_TO_FIELDS.get(message_name, ProtobufFieldType(object)) if field.type == FieldDescriptor.TYPE_ENUM: return EnumField(enum_mapping_from_field_descriptor(field)) From a37cc701f0c235b9f429060e5e7aabfc7fd9e3a2 Mon Sep 17 00:00:00 2001 From: Lukas Bindreiter Date: Mon, 27 Jul 2026 17:33:35 +0200 Subject: [PATCH 4/4] Prepare release --- CHANGELOG.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 52ca834..a5a7320 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.57.0] - 2026-07-27 + ### Added - `tilebox-datasets`: Added typed server-side dataset query filters with comparison, logical, and null-check @@ -427,7 +429,8 @@ the first client that does not cache data (since it's already on the local file - Released under the [MIT](https://opensource.org/license/mit) license. - Released packages: `tilebox-datasets`, `tilebox-workflows`, `tilebox-storage`, `tilebox-grpc` -[Unreleased]: https://github.com/tilebox/tilebox-python/compare/v0.56.0...HEAD +[Unreleased]: https://github.com/tilebox/tilebox-python/compare/v0.57.0...HEAD +[0.57.0]: https://github.com/tilebox/tilebox-python/compare/v0.56.0...v0.57.0 [0.56.0]: https://github.com/tilebox/tilebox-python/compare/v0.55.1...v0.56.0 [0.55.1]: https://github.com/tilebox/tilebox-python/compare/v0.55.0...v0.55.1 [0.55.0]: https://github.com/tilebox/tilebox-python/compare/v0.54.0...v0.55.0