From dd34ab593d56f019ddc3676935874f39a4f4a382 Mon Sep 17 00:00:00 2001 From: Lukas Bindreiter Date: Tue, 27 Oct 2026 14:15:56 +0100 Subject: [PATCH 1/4] Migrate from grpc / google protobuf to connect/buf protobuf-py --- .docs/packages.d2 | 24 +- .docs/packages.svg | 135 +- .github/workflows/main.yml | 4 +- .github/workflows/publish_tilebox_core.yml | 33 + AGENTS.md | 32 +- CHANGELOG.md | 38 + CONTRIBUTING.md | 24 +- README.md | 6 + buf.gen.datasets-bufpy.yaml | 13 - buf.gen.datasets.yaml | 25 - buf.gen.workflows.yaml | 24 - buf.gen.yaml | 30 + matrix.toml | 8 +- pyproject.toml | 26 +- tests.sh | 24 +- tilebox-core/README.md | 16 + tilebox-core/_tilebox/__init__.py | 1 + tilebox-core/_tilebox/aio/__init__.py | 1 + .../_tilebox/aio/concurrency.py | 11 +- .../_tilebox}/aio/pagination.py | 4 +- tilebox-core/_tilebox/client.py | 211 +++ .../_tilebox}/client_metadata.py | 2 +- tilebox-core/_tilebox/concurrency.py | 45 + tilebox-core/_tilebox/errors.py | 65 + .../_tilebox}/pagination.py | 2 +- .../_tilebox/proto/tilebox/v1/id_pb.py | 60 + .../_tilebox/proto/tilebox/v1/query_pb.py | 231 +++ tilebox-core/_tilebox/replay.py | 147 ++ tilebox-core/_tilebox/time.py | 62 + tilebox-core/_tilebox/uuid.py | 44 + tilebox-core/pyproject.toml | 43 + .../tests/test_client_metadata.py | 29 +- tilebox-core/tests/test_iteration.py | 65 + tilebox-core/tests/test_query.py | 49 + tilebox-core/tests/test_transport.py | 255 +++ tilebox-core/tilebox/errors.py | 47 + tilebox-core/tilebox/query/__init__.py | 7 + tilebox-core/tilebox/query/id_interval.py | 70 + tilebox-core/tilebox/query/pagination.py | 21 + tilebox-core/tilebox/query/time_interval.py | 212 +++ tilebox-datasets/pyproject.toml | 9 +- tilebox-datasets/tests/data/datapoint.py | 8 +- tilebox-datasets/tests/data/datasets.py | 31 +- .../tests/data/test_data_access.py | 13 +- tilebox-datasets/tests/data/test_datasets.py | 83 +- .../tests/data/well_known_types.py | 42 +- .../example_dataset/example_dataset_pb2.py | 46 - .../example_dataset/example_dataset_pb2.pyi | 125 -- .../tests/example_dataset/schema.py | 86 + .../test_protobuf_xarray.py | 234 ++- .../protobuf_conversion/test_to_protobuf.py | 43 +- .../tests/query/test_expression.py | 67 +- .../tests/query/test_pagination.py | 42 + .../tests/query/test_time_interval.py | 30 +- tilebox-datasets/tests/test_aio.py | 18 +- tilebox-datasets/tests/test_assets.py | 31 +- tilebox-datasets/tests/test_client.py | 126 +- tilebox-datasets/tests/test_notices.py | 8 +- tilebox-datasets/tests/test_timeseries.py | 146 +- tilebox-datasets/tilebox/datasets/__init__.py | 13 +- .../tilebox/datasets/aio/client.py | 204 +-- .../tilebox/datasets/aio/dataset.py | 187 ++- .../tilebox/datasets/aio/pagination.py | 6 +- .../tilebox/datasets/assets/assets.py | 24 +- tilebox-datasets/tilebox/datasets/client.py | 128 +- .../tilebox/datasets/data/collection.py | 22 +- .../tilebox/datasets/data/data_access.py | 40 +- .../tilebox/datasets/data/datapoint.py | 45 +- .../tilebox/datasets/data/datasets.py | 175 +- .../tilebox/datasets/data/timeseries.py | 44 +- .../tilebox/datasets/datasets/__init__.py | 1 - .../datasets/datasets/stac/__init__.py | 1 - .../datasets/datasets/stac/v1/__init__.py | 1 - .../datasets/stac/v1/asset_metadata_pb2.py | 101 -- .../datasets/stac/v1/asset_metadata_pb2.pyi | 152 -- .../stac/v1/asset_metadata_pb2_grpc.py | 3 - .../datasets/datasets/stac/v1/asset_pb2.py | 86 - .../datasets/datasets/stac/v1/asset_pb2.pyi | 229 --- .../datasets/stac/v1/asset_pb2_grpc.py | 3 - .../datasets/stac/v1/authentication_pb2.py | 88 - .../datasets/stac/v1/authentication_pb2.pyi | 119 -- .../stac/v1/authentication_pb2_grpc.py | 3 - .../datasets/datasets/stac/v1/core_pb2.py | 59 - .../datasets/datasets/stac/v1/core_pb2.pyi | 120 -- .../datasets/stac/v1/core_pb2_grpc.py | 3 - .../datasets/stac/v1/processing_pb2.py | 41 - .../datasets/stac/v1/processing_pb2.pyi | 20 - .../datasets/stac/v1/processing_pb2_grpc.py | 3 - .../datasets/datasets/stac/v1/product_pb2.py | 45 - .../datasets/datasets/stac/v1/product_pb2.pyi | 29 - .../datasets/stac/v1/product_pb2_grpc.py | 3 - .../datasets/datasets/stac/v1/sar_pb2.py | 65 - .../datasets/datasets/stac/v1/sar_pb2.pyi | 97 -- .../datasets/datasets/stac/v1/sar_pb2_grpc.py | 3 - .../datasets/stac/v1/satellite_pb2.py | 52 - .../datasets/stac/v1/satellite_pb2.pyi | 50 - .../datasets/stac/v1/satellite_pb2_grpc.py | 3 - .../datasets/datasets/stac/v1/storage_pb2.py | 68 - .../datasets/datasets/stac/v1/storage_pb2.pyi | 68 - .../datasets/stac/v1/storage_pb2_grpc.py | 3 - .../datasets/datasets/v1/collections_pb2.py | 53 - .../datasets/datasets/v1/collections_pb2.pyi | 61 - .../datasets/v1/collections_pb2_grpc.py | 210 --- .../tilebox/datasets/datasets/v1/core_pb2.py | 58 - .../tilebox/datasets/datasets/v1/core_pb2.pyi | 111 -- .../datasets/datasets/v1/core_pb2_grpc.py | 3 - .../datasets/v1/data_access_connect.py | 195 --- .../datasets/datasets/v1/data_access_pb2.py | 89 -- .../datasets/datasets/v1/data_access_pb2.pyi | 174 -- .../datasets/v1/data_access_pb2_grpc.py | 126 -- .../datasets/v1/data_ingestion_pb2.py | 46 - .../datasets/v1/data_ingestion_pb2.pyi | 42 - .../datasets/v1/data_ingestion_pb2_grpc.py | 123 -- .../datasets/datasets/v1/dataset_type_pb2.py | 59 - .../datasets/datasets/v1/dataset_type_pb2.pyi | 94 -- .../datasets/v1/dataset_type_pb2_grpc.py | 3 - .../datasets/datasets/v1/datasets_pb2.py | 66 - .../datasets/datasets/v1/datasets_pb2.pyi | 78 - .../datasets/datasets/v1/datasets_pb2_grpc.py | 253 --- .../datasets/datasets/v1/timeseries_pb2.py | 42 - .../datasets/datasets/v1/timeseries_pb2.pyi | 35 - .../datasets/v1/timeseries_pb2_grpc.py | 3 - .../datasets/v1/well_known_types_pb2.py | 59 - .../datasets/v1/well_known_types_pb2.pyi | 170 -- .../datasets/v1/well_known_types_pb2_grpc.py | 3 - .../tilebox/datasets/message_pool.py | 139 +- .../protobuf_conversion/field_types.py | 175 +- .../protobuf_conversion/protobuf_xarray.py | 96 +- .../protobuf_conversion/to_protobuf.py | 27 +- .../tilebox/datasets/query/expression.py | 105 +- .../tilebox/datasets/query/id_interval.py | 71 +- .../tilebox/datasets/query/pagination.py | 22 +- .../tilebox/datasets/query/time_interval.py | 275 +--- tilebox-datasets/tilebox/datasets/schema.py | 37 +- tilebox-datasets/tilebox/datasets/service.py | 326 ---- .../stac/v1/asset_metadata_pb.py | 2 +- .../{datasets => }/stac/v1/asset_pb.py | 16 +- .../stac/v1/authentication_pb.py | 6 +- .../{datasets => }/stac/v1/core_pb.py | 2 +- .../{datasets => }/stac/v1/processing_pb.py | 2 +- .../{datasets => }/stac/v1/product_pb.py | 2 +- .../datasets/{datasets => }/stac/v1/sar_pb.py | 2 +- .../{datasets => }/stac/v1/satellite_pb.py | 6 +- .../{datasets => }/stac/v1/storage_pb.py | 6 +- .../tilebox/datasets/sync/client.py | 208 +-- .../tilebox/datasets/sync/dataset.py | 187 ++- .../tilebox/datasets/sync/pagination.py | 8 +- .../tilebox/datasets/tilebox/v1/id_pb2.py | 37 - .../tilebox/datasets/tilebox/v1/id_pb2.pyi | 11 - .../datasets/tilebox/v1/id_pb2_grpc.py | 3 - .../tilebox/datasets/tilebox/v1/query_pb2.py | 49 - .../tilebox/datasets/tilebox/v1/query_pb2.pyi | 50 - .../datasets/tilebox/v1/query_pb2_grpc.py | 3 - tilebox-datasets/tilebox/datasets/uuid.py | 56 +- .../{datasets => }/v1/collections_connect.py | 206 ++- .../tilebox/datasets/v1/collections_pb.py | 298 ++++ .../tilebox/datasets/v1/core_pb.py | 462 ++++++ .../datasets/v1/data_access_connect.py | 259 +++ .../tilebox/datasets/v1/data_access_pb.py | 731 +++++++++ .../v1/data_ingestion_connect.py | 140 +- .../tilebox/datasets/v1/data_ingestion_pb.py | 206 +++ .../tilebox/datasets/v1/dataset_type_pb.py | 424 +++++ .../{datasets => }/v1/datasets_connect.py | 238 +-- .../tilebox/datasets/v1/datasets_pb.py | 371 +++++ .../tilebox/datasets/v1/timeseries_pb.py | 146 ++ .../datasets/v1/well_known_types_pb.py | 637 ++++++++ tilebox-grpc/README.md | 58 +- tilebox-grpc/_tilebox/grpc/__init__.py | 6 - tilebox-grpc/_tilebox/grpc/aio/__init__.py | 0 tilebox-grpc/_tilebox/grpc/aio/channel.py | 120 -- tilebox-grpc/_tilebox/grpc/aio/error.py | 40 - tilebox-grpc/_tilebox/grpc/channel.py | 355 ----- tilebox-grpc/_tilebox/grpc/error.py | 303 ---- .../_tilebox/grpc/producer_consumer.py | 56 - tilebox-grpc/_tilebox/grpc/public.py | 1 - tilebox-grpc/_tilebox/grpc/replay.py | 173 -- tilebox-grpc/pyproject.toml | 42 +- tilebox-grpc/tests/__init__.py | 0 tilebox-grpc/tests/aio/test_channel.py | 61 - tilebox-grpc/tests/aio/test_error.py | 38 - .../tests/aio/test_producer_consumer.py | 73 - tilebox-grpc/tests/conftest.py | 0 tilebox-grpc/tests/test_channel.py | 210 --- tilebox-grpc/tests/test_error.py | 57 - tilebox-grpc/tests/test_fork_logging.py | 81 - tilebox-storage/pyproject.toml | 3 +- tilebox-storage/tests/test_asset_client.py | 6 +- .../tests/test_planetary_computer.py | 4 +- .../tilebox/storage/_authentication.py | 2 +- tilebox-storage/tilebox/storage/_backends.py | 2 +- .../tilebox/storage/_planetary_computer.py | 2 +- tilebox-storage/tilebox/storage/_registry.py | 4 +- tilebox-storage/tilebox/storage/aio.py | 2 +- tilebox-storage/tilebox/storage/client.py | 4 +- tilebox-workflows/pyproject.toml | 10 +- .../tests/automations/test_client.py | 35 +- .../tests/automations/test_cron.py | 2 +- .../tests/automations/test_storage_event.py | 2 +- .../tests/automations/test_storage_read.py | 40 +- .../tests/clusters/test_client.py | 35 +- tilebox-workflows/tests/jobs/test_client.py | 123 +- tilebox-workflows/tests/log_values.py | 9 + .../tests/observability/test_logging.py | 87 +- .../tests/observability/test_tracing.py | 43 +- tilebox-workflows/tests/proto/test_pb.py | 64 + tilebox-workflows/tests/proto/test_pb2.py | 27 - tilebox-workflows/tests/proto/test_pb2.pyi | 14 - .../tests/runner/test_log_stream.py | 151 +- tilebox-workflows/tests/runner/test_runner.py | 31 +- .../tests/runner/test_runtime_logging.py | 46 +- .../tests/runner/test_worker_concurrency.py | 72 +- .../tests/runner/test_worker_server.py | 367 +++-- .../tests/storage_locations/test_client.py | 49 +- tilebox-workflows/tests/test_client.py | 45 +- tilebox-workflows/tests/test_data.py | 64 +- tilebox-workflows/tests/test_task.py | 2 +- .../tests/test_task_serialization.py | 23 +- .../tests/workflows/test_client.py | 110 +- .../tilebox/workflows/__init__.py | 3 - tilebox-workflows/tilebox/workflows/_codec.py | 1 + .../tilebox/workflows/_codecs/tilebox.py | 29 +- .../tilebox/workflows/_otel/LICENSE | 201 +++ .../tilebox/workflows/_otel/NOTICE | 45 + .../proto/common/v1/common_pb.py | 343 ++++ .../opentelemetry/proto/logs/v1/logs_pb.py | 534 +++++++ .../proto/resource/v1/resource_pb.py | 90 ++ .../opentelemetry/proto/trace/v1/trace_pb.py | 776 +++++++++ .../tilebox/workflows/_serialization.py | 8 +- .../tilebox/workflows/automations/client.py | 21 +- .../tilebox/workflows/automations/cron.py | 14 +- .../tilebox/workflows/automations/service.py | 50 - .../workflows/automations/storage_event.py | 14 +- tilebox-workflows/tilebox/workflows/client.py | 115 +- .../tilebox/workflows/clusters/client.py | 28 +- .../tilebox/workflows/clusters/service.py | 53 - tilebox-workflows/tilebox/workflows/data.py | 428 +++-- .../tilebox/workflows/jobs/client.py | 86 +- .../tilebox/workflows/jobs/service.py | 81 - .../workflows/jobs/telemetry_service.py | 64 - .../workflows/observability/_log_stream.py | 50 +- .../workflows/observability/tracing.py | 21 +- .../tilebox/workflows/runner/task_runner.py | 18 +- .../tilebox/workflows/runner/task_service.py | 31 +- .../tilebox/workflows/runner/worker_server.py | 179 ++- .../workflows/runner/worker_service.py | 148 +- .../workflows/storage_locations/client.py | 42 +- .../workflows/storage_locations/data.py | 8 +- .../workflows/storage_locations/service.py | 46 - tilebox-workflows/tilebox/workflows/task.py | 10 +- .../{workflows => }/v1/automation_connect.py | 308 ++-- .../tilebox/workflows/v1/automation_pb.py | 365 +++++ .../tilebox/workflows/v1/core_pb.py | 999 ++++++++++++ .../{workflows => }/v1/diagram_connect.py | 106 +- .../tilebox/workflows/v1/diagram_pb.py | 167 ++ .../{workflows => }/v1/job_connect.py | 444 +++--- .../tilebox/workflows/v1/job_pb.py | 1200 ++++++++++++++ .../v1/storage_location_connect.py | 608 ++++--- .../workflows/v1/storage_location_pb.py | 1215 ++++++++++++++ .../tilebox/workflows/v1/task_connect.py | 380 +++++ .../tilebox/workflows/v1/task_pb.py | 399 +++++ .../tilebox/workflows/v1/telemetry_connect.py | 433 +++++ .../tilebox/workflows/v1/telemetry_pb.py | 534 +++++++ .../tilebox/workflows/v1/worker_connect.py | 584 +++++++ .../tilebox/workflows/v1/worker_pb.py | 218 +++ .../{workflows => }/v1/workflows_connect.py | 576 +++---- .../tilebox/workflows/v1/workflows_pb.py | 1415 +++++++++++++++++ .../tilebox/workflows/workflows/client.py | 64 +- .../tilebox/workflows/workflows/service.py | 110 -- .../workflows/workflows/v1/automation_pb2.py | 59 - .../workflows/workflows/v1/automation_pb2.pyi | 77 - .../workflows/v1/automation_pb2_grpc.py | 265 --- .../workflows/workflows/v1/core_pb2.py | 78 - .../workflows/workflows/v1/core_pb2.pyi | 216 --- .../workflows/workflows/v1/core_pb2_grpc.py | 3 - .../workflows/workflows/v1/diagram_pb2.py | 45 - .../workflows/workflows/v1/diagram_pb2.pyi | 34 - .../workflows/v1/diagram_pb2_grpc.py | 80 - .../tilebox/workflows/workflows/v1/job_pb2.py | 111 -- .../workflows/workflows/v1/job_pb2.pyi | 249 --- .../workflows/workflows/v1/job_pb2_grpc.py | 512 ------ .../workflows/v1/storage_location_pb2.py | 116 -- .../workflows/v1/storage_location_pb2.pyi | 274 ---- .../workflows/v1/storage_location_pb2_grpc.py | 524 ------ .../workflows/workflows/v1/task_connect.py | 260 --- .../workflows/workflows/v1/task_pb2.py | 62 - .../workflows/workflows/v1/task_pb2.pyi | 80 - .../workflows/workflows/v1/task_pb2_grpc.py | 181 --- .../workflows/v1/task_release_pb2.py | 60 - .../workflows/v1/task_release_pb2.pyi | 106 -- .../workflows/v1/task_release_pb2_grpc.py | 166 -- .../workflows/v1/telemetry_connect.py | 324 ---- .../workflows/workflows/v1/telemetry_pb2.py | 75 - .../workflows/workflows/v1/telemetry_pb2.pyi | 109 -- .../workflows/v1/telemetry_pb2_grpc.py | 212 --- .../workflows/workflows/v1/worker_connect.py | 392 ----- .../workflows/workflows/v1/worker_pb2.py | 51 - .../workflows/workflows/v1/worker_pb2.pyi | 46 - .../workflows/workflows/v1/worker_pb2_grpc.py | 276 ---- .../workflows/workflows/v1/workflows_pb2.py | 121 -- .../workflows/workflows/v1/workflows_pb2.pyi | 271 ---- .../workflows/v1/workflows_pb2_grpc.py | 682 -------- tools/generate-protobuf.sh | 23 + tools/generate_protobuf.py | 73 - uv.lock | 201 +-- 304 files changed, 20468 insertions(+), 16706 deletions(-) create mode 100644 .github/workflows/publish_tilebox_core.yml delete mode 100644 buf.gen.datasets-bufpy.yaml delete mode 100644 buf.gen.datasets.yaml delete mode 100644 buf.gen.workflows.yaml create mode 100644 buf.gen.yaml create mode 100644 tilebox-core/README.md create mode 100644 tilebox-core/_tilebox/__init__.py create mode 100644 tilebox-core/_tilebox/aio/__init__.py rename tilebox-grpc/_tilebox/grpc/aio/producer_consumer.py => tilebox-core/_tilebox/aio/concurrency.py (91%) rename {tilebox-grpc/_tilebox/grpc => tilebox-core/_tilebox}/aio/pagination.py (88%) create mode 100644 tilebox-core/_tilebox/client.py rename {tilebox-grpc/_tilebox/grpc => tilebox-core/_tilebox}/client_metadata.py (99%) create mode 100644 tilebox-core/_tilebox/concurrency.py create mode 100644 tilebox-core/_tilebox/errors.py rename {tilebox-grpc/_tilebox/grpc => tilebox-core/_tilebox}/pagination.py (95%) create mode 100644 tilebox-core/_tilebox/proto/tilebox/v1/id_pb.py create mode 100644 tilebox-core/_tilebox/proto/tilebox/v1/query_pb.py create mode 100644 tilebox-core/_tilebox/replay.py create mode 100644 tilebox-core/_tilebox/time.py create mode 100644 tilebox-core/_tilebox/uuid.py create mode 100644 tilebox-core/pyproject.toml rename {tilebox-grpc => tilebox-core}/tests/test_client_metadata.py (84%) create mode 100644 tilebox-core/tests/test_iteration.py create mode 100644 tilebox-core/tests/test_query.py create mode 100644 tilebox-core/tests/test_transport.py create mode 100644 tilebox-core/tilebox/errors.py create mode 100644 tilebox-core/tilebox/query/__init__.py create mode 100644 tilebox-core/tilebox/query/id_interval.py create mode 100644 tilebox-core/tilebox/query/pagination.py create mode 100644 tilebox-core/tilebox/query/time_interval.py delete mode 100644 tilebox-datasets/tests/example_dataset/example_dataset_pb2.py delete mode 100644 tilebox-datasets/tests/example_dataset/example_dataset_pb2.pyi create mode 100644 tilebox-datasets/tests/example_dataset/schema.py delete mode 100644 tilebox-datasets/tilebox/datasets/datasets/__init__.py delete mode 100644 tilebox-datasets/tilebox/datasets/datasets/stac/__init__.py delete mode 100644 tilebox-datasets/tilebox/datasets/datasets/stac/v1/__init__.py delete mode 100644 tilebox-datasets/tilebox/datasets/datasets/stac/v1/asset_metadata_pb2.py delete mode 100644 tilebox-datasets/tilebox/datasets/datasets/stac/v1/asset_metadata_pb2.pyi delete mode 100644 tilebox-datasets/tilebox/datasets/datasets/stac/v1/asset_metadata_pb2_grpc.py delete mode 100644 tilebox-datasets/tilebox/datasets/datasets/stac/v1/asset_pb2.py delete mode 100644 tilebox-datasets/tilebox/datasets/datasets/stac/v1/asset_pb2.pyi delete mode 100644 tilebox-datasets/tilebox/datasets/datasets/stac/v1/asset_pb2_grpc.py delete mode 100644 tilebox-datasets/tilebox/datasets/datasets/stac/v1/authentication_pb2.py delete mode 100644 tilebox-datasets/tilebox/datasets/datasets/stac/v1/authentication_pb2.pyi delete mode 100644 tilebox-datasets/tilebox/datasets/datasets/stac/v1/authentication_pb2_grpc.py delete mode 100644 tilebox-datasets/tilebox/datasets/datasets/stac/v1/core_pb2.py delete mode 100644 tilebox-datasets/tilebox/datasets/datasets/stac/v1/core_pb2.pyi delete mode 100644 tilebox-datasets/tilebox/datasets/datasets/stac/v1/core_pb2_grpc.py delete mode 100644 tilebox-datasets/tilebox/datasets/datasets/stac/v1/processing_pb2.py delete mode 100644 tilebox-datasets/tilebox/datasets/datasets/stac/v1/processing_pb2.pyi delete mode 100644 tilebox-datasets/tilebox/datasets/datasets/stac/v1/processing_pb2_grpc.py delete mode 100644 tilebox-datasets/tilebox/datasets/datasets/stac/v1/product_pb2.py delete mode 100644 tilebox-datasets/tilebox/datasets/datasets/stac/v1/product_pb2.pyi delete mode 100644 tilebox-datasets/tilebox/datasets/datasets/stac/v1/product_pb2_grpc.py delete mode 100644 tilebox-datasets/tilebox/datasets/datasets/stac/v1/sar_pb2.py delete mode 100644 tilebox-datasets/tilebox/datasets/datasets/stac/v1/sar_pb2.pyi delete mode 100644 tilebox-datasets/tilebox/datasets/datasets/stac/v1/sar_pb2_grpc.py delete mode 100644 tilebox-datasets/tilebox/datasets/datasets/stac/v1/satellite_pb2.py delete mode 100644 tilebox-datasets/tilebox/datasets/datasets/stac/v1/satellite_pb2.pyi delete mode 100644 tilebox-datasets/tilebox/datasets/datasets/stac/v1/satellite_pb2_grpc.py delete mode 100644 tilebox-datasets/tilebox/datasets/datasets/stac/v1/storage_pb2.py delete mode 100644 tilebox-datasets/tilebox/datasets/datasets/stac/v1/storage_pb2.pyi delete mode 100644 tilebox-datasets/tilebox/datasets/datasets/stac/v1/storage_pb2_grpc.py delete mode 100644 tilebox-datasets/tilebox/datasets/datasets/v1/collections_pb2.py delete mode 100644 tilebox-datasets/tilebox/datasets/datasets/v1/collections_pb2.pyi delete mode 100644 tilebox-datasets/tilebox/datasets/datasets/v1/collections_pb2_grpc.py delete mode 100644 tilebox-datasets/tilebox/datasets/datasets/v1/core_pb2.py delete mode 100644 tilebox-datasets/tilebox/datasets/datasets/v1/core_pb2.pyi delete mode 100644 tilebox-datasets/tilebox/datasets/datasets/v1/core_pb2_grpc.py delete mode 100644 tilebox-datasets/tilebox/datasets/datasets/v1/data_access_connect.py delete mode 100644 tilebox-datasets/tilebox/datasets/datasets/v1/data_access_pb2.py delete mode 100644 tilebox-datasets/tilebox/datasets/datasets/v1/data_access_pb2.pyi delete mode 100644 tilebox-datasets/tilebox/datasets/datasets/v1/data_access_pb2_grpc.py delete mode 100644 tilebox-datasets/tilebox/datasets/datasets/v1/data_ingestion_pb2.py delete mode 100644 tilebox-datasets/tilebox/datasets/datasets/v1/data_ingestion_pb2.pyi delete mode 100644 tilebox-datasets/tilebox/datasets/datasets/v1/data_ingestion_pb2_grpc.py delete mode 100644 tilebox-datasets/tilebox/datasets/datasets/v1/dataset_type_pb2.py delete mode 100644 tilebox-datasets/tilebox/datasets/datasets/v1/dataset_type_pb2.pyi delete mode 100644 tilebox-datasets/tilebox/datasets/datasets/v1/dataset_type_pb2_grpc.py delete mode 100644 tilebox-datasets/tilebox/datasets/datasets/v1/datasets_pb2.py delete mode 100644 tilebox-datasets/tilebox/datasets/datasets/v1/datasets_pb2.pyi delete mode 100644 tilebox-datasets/tilebox/datasets/datasets/v1/datasets_pb2_grpc.py delete mode 100644 tilebox-datasets/tilebox/datasets/datasets/v1/timeseries_pb2.py delete mode 100644 tilebox-datasets/tilebox/datasets/datasets/v1/timeseries_pb2.pyi delete mode 100644 tilebox-datasets/tilebox/datasets/datasets/v1/timeseries_pb2_grpc.py delete mode 100644 tilebox-datasets/tilebox/datasets/datasets/v1/well_known_types_pb2.py delete mode 100644 tilebox-datasets/tilebox/datasets/datasets/v1/well_known_types_pb2.pyi delete mode 100644 tilebox-datasets/tilebox/datasets/datasets/v1/well_known_types_pb2_grpc.py delete mode 100644 tilebox-datasets/tilebox/datasets/service.py rename tilebox-datasets/tilebox/datasets/{datasets => }/stac/v1/asset_metadata_pb.py (98%) rename tilebox-datasets/tilebox/datasets/{datasets => }/stac/v1/asset_pb.py (97%) rename tilebox-datasets/tilebox/datasets/{datasets => }/stac/v1/authentication_pb.py (97%) rename tilebox-datasets/tilebox/datasets/{datasets => }/stac/v1/core_pb.py (98%) rename tilebox-datasets/tilebox/datasets/{datasets => }/stac/v1/processing_pb.py (87%) rename tilebox-datasets/tilebox/datasets/{datasets => }/stac/v1/product_pb.py (93%) rename tilebox-datasets/tilebox/datasets/{datasets => }/stac/v1/sar_pb.py (97%) rename tilebox-datasets/tilebox/datasets/{datasets => }/stac/v1/satellite_pb.py (95%) rename tilebox-datasets/tilebox/datasets/{datasets => }/stac/v1/storage_pb.py (95%) delete mode 100644 tilebox-datasets/tilebox/datasets/tilebox/v1/id_pb2.py delete mode 100644 tilebox-datasets/tilebox/datasets/tilebox/v1/id_pb2.pyi delete mode 100644 tilebox-datasets/tilebox/datasets/tilebox/v1/id_pb2_grpc.py delete mode 100644 tilebox-datasets/tilebox/datasets/tilebox/v1/query_pb2.py delete mode 100644 tilebox-datasets/tilebox/datasets/tilebox/v1/query_pb2.pyi delete mode 100644 tilebox-datasets/tilebox/datasets/tilebox/v1/query_pb2_grpc.py rename tilebox-datasets/tilebox/datasets/{datasets => }/v1/collections_connect.py (55%) create mode 100644 tilebox-datasets/tilebox/datasets/v1/collections_pb.py create mode 100644 tilebox-datasets/tilebox/datasets/v1/core_pb.py create mode 100644 tilebox-datasets/tilebox/datasets/v1/data_access_connect.py create mode 100644 tilebox-datasets/tilebox/datasets/v1/data_access_pb.py rename tilebox-datasets/tilebox/datasets/{datasets => }/v1/data_ingestion_connect.py (54%) create mode 100644 tilebox-datasets/tilebox/datasets/v1/data_ingestion_pb.py create mode 100644 tilebox-datasets/tilebox/datasets/v1/dataset_type_pb.py rename tilebox-datasets/tilebox/datasets/{datasets => }/v1/datasets_connect.py (56%) create mode 100644 tilebox-datasets/tilebox/datasets/v1/datasets_pb.py create mode 100644 tilebox-datasets/tilebox/datasets/v1/timeseries_pb.py create mode 100644 tilebox-datasets/tilebox/datasets/v1/well_known_types_pb.py delete mode 100644 tilebox-grpc/_tilebox/grpc/__init__.py delete mode 100644 tilebox-grpc/_tilebox/grpc/aio/__init__.py delete mode 100644 tilebox-grpc/_tilebox/grpc/aio/channel.py delete mode 100644 tilebox-grpc/_tilebox/grpc/aio/error.py delete mode 100644 tilebox-grpc/_tilebox/grpc/channel.py delete mode 100644 tilebox-grpc/_tilebox/grpc/error.py delete mode 100644 tilebox-grpc/_tilebox/grpc/producer_consumer.py delete mode 100644 tilebox-grpc/_tilebox/grpc/public.py delete mode 100644 tilebox-grpc/_tilebox/grpc/replay.py delete mode 100644 tilebox-grpc/tests/__init__.py delete mode 100644 tilebox-grpc/tests/aio/test_channel.py delete mode 100644 tilebox-grpc/tests/aio/test_error.py delete mode 100644 tilebox-grpc/tests/aio/test_producer_consumer.py delete mode 100644 tilebox-grpc/tests/conftest.py delete mode 100644 tilebox-grpc/tests/test_channel.py delete mode 100644 tilebox-grpc/tests/test_error.py delete mode 100644 tilebox-grpc/tests/test_fork_logging.py create mode 100644 tilebox-workflows/tests/log_values.py create mode 100644 tilebox-workflows/tests/proto/test_pb.py delete mode 100644 tilebox-workflows/tests/proto/test_pb2.py delete mode 100644 tilebox-workflows/tests/proto/test_pb2.pyi create mode 100644 tilebox-workflows/tilebox/workflows/_otel/LICENSE create mode 100644 tilebox-workflows/tilebox/workflows/_otel/NOTICE create mode 100644 tilebox-workflows/tilebox/workflows/_otel/opentelemetry/proto/common/v1/common_pb.py create mode 100644 tilebox-workflows/tilebox/workflows/_otel/opentelemetry/proto/logs/v1/logs_pb.py create mode 100644 tilebox-workflows/tilebox/workflows/_otel/opentelemetry/proto/resource/v1/resource_pb.py create mode 100644 tilebox-workflows/tilebox/workflows/_otel/opentelemetry/proto/trace/v1/trace_pb.py delete mode 100644 tilebox-workflows/tilebox/workflows/automations/service.py delete mode 100644 tilebox-workflows/tilebox/workflows/clusters/service.py delete mode 100644 tilebox-workflows/tilebox/workflows/jobs/service.py delete mode 100644 tilebox-workflows/tilebox/workflows/jobs/telemetry_service.py delete mode 100644 tilebox-workflows/tilebox/workflows/storage_locations/service.py rename tilebox-workflows/tilebox/workflows/{workflows => }/v1/automation_connect.py (52%) create mode 100644 tilebox-workflows/tilebox/workflows/v1/automation_pb.py create mode 100644 tilebox-workflows/tilebox/workflows/v1/core_pb.py rename tilebox-workflows/tilebox/workflows/{workflows => }/v1/diagram_connect.py (51%) create mode 100644 tilebox-workflows/tilebox/workflows/v1/diagram_pb.py rename tilebox-workflows/tilebox/workflows/{workflows => }/v1/job_connect.py (57%) create mode 100644 tilebox-workflows/tilebox/workflows/v1/job_pb.py rename tilebox-workflows/tilebox/workflows/{workflows => }/v1/storage_location_connect.py (55%) create mode 100644 tilebox-workflows/tilebox/workflows/v1/storage_location_pb.py create mode 100644 tilebox-workflows/tilebox/workflows/v1/task_connect.py create mode 100644 tilebox-workflows/tilebox/workflows/v1/task_pb.py create mode 100644 tilebox-workflows/tilebox/workflows/v1/telemetry_connect.py create mode 100644 tilebox-workflows/tilebox/workflows/v1/telemetry_pb.py create mode 100644 tilebox-workflows/tilebox/workflows/v1/worker_connect.py create mode 100644 tilebox-workflows/tilebox/workflows/v1/worker_pb.py rename tilebox-workflows/tilebox/workflows/{workflows => }/v1/workflows_connect.py (55%) create mode 100644 tilebox-workflows/tilebox/workflows/v1/workflows_pb.py delete mode 100644 tilebox-workflows/tilebox/workflows/workflows/service.py delete mode 100644 tilebox-workflows/tilebox/workflows/workflows/v1/automation_pb2.py delete mode 100644 tilebox-workflows/tilebox/workflows/workflows/v1/automation_pb2.pyi delete mode 100644 tilebox-workflows/tilebox/workflows/workflows/v1/automation_pb2_grpc.py delete mode 100644 tilebox-workflows/tilebox/workflows/workflows/v1/core_pb2.py delete mode 100644 tilebox-workflows/tilebox/workflows/workflows/v1/core_pb2.pyi delete mode 100644 tilebox-workflows/tilebox/workflows/workflows/v1/core_pb2_grpc.py delete mode 100644 tilebox-workflows/tilebox/workflows/workflows/v1/diagram_pb2.py delete mode 100644 tilebox-workflows/tilebox/workflows/workflows/v1/diagram_pb2.pyi delete mode 100644 tilebox-workflows/tilebox/workflows/workflows/v1/diagram_pb2_grpc.py delete mode 100644 tilebox-workflows/tilebox/workflows/workflows/v1/job_pb2.py delete mode 100644 tilebox-workflows/tilebox/workflows/workflows/v1/job_pb2.pyi delete mode 100644 tilebox-workflows/tilebox/workflows/workflows/v1/job_pb2_grpc.py delete mode 100644 tilebox-workflows/tilebox/workflows/workflows/v1/storage_location_pb2.py delete mode 100644 tilebox-workflows/tilebox/workflows/workflows/v1/storage_location_pb2.pyi delete mode 100644 tilebox-workflows/tilebox/workflows/workflows/v1/storage_location_pb2_grpc.py delete mode 100644 tilebox-workflows/tilebox/workflows/workflows/v1/task_connect.py delete mode 100644 tilebox-workflows/tilebox/workflows/workflows/v1/task_pb2.py delete mode 100644 tilebox-workflows/tilebox/workflows/workflows/v1/task_pb2.pyi delete mode 100644 tilebox-workflows/tilebox/workflows/workflows/v1/task_pb2_grpc.py delete mode 100644 tilebox-workflows/tilebox/workflows/workflows/v1/task_release_pb2.py delete mode 100644 tilebox-workflows/tilebox/workflows/workflows/v1/task_release_pb2.pyi delete mode 100644 tilebox-workflows/tilebox/workflows/workflows/v1/task_release_pb2_grpc.py delete mode 100644 tilebox-workflows/tilebox/workflows/workflows/v1/telemetry_connect.py delete mode 100644 tilebox-workflows/tilebox/workflows/workflows/v1/telemetry_pb2.py delete mode 100644 tilebox-workflows/tilebox/workflows/workflows/v1/telemetry_pb2.pyi delete mode 100644 tilebox-workflows/tilebox/workflows/workflows/v1/telemetry_pb2_grpc.py delete mode 100644 tilebox-workflows/tilebox/workflows/workflows/v1/worker_connect.py delete mode 100644 tilebox-workflows/tilebox/workflows/workflows/v1/worker_pb2.py delete mode 100644 tilebox-workflows/tilebox/workflows/workflows/v1/worker_pb2.pyi delete mode 100644 tilebox-workflows/tilebox/workflows/workflows/v1/worker_pb2_grpc.py delete mode 100644 tilebox-workflows/tilebox/workflows/workflows/v1/workflows_pb2.py delete mode 100644 tilebox-workflows/tilebox/workflows/workflows/v1/workflows_pb2.pyi delete mode 100644 tilebox-workflows/tilebox/workflows/workflows/v1/workflows_pb2_grpc.py create mode 100755 tools/generate-protobuf.sh delete mode 100755 tools/generate_protobuf.py diff --git a/.docs/packages.d2 b/.docs/packages.d2 index 23dadc2..56ee106 100644 --- a/.docs/packages.d2 +++ b/.docs/packages.d2 @@ -16,7 +16,19 @@ private: { style.fill: '#F5EFED' } -public.tilebox-datasets -> private.tilebox-grpc: { +public.tilebox-core: { + label: tilebox-core\ntilebox.query ยท tilebox.errors +} + +private._tilebox: { + label: _tilebox\nOwned by tilebox-core +} + +public.tilebox-core -> private._tilebox: { + label: implements +} + +public.tilebox-datasets -> public.tilebox-core: { label: depends on } @@ -24,6 +36,14 @@ public.tilebox-storage -> public.tilebox-datasets: { label: enhances } -public.tilebox-workflows -> private.tilebox-grpc: { +public.tilebox-workflows -> public.tilebox-core: { label: depends on } + +public.tilebox-storage -> public.tilebox-core: { + label: depends on +} + +public.tilebox-grpc -> public.tilebox-core: { + label: compatibility dependency only +} diff --git a/.docs/packages.svg b/.docs/packages.svg index f9b9a5c..143e79d 100644 --- a/.docs/packages.svg +++ b/.docs/packages.svg @@ -1,27 +1,27 @@ - + .d2-2579381019 .fill-N1{fill:#170206;} + .d2-2579381019 .fill-N2{fill:#535152;} + .d2-2579381019 .fill-N7{fill:#FFFFFF;} + .d2-2579381019 .fill-B1{fill:#31021D;} + .d2-2579381019 .fill-B4{fill:#EA99B6;} + .d2-2579381019 .fill-B5{fill:#FFDAE7;} + .d2-2579381019 .stroke-B1{stroke:#31021D;} + .d2-2579381019 .stroke-B2{stroke:#9B1A48;}.appendix text.text{fill:#170206}.md{--color-fg-default:#170206;--color-fg-muted:#535152;--color-fg-subtle:#787777;--color-canvas-default:#FFFFFF;--color-canvas-subtle:#ECEBEB;--color-border-default:#31021D;--color-border-muted:#9B1A48;--color-neutral-muted:#ECEBEB;--color-accent-fg:#9B1A48;--color-accent-emphasis:#9B1A48;--color-attention-subtle:#535152;--color-danger-fg:red;}.sketch-overlay-B4{fill:url(#streaks-normal-d2-2579381019);mix-blend-mode:color-burn}.sketch-overlay-B5{fill:url(#streaks-bright-d2-2579381019);mix-blend-mode:darken}.light-code{display: block}.dark-code{display: none}]]> - + + - - - - -Public APIInternal APItilebox-datasetstilebox-grpctilebox-storagetilebox-workflows depends on enhances depends on - - - - - - - - - - + +Public APIInternal APItilebox-coretilebox.query ยท tilebox.errors_tileboxOwned by tilebox-coretilebox-datasetstilebox-storagetilebox-workflowstilebox-grpc implements depends on enhances depends on depends on compatibility dependency only + + + + + + + diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 05dd793..da503cb 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -104,10 +104,10 @@ jobs: # Select the SDK packages, not the root's unbounded lint/build tooling. run: >- uv sync --resolution lowest-direct --upgrade - --package tilebox-grpc --package tilebox-datasets + --package tilebox-core --package tilebox-datasets --package tilebox-storage --package tilebox-workflows - name: Run tests without replacing the minimum-version environment run: | - for package in tilebox-grpc tilebox-datasets tilebox-storage tilebox-workflows; do + for package in tilebox-core tilebox-datasets tilebox-storage tilebox-workflows; do uv run --no-sync pytest "$package" -Wall -Werror --import-mode=importlib done diff --git a/.github/workflows/publish_tilebox_core.yml b/.github/workflows/publish_tilebox_core.yml new file mode 100644 index 0000000..6febd88 --- /dev/null +++ b/.github/workflows/publish_tilebox_core.yml @@ -0,0 +1,33 @@ +name: Publish Tilebox Core + +on: + release: + types: [published] + +jobs: + release: + name: Build and Publish Tilebox Core + runs-on: ubuntu-latest + permissions: + contents: read + id-token: write + steps: + - uses: actions/checkout@v7 + with: + fetch-depth: 0 + lfs: true + - name: Set up uv + uses: astral-sh/setup-uv@v10.1.0 + with: + python-version: "3.13" + enable-cache: true + cache-python: true + cache-dependency-glob: "uv.lock" + - name: Print version + run: git describe --tags + - name: Build + run: mkdir dist && uv run python -m build tilebox-core && mv tilebox-core/dist/* dist/ + - name: Publish to PyPi.org + if: github.event_name == 'release' + # Configure this workflow as a trusted publisher for tilebox-core on PyPI before releasing. + uses: pypa/gh-action-pypi-publish@release/v1 diff --git a/AGENTS.md b/AGENTS.md index 55e6d2b..efeaed5 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -6,9 +6,10 @@ This repository is the Python monorepo for Tilebox SDK packages. It is organized ## Monorepo Architecture -The workspace is split into four packages: +The workspace contains four implementation packages and a compatibility package: -- `tilebox-grpc`: Shared low-level gRPC/protobuf plumbing (`_tilebox.grpc`), sync and async channel handling, error mapping, pagination, replay, and async-to-sync helpers. +- `tilebox-core`: Public `tilebox.query` value types and `tilebox.errors` exceptions; private shared infrastructure, generated messages, conversions, transports, pagination, replay, and concurrency helpers under `_tilebox`. Only this distribution packages `_tilebox`. +- `tilebox-grpc`: Empty compatibility package depending on `tilebox-core>=0.67.0`, retained for one release. - `tilebox-datasets`: Datasets SDK (`tilebox.datasets`) with sync + async clients, query/data model helpers, protobuf conversion, and generated datasets-related protobuf stubs. - `tilebox-workflows`: Workflows SDK (`tilebox.workflows`) with job/cluster/automation clients, task model + runner, observability/tracing, and generated workflows protobuf stubs. - `tilebox-storage`: Storage SDK (`tilebox.storage`) for provider-specific payload downloads (ASF, Copernicus, Umbra, Landsat, local FS), built mostly on async internals with sync wrappers. @@ -16,9 +17,9 @@ The workspace is split into four packages: Design choices to keep in mind: - Namespace package layout: multiple packages contribute modules under shared top-level namespaces like `tilebox.*`. -- Layering pattern: generated protobuf/gRPC types -> service wrappers -> data/domain classes -> user-facing clients. -- Sync + async parity: many APIs exist in both forms; sync wrappers are often derived from async implementations. -- External API communication is gRPC-first, with protobuf-generated contracts. +- Public clients call generated Connect clients directly and convert protobuf messages to domain objects. +- Sync + async parity: datasets has native blocking and async clients with shared conversion helpers. +- External API communication uses native Connect RPC clients and protobuf-py messages. ## Developer Tooling @@ -27,7 +28,7 @@ Use `uv` for all local development. ### Setup ```bash -uv sync +uv sync --all-packages ``` ### Test Commands @@ -40,7 +41,7 @@ uv sync uv run --package tilebox-datasets pytest tilebox-datasets uv run --package tilebox-workflows pytest tilebox-workflows uv run --package tilebox-storage pytest tilebox-storage -uv run --package tilebox-grpc pytest tilebox-grpc +uv run --no-sync pytest tilebox-core ``` ### Lint / Format / Type Check @@ -65,7 +66,7 @@ Pre-commit hooks include YAML checks, EOF fixer, `sync-with-uv`, Ruff, and `ty`. - Linting/formatting is Ruff-based and fairly strict (`select = ["ALL"]`) with explicit ignores configured in root `pyproject.toml`. - Type hints are used broadly across public APIs and internals. - Prefer dataclasses and explicit domain objects for request/response translation. -- Service modules generally wrap generated gRPC stubs and convert to internal Pythonic types. +- Keep transport and error translation in `_tilebox`; request construction and response conversion belong in clients. - Workflows uses a lightweight structured facade over stdlib logging, with separate internal and task channels; - Tests use `pytest`, with async coverage (`pytest-asyncio`) and property-based testing (`hypothesis`) in multiple packages. @@ -81,21 +82,24 @@ Pre-commit hooks include YAML checks, EOF fixer, `sync-with-uv`, Ruff, and `ty`. Generated files live under paths such as: -- `tilebox-datasets/tilebox/datasets/datasets/v1/*_pb2*.py` -- `tilebox-datasets/tilebox/datasets/tilebox/v1/*_pb2*.py` -- `tilebox-workflows/tilebox/workflows/workflows/v1/*_pb2*.py` +- `tilebox-datasets/tilebox/datasets/v1/*_pb.py` +- `tilebox-datasets/tilebox/datasets/stac/v1/*_pb.py` +- `tilebox-core/_tilebox/proto/tilebox/v1/*_pb.py` +- `tilebox-workflows/tilebox/workflows/v1/*_pb.py` +- `tilebox-workflows/tilebox/workflows/_otel/opentelemetry/proto/**` Regenerate protobuf code with: ```bash -uv run generate-protobuf +./tools/generate-protobuf.sh ``` -This uses `buf.gen.datasets.yaml` and `buf.gen.workflows.yaml`, then runs import-fixup logic via `tools/generate_protobuf.py`. +This uses `buf.gen.yaml` with pinned local plugins and import mappings. +The script strips upstream trailing whitespace with Ruff and places the generated modules in their owning packages. ### Important Boundary: API Schema Source Of Truth - Many protobuf contracts are sourced from the separate `api` repository/module (`buf.build/tilebox/api`, with optional local `../api` input in buf configs). -- Do **not** hand-edit generated `*_pb2.py`, `*_pb2.pyi`, or `*_pb2_grpc.py` files. +- Do **not** hand-edit generated `*_pb.py` or `*_connect.py` files. - If a requested change requires modifying protobuf schema/contracts, that change must be made in the `api` repo first. - Whenever schema/proto edits are needed, ask the developer to point you to the `api` repo so those changes can be implemented there and then regenerated here. diff --git a/CHANGELOG.md b/CHANGELOG.md index 3e1ebf9..62fb9c5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,44 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Changed + +- `tilebox-core`: Own shared Connect RPC infrastructure and protobuf-py messages, public + `tilebox.query` interval/pagination types, and `tilebox.errors` exceptions with a common `TileboxError` base. +- `tilebox-grpc`: Retain an empty compatibility package depending on `tilebox-core>=0.67.0` for one release. +- `tilebox-datasets`, `tilebox-workflows`, and `tilebox-storage`: Depend explicitly on + `tilebox-core`; datasets retains its scientific and LZ4 dependencies. +- `tilebox-workflows`: Remove the runtime dependency on datasets. Existing public datasets imports + of shared intervals, pagination, and conversions remain compatible. Task interval serialization is unchanged. +- Parse ordinary Python datetimes without pandas. Strings and scientific datetime values still use pandas; + workflows-only users need to install pandas explicitly for those inputs. Dataset installations are unchanged. +- Raise `tilebox.errors.RequestCanceledError` for remote request cancellation instead of `KeyboardInterrupt`. +- Require Tilebox sibling dependencies version 0.67.0 or newer. When upgrading from older releases, + upgrade all installed Tilebox packages together; older releases do not enforce this minimum. +- Use Connect over HTTP/2 by default. Keep `transport="http1"` for proxies that block HTTP/2, + and accept `transport="grpc"` as a silent alias for `"http2"`. +- Replace generated `_pb2` modules with native `_pb` modules. User-defined protobuf task + arguments use protobuf-py messages; existing stored binary task payloads remain compatible. +- Resolve dynamic dataset schemas by descriptor content and compile scientific conversion + plans per schema, including projected metadata-only queries. +- Bundle native OTEL response types in workflows and convert them directly into log/span + objects. Keep the official OpenTelemetry SDK and HTTP exporters for telemetry export. +- Serve managed workers over Connect HTTP/1 with independent log streams and task calls. + Managed workers require a CLI with Connect worker support; that CLI still supports older workers. + +### Deprecated + +- `tilebox-datasets`: Deprecate the root `TimeseriesDataset` and `TimeseriesCollection` aliases. + They still refer to asynchronous clients. Use `tilebox.datasets.aio.DatasetClient` and + `CollectionClient` for the same classes, or the root `DatasetClient` and `CollectionClient` + for synchronous code. + +### Fixed + +- `tilebox-workflows`: Flush and close client-owned tracing exporters when closing a client, + without shutting down shared exporters. Release closed clients from the tracing registry. +- `tilebox-datasets`: Count each page's actual datapoints in synchronous query progress bars. + ## [0.66.0] - 2026-10-14 ### Added diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ec5195e..384a2b7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -7,6 +7,12 @@ Welcome! We're excited that you're interested in contributing to Tilebox Python. This repository contains multiple python packages. We use python [namespace packages](https://packaging.python.org/en/latest/guides/packaging-namespace-packages/) to organize our code into multiple packages that then share the common `tilebox` python module name. +`tilebox-core` owns the public `tilebox.query` and `tilebox.errors` APIs and the private +`_tilebox` infrastructure namespace. Datasets, workflows, and storage depend on core; +scientific dependencies stay in datasets. Core and workflows have no runtime dependency +on datasets. Their cross-SDK tests run in the all-packages workspace environment. +`tilebox-grpc` is a dependency-only compatibility package retained for the 0.67 release. + ## Required tooling Before starting to work on this repository, make sure you have uv installed. [Install instructions](https://docs.astral.sh/uv/getting-started/installation/). @@ -23,7 +29,7 @@ them. ### Initial setup ```bash -uv sync +uv sync --all-packages ``` ### Running tests @@ -33,6 +39,7 @@ uv sync # testing a single package uv run --package tilebox-datasets pytest tilebox-datasets +uv run --no-sync pytest tilebox-core ``` ### Running code analysis @@ -60,17 +67,28 @@ uv add --package tilebox-datasets "numpy>=2" ## Protobuf usage We use [protobuf](https://protobuf.dev/) for defining our API contracts. The protobuf files are generated from the messages -defined in the [Tilebox Go repository](https://github.com/tilebox/tilebox-go/tree/main/apis). +defined in [Tilebox API](https://buf.build/tilebox/api). `buf.gen.yaml` defines schema inputs, import mappings, and pinned native Python plugins. ### Updating protobuf files ```bash -uv run generate-protobuf +./tools/generate-protobuf.sh ``` +The script generates into a temporary directory, strips trailing whitespace with Ruff, and replaces the generated +modules in each package. API types live in `tilebox.datasets.v1`, `tilebox.datasets.stac.v1`, and `tilebox.workflows.v1`; +shared generated types live in `_tilebox.proto.tilebox.v1`. Workflows bundles its API telemetry types privately under +`tilebox.workflows._otel`. The OpenTelemetry SDK and exporters continue to use their own generated types. + ## Releasing and deploying a new version There is a Github CI workflow set up for building the packages and pushing them to the internal `pypi`. +Sibling-package dependencies require at least version 0.67.0 for the Connect migration. +Raise these minimums only when a package needs a newer sibling. Make core available before its dependents, +and datasets before storage. The release workflows run independently; do not announce installation availability +until all packages have published successfully. +Before the first core release, configure PyPI trusted publishing for `tilebox-core` using this repository's +`publish_tilebox_core.yml` workflow. The compatibility `tilebox-grpc` wheel must contain no Python modules. All that is needed is to create a github release for a given tag. This can be done in the github web UI or on the command line. diff --git a/README.md b/README.md index 70d49ab..2ee82e1 100644 --- a/README.md +++ b/README.md @@ -48,6 +48,12 @@ pip install tilebox Documentation is available at [docs.tilebox.com](https://docs.tilebox.com). +The SDKs share types and native Connect RPC infrastructure in `tilebox-core`, installed +automatically with datasets, workflows, and storage. Use `tilebox.query` for shared +intervals and pagination, and `tilebox.errors` for SDK exceptions. Infrastructure under +`_tilebox` is private. Core and workflows do not require the datasets scientific stack. +`tilebox-grpc` is a dependency-only compatibility package for the 0.67 release. + ## Getting started ### Tilebox Datasets diff --git a/buf.gen.datasets-bufpy.yaml b/buf.gen.datasets-bufpy.yaml deleted file mode 100644 index f63bb4c..0000000 --- a/buf.gen.datasets-bufpy.yaml +++ /dev/null @@ -1,13 +0,0 @@ -# yaml-language-server: $schema=https://www.schemastore.org/buf.gen.json -version: v2 -plugins: - - remote: buf.build/bufbuild/py:v0.2.0 - out: tilebox-datasets/tilebox/datasets - opt: init_files=false -inputs: - - module: buf.build/tilebox/api - paths: - - "datasets/stac/v1" - # Keep this experimental package validation-blind, matching the existing generated clients. - exclude_types: - - "buf.validate.**" diff --git a/buf.gen.datasets.yaml b/buf.gen.datasets.yaml deleted file mode 100644 index 06c1c14..0000000 --- a/buf.gen.datasets.yaml +++ /dev/null @@ -1,25 +0,0 @@ -# yaml-language-server: $schema=https://www.schemastore.org/buf.gen.json -# please keep this file in sync with buf.gen.workflows.yaml, especially the plugins versions -version: v2 -managed: - enabled: true -plugins: - - remote: buf.build/grpc/python - out: tilebox-datasets/tilebox/datasets - - remote: buf.build/connectrpc/python:v0.10.0 - out: tilebox-datasets/tilebox/datasets - - remote: buf.build/protocolbuffers/python:v30.0 # use the minimum version that supports protobuf 6 - out: tilebox-datasets/tilebox/datasets - - remote: buf.build/protocolbuffers/pyi:v30.0 # use the minimum version that supports protobuf 6 - out: tilebox-datasets/tilebox/datasets -inputs: - # for local development - # - directory: ../api - - module: buf.build/tilebox/api - paths: - - "datasets" - - "tilebox" - # Deliberately generate validation-blind client messages: the SDK does not validate - # client-side, and omitting buf.validate avoids conflicts in protobuf's global descriptor pool. - exclude_types: - - "buf.validate.**" diff --git a/buf.gen.workflows.yaml b/buf.gen.workflows.yaml deleted file mode 100644 index 0e60b16..0000000 --- a/buf.gen.workflows.yaml +++ /dev/null @@ -1,24 +0,0 @@ -# yaml-language-server: $schema=https://www.schemastore.org/buf.gen.json -# please keep this file in sync with buf.gen.datasets.yaml, especially the plugins versions -version: v2 -managed: - enabled: true -plugins: - - remote: buf.build/grpc/python - out: tilebox-workflows/tilebox/workflows - - remote: buf.build/connectrpc/python:v0.10.0 - out: tilebox-workflows/tilebox/workflows - - remote: buf.build/protocolbuffers/python:v30.0 # use the minimum version that supports protobuf 6 - out: tilebox-workflows/tilebox/workflows - - remote: buf.build/protocolbuffers/pyi:v30.0 # use the minimum version that supports protobuf 6 - out: tilebox-workflows/tilebox/workflows -inputs: - # for local development - # directory: ../api - - module: buf.build/tilebox/api - paths: - - "workflows" - # Deliberately generate validation-blind client messages: the SDK does not validate - # client-side, and omitting buf.validate avoids conflicts in protobuf's global descriptor pool. - exclude_types: - - "buf.validate.**" diff --git a/buf.gen.yaml b/buf.gen.yaml new file mode 100644 index 0000000..398d862 --- /dev/null +++ b/buf.gen.yaml @@ -0,0 +1,30 @@ +# yaml-language-server: $schema=https://www.schemastore.org/buf.gen.json +version: v2 +plugins: + - local: [uv, tool, run, --from, protoc-gen-py==0.6.0, --with, protobuf-py==0.6.0, protoc-gen-py] + out: . + opt: + - init_files=false + - map_imports=datasets/**:tilebox + - map_imports=workflows/**:tilebox + - map_imports=tilebox/**:_tilebox.proto + - map_imports=opentelemetry/**:tilebox.workflows._otel + - local: [uv, tool, run, --from, protoc-gen-connectrpc==0.12.1, --with, protobuf-py==0.6.0, protoc-gen-connectrpc] + out: . + opt: + - map_imports=datasets/**:tilebox + - map_imports=workflows/**:tilebox + - map_imports=tilebox/**:_tilebox.proto + - map_imports=opentelemetry/**:tilebox.workflows._otel +inputs: + # For local schema development, replace module with directory: ../api. + - module: buf.build/tilebox/api + paths: [datasets, tilebox, workflows] + # Server-side annotations are not needed by the SDK. + exclude_types: ["buf.validate.**", "google.api.**"] + - module: buf.build/opentelemetry/opentelemetry:5f2c7d4f740541589805e0816dad4bb0 + paths: + - opentelemetry/proto/common/v1/common.proto + - opentelemetry/proto/resource/v1/resource.proto + - opentelemetry/proto/logs/v1/logs.proto + - opentelemetry/proto/trace/v1/trace.proto diff --git a/matrix.toml b/matrix.toml index c5074bd..a961a08 100644 --- a/matrix.toml +++ b/matrix.toml @@ -45,9 +45,9 @@ test-args = ["-v"] pandas = ["3.0.0"] [[scenarios]] -name = "grpc-pandas2" +name = "core-pandas2" python = ["3.11", "3.12", "3.13", "3.14"] -working-dir = "tilebox-grpc" +working-dir = "tilebox-core" test-command = "pytest" test-args = ["-v"] @@ -55,9 +55,9 @@ test-args = ["-v"] pandas = ["2.3.3"] [[scenarios]] -name = "grpc-pandas3" +name = "core-pandas3" python = ["3.11", "3.12", "3.13", "3.14"] -working-dir = "tilebox-grpc" +working-dir = "tilebox-core" test-command = "pytest" test-args = ["-v"] diff --git a/pyproject.toml b/pyproject.toml index 69a9760..688ef3c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,10 +6,9 @@ authors = [{ name = "Tilebox, Inc.", email = "support@tilebox.com" }] readme = "README.md" requires-python = ">=3.11" dependencies = [ - "tilebox-datasets>=0.56.0", - "tilebox-grpc", - "tilebox-storage", - "tilebox-workflows", + "tilebox-datasets>=0.67.0", + "tilebox-storage>=0.67.0", + "tilebox-workflows>=0.67.0", ] [dependency-groups] @@ -30,26 +29,25 @@ dev = [ "junitparser>=3.2.0", ] -[project.scripts] -generate-protobuf = "tools.generate_protobuf:main" - [tool.hatch.build.targets.sdist] -packages = ["tools"] +include = ["pyproject.toml", "README.md", "LICENSE"] [tool.hatch.build.targets.wheel] -packages = ["tools"] +bypass-selection = true [tool.uv.workspace] members = [ - "tilebox-datasets", + "tilebox-core", "tilebox-grpc", + "tilebox-datasets", "tilebox-storage", "tilebox-workflows", ] [tool.uv.sources] # don't install from pypi, use the workspace packages -tilebox-datasets = { workspace = true } +tilebox-core = { workspace = true } tilebox-grpc = { workspace = true } +tilebox-datasets = { workspace = true } tilebox-storage = { workspace = true } tilebox-workflows = { workspace = true } @@ -71,6 +69,7 @@ exclude = [ "*/datasets/stac/v1/*", "*/workflows/v1/*", "*/tilebox/v1/*", + "*/_otel/opentelemetry/*", ] [tool.ruff.lint] @@ -115,8 +114,5 @@ known-first-party = ["tilebox", "_tilebox"] [tool.ty.src] exclude = [ # auto-generated code - "**/*_pb2.py", - "**/*_pb2.pyi", - "**/*pb2_grpc.py", - "**/*pb.py", + "**/*_pb.py", ] diff --git a/tests.sh b/tests.sh index e124028..21f5e9c 100755 --- a/tests.sh +++ b/tests.sh @@ -2,24 +2,32 @@ # Helper script to run tests on all python packages in the monorepo # Needed because you can't run pytest on multiple packages at once -# finds also packages in the `.venv` subdirectory, so we need to filter them out => grep tilebox -packages=$(find . -name pyproject.toml -exec dirname {} \; | grep tilebox | cut -d '/' -f 2 | sort) +# Keep generated SDK clients available to the RPC tests without a runtime dependency cycle. +packages="tilebox-datasets tilebox-core tilebox-storage tilebox-workflows" for package in $packages; do cd "$package" || exit 1 # cd into the package directory echo "Running tests of $package" - module=tilebox + set -- --cov=tilebox if [ -d _tilebox ]; then - module=_tilebox + set -- "$@" --cov=_tilebox fi - uv run --all-packages pytest -Wall -Werror --cov=$module --cov-branch -v --junitxml=test-report.xml . || exit 1 + uv run --no-sync pytest -Wall -Werror "$@" --cov-branch -v --junitxml=test-report.xml . || exit 1 cd .. || exit 1 # cd back to the root of the monorepo done -uv run coverage combine */.coverage -uv run coverage xml +set -- +for package in $packages; do + set -- "$@" "$package/.coverage" +done +uv run --no-sync coverage combine "$@" +uv run --no-sync coverage xml -uv run junitparser merge */test-report.xml test-report.xml +set -- +for package in $packages; do + set -- "$@" "$package/test-report.xml" +done +uv run --no-sync junitparser merge "$@" test-report.xml diff --git a/tilebox-core/README.md b/tilebox-core/README.md new file mode 100644 index 0000000..35545fa --- /dev/null +++ b/tilebox-core/README.md @@ -0,0 +1,16 @@ +# Tilebox Core + +Shared types and client infrastructure for the Tilebox Python SDKs. + +Public imports are `tilebox.query` for `TimeInterval`, `IDInterval`, and `Pagination`, +and `tilebox.errors` for SDK exceptions. The `_tilebox` namespace contains private +connections, generated messages, conversions, retries, pagination, and test utilities. + +Core does not depend on datasets or its scientific libraries. Parsing ordinary Python +datetimes requires no optional libraries. Strings and scientific datetime values use +pandas when parsed; xarray inputs also require xarray. Install pandas explicitly for +these parsing conveniences in workflows-only environments. Datasets already installs +these libraries. + +Run tests in the workspace with `uv sync --all-packages` followed by +`uv run --no-sync pytest tilebox-core`. Transport tests use generated datasets clients. diff --git a/tilebox-core/_tilebox/__init__.py b/tilebox-core/_tilebox/__init__.py new file mode 100644 index 0000000..53a7012 --- /dev/null +++ b/tilebox-core/_tilebox/__init__.py @@ -0,0 +1 @@ +"""Shared native Connect transport support for Tilebox SDKs.""" diff --git a/tilebox-core/_tilebox/aio/__init__.py b/tilebox-core/_tilebox/aio/__init__.py new file mode 100644 index 0000000..791aa76 --- /dev/null +++ b/tilebox-core/_tilebox/aio/__init__.py @@ -0,0 +1 @@ +"""Asynchronous pagination and concurrency helpers.""" diff --git a/tilebox-grpc/_tilebox/grpc/aio/producer_consumer.py b/tilebox-core/_tilebox/aio/concurrency.py similarity index 91% rename from tilebox-grpc/_tilebox/grpc/aio/producer_consumer.py rename to tilebox-core/_tilebox/aio/concurrency.py index da588da..f2ed736 100644 --- a/tilebox-grpc/_tilebox/grpc/aio/producer_consumer.py +++ b/tilebox-core/_tilebox/aio/concurrency.py @@ -39,9 +39,9 @@ async def _producer(producer: AsyncIterator[T], send_stream: MemoryObjectSendStr """ Forward all messages from a producer (async iterator) and send them to a buffered send stream. """ - async for message in producer: - await send_stream.send(message) - send_stream.close() + async with send_stream: + async for message in producer: + await send_stream.send(message) async def _consumer( @@ -53,14 +53,13 @@ async def _consumer( loop = asyncio.get_running_loop() - with ThreadPoolExecutor() as executor: + with ThreadPoolExecutor(max_workers=1) as executor, receive_stream: try: while True: message = await receive_stream.receive() # result = consume(message), but in a background thread so that async loop can continue in the meantime: result = await loop.run_in_executor(executor, consume, message) - if inspect.iscoroutine(result): + if inspect.isawaitable(result): await result except EndOfStream: pass - receive_stream.close() diff --git a/tilebox-grpc/_tilebox/grpc/aio/pagination.py b/tilebox-core/_tilebox/aio/pagination.py similarity index 88% rename from tilebox-grpc/_tilebox/grpc/aio/pagination.py rename to tilebox-core/_tilebox/aio/pagination.py index 4f3ecad..9a611f0 100644 --- a/tilebox-grpc/_tilebox/grpc/aio/pagination.py +++ b/tilebox-core/_tilebox/aio/pagination.py @@ -1,13 +1,13 @@ from collections.abc import AsyncIterator, Awaitable, Callable -from _tilebox.grpc.pagination import AnyResultPage, Pagination +from _tilebox.pagination import AnyResultPage, Pagination async def paginated_request( paging_request: Callable[[Pagination], Awaitable[AnyResultPage]], initial_page: Pagination, ) -> AsyncIterator[AnyResultPage]: - """Make a paginated request to a gRPC service endpoint. + """Make a paginated request to a service endpoint. The endpoint is expected to return a next_page field, which is used for subsequent requests. Once no such next_page field is returned, the request is completed. diff --git a/tilebox-core/_tilebox/client.py b/tilebox-core/_tilebox/client.py new file mode 100644 index 0000000..eb3c1cb --- /dev/null +++ b/tilebox-core/_tilebox/client.py @@ -0,0 +1,211 @@ +"""Owned HTTP transports for native Connect clients.""" + +import asyncio +import time +from collections.abc import Awaitable, Callable, Iterable +from typing import Literal, Self, TypeAlias, TypeVar +from urllib.parse import urlsplit, urlunsplit + +from connectrpc.client import ConnectClient, ConnectClientSync +from connectrpc.code import Code +from connectrpc.errors import ConnectError +from connectrpc.interceptor import Interceptor, InterceptorSync +from connectrpc.request import RequestContext +from pyqwest import Client, HTTPTransport, HTTPVersion, SyncClient, SyncHTTPTransport + +from _tilebox.client_metadata import client_metadata +from _tilebox.errors import translate_connect_error + +Transport: TypeAlias = Literal["http2", "http1", "grpc"] + +Sync = TypeVar("Sync", bound=ConnectClientSync) +Async = TypeVar("Async", bound=ConnectClient) +Req = TypeVar("Req") +Res = TypeVar("Res") + + +def _http_version(transport: Transport) -> HTTPVersion: + match transport: + case "http2": + return HTTPVersion.HTTP2 + case "http1": + return HTTPVersion.HTTP1 + case _: + raise ValueError(f"Unsupported transport {transport!r}; use 'http2' or 'http1' (both use Connect)") + + +def _retry_delay(ctx: RequestContext, error: ConnectError, attempt: int) -> float | None: + delay = min(0.02 * 3**attempt, 1.0) + if ( + attempt >= 4 + or error.code not in {Code.UNAVAILABLE, Code.RESOURCE_EXHAUSTED} + or ctx.timeout_ms is None + or ctx.timeout_ms <= delay * 1000 + ): + return None + return delay + + +def connect_address(url: str, rpc_method_prefix: str | None = None) -> str: + """Normalize endpoints, defaulting bare hosts to TLS and development ports to HTTP.""" + if url.startswith("unix:"): + raise ValueError("Connect HTTP transport does not support unix socket URLs") + if "://" not in url: + parsed = urlsplit(f"//{url}") + scheme = "http" if parsed.port not in (None, 443) else "https" + url = f"{scheme}://{url}" + parsed = urlsplit(url) + if ( + parsed.scheme not in {"http", "https"} + or not parsed.hostname + or parsed.username + or parsed.password + or parsed.query + or parsed.fragment + or any(character.isspace() for character in url) + ): + raise ValueError(f"Invalid URL: {url}") + if parsed.scheme == "http" and parsed.port is None: + raise ValueError("Explicit port required for insecure HTTP connection") + if parsed.scheme == "https" and parsed.port == 443: + url = urlunsplit((parsed.scheme, parsed.netloc.removesuffix(":443"), parsed.path, "", "")) + prefix = (rpc_method_prefix or "").strip("/") + return url.rstrip("/") + (f"/{prefix}" if prefix else "") + + +class _MetadataErrors: + def __init__(self, token: str | None, address: str) -> None: + self.headers = client_metadata() + self.token = token + self.prefix = urlsplit(address).path.rstrip("/") + + def _prepare(self, ctx: RequestContext) -> str: + for name, value in self.headers.items(): + ctx.request_headers[name] = value + if self.token is not None: + ctx.request_headers["authorization"] = f"Bearer {self.token}" + return f"{self.prefix}/{ctx.method.service_name}/{ctx.method.name}" + + def intercept_unary_sync( + self, + call_next: Callable[[Req, RequestContext], Res], + request: Req, + ctx: RequestContext, + ) -> Res: + method = self._prepare(ctx) + attempt = 0 + while True: + try: + return call_next(request, ctx) + except ConnectError as error: + delay = _retry_delay(ctx, error, attempt) + if delay is None: + raise translate_connect_error(error, method) from None + time.sleep(delay) + attempt += 1 + + async def intercept_unary( + self, + call_next: Callable[[Req, RequestContext], Awaitable[Res]], + request: Req, + ctx: RequestContext, + ) -> Res: + method = self._prepare(ctx) + attempt = 0 + while True: + try: + return await call_next(request, ctx) + except ConnectError as error: + delay = _retry_delay(ctx, error, attempt) + if delay is None: + raise translate_connect_error(error, method) from None + await asyncio.sleep(delay) + attempt += 1 + + +class Connection: + """Share one explicitly owned HTTP transport across generated synchronous clients.""" + + def __init__( + self, + url: str, + token: str | None = None, + rpc_method_prefix: str | None = None, + *, + transport: Transport = "http2", + interceptors: Iterable[InterceptorSync] = (), + ) -> None: + self.address = connect_address(url, rpc_method_prefix) + self.transport = "http2" if transport == "grpc" else transport + self._transport = SyncHTTPTransport(http_version=_http_version(self.transport), tls_include_system_certs=True) + self._http = SyncClient(self._transport) + self._interceptors = (_MetadataErrors(token, self.address), *interceptors) + self._closed = False + + def client(self, client_class: type[Sync]) -> Sync: + """Construct a native generated client using this connection's HTTP client.""" + if self._closed: + raise RuntimeError("Connection is closed") + return client_class( + self.address, + http_client=self._http, + interceptors=self._interceptors, + read_max_bytes=512 * 1024 * 1024, + timeout_ms=60_000, + ) + + def close(self) -> None: + """Release this connection's transport; safe to call more than once.""" + if not self._closed: + self._closed = True + self._transport.close() + + def __enter__(self) -> Self: + return self + + def __exit__(self, *exc: object) -> None: + self.close() + + +class AsyncConnection: + """Share one explicitly owned HTTP transport across generated asynchronous clients.""" + + def __init__( + self, + url: str, + token: str | None = None, + rpc_method_prefix: str | None = None, + *, + transport: Transport = "http2", + interceptors: Iterable[Interceptor] = (), + ) -> None: + self.address = connect_address(url, rpc_method_prefix) + self.transport = "http2" if transport == "grpc" else transport + self._transport = HTTPTransport(http_version=_http_version(self.transport), tls_include_system_certs=True) + self._http = Client(self._transport) + self._interceptors = (_MetadataErrors(token, self.address), *interceptors) + self._closed = False + + def client(self, client_class: type[Async]) -> Async: + """Construct a native generated client without opening an event loop.""" + if self._closed: + raise RuntimeError("Connection is closed") + return client_class( + self.address, + http_client=self._http, + interceptors=self._interceptors, + read_max_bytes=512 * 1024 * 1024, + timeout_ms=60_000, + ) + + async def close(self) -> None: + """Release this connection's transport; safe to call more than once.""" + if not self._closed: + self._closed = True + await self._transport.aclose() + + async def __aenter__(self) -> Self: + return self + + async def __aexit__(self, *exc: object) -> None: + await self.close() diff --git a/tilebox-grpc/_tilebox/grpc/client_metadata.py b/tilebox-core/_tilebox/client_metadata.py similarity index 99% rename from tilebox-grpc/_tilebox/grpc/client_metadata.py rename to tilebox-core/_tilebox/client_metadata.py index 3ddd815..f5645f4 100644 --- a/tilebox-grpc/_tilebox/grpc/client_metadata.py +++ b/tilebox-core/_tilebox/client_metadata.py @@ -40,7 +40,7 @@ def _package_version() -> str | None: return package_version("tilebox-python") except PackageNotFoundError: try: - return package_version("tilebox-grpc") + return package_version("tilebox-core") except PackageNotFoundError: return None diff --git a/tilebox-core/_tilebox/concurrency.py b/tilebox-core/_tilebox/concurrency.py new file mode 100644 index 0000000..b6cedc5 --- /dev/null +++ b/tilebox-core/_tilebox/concurrency.py @@ -0,0 +1,45 @@ +"""Bounded producer/consumer concurrency with exception propagation.""" + +from collections.abc import Callable, Iterator +from concurrent.futures import ThreadPoolExecutor +from queue import Empty, Full, Queue +from threading import Event +from typing import TypeVar + +T = TypeVar("T") + + +def concurrent_producer_consumer( + producer: Iterator[T], + consumer: Callable[[T], object], + buffer_size: int = 10, +) -> None: + """Produce on the calling thread and consume on one worker, propagating failures.""" + if buffer_size < 1: + raise ValueError("buffer_size must be positive") + queue: Queue[T] = Queue(buffer_size) + done = Event() + + def consume() -> None: + while not done.is_set() or not queue.empty(): + try: + item = queue.get(timeout=0.05) + except Empty: + continue + consumer(item) + + with ThreadPoolExecutor(max_workers=1) as executor: + future = executor.submit(consume) + try: + for item in producer: + while True: + if future.done(): + future.result() + try: + queue.put(item, timeout=0.05) + break + except Full: + continue + finally: + done.set() + future.result() diff --git a/tilebox-core/_tilebox/errors.py b/tilebox-core/_tilebox/errors.py new file mode 100644 index 0000000..f51c403 --- /dev/null +++ b/tilebox-core/_tilebox/errors.py @@ -0,0 +1,65 @@ +"""Translate Connect failures into public Tilebox exceptions.""" + +from connectrpc.code import Code +from connectrpc.errors import ConnectError + +from tilebox.errors import ( + ArgumentError, + AuthenticationError, + InternalServerError, + InvalidRequestError, + NetworkError, + NetworkTimeoutError, + NotFoundError, + RequestCanceledError, + SubscriptionLimitExceededError, + TileboxError, + TooManyRequestsError, +) + + +def translate_connect_error(error: ConnectError, method_name: str = "") -> TileboxError: # noqa: C901, PLR0911, PLR0912 + """Map native Connect status codes to the SDK's public exception types.""" + message = error.message + public = method_name.startswith("/public/") + match error.code: + case Code.NOT_FOUND: + return NotFoundError(message) + case Code.INVALID_ARGUMENT: + return ArgumentError(message) + case Code.UNAUTHENTICATED: + return AuthenticationError(f"Unauthenticated: {message}") + case Code.PERMISSION_DENIED: + return AuthenticationError(f"Unauthorized: {message}") + case Code.RESOURCE_EXHAUSTED | Code.UNAVAILABLE: + if "429" in message or "too many requests" in message.lower(): + text = "Too many requests, try again later." + if public: + text += ( + " To increase your request quota create a free account at " + "https://console.tilebox.com and authenticate with an API Key." + ) + return TooManyRequestsError(text) + if error.code == Code.RESOURCE_EXHAUSTED: + return SubscriptionLimitExceededError(message) + return NetworkError(f"network error when requesting {method_name}: {message}" if method_name else message) + case Code.DEADLINE_EXCEEDED: + return NetworkTimeoutError(f"Request timed out: {message}") + case Code.ABORTED: + return NetworkError(f"Request aborted: {message}") + case Code.CANCELED: + return RequestCanceledError(f"Request canceled: {message}") + case Code.UNIMPLEMENTED: + if public: + action = method_name.rsplit("/", maxsplit=1)[-1] + return AuthenticationError( + f"{action} is only available for authenticated users. Please provide a valid API Key " + "token in your Client. Create a free account at https://console.tilebox.com" + ) + return InvalidRequestError( + f"Requested URL {method_name} not found. Update your Tilebox Python Clients to the latest version." + if method_name + else message + ) + case _: + return InternalServerError(f"Oops, something went wrong: {message}") diff --git a/tilebox-grpc/_tilebox/grpc/pagination.py b/tilebox-core/_tilebox/pagination.py similarity index 95% rename from tilebox-grpc/_tilebox/grpc/pagination.py rename to tilebox-core/_tilebox/pagination.py index 46ff8f5..f5de83e 100644 --- a/tilebox-grpc/_tilebox/grpc/pagination.py +++ b/tilebox-core/_tilebox/pagination.py @@ -20,7 +20,7 @@ def paginated_request( paging_request: Callable[[Pagination], AnyResultPage], initial_page: Pagination, ) -> Iterator[AnyResultPage]: - """Make a paginated request to a gRPC service endpoint. + """Make a paginated request to a service endpoint. The endpoint is expected to return a next_page field, which is used for subsequent requests. Once no such next_page field is returned, the request is completed. diff --git a/tilebox-core/_tilebox/proto/tilebox/v1/id_pb.py b/tilebox-core/_tilebox/proto/tilebox/v1/id_pb.py new file mode 100644 index 0000000..34196f9 --- /dev/null +++ b/tilebox-core/_tilebox/proto/tilebox/v1/id_pb.py @@ -0,0 +1,60 @@ +# Generated from tilebox/v1/id.proto. DO NOT EDIT. +# Generated by protoc-gen-py v0.6.0 with parameter "init_files=false,map_imports=datasets/**:tilebox,map_imports=workflows/**:tilebox,map_imports=tilebox/**:_tilebox.proto,map_imports=opentelemetry/**:tilebox.workflows._otel". +# ruff: noqa: PGH004 +# ruff: noqa +# fmt: off + +from __future__ import annotations + +from typing import Literal, TYPE_CHECKING, TypeAlias + +from protobuf import Message +from protobuf._codegen import file_desc + +if TYPE_CHECKING: + from protobuf import DescFile + + +_IDFields: TypeAlias = Literal["uuid"] + +class ID(Message[_IDFields]): + """ + Bytes field (in message) + + ```proto + message tilebox.v1.ID + ``` + + Attributes: + uuid: + ```proto + bytes uuid = 1; + ``` + """ + + __slots__ = ("uuid",) + + if TYPE_CHECKING: + + def __init__( + self, + *, + uuid: bytes = b"", + ) -> None: + pass + + uuid: bytes + + +_DESC = file_desc( + b'\n\x13tilebox/v1/id.proto\x12\ntilebox.v1"\x18\n\x02ID\x12\x12\n\x04uuid\x18\x01 \x01(\x0cR\x04uuidB\x05\x92\x03\x02\x08\x02b\x08editionsp\xe8\x07', + [], + { + "ID": ID, + }, +) + + +def desc() -> DescFile: + """Returns the descriptor for the file `tilebox/v1/id.proto`.""" + return _DESC diff --git a/tilebox-core/_tilebox/proto/tilebox/v1/query_pb.py b/tilebox-core/_tilebox/proto/tilebox/v1/query_pb.py new file mode 100644 index 0000000..c70f465 --- /dev/null +++ b/tilebox-core/_tilebox/proto/tilebox/v1/query_pb.py @@ -0,0 +1,231 @@ +# Generated from tilebox/v1/query.proto. DO NOT EDIT. +# Generated by protoc-gen-py v0.6.0 with parameter "init_files=false,map_imports=datasets/**:tilebox,map_imports=workflows/**:tilebox,map_imports=tilebox/**:_tilebox.proto,map_imports=opentelemetry/**:tilebox.workflows._otel". +# ruff: noqa: PGH004 +# ruff: noqa +# fmt: off + +from __future__ import annotations + +from typing import Literal, TYPE_CHECKING, TypeAlias + +from _tilebox.proto.tilebox.v1 import id_pb +from protobuf import Enum, Message +from protobuf._codegen import file_desc +from protobuf.wkt.google.protobuf import timestamp_pb + +if TYPE_CHECKING: + from _tilebox.proto.tilebox.v1.id_pb import ID + from protobuf import DescFile + from protobuf.wkt.google.protobuf.timestamp_pb import Timestamp + + +_TimeIntervalFields: TypeAlias = Literal["start_time", "end_time", "start_exclusive", "end_inclusive"] + +class TimeInterval(Message[_TimeIntervalFields]): + """ + A time interval + + ```proto + message tilebox.v1.TimeInterval + ``` + + Attributes: + start_time: + Start time of the interval. + + ```proto + optional google.protobuf.Timestamp start_time = 1; + ``` + end_time: + End time of the interval. + + ```proto + optional google.protobuf.Timestamp end_time = 2; + ``` + start_exclusive: + We use exclusive for start and inclusive for end, because that way when both are false + we have a half-open interval [start, end) which is the default behaviour we want to achieve. + Flag indicating whether the start time is exclusive. If true, the start time is not included in the interval. + + ```proto + bool start_exclusive = 3; + ``` + end_inclusive: + Flag indicating whether the end time is inclusive. If true, the end time is included in the interval. + + ```proto + bool end_inclusive = 4; + ``` + """ + + __slots__ = ("start_time", "end_time", "start_exclusive", "end_inclusive") + + if TYPE_CHECKING: + + def __init__( + self, + *, + start_time: Timestamp | None = None, + end_time: Timestamp | None = None, + start_exclusive: bool = False, + end_inclusive: bool = False, + ) -> None: + pass + + start_time: Timestamp | None + end_time: Timestamp | None + start_exclusive: bool + end_inclusive: bool + +_IDIntervalFields: TypeAlias = Literal["start_id", "end_id", "start_exclusive", "end_inclusive"] + +class IDInterval(Message[_IDIntervalFields]): + """ + An ID interval + + ```proto + message tilebox.v1.IDInterval + ``` + + Attributes: + start_id: + The id of the first data point in the interval. + + ```proto + optional tilebox.v1.ID start_id = 1; + ``` + end_id: + The id of the last data point in the interval. + + ```proto + optional tilebox.v1.ID end_id = 2; + ``` + start_exclusive: + We use exclusive for start and inclusive for end, because that way when both are false + we have a half-open interval [start, end) which is the default behaviour we want to achieve. + Flag indicating whether the start id is exclusive. + If true, the datapoint with the given start id is not included in the interval. + + ```proto + bool start_exclusive = 3; + ``` + end_inclusive: + Flag indicating whether the end id is inclusive. + If true, the datapoint with the given end id is included in the interval. + + ```proto + bool end_inclusive = 4; + ``` + """ + + __slots__ = ("start_id", "end_id", "start_exclusive", "end_inclusive") + + if TYPE_CHECKING: + + def __init__( + self, + *, + start_id: ID | None = None, + end_id: ID | None = None, + start_exclusive: bool = False, + end_inclusive: bool = False, + ) -> None: + pass + + start_id: ID | None + end_id: ID | None + start_exclusive: bool + end_inclusive: bool + +_PaginationFields: TypeAlias = Literal["limit", "starting_after"] + +class Pagination(Message[_PaginationFields]): + """ + Pagination information for paginated queries + + ```proto + message tilebox.v1.Pagination + ``` + + Attributes: + limit: + The maximum number of entries to return. + + ```proto + optional int64 limit = 1; + ``` + starting_after: + Return entries starting after this entry. + This is the id of the last entry returned in the previous page as the next parameter in each paginated query. + + ```proto + optional tilebox.v1.ID starting_after = 2; + ``` + """ + + __slots__ = ("limit", "starting_after") + + if TYPE_CHECKING: + + def __init__( + self, + *, + limit: int | None = None, + starting_after: ID | None = None, + ) -> None: + pass + + limit: int + starting_after: ID | None + +class SortDirection(Enum): + """ + SortDirection specifies the direction in which to sort log entries when querying logs. It can be used in combination + with the cursor field in TelemetryPagination to paginate through log entries in either ascending or descending order. + + ```proto + enum tilebox.v1.SortDirection + ``` + + Attributes: + UNSPECIFIED: + ```proto + SORT_DIRECTION_UNSPECIFIED = 0 + ``` + ASCENDING: + Sort in ascending order (oldest entries first). + + ```proto + SORT_DIRECTION_ASCENDING = 1 + ``` + DESCENDING: + Sort in descending order (newest entries first). + + ```proto + SORT_DIRECTION_DESCENDING = 2 + ``` + """ + + UNSPECIFIED = 0 + ASCENDING = 1 + DESCENDING = 2 + + +_DESC = file_desc( + b'\n\x16tilebox/v1/query.proto\x12\ntilebox.v1\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\x13tilebox/v1/id.proto"\xce\x01\n\x0cTimeInterval\x129\n\nstart_time\x18\x01 \x01(\x0b2\x1a.google.protobuf.TimestampR\tstartTime\x125\n\x08end_time\x18\x02 \x01(\x0b2\x1a.google.protobuf.TimestampR\x07endTime\x12\'\n\x0fstart_exclusive\x18\x03 \x01(\x08R\x0estartExclusive\x12#\n\rend_inclusive\x18\x04 \x01(\x08R\x0cendInclusive"\xac\x01\n\nIDInterval\x12)\n\x08start_id\x18\x01 \x01(\x0b2\x0e.tilebox.v1.IDR\x07startId\x12%\n\x06end_id\x18\x02 \x01(\x0b2\x0e.tilebox.v1.IDR\x05endId\x12\'\n\x0fstart_exclusive\x18\x03 \x01(\x08R\x0estartExclusive\x12#\n\rend_inclusive\x18\x04 \x01(\x08R\x0cendInclusive"g\n\nPagination\x12\x1b\n\x05limit\x18\x01 \x01(\x03R\x05limitB\x05\xaa\x01\x02\x08\x01\x12<\n\x0estarting_after\x18\x02 \x01(\x0b2\x0e.tilebox.v1.IDR\rstartingAfterB\x05\xaa\x01\x02\x08\x01*l\n\rSortDirection\x12\x1e\n\x1aSORT_DIRECTION_UNSPECIFIED\x10\x00\x12\x1c\n\x18SORT_DIRECTION_ASCENDING\x10\x01\x12\x1d\n\x19SORT_DIRECTION_DESCENDING\x10\x02B\x05\x92\x03\x02\x08\x02b\x08editionsp\xe8\x07', + [ + timestamp_pb.desc(), + id_pb.desc(), + ], + { + "TimeInterval": TimeInterval, + "IDInterval": IDInterval, + "Pagination": Pagination, + "SortDirection": SortDirection, + }, +) + + +def desc() -> DescFile: + """Returns the descriptor for the file `tilebox/v1/query.proto`.""" + return _DESC diff --git a/tilebox-core/_tilebox/replay.py b/tilebox-core/_tilebox/replay.py new file mode 100644 index 0000000..088fcd5 --- /dev/null +++ b/tilebox-core/_tilebox/replay.py @@ -0,0 +1,147 @@ +"""Native unary recording and replay using the four-line binary fixture format.""" + +import base64 +from collections.abc import Awaitable, Callable +from pathlib import Path +from threading import Lock +from typing import TypeVar + +from connectrpc.code import Code +from connectrpc.errors import ConnectError +from connectrpc.request import RequestContext +from protobuf import Message + +Req = TypeVar("Req", bound=Message) +Res = TypeVar("Res", bound=Message) + +# The fixture format stores the canonical numeric RPC status, not Connect's string enum. +_CODES = ( + None, + Code.CANCELED, + Code.UNKNOWN, + Code.INVALID_ARGUMENT, + Code.DEADLINE_EXCEEDED, + Code.NOT_FOUND, + Code.ALREADY_EXISTS, + Code.PERMISSION_DENIED, + Code.RESOURCE_EXHAUSTED, + Code.FAILED_PRECONDITION, + Code.ABORTED, + Code.OUT_OF_RANGE, + Code.UNIMPLEMENTED, + Code.INTERNAL, + Code.UNAVAILABLE, + Code.DATA_LOSS, + Code.UNAUTHENTICATED, +) + + +def _method(ctx: RequestContext) -> str: + return f"/{ctx.method.service_name}/{ctx.method.name}" + + +class Replay: + """Replay recorded unary calls through synchronous or asynchronous interceptors.""" + + def __init__(self, recording: str | Path, assert_request_matches: bool = True) -> None: + self._lines = iter(Path(recording).read_bytes().split(b"\n")) + self.assert_request_matches = assert_request_matches + self._lock = Lock() + + def _replay(self, request: Req, ctx: RequestContext[Req, Res]) -> Res: + with self._lock: + try: + method, expected, status, response = (next(self._lines) for _ in range(4)) + except (StopIteration, RuntimeError) as error: + raise ValueError(f"Replayed call to {_method(ctx)} was never recorded!") from error + if method.decode() != _method(ctx): + raise ValueError(f"Expected method {method.decode()}, but got {_method(ctx)}") + if self.assert_request_matches and request != type(request).from_binary(base64.b64decode(expected)): + raise ValueError(f"Expected request for RPC call {_method(ctx)} differs from actual received request") + code = int(status) + if code: + status_code = _CODES[code] + if status_code is None: + raise ValueError(f"Invalid recorded status: {code}") + raise ConnectError(status_code, response.decode()) + return ctx.method.output.from_binary(base64.b64decode(response)) + + def intercept_unary_sync( + self, + call_next: Callable[[Req, RequestContext], Res], + request: Req, + ctx: RequestContext[Req, Res], + ) -> Res: + """Return the next recorded synchronous response.""" + _ = call_next + return self._replay(request, ctx) + + async def intercept_unary( + self, + call_next: Callable[[Req, RequestContext], Awaitable[Res]], + request: Req, + ctx: RequestContext[Req, Res], + ) -> Res: + """Return the next recorded asynchronous response.""" + _ = call_next + return self._replay(request, ctx) + + +class Record: + """Record native unary successes and Connect failures for deterministic replay.""" + + def __init__(self, recording: str | Path) -> None: + self.recording = Path(recording) + self.recording.parent.mkdir(parents=True, exist_ok=True) + self.recording.write_bytes(b"") + self._lock = Lock() + + def _write( + self, + request: Message, + ctx: RequestContext, + response: Message | None = None, + error: ConnectError | None = None, + ) -> None: + status = _CODES.index(error.code) if error is not None else 0 + if error is not None: + payload = error.message.encode().replace(b"\n", b" ") + elif response is not None: + payload = base64.b64encode(response.to_binary()) + else: + raise ValueError("A recorded call requires a response or error") + entry = b"\n".join( + (_method(ctx).encode(), base64.b64encode(request.to_binary()), str(status).encode(), payload) + ) + with self._lock, self.recording.open("ab") as file: + file.write(entry + b"\n") + + def intercept_unary_sync( + self, + call_next: Callable[[Req, RequestContext], Res], + request: Req, + ctx: RequestContext[Req, Res], + ) -> Res: + """Record a synchronous unary call without changing its result.""" + try: + response = call_next(request, ctx) + except ConnectError as error: + self._write(request, ctx, error=error) + raise + self._write(request, ctx, response=response) + return response + + async def intercept_unary( + self, + call_next: Callable[[Req, RequestContext], Awaitable[Res]], + request: Req, + ctx: RequestContext[Req, Res], + ) -> Res: + """Record an asynchronous unary call without changing its result.""" + try: + response = await call_next(request, ctx) + except ConnectError as error: + self._write(request, ctx, error=error) + raise + self._write(request, ctx, response=response) + return response diff --git a/tilebox-core/_tilebox/time.py b/tilebox-core/_tilebox/time.py new file mode 100644 index 0000000..3a482f7 --- /dev/null +++ b/tilebox-core/_tilebox/time.py @@ -0,0 +1,62 @@ +"""Internal datetime and protobuf conversions.""" + +from datetime import UTC, datetime, timedelta +from typing import Any + +from protobuf.wkt.google.protobuf.duration_pb import Duration +from protobuf.wkt.google.protobuf.timestamp_pb import Timestamp + +_EPOCH = datetime(1970, 1, 1, tzinfo=UTC) + + +def _convert_to_datetime(arg: Any) -> datetime: + """Convert a datetime scalar to UTC, loading pandas only for non-native values.""" + # pandas.Timestamp subclasses datetime; retain its existing conversion and precision behavior. + if type(arg) is datetime: + return arg.replace(tzinfo=UTC) if arg.tzinfo is None else arg.astimezone(UTC) + + try: + from pandas.core.tools.datetimes import to_datetime # noqa: PLC0415 + except ModuleNotFoundError as error: + if error.name != "pandas": + raise + raise ImportError( + "Parsing strings and scientific datetime values requires pandas. " + "Install pandas or pass Python datetime objects." + ) from error + + dt: datetime = to_datetime(arg, utc=True).to_pydatetime() + if dt.tzinfo is None: + dt = dt.replace(tzinfo=UTC) + return dt.astimezone(UTC) + + +def timestamp_to_datetime(timestamp: Timestamp | None) -> datetime: + """Convert a protobuf timestamp without floating-point rounding.""" + if timestamp is None: + return _EPOCH + return _EPOCH + timedelta(seconds=timestamp.seconds, microseconds=timestamp.nanos // 1000) + + +def datetime_to_timestamp(dt: datetime) -> Timestamp: + if dt.tzinfo is None: + dt = dt.replace(tzinfo=UTC) + seconds, us = divmod(datetime_to_us(dt.astimezone(UTC)), 10**6) + return Timestamp(seconds=seconds, nanos=us * 10**3) + + +def datetime_to_us(dt: datetime) -> int: + offset = dt - _EPOCH + return (offset.days * 86400 + offset.seconds) * 10**6 + offset.microseconds + + +def us_to_datetime(us: int) -> datetime: + return _EPOCH + timedelta(microseconds=us) + + +def timedelta_to_duration(td: timedelta) -> Duration: + return Duration.from_nanos(((td.days * 86400 + td.seconds) * 10**6 + td.microseconds) * 1000) + + +def duration_to_timedelta(duration: Duration) -> timedelta: + return timedelta(seconds=duration.seconds, microseconds=duration.nanos // 1000) diff --git a/tilebox-core/_tilebox/uuid.py b/tilebox-core/_tilebox/uuid.py new file mode 100644 index 0000000..b3e52e4 --- /dev/null +++ b/tilebox-core/_tilebox/uuid.py @@ -0,0 +1,44 @@ +# allow the uuid module name which shadows the builtin: +from typing import Protocol +from uuid import UUID + +from _tilebox.proto.tilebox.v1 import id_pb + +_NIL_UUID = UUID(int=0) + + +class _UUIDMessage(Protocol): + @property + def uuid(self) -> bytes: ... + + +def uuid_message_to_uuid(uuid_message: _UUIDMessage | None) -> UUID: + if uuid_message is None or uuid_message.uuid == b"": + return _NIL_UUID + return UUID(bytes=uuid_message.uuid) + + +def uuid_message_to_optional_uuid(uuid_message: _UUIDMessage | None) -> UUID | None: + if uuid_message is None: + return None + if uuid_message.uuid == b"": + return None + return UUID(bytes=uuid_message.uuid) + + +def uuid_to_uuid_message(uuid: UUID | None) -> id_pb.ID | None: + if uuid is None or uuid == _NIL_UUID: + return None + return id_pb.ID(uuid=uuid.bytes) + + +def must_uuid_to_uuid_message(uuid: UUID) -> id_pb.ID: + if uuid == _NIL_UUID: + raise ValueError("UUID must not be Nil") + return id_pb.ID(uuid=uuid.bytes) + + +def as_uuid(uuid: str | UUID) -> UUID: + if isinstance(uuid, str): + return UUID(uuid) + return uuid diff --git a/tilebox-core/pyproject.toml b/tilebox-core/pyproject.toml new file mode 100644 index 0000000..a04ef9d --- /dev/null +++ b/tilebox-core/pyproject.toml @@ -0,0 +1,43 @@ +[project] +name = "tilebox-core" +dynamic = ["version"] +description = "Shared types and client infrastructure for Tilebox" +authors = [{ name = "Tilebox, Inc.", email = "support@tilebox.com" }] +readme = "README.md" +license = { text = "MIT License" } +requires-python = ">=3.11" +dependencies = [ + "connectrpc>=0.12.1,<0.13", + "protobuf-py>=0.6.0,<0.7", + "pyqwest>=0.7.0", + "anyio>=4", +] + +[dependency-groups] +dev = ["pytest-asyncio>=0.24.0", "pytest-cov>=5.0.0", "pytest>=8.3.2"] + +[project.urls] +Homepage = "https://tilebox.com" +Documentation = "https://docs.tilebox.com/" + +[tool.hatch.build.targets.sdist] +packages = ["_tilebox", "tilebox"] + +[tool.hatch.build.targets.wheel] +packages = ["_tilebox", "tilebox"] + +[tool.coverage.run] +omit = ["_tilebox/proto/*"] + +[tool.pytest.ini_options] +minversion = "6.0" +testpaths = ["tests"] +addopts = "-Wall -Werror --import-mode importlib" +asyncio_default_fixture_loop_scope = "function" + +[build-system] +requires = ["hatchling", "uv-dynamic-versioning"] +build-backend = "hatchling.build" + +[tool.hatch.version] +source = "uv-dynamic-versioning" diff --git a/tilebox-grpc/tests/test_client_metadata.py b/tilebox-core/tests/test_client_metadata.py similarity index 84% rename from tilebox-grpc/tests/test_client_metadata.py rename to tilebox-core/tests/test_client_metadata.py index 9e78fcf..11fadc1 100644 --- a/tilebox-grpc/tests/test_client_metadata.py +++ b/tilebox-core/tests/test_client_metadata.py @@ -3,7 +3,7 @@ import pytest -from _tilebox.grpc.client_metadata import ( +from _tilebox.client_metadata import ( CLIENT_HEADER, _architecture, _cloud_environment, @@ -37,14 +37,14 @@ def test_serializes_full_client_header() -> None: ) -@patch("_tilebox.grpc.client_metadata._cloud_environment", return_value={}) -@patch("_tilebox.grpc.client_metadata._invoker", return_value={}) -@patch("_tilebox.grpc.client_metadata._execution_environment", return_value=None) -@patch("_tilebox.grpc.client_metadata._architecture", return_value=None) -@patch("_tilebox.grpc.client_metadata._os_name", return_value=None) -@patch("_tilebox.grpc.client_metadata.platform.release", return_value="") -@patch("_tilebox.grpc.client_metadata.platform.python_version", return_value="") -@patch("_tilebox.grpc.client_metadata._package_version", return_value="1.2.3") +@patch("_tilebox.client_metadata._cloud_environment", return_value={}) +@patch("_tilebox.client_metadata._invoker", return_value={}) +@patch("_tilebox.client_metadata._execution_environment", return_value=None) +@patch("_tilebox.client_metadata._architecture", return_value=None) +@patch("_tilebox.client_metadata._os_name", return_value=None) +@patch("_tilebox.client_metadata.platform.release", return_value="") +@patch("_tilebox.client_metadata.platform.python_version", return_value="") +@patch("_tilebox.client_metadata._package_version", return_value="1.2.3") def test_minimal_client_header(*_: object) -> None: client_metadata.cache_clear() @@ -52,11 +52,12 @@ def test_minimal_client_header(*_: object) -> None: client_metadata.cache_clear() -@patch("_tilebox.grpc.client_metadata.package_version") -def test_uses_umbrella_package_version(package_version: object) -> None: +@patch("_tilebox.client_metadata.package_version") +def test_falls_back_to_core_package_version(package_version: object) -> None: package_version.side_effect = [PackageNotFoundError, "1.2.3"] # ty: ignore[unresolved-attribute] assert _package_version() == "1.2.3" + package_version.assert_any_call("tilebox-core") # ty: ignore[unresolved-attribute] @pytest.mark.parametrize( @@ -70,7 +71,7 @@ def test_uses_umbrella_package_version(package_version: object) -> None: ], ) def test_normalizes_architecture(machine: str, expected: str) -> None: - with patch("_tilebox.grpc.client_metadata.platform.machine", return_value=machine): + with patch("_tilebox.client_metadata.platform.machine", return_value=machine): assert _architecture() == expected @@ -99,8 +100,8 @@ def test_normalizes_architecture(machine: str, expected: str) -> None: ({}, None), ], ) -@patch("_tilebox.grpc.client_metadata.sys.stdin.isatty", return_value=False) -@patch("_tilebox.grpc.client_metadata._interactive_shell", return_value=None) +@patch("_tilebox.client_metadata.sys.stdin.isatty", return_value=False) +@patch("_tilebox.client_metadata._interactive_shell", return_value=None) def test_detects_execution_environment( interactive_shell: object, stdin_isatty: object, environ: dict[str, str], expected: str | None ) -> None: diff --git a/tilebox-core/tests/test_iteration.py b/tilebox-core/tests/test_iteration.py new file mode 100644 index 0000000..f232ca5 --- /dev/null +++ b/tilebox-core/tests/test_iteration.py @@ -0,0 +1,65 @@ +"""Verify shared pagination and bounded asynchronous consumption through their public helpers.""" + +from collections.abc import AsyncIterator +from dataclasses import dataclass +from uuid import UUID + +import pytest + +from _tilebox.aio.concurrency import async_producer_consumer +from _tilebox.aio.pagination import paginated_request as async_paginated_request +from _tilebox.pagination import Pagination, paginated_request + + +@dataclass +class Page: + limit: int | None = None + starting_after: UUID | None = None + + +@dataclass +class Result: + next_page: Page + + +@pytest.mark.asyncio +async def test_pagination_follows_cursor_until_exhausted() -> None: + """Sync and async iteration request the next cursor once and stop after the final page.""" + initial = Page(limit=10) + continuation = Page(limit=10, starting_after=UUID(int=1)) + first, last = Result(continuation), Result(Page()) + calls: list[Pagination] = [] + + def request(page: Pagination) -> Result: + calls.append(page) + return first if page.starting_after is None else last + + assert list(paginated_request(request, initial)) == [first, last] + assert calls == [initial, continuation] + calls.clear() + + async def async_request(page: Pagination) -> Result: + return request(page) + + assert [page async for page in async_paginated_request(async_request, initial)] == [first, last] + assert calls == [initial, continuation] + + +@pytest.mark.asyncio +async def test_async_consumption_accepts_sync_and_async_callbacks() -> None: + """Both callback forms receive every item in order, including None as a payload.""" + received: list[int | None] = [] + + async def produce() -> AsyncIterator[int | None]: + for value in (1, None, 2): + yield value + + await async_producer_consumer(produce(), received.append, buffer_size=1) + assert received == [1, None, 2] + received.clear() + + async def consume(value: int | None) -> None: + received.append(value) + + await async_producer_consumer(produce(), consume, buffer_size=1) + assert received == [1, None, 2] diff --git a/tilebox-core/tests/test_query.py b/tilebox-core/tests/test_query.py new file mode 100644 index 0000000..37cbb53 --- /dev/null +++ b/tilebox-core/tests/test_query.py @@ -0,0 +1,49 @@ +import subprocess +import sys + + +def test_shared_query_types_work_without_scientific_libraries() -> None: + script = """ +import sys +from importlib.abc import MetaPathFinder +from datetime import UTC, datetime, timedelta, timezone +from uuid import UUID +from tilebox.query import IDInterval, Pagination, TimeInterval + +# Simulate a core-only installation even when pytest runs in the full workspace. +blocked = ('pandas', 'numpy', 'xarray', 'tilebox.datasets') +for name in blocked: + assert name not in sys.modules + +class MissingDependencies(MetaPathFinder): + def find_spec(self, fullname, path, target=None): + if fullname in blocked: + raise ModuleNotFoundError(f'No module named {fullname!r}', name=fullname) + +sys.meta_path.insert(0, MissingDependencies()) + +start = datetime(2026, 3, 4, 2, 30, tzinfo=timezone(timedelta(hours=2))) +end = datetime(2026, 3, 5, 1, 15) +interval = TimeInterval.parse((start, end)) +assert interval.start == datetime(2026, 3, 4, 0, 30, tzinfo=UTC) +assert interval.end == datetime(2026, 3, 5, 1, 15, tzinfo=UTC) +assert not interval.start_exclusive and not interval.end_inclusive +assert TimeInterval.from_message(interval.to_message()) == interval +instant = TimeInterval.parse(start) +assert instant.start == interval.start == instant.end +assert instant.end_inclusive +assert TimeInterval.parse(interval) is interval +ids = IDInterval.parse((UUID(int=1), UUID(int=9))) +assert IDInterval.from_message(ids.to_message()) == ids +page = Pagination(limit=7, starting_after=UUID(int=3)) +assert Pagination.from_message(page.to_message()) == page + +for value in ('2026-01-01', ('2026-01-01', '2026-01-02')): + try: + TimeInterval.parse(value) + except ImportError as error: + assert 'Install pandas or pass Python datetime objects' in str(error) + else: + raise AssertionError('String parsing should explain the missing pandas dependency') +""" + subprocess.run([sys.executable, "-c", script], check=True) # noqa: S603 diff --git a/tilebox-core/tests/test_transport.py b/tilebox-core/tests/test_transport.py new file mode 100644 index 0000000..c682461 --- /dev/null +++ b/tilebox-core/tests/test_transport.py @@ -0,0 +1,255 @@ +"""Exercise native generated clients against a real local HTTP endpoint and binary recordings.""" + +import base64 +import gzip +import json +from collections.abc import Iterator +from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer +from pathlib import Path +from threading import Thread +from typing import TypeAlias + +import pytest +from connectrpc.client import ConnectClient, ConnectClientSync +from connectrpc.code import Code +from connectrpc.errors import ConnectError +from connectrpc.method import IdempotencyLevel, MethodInfo +from connectrpc.request import RequestContext +from protobuf.wkt import Empty, Timestamp + +from _tilebox.client import AsyncConnection, Connection, connect_address +from _tilebox.concurrency import concurrent_producer_consumer +from _tilebox.replay import Record, Replay +from tilebox.datasets import Client as DatasetsClient +from tilebox.datasets.aio import Client as AsyncDatasetsClient +from tilebox.datasets.v1.datasets_connect import DatasetServiceClient, DatasetServiceClientSync +from tilebox.datasets.v1.datasets_pb import ListDatasetsRequest, ListDatasetsResponse +from tilebox.errors import ( + AuthenticationError, + NetworkError, + RequestCanceledError, + SubscriptionLimitExceededError, + TileboxError, + TooManyRequestsError, +) + +Endpoint: TypeAlias = tuple[str, list[tuple[str, dict[str, str], bytes]]] + + +@pytest.fixture +def endpoint() -> Iterator[Endpoint]: + """Serve minimal Connect unary responses and retain the received HTTP requests.""" + calls: list[tuple[str, dict[str, str], bytes]] = [] + + class Handler(BaseHTTPRequestHandler): + def do_POST(self) -> None: + data = self.rfile.read(int(self.headers["Content-Length"])) + if self.headers.get("Content-Encoding") == "gzip": + data = gzip.decompress(data) + calls.append((self.path, dict(self.headers.items()), data)) + public = self.path.startswith("/public/") + payload = ( + json.dumps({"code": "unimplemented", "message": "Not found"}).encode() + if public + else ListDatasetsResponse().to_binary() + ) + self.send_response(501 if public else 200) + self.send_header("Content-Type", "application/json" if public else "application/proto") + self.send_header("Content-Length", str(len(payload))) + self.end_headers() + self.wfile.write(payload) + + def log_message(self, format: str, *args: object) -> None: # noqa: A002 + pass + + server = ThreadingHTTPServer(("127.0.0.1", 0), Handler) + thread = Thread(target=server.serve_forever) + thread.start() + try: + yield f"http://127.0.0.1:{server.server_port}", calls + finally: + server.shutdown() + server.server_close() + thread.join() + + +def test_native_http_metadata_record_and_replay(endpoint: Endpoint, tmp_path: Path) -> None: + """Native clients send auth/metadata and binary requests; recorded calls replay without HTTP.""" + address, calls = endpoint + recording = tmp_path / "calls.bin" + request = ListDatasetsRequest() + with Connection(address, "secret", transport="http1", interceptors=[Record(recording)]) as connection: + first = connection.client(DatasetServiceClientSync) + second = connection.client(DatasetServiceClientSync) + assert first._http_client is second._http_client + assert first._read_max_bytes == 512 * 1024 * 1024 + assert first.list_datasets(request) == ListDatasetsResponse() + connection.close() + with pytest.raises(RuntimeError, match="closed"): + connection.client(DatasetServiceClientSync) + + path, headers, data = calls[0] + assert path == "/datasets.v1.DatasetService/ListDatasets" + headers = {name.lower(): value for name, value in headers.items()} + assert headers["authorization"] == "Bearer secret" + assert 'name="python"' in headers["tilebox-client"] + assert "gzip" in headers["accept-encoding"] + assert data == request.to_binary() + assert recording.read_bytes().split(b"\n") == [ + path.encode(), + base64.b64encode(request.to_binary()), + b"0", + b"", + b"", + ] + with Connection("http://127.0.0.1:1", interceptors=[Replay(recording)]) as connection: + assert connection.client(DatasetServiceClientSync).list_datasets(request) == ListDatasetsResponse() + assert len(calls) == 1 + + +def test_replay_compares_message_values_not_wire_field_order(tmp_path: Path) -> None: + """Equivalent field orderings match, while a changed field still fails replay.""" + recording = tmp_path / "ordered.bin" + recording.write_bytes(b"/test.Service/Call\n" + base64.b64encode(b"\x10\x1d\x08\x11") + b"\n0\n\n") + method = MethodInfo( + name="Call", + service_name="test.Service", + input=Timestamp, + output=Empty, + idempotency_level=IdempotencyLevel.UNKNOWN, + ) + for seconds in (17, 18): + with Connection("http://localhost:1", interceptors=[Replay(recording)]) as connection: + client = connection.client(ConnectClientSync) + request = Timestamp(seconds=seconds, nanos=29) + if seconds == 17: + assert client.execute_unary(request=request, method=method) == Empty() + else: + with pytest.raises(ValueError, match="differs"): + client.execute_unary(request=request, method=method) + + +@pytest.mark.asyncio +async def test_async_native_http_and_replay(endpoint: Endpoint, tmp_path: Path) -> None: + """Async client construction is synchronous and async recording/replay uses native methods.""" + address, calls = endpoint + recording = tmp_path / "async.bin" + async with AsyncConnection(address, transport="http1", interceptors=[Record(recording)]) as connection: + client = connection.client(DatasetServiceClient) + assert await client.list_datasets(ListDatasetsRequest()) == ListDatasetsResponse() + await connection.close() + async with AsyncConnection(address, interceptors=[Replay(recording)]) as connection: + assert ( + await connection.client(DatasetServiceClient).list_datasets(ListDatasetsRequest()) == ListDatasetsResponse() + ) + assert len(calls) == 1 + + +def test_public_route_errors_and_recorded_status(endpoint: Endpoint, tmp_path: Path) -> None: + """Public missing routes explain authentication, and legacy numeric statuses remain readable.""" + address, calls = endpoint + with ( + Connection(address, rpc_method_prefix="/public/", transport="http1") as connection, + pytest.raises(AuthenticationError, match="ListDatasets is only available"), + ): + connection.client(DatasetServiceClientSync).list_datasets(ListDatasetsRequest()) + assert calls[0][0] == "/public/datasets.v1.DatasetService/ListDatasets" + recording = tmp_path / "error.bin" + # Record interceptors capture each attempt, including all five retryable failures. + recording.write_bytes(b"/datasets.v1.DatasetService/ListDatasets\n\n14\nstatus: 429\n" * 5) + with ( + Connection(address, rpc_method_prefix="public", interceptors=[Replay(recording)]) as connection, + pytest.raises(TooManyRequestsError, match="create a free account"), + ): + connection.client(DatasetServiceClientSync).list_datasets(ListDatasetsRequest()) + + +@pytest.mark.asyncio +async def test_public_clients_force_http1(endpoint: Endpoint) -> None: + """The user-facing proxy fallback must reach the HTTP/1-only endpoint in both clients.""" + address, calls = endpoint + with ( + DatasetsClient(url=f"{address}/public", transport="http1") as client, + pytest.raises(AuthenticationError, match="ListDatasets is only available"), + ): + client.datasets() + async with AsyncDatasetsClient(url=f"{address}/public", transport="http1") as async_client: + with pytest.raises(AuthenticationError, match="ListDatasets is only available"): + await async_client.datasets() + assert len(calls) == 2 + + +@pytest.mark.parametrize( + ("url", "expected"), + [ + ("api.tilebox.com/", "https://api.tilebox.com/public"), + ("localhost:8080", "http://localhost:8080/public"), + ("https://api.tilebox.com:443", "https://api.tilebox.com/public"), + ], +) +def test_connect_address(url: str, expected: str) -> None: + """Bare hosts default to TLS while explicit development ports use HTTP.""" + assert connect_address(url, "/public/") == expected + + +def test_consumer_errors_propagate_without_deadlock() -> None: + """Consumer failure unblocks a producer even when the bounded queue is full.""" + + def fail(_: int) -> None: + raise ValueError("consumer failed") + + with pytest.raises(ValueError, match="consumer failed"): + concurrent_producer_consumer(iter(range(100)), fail, buffer_size=1) + + +@pytest.mark.asyncio +@pytest.mark.parametrize( + ("idempotency", "code", "timeout_ms", "attempts", "expected_error"), + [ + (IdempotencyLevel.UNKNOWN, Code.UNAVAILABLE, 60_000, 5, NetworkError), + (IdempotencyLevel.UNKNOWN, Code.RESOURCE_EXHAUSTED, 60_000, 5, SubscriptionLimitExceededError), + (IdempotencyLevel.UNKNOWN, Code.UNAUTHENTICATED, 60_000, 1, AuthenticationError), + (IdempotencyLevel.UNKNOWN, Code.CANCELED, 60_000, 1, RequestCanceledError), + (IdempotencyLevel.NO_SIDE_EFFECTS, Code.UNAVAILABLE, 60_000, 5, NetworkError), + (IdempotencyLevel.IDEMPOTENT, Code.UNAVAILABLE, 1, 1, NetworkError), + ], +) +async def test_retries_respect_error_code_attempt_limit_and_deadline( + idempotency: IdempotencyLevel, + code: Code, + timeout_ms: int, + attempts: int, + expected_error: type[Exception], +) -> None: + """Both invocation paths retry transient errors regardless of method idempotency.""" + assert issubclass(expected_error, TileboxError) + + class Failing: + def __init__(self) -> None: + self.calls = 0 + + def intercept_unary_sync(self, call_next: object, request: Empty, ctx: RequestContext) -> Empty: + _ = call_next, request, ctx + self.calls += 1 + raise ConnectError(code, "request failed") + + async def intercept_unary(self, call_next: object, request: Empty, ctx: RequestContext) -> Empty: + _ = call_next, request, ctx + self.calls += 1 + raise ConnectError(code, "request failed") + + method = MethodInfo( + name="Call", service_name="test.Service", input=Empty, output=Empty, idempotency_level=idempotency + ) + failing = Failing() + with ( + Connection("http://localhost:1", interceptors=[failing]) as connection, + pytest.raises(expected_error), + ): + connection.client(ConnectClientSync).execute_unary(request=Empty(), method=method, timeout_ms=timeout_ms) + assert failing.calls == attempts + failing.calls = 0 + async with AsyncConnection("http://localhost:1", interceptors=[failing]) as connection: + with pytest.raises(expected_error): + await connection.client(ConnectClient).execute_unary(request=Empty(), method=method, timeout_ms=timeout_ms) + assert failing.calls == attempts diff --git a/tilebox-core/tilebox/errors.py b/tilebox-core/tilebox/errors.py new file mode 100644 index 0000000..3692628 --- /dev/null +++ b/tilebox-core/tilebox/errors.py @@ -0,0 +1,47 @@ +"""Exceptions raised by Tilebox API clients.""" + +# ruff: noqa: INP001 -- tilebox is shared by multiple distributions + + +class TileboxError(Exception): + """Base class for Tilebox service errors.""" + + +class NetworkError(TileboxError, OSError): + """Communication with the service failed.""" + + +class TooManyRequestsError(NetworkError): + """The request quota has been exhausted.""" + + +class NetworkTimeoutError(NetworkError, TimeoutError): + """The request deadline expired.""" + + +class AuthenticationError(TileboxError, OSError): + """Authentication or authorization failed.""" + + +class ArgumentError(TileboxError, ValueError): + """The request contains invalid arguments.""" + + +class NotFoundError(TileboxError, KeyError): + """The requested resource does not exist.""" + + +class SubscriptionLimitExceededError(TileboxError, OSError): + """The subscription's resource limit was exceeded.""" + + +class InternalServerError(TileboxError, KeyError): + """The service encountered an unexpected error.""" + + +class InvalidRequestError(TileboxError, OSError): + """The requested endpoint is unavailable.""" + + +class RequestCanceledError(TileboxError): + """The remote request was canceled.""" diff --git a/tilebox-core/tilebox/query/__init__.py b/tilebox-core/tilebox/query/__init__.py new file mode 100644 index 0000000..4093df9 --- /dev/null +++ b/tilebox-core/tilebox/query/__init__.py @@ -0,0 +1,7 @@ +"""Shared query value types for Tilebox clients.""" + +from tilebox.query.id_interval import IDInterval, IDIntervalLike +from tilebox.query.pagination import Pagination +from tilebox.query.time_interval import TimeInterval, TimeIntervalLike + +__all__ = ["IDInterval", "IDIntervalLike", "Pagination", "TimeInterval", "TimeIntervalLike"] diff --git a/tilebox-core/tilebox/query/id_interval.py b/tilebox-core/tilebox/query/id_interval.py new file mode 100644 index 0000000..f0d7161 --- /dev/null +++ b/tilebox-core/tilebox/query/id_interval.py @@ -0,0 +1,70 @@ +from dataclasses import dataclass +from typing import TypeAlias +from uuid import UUID + +from _tilebox.proto.tilebox.v1 import query_pb +from _tilebox.uuid import uuid_message_to_uuid, uuid_to_uuid_message + +IDIntervalLike: TypeAlias = "tuple[str, str] | tuple[UUID, UUID] | IDInterval" + + +@dataclass(frozen=True) +class IDInterval: + start_id: UUID + end_id: UUID + start_exclusive: bool + end_inclusive: bool + + @classmethod + def from_message(cls, interval: query_pb.IDInterval) -> "IDInterval": + return cls( + start_id=uuid_message_to_uuid(interval.start_id), + end_id=uuid_message_to_uuid(interval.end_id), + start_exclusive=interval.start_exclusive, + end_inclusive=interval.end_inclusive, + ) + + def to_message(self) -> query_pb.IDInterval: + return query_pb.IDInterval( + start_id=uuid_to_uuid_message(self.start_id), + end_id=uuid_to_uuid_message(self.end_id), + start_exclusive=self.start_exclusive, + end_inclusive=self.end_inclusive, + ) + + @classmethod + def parse(cls, arg: IDIntervalLike, start_exclusive: bool = False, end_inclusive: bool = True) -> "IDInterval": + """ + Convert a variety of input types to a IDInterval. + + Supported input types: + - IDInterval: Return the input as is + - tuple of two UUIDs: Return an IDInterval with start and end id set to the given values + - tuple of two strings: Return an IDInterval with start and end id set to the UUIDs parsed from the given strings + + Args: + arg: The input to convert + start_exclusive: Whether the start id is exclusive + end_inclusive: Whether the end id is inclusive + + Returns: + IDInterval: The parsed ID interval + """ + if isinstance(arg, IDInterval): + return arg + + if isinstance(arg, tuple) and len(arg) == 2: + start, end = arg + if isinstance(start, UUID) and isinstance(end, UUID): + return IDInterval( + start_id=start, end_id=end, start_exclusive=start_exclusive, end_inclusive=end_inclusive + ) + if isinstance(start, str) and isinstance(end, str): + return IDInterval( + start_id=UUID(start), + end_id=UUID(end), + start_exclusive=start_exclusive, + end_inclusive=end_inclusive, + ) + + raise ValueError(f"Failed to convert {arg} ({type(arg)}) to IDInterval") diff --git a/tilebox-core/tilebox/query/pagination.py b/tilebox-core/tilebox/query/pagination.py new file mode 100644 index 0000000..5b0134f --- /dev/null +++ b/tilebox-core/tilebox/query/pagination.py @@ -0,0 +1,21 @@ +from dataclasses import dataclass +from uuid import UUID + +from _tilebox.proto.tilebox.v1 import query_pb +from _tilebox.uuid import uuid_message_to_optional_uuid, uuid_to_uuid_message + + +@dataclass +class Pagination: + limit: int | None = None + starting_after: UUID | None = None + + @classmethod + def from_message(cls, page: query_pb.Pagination | None) -> "Pagination": + if page is None: + return cls() + # convert falsish values (0 or empty string) to None + return cls(limit=page.limit or None, starting_after=uuid_message_to_optional_uuid(page.starting_after)) + + def to_message(self) -> query_pb.Pagination: + return query_pb.Pagination(limit=self.limit, starting_after=uuid_to_uuid_message(self.starting_after)) diff --git a/tilebox-core/tilebox/query/time_interval.py b/tilebox-core/tilebox/query/time_interval.py new file mode 100644 index 0000000..8382057 --- /dev/null +++ b/tilebox-core/tilebox/query/time_interval.py @@ -0,0 +1,212 @@ +import sys +from dataclasses import dataclass +from datetime import UTC, datetime, timedelta, timezone +from typing import TYPE_CHECKING, Self, TypeAlias + +from _tilebox.proto.tilebox.v1 import query_pb +from _tilebox.time import _convert_to_datetime, datetime_to_timestamp, timestamp_to_datetime + +if TYPE_CHECKING: + from pandas.core.tools.datetimes import DatetimeScalar + from xarray import DataArray, Dataset + +_SMALLEST_POSSIBLE_TIMEDELTA = timedelta(microseconds=1) +_EPOCH = datetime(1970, 1, 1, tzinfo=UTC) + +# A type alias for the different types that can be used to specify a time interval +TimeIntervalLike: TypeAlias = ( + "DatetimeScalar | tuple[DatetimeScalar, DatetimeScalar] | list[DatetimeScalar] | DataArray | Dataset | TimeInterval" +) +# once we require python >= 3.12 we can replace this with a type statement, which doesn't require a string at all +# type TimeIntervalLike = DatetimeScalar | tuple[DatetimeScalar ... | TimeInterval + + +@dataclass(frozen=True) +class TimeInterval: + """ + A time interval from a given start to a given end time. + Both the start and end time can be exclusive or inclusive. + """ + + start: datetime + end: datetime + + # We use exclusive for start and inclusive for end, because that way when both are false + # we have a half-open interval [start, end) which is the default behaviour we want to achieve. + start_exclusive: bool = False + end_inclusive: bool = False + + def __post_init__(self) -> None: + """Validate the time interval""" + if not isinstance(self.start, datetime): + raise TypeError(f"Start time {self.start} must be a datetime object") + if not isinstance(self.end, datetime): + raise TypeError(f"End time {self.end} must be a datetime object") + + # in case datetime objects are timezone naive, we assume they are in UTC + if self.start.tzinfo is None: + object.__setattr__(self, "start", self.start.replace(tzinfo=UTC)) # since self is frozen + if self.end.tzinfo is None: + object.__setattr__(self, "end", self.end.replace(tzinfo=UTC)) # since self is frozen + + def to_half_open(self) -> "TimeInterval": + """Convert the time interval to a half-open interval [start, end)""" + return TimeInterval( + start=self.start + int(self.start_exclusive) * _SMALLEST_POSSIBLE_TIMEDELTA, + end=self.end + int(self.end_inclusive) * _SMALLEST_POSSIBLE_TIMEDELTA, + start_exclusive=False, + end_inclusive=False, + ) + + def astimezone(self, tzinfo: timezone) -> "TimeInterval": + """Convert start and end time to the given timezone""" + return TimeInterval( + start=self.start.astimezone(tzinfo), + end=self.end.astimezone(tzinfo), + start_exclusive=self.start_exclusive, + end_inclusive=self.end_inclusive, + ) + + def __eq__(self, other: object) -> bool: + """Check whether two intervals are equal""" + if not isinstance(other, TimeInterval): + return False + + a, b = self.to_half_open(), other.to_half_open() + return (a.start, a.end) == (b.start, b.end) + + def __hash__(self) -> int: + """Hash the time interval""" + + # if two intervals are equal, they should have the same hash, so we convert to half-open intervals first + half_open = self.to_half_open() + return hash((half_open.start, half_open.end)) + + def __repr__(self) -> str: + return self.format() + + def format(self, endpoints: bool = True, sep: str = ", ", timespec: str = "milliseconds") -> str: + """Human readable representation of the time interval + + Args: + endpoints: Whether to format as interval using scientific interval notation [start, end]. + "[", "]" for closed intervals and "(", ")" for open intervals. + sep: The separator to use between the start and end of the interval. + timespec: Specifies the number of additional terms of the time to include. Valid options are 'auto', + 'hours', 'minutes', 'seconds', 'milliseconds' and 'microseconds'. + """ + if self == _EMPTY_TIME_INTERVAL: + return "" + + start = self.start.isoformat(timespec=timespec).replace("+00:00", " UTC") + end = self.end.isoformat(timespec=timespec).replace("+00:00", " UTC") + + formatted = f"{start}{sep}{end}" + if endpoints: + start_ch = "[" if not self.start_exclusive else "(" + end_ch = "]" if self.end_inclusive else ")" + formatted = f"{start_ch}{formatted}{end_ch}" + return formatted + + def __str__(self) -> str: + """Human readable representation of the time interval""" + return self.format() + + @classmethod + def parse(cls, arg: TimeIntervalLike) -> "TimeInterval": + """ + Convert a variety of input types to a TimeInterval. + + Supported input types: + - TimeInterval: Return the input as is + - DatetimeScalar: Return a TimeInterval with start and end time set to the same value and the end time inclusive + - tuple of two DatetimeScalar: Return a TimeInterval with start and end time + - xr.DataArray: Return a TimeInterval with start and end time set to the first and last value in the array and + the end time inclusive + - xr.Dataset: Return 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 + + Args: + arg: The input to convert + + Returns: + TimeInterval: The parsed time interval + """ + + if isinstance(arg, TimeInterval): + return arg + + if type(arg) is datetime: + dt = _convert_to_datetime(arg) + return TimeInterval(start=dt, end=dt, start_exclusive=False, end_inclusive=True) + + if isinstance(arg, list | tuple) and len(arg) == 2: + start, end = arg + return TimeInterval(start=_convert_to_datetime(start), end=_convert_to_datetime(end)) + + try: + from pandas.core.tools.datetimes import DatetimeScalar # noqa: PLC0415 + except ModuleNotFoundError as error: + if error.name != "pandas": + raise + raise ImportError( + "Parsing strings and scientific datetime values requires pandas. " + "Install pandas or pass Python datetime objects." + ) from error + + if isinstance(arg, DatetimeScalar | int): + dt = _convert_to_datetime(arg) + return TimeInterval(start=dt, end=dt, start_exclusive=False, end_inclusive=True) + + # An xarray input already has its module loaded; don't import it for other values. + xr = sys.modules.get("xarray") + + if ( + xr is not None + and isinstance(arg, xr.DataArray) + and arg.ndim == 1 + and arg.size > 0 + and arg.dtype == "datetime64[ns]" + ): + start = arg.data[0] + end = arg.data[-1] + return TimeInterval( + start=_convert_to_datetime(start), + end=_convert_to_datetime(end), + start_exclusive=False, + end_inclusive=True, + ) + + if xr is not None and isinstance(arg, xr.Dataset) and "time" in arg.coords: + return cls.parse(arg.time) + + raise ValueError(f"Failed to convert {arg} ({type(arg)}) to TimeInterval)") + + @classmethod + def from_message(cls, interval: query_pb.TimeInterval) -> Self: + """Convert a TimeInterval protobuf message to a TimeInterval object.""" + + start = timestamp_to_datetime(interval.start_time) + end = timestamp_to_datetime(interval.end_time) + if start == _EPOCH and end == _EPOCH and not interval.start_exclusive and not interval.end_inclusive: + return cls(start, end, start_exclusive=True, end_inclusive=False) + + return cls( + start=start, + end=end, + start_exclusive=interval.start_exclusive, + end_inclusive=interval.end_inclusive, + ) + + def to_message(self) -> query_pb.TimeInterval: + """Convert a TimeInterval object to a TimeInterval protobuf message.""" + return query_pb.TimeInterval( + start_time=datetime_to_timestamp(self.start), + end_time=datetime_to_timestamp(self.end), + start_exclusive=self.start_exclusive, + end_inclusive=self.end_inclusive, + ) + + +# A sentinel value to use for time interval that are empty +_EMPTY_TIME_INTERVAL = TimeInterval(_EPOCH, _EPOCH, start_exclusive=True, end_inclusive=False) diff --git a/tilebox-datasets/pyproject.toml b/tilebox-datasets/pyproject.toml index 1244751..ea0f9cf 100644 --- a/tilebox-datasets/pyproject.toml +++ b/tilebox-datasets/pyproject.toml @@ -21,15 +21,14 @@ classifiers = [ ] requires-python = ">=3.11" dependencies = [ - "tilebox-grpc>=0.28.0", + "tilebox-core>=0.67.0", + "lz4>=4", # below version pins are some minimum sane versions that we tested against to make sure they are working "numpy>=1.25", "xarray>=2024.6", "tqdm>=4.65", "pandas>=2.2.2", "shapely>=2.0.6", - "promise>=2.3", - "protobuf-py>=0.2.0", # supports integer values in floating-point fields ] [dependency-groups] @@ -52,8 +51,8 @@ packages = ["tilebox"] [tool.coverage.run] omit = [ - "tilebox/*/datasets/v1/*", - "tilebox/*/tilebox/v1/*", + "tilebox/datasets/v1/*", + "tilebox/datasets/stac/v1/*", ] [tool.pytest.ini_options] diff --git a/tilebox-datasets/tests/data/datapoint.py b/tilebox-datasets/tests/data/datapoint.py index abeea97..82c6331 100644 --- a/tilebox-datasets/tests/data/datapoint.py +++ b/tilebox-datasets/tests/data/datapoint.py @@ -28,7 +28,7 @@ uuid_messages, vec3_messages, ) -from tests.example_dataset.example_dataset_pb2 import ExampleDatapoint +from tests.example_dataset.schema import ExampleDatapoint from tests.query.pagination import paginations from tests.query.time_interval import i64_datetimes from tilebox.datasets.data.datapoint import AnyMessage, IngestResponse, QueryResultPage, RepeatedAny @@ -138,7 +138,7 @@ def anys(draw: DrawFn, generated_fields: bool = False, missing_fields: bool = Fa """A hypothesis strategy for generating random Any messages""" # we need a random byte string here, but let's actually use a valid protobuf message datapoint = draw(example_datapoints(generated_fields, missing_fields)) - return AnyMessage(example_dataset_type_url(), datapoint.SerializeToString()) + return AnyMessage(example_dataset_type_url(), datapoint.to_binary()) @composite @@ -152,7 +152,7 @@ def repeated_anys( ) else: datapoints = draw(lists(example_datapoints(generated_fields, missing_fields), min_size=1, max_size=5)) - return RepeatedAny(example_dataset_type_url(), [dp.SerializeToString() for dp in datapoints]) + return RepeatedAny(example_dataset_type_url(), [dp.to_binary() for dp in datapoints]) @composite @@ -174,7 +174,7 @@ def query_result_pages( data = draw(repeated_anys(generated_fields, missing_fields)) next_page = draw(paginations(empty_next_page)) byte_size = sum(len(m) for m in data.value) - return QueryResultPage(data, next_page, byte_size) + return QueryResultPage(data, next_page, byte_size, ExampleDatapoint) @composite diff --git a/tilebox-datasets/tests/data/datasets.py b/tilebox-datasets/tests/data/datasets.py index acf712e..c9b22ec 100644 --- a/tilebox-datasets/tests/data/datasets.py +++ b/tilebox-datasets/tests/data/datasets.py @@ -6,7 +6,6 @@ from uuid import UUID import numpy as np -from google.protobuf.descriptor_pb2 import FieldDescriptorProto, FileDescriptorProto, FileDescriptorSet from hypothesis.strategies import ( DrawFn, booleans, @@ -20,9 +19,10 @@ text, uuids, ) +from protobuf.wkt.google.protobuf.descriptor_pb import FieldDescriptorProto, FileDescriptorProto, FileDescriptorSet from shapely import Geometry -from tests.example_dataset.example_dataset_pb2 import DESCRIPTOR_PROTO +from tests.example_dataset.schema import DESCRIPTOR_PROTO from tilebox.datasets.data.datasets import ( AnnotatedType, Dataset, @@ -35,7 +35,6 @@ FieldRole, ListDatasetsResponse, ) -from tilebox.datasets.message_pool import register_once @composite @@ -105,19 +104,17 @@ def fields(draw: DrawFn) -> Field: name = draw(text(alphabet=string.ascii_lowercase + "_", min_size=3, max_size=25)) field_type = draw( one_of( - just(FieldDescriptorProto.Type.TYPE_STRING), - just(FieldDescriptorProto.Type.TYPE_BYTES), - just(FieldDescriptorProto.Type.TYPE_BOOL), - just(FieldDescriptorProto.Type.TYPE_INT64), - just(FieldDescriptorProto.Type.TYPE_UINT64), - just(FieldDescriptorProto.Type.TYPE_DOUBLE), - just(FieldDescriptorProto.Type.TYPE_MESSAGE), + just(FieldDescriptorProto.Type.STRING), + just(FieldDescriptorProto.Type.BYTES), + just(FieldDescriptorProto.Type.BOOL), + just(FieldDescriptorProto.Type.INT64), + just(FieldDescriptorProto.Type.UINT64), + just(FieldDescriptorProto.Type.DOUBLE), + just(FieldDescriptorProto.Type.MESSAGE), ) ) - type_name = f".datasets.v1.{name}" if field_type == FieldDescriptorProto.Type.TYPE_MESSAGE else None - label = draw( - one_of(just(FieldDescriptorProto.Label.LABEL_OPTIONAL), just(FieldDescriptorProto.Label.LABEL_REPEATED)) - ) + type_name = f".datasets.v1.{name}" if field_type == FieldDescriptorProto.Type.MESSAGE else None + label = draw(one_of(just(FieldDescriptorProto.Label.OPTIONAL), just(FieldDescriptorProto.Label.REPEATED))) descriptor = FieldDescriptorProto(name=name, type=field_type, type_name=type_name, label=label) annotation = draw(field_annotations()) @@ -134,7 +131,7 @@ def dataset_types(draw: DrawFn) -> DatasetType: @lru_cache def example_dataset_type() -> AnnotatedType: - descriptor = FileDescriptorProto.FromString(DESCRIPTOR_PROTO) + descriptor = FileDescriptorProto.from_binary(DESCRIPTOR_PROTO) # we deliberately change the package name to something other than the generated default, to simulate a protobuf # file that was sent from the server and is not actually available and already registered descriptor.name = "example_dynamic_dataset/v1/example_dynamic_dataset.proto" @@ -143,9 +140,7 @@ def example_dataset_type() -> AnnotatedType: descriptor_set = FileDescriptorSet(file=[descriptor]) type_url = f"{descriptor.package}.{descriptor.message_type[0].name}" - annotated_type = AnnotatedType(descriptor_set, type_url, []) - register_once(annotated_type) - return annotated_type + return AnnotatedType(descriptor_set, type_url, []) @lru_cache diff --git a/tilebox-datasets/tests/data/test_data_access.py b/tilebox-datasets/tests/data/test_data_access.py index 3b8dc40..dab7183 100644 --- a/tilebox-datasets/tests/data/test_data_access.py +++ b/tilebox-datasets/tests/data/test_data_access.py @@ -7,9 +7,9 @@ from tests.data.data_access import query_filters, spatial_filter_likes, spatial_filters from tilebox.datasets.data.data_access import QueryFilters, SpatialFilter, SpatialFilterDict, SpatialFilterMode -from tilebox.datasets.datasets.v1 import data_access_pb2 from tilebox.datasets.query import TimeInterval, field from tilebox.datasets.query.id_interval import IDInterval +from tilebox.datasets.v1 import data_access_pb @given(spatial_filters()) @@ -47,8 +47,8 @@ def test_contains_spatial_filter_mode_is_deprecated() -> None: @pytest.mark.parametrize( ("mode", "wire_mode"), [ - (SpatialFilterMode.FILTER_CONTAINS_GEOMETRY, data_access_pb2.SPATIAL_FILTER_MODE_FILTER_CONTAINS_GEOMETRY), - (SpatialFilterMode.GEOMETRY_CONTAINS_FILTER, data_access_pb2.SPATIAL_FILTER_MODE_GEOMETRY_CONTAINS_FILTER), + (SpatialFilterMode.FILTER_CONTAINS_GEOMETRY, data_access_pb.SpatialFilterMode.FILTER_CONTAINS_GEOMETRY), + (SpatialFilterMode.GEOMETRY_CONTAINS_FILTER, data_access_pb.SpatialFilterMode.GEOMETRY_CONTAINS_FILTER), ], ) def test_directional_spatial_filter_modes(mode: SpatialFilterMode, wire_mode: int) -> None: @@ -92,7 +92,8 @@ def test_multiple_wire_expressions_are_combined_with_and() -> None: 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 round_tripped.expressions[0].logical is not None + assert round_tripped.expressions[0].logical.operator == data_access_pb.LogicalOperator.AND assert len(round_tripped.expressions[0].logical.operands) == 2 @@ -109,7 +110,7 @@ def test_query_filters_reject_invalid_filter() -> None: 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()) + QueryFilters.from_message(data_access_pb.QueryFilters()) message = QueryFilters( TimeInterval( @@ -117,6 +118,6 @@ def test_query_filters_reject_invalid_interval_variants() -> None: datetime(2026, 7, 28, tzinfo=UTC), ) ).to_message() - message.datapoint_interval.CopyFrom(IDInterval(uuid4(), uuid4(), False, False).to_message()) + message.datapoint_interval = 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 2b7d9cd..9e97b78 100644 --- a/tilebox-datasets/tests/data/test_datasets.py +++ b/tilebox-datasets/tests/data/test_datasets.py @@ -1,10 +1,14 @@ +from collections.abc import Callable +from dataclasses import replace +from typing import Any, Literal from unittest.mock import MagicMock, patch from uuid import uuid4 import pytest -from google.protobuf.descriptor_pb2 import FieldDescriptorProto from hypothesis import given +from protobuf.wkt.google.protobuf.descriptor_pb import FieldDescriptorProto, FileDescriptorProto, FileDescriptorSet +from _tilebox.errors import NotFoundError from tests.data.datasets import ( annotated_types, dataset_groups, @@ -15,6 +19,10 @@ fields, list_datasets_responses, ) +from tests.example_dataset.schema import DESCRIPTOR +from tilebox.datasets import Client +from tilebox.datasets.data.collection import CollectionInfo +from tilebox.datasets.data.data_access import SpatialFilter from tilebox.datasets.data.datasets import ( AnnotatedType, Dataset, @@ -27,8 +35,9 @@ FieldRole, ListDatasetsResponse, ) +from tilebox.datasets.message_pool import get_message_type from tilebox.datasets.schema import Assets, Authentication, Links, ProcessingSoftware, Provider, Storage -from tilebox.datasets.service import TileboxDatasetService +from tilebox.datasets.v1 import core_pb, data_access_pb, dataset_type_pb @given(field_annotations()) @@ -36,16 +45,50 @@ def test_field_annotations_to_message_and_back(annotation: FieldAnnotation) -> N assert FieldAnnotation.from_message(annotation.to_message()) == annotation +def test_schema_cache_reuses_content_without_conflating_same_named_types() -> None: + """Reuse equivalent schemas while isolating different descriptors with the same message name.""" + schema = AnnotatedType(FileDescriptorSet(file=[DESCRIPTOR]), "tests.scientific.Datapoint", []) + original = get_message_type(schema) + equivalent = AnnotatedType.from_message(schema.to_message()) + assert get_message_type(equivalent) is original + assert get_message_type(replace(schema, type_url="type.googleapis.com/tests.scientific.Datapoint")) is original + + changed = FileDescriptorProto.from_binary(DESCRIPTOR.to_binary()) + changed.message_type[0].field[4].type = FieldDescriptorProto.Type.INT64 + other = get_message_type(replace(schema, descriptor_set=FileDescriptorSet(file=[changed]))) + assert other is not original + field_name = changed.message_type[0].field[4].name + assert getattr(original.from_binary(original(some_string="value").to_binary()), field_name) == "value" + assert getattr(other.from_binary(other(some_string=42).to_binary()), field_name) == 42 + + +@pytest.mark.parametrize( + ("convert", "message", "error"), + [ + (Field.from_message, dataset_type_pb.Field(), "missing its descriptor"), + (AnnotatedType.from_message, dataset_type_pb.AnnotatedType(), "missing its descriptor set"), + (Dataset.from_message, core_pb.Dataset(), "missing its type"), + (CollectionInfo.from_message, core_pb.CollectionInfo(), "missing its collection"), + (SpatialFilter.from_message, data_access_pb.SpatialFilter(), "missing its geometry"), + ], +) +def test_required_nested_messages_are_rejected(convert: Callable[[Any], Any], message: Any, error: str) -> None: + """Reject incomplete responses with a useful error instead of dereferencing absent messages.""" + with pytest.raises(ValueError, match=error): + convert(message) + + @pytest.mark.parametrize("attribute", ["source_json_pointer", "json_schema_ref"]) -def test_optional_field_annotation_presence(attribute: str) -> None: +def test_optional_field_annotation_presence(attribute: Literal["source_json_pointer", "json_schema_ref"]) -> None: absent = FieldAnnotation("description", "example").to_message() - assert not absent.HasField(attribute) + assert not absent.has_field(attribute) if attribute == "source_json_pointer": present = FieldAnnotation("description", "example", source_json_pointer="").to_message() else: present = FieldAnnotation("description", "example", json_schema_ref="").to_message() - assert present.HasField(attribute) + assert present.has_field(attribute) + assert getattr(present, attribute) == "" @given(field_dicts()) @@ -78,11 +121,11 @@ def test_message_types_can_define_dataset_fields(message_type: type, message_nam scalar = Field.from_dict({"name": "metadata", "type": message_type}) repeated = Field.from_dict({"name": "metadata", "type": list[message_type]}) # ty: ignore[invalid-type-form] - assert scalar.descriptor.type == FieldDescriptorProto.TYPE_MESSAGE + assert scalar.descriptor.type == FieldDescriptorProto.Type.MESSAGE assert scalar.descriptor.type_name == f".{message_name}" - assert scalar.descriptor.label == FieldDescriptorProto.LABEL_OPTIONAL + assert scalar.descriptor.label == FieldDescriptorProto.Label.OPTIONAL assert repeated.descriptor.type_name == f".{message_name}" - assert repeated.descriptor.label == FieldDescriptorProto.LABEL_REPEATED + assert repeated.descriptor.label == FieldDescriptorProto.Label.REPEATED @given(fields()) @@ -117,8 +160,10 @@ def test_list_datasets_responses_to_message_and_back(response: ListDatasetsRespo @pytest.mark.parametrize("operation", ["create", "update"]) def test_create_and_update_dataset_include_field_annotations(operation: str) -> None: + """Public dataset writes send required and custom field annotations to native clients.""" dataset_service = MagicMock() - service = TileboxDatasetService(dataset_service, MagicMock(), MagicMock(), MagicMock()) + if operation == "create": + dataset_service.get_dataset.side_effect = NotFoundError custom_fields: list[FieldDict] = [ { "name": "title", @@ -130,21 +175,23 @@ def test_create_and_update_dataset_include_field_annotations(operation: str) -> } ] - with patch.object(Dataset, "from_message", side_effect=lambda message: message): - if operation == "create": - service.create_dataset(DatasetKind.SPATIOTEMPORAL, "example", "Example", custom_fields).get() - request = dataset_service.CreateDataset.call_args.args[0] - else: - service.update_dataset(DatasetKind.SPATIOTEMPORAL, uuid4(), "Example", custom_fields).get() - request = dataset_service.UpdateDataset.call_args.args[0] + with ( + patch("tilebox.datasets.sync.client.Connection") as connection, + patch("tilebox.datasets.sync.client.DatasetClient"), + patch.object(Dataset, "from_message", return_value=MagicMock(id=uuid4(), name="Example")), + ): + connection.return_value.client.return_value = dataset_service + with Client(url="http://localhost", token="test") as client: # noqa: S106 + client.create_or_update_dataset(DatasetKind.SPATIOTEMPORAL, "example", custom_fields, name="Example") + request = getattr(dataset_service, f"{operation}_dataset").call_args.args[0] 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 not annotations["id"].HasField("json_schema_ref") + assert not annotations["id"].has_field("json_schema_ref") assert annotations["ingestion_time"].source_json_pointer == "/properties/tilebox:ingestion_time" - assert not annotations["ingestion_time"].HasField("json_schema_ref") + assert not annotations["ingestion_time"].has_field("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/data/well_known_types.py b/tilebox-datasets/tests/data/well_known_types.py index efd063a..d64e940 100644 --- a/tilebox-datasets/tests/data/well_known_types.py +++ b/tilebox-datasets/tests/data/well_known_types.py @@ -1,4 +1,3 @@ -from google.protobuf import duration_pb2, timestamp_pb2 from hypothesis.strategies import ( DrawFn, booleans, @@ -8,34 +7,35 @@ sampled_from, uuids, ) +from protobuf.wkt.google.protobuf import duration_pb, timestamp_pb from shapely import MultiPolygon, Polygon, box, to_wkb from tests.query.time_interval import i64_datetimes -from tilebox.datasets.datasets.v1 import well_known_types_pb2 from tilebox.datasets.query.time_interval import datetime_to_timestamp +from tilebox.datasets.v1 import well_known_types_pb @composite -def datetime_messages(draw: DrawFn) -> timestamp_pb2.Timestamp: +def datetime_messages(draw: DrawFn) -> timestamp_pb.Timestamp: return datetime_to_timestamp(draw(i64_datetimes)) @composite -def duration_messages(draw: DrawFn) -> duration_pb2.Duration: - return duration_pb2.Duration( +def duration_messages(draw: DrawFn) -> duration_pb.Duration: + return duration_pb.Duration( seconds=draw(integers(min_value=0, max_value=1000000000)), nanos=draw(integers(min_value=0, max_value=1000000000 - 1)), ) @composite -def uuid_messages(draw: DrawFn) -> well_known_types_pb2.UUID: - return well_known_types_pb2.UUID(uuid=draw(uuids(version=4)).bytes) +def uuid_messages(draw: DrawFn) -> well_known_types_pb.UUID: + return well_known_types_pb.UUID(uuid=draw(uuids(version=4)).bytes) @composite -def vec3_messages(draw: DrawFn) -> well_known_types_pb2.Vec3: - return well_known_types_pb2.Vec3( +def vec3_messages(draw: DrawFn) -> well_known_types_pb.Vec3: + return well_known_types_pb.Vec3( x=draw(floats(min_value=-100, max_value=100)), y=draw(floats(min_value=-100, max_value=100)), z=draw(floats(min_value=-100, max_value=100)), @@ -43,8 +43,8 @@ def vec3_messages(draw: DrawFn) -> well_known_types_pb2.Vec3: @composite -def quaternion_messages(draw: DrawFn) -> well_known_types_pb2.Quaternion: - return well_known_types_pb2.Quaternion( +def quaternion_messages(draw: DrawFn) -> well_known_types_pb.Quaternion: + return well_known_types_pb.Quaternion( q1=draw(floats(min_value=-1, max_value=1)), q2=draw(floats(min_value=-1, max_value=1)), q3=draw(floats(min_value=-1, max_value=1)), @@ -53,16 +53,16 @@ def quaternion_messages(draw: DrawFn) -> well_known_types_pb2.Quaternion: @composite -def latlon_messages(draw: DrawFn) -> well_known_types_pb2.LatLon: - return well_known_types_pb2.LatLon( +def latlon_messages(draw: DrawFn) -> well_known_types_pb.LatLon: + return well_known_types_pb.LatLon( latitude=draw(floats(min_value=-90, max_value=90)), longitude=draw(floats(min_value=-180, max_value=180)), ) @composite -def latlonalt_messages(draw: DrawFn) -> well_known_types_pb2.LatLonAlt: - return well_known_types_pb2.LatLonAlt( +def latlonalt_messages(draw: DrawFn) -> well_known_types_pb.LatLonAlt: + return well_known_types_pb.LatLonAlt( latitude=draw(floats(min_value=-90, max_value=90)), longitude=draw(floats(min_value=-180, max_value=180)), altitude=draw(floats(min_value=-100, max_value=1000)), @@ -81,24 +81,24 @@ def shapely_polygons(draw: DrawFn) -> Polygon: @composite -def geometry_messages(draw: DrawFn) -> well_known_types_pb2.Geometry: +def geometry_messages(draw: DrawFn) -> well_known_types_pb.Geometry: is_multi = draw(booleans()) geom = draw(shapely_polygons()) if is_multi: other = draw(shapely_polygons()) geom = MultiPolygon([geom, other]) - return well_known_types_pb2.Geometry(wkb=to_wkb(geom)) + return well_known_types_pb.Geometry(wkb=to_wkb(geom)) @composite -def processing_levels(draw: DrawFn) -> well_known_types_pb2.ProcessingLevel: +def processing_levels(draw: DrawFn) -> well_known_types_pb.ProcessingLevel: return draw( sampled_from( [ - well_known_types_pb2.ProcessingLevel.PROCESSING_LEVEL_L0, - well_known_types_pb2.ProcessingLevel.PROCESSING_LEVEL_L1, - well_known_types_pb2.ProcessingLevel.PROCESSING_LEVEL_L1A, + well_known_types_pb.ProcessingLevel.L0, + well_known_types_pb.ProcessingLevel.L1, + well_known_types_pb.ProcessingLevel.L1A, ] ) ) diff --git a/tilebox-datasets/tests/example_dataset/example_dataset_pb2.py b/tilebox-datasets/tests/example_dataset/example_dataset_pb2.py deleted file mode 100644 index 43686d1..0000000 --- a/tilebox-datasets/tests/example_dataset/example_dataset_pb2.py +++ /dev/null @@ -1,46 +0,0 @@ -# fmt: off -# ruff: isort: skip_file -# ruff: noqa: ERA001, F401, E402 -# -*- coding: utf-8 -*- -# Generated by the protocol buffer compiler. DO NOT EDIT! -# NO CHECKED-IN PROTOBUF GENCODE -# source: datasets/v1/example_dataset.proto -# Protobuf Python Version: 6.30.0 -"""Generated protocol buffer code.""" -from google.protobuf import descriptor as _descriptor -from google.protobuf import descriptor_pool as _descriptor_pool -from google.protobuf import runtime_version as _runtime_version -from google.protobuf import symbol_database as _symbol_database -from google.protobuf.internal import builder as _builder -_runtime_version.ValidateProtobufRuntimeVersion( - _runtime_version.Domain.PUBLIC, - 6, - 30, - 0, - '', - 'datasets/v1/example_dataset.proto' -) -# @@protoc_insertion_point(imports) - -_sym_db = _symbol_database.Default() - - -from tilebox.datasets.datasets.v1 import well_known_types_pb2 as datasets_dot_v1_dot_well__known__types__pb2 -from google.protobuf import duration_pb2 as google_dot_protobuf_dot_duration__pb2 -from google.protobuf import timestamp_pb2 as google_dot_protobuf_dot_timestamp__pb2 - -DESCRIPTOR_PROTO = b'\n!datasets/v1/example_dataset.proto\x12\x0b\x64\x61tasets.v1\x1a\"datasets/v1/well_known_types.proto\x1a\x1egoogle/protobuf/duration.proto\x1a\x1fgoogle/protobuf/timestamp.proto\"\xf6\n\n\x10\x45xampleDatapoint\x12.\n\x04time\x18\x01 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\x04time\x12!\n\x02id\x18\x02 \x01(\x0b\x32\x11.datasets.v1.UUIDR\x02id\x12\x41\n\x0eingestion_time\x18\x03 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\ringestionTime\x12\x31\n\x08geometry\x18\x04 \x01(\x0b\x32\x15.datasets.v1.GeometryR\x08geometry\x12\x1f\n\x0bsome_string\x18\x05 \x01(\tR\nsomeString\x12\x19\n\x08some_int\x18\x06 \x01(\x03R\x07someInt\x12\x1f\n\x0bsome_double\x18\x07 \x01(\x01R\nsomeDouble\x12\x37\n\tsome_time\x18\x08 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\x08someTime\x12>\n\rsome_duration\x18\t \x01(\x0b\x32\x19.google.protobuf.DurationR\x0csomeDuration\x12\x1d\n\nsome_bytes\x18\n \x01(\x0cR\tsomeBytes\x12\x1b\n\tsome_bool\x18\x0b \x01(\x08R\x08someBool\x12:\n\x0fsome_identifier\x18\x0c \x01(\x0b\x32\x11.datasets.v1.UUIDR\x0esomeIdentifier\x12.\n\tsome_vec3\x18\r \x01(\x0b\x32\x11.datasets.v1.Vec3R\x08someVec3\x12@\n\x0fsome_quaternion\x18\x0e \x01(\x0b\x32\x17.datasets.v1.QuaternionR\x0esomeQuaternion\x12:\n\rsome_geometry\x18\x0f \x01(\x0b\x32\x15.datasets.v1.GeometryR\x0csomeGeometry\x12\x39\n\tsome_enum\x18\x10 \x01(\x0e\x32\x1c.datasets.v1.ProcessingLevelR\x08someEnum\x12\x30\n\x14some_repeated_string\x18\x11 \x03(\tR\x12someRepeatedString\x12*\n\x11some_repeated_int\x18\x12 \x03(\x03R\x0fsomeRepeatedInt\x12\x30\n\x14some_repeated_double\x18\x13 \x03(\x01R\x12someRepeatedDouble\x12.\n\x13some_repeated_bytes\x18\x14 \x03(\x0cR\x11someRepeatedBytes\x12,\n\x12some_repeated_bool\x18\x15 \x03(\x08R\x10someRepeatedBool\x12H\n\x12some_repeated_time\x18\x16 \x03(\x0b\x32\x1a.google.protobuf.TimestampR\x10someRepeatedTime\x12O\n\x16some_repeated_duration\x18\x17 \x03(\x0b\x32\x19.google.protobuf.DurationR\x14someRepeatedDuration\x12K\n\x18some_repeated_identifier\x18\x18 \x03(\x0b\x32\x11.datasets.v1.UUIDR\x16someRepeatedIdentifier\x12?\n\x12some_repeated_vec3\x18\x19 \x03(\x0b\x32\x11.datasets.v1.Vec3R\x10someRepeatedVec3\x12K\n\x16some_repeated_geometry\x18\x1a \x03(\x0b\x32\x15.datasets.v1.GeometryR\x14someRepeatedGeometryB\x05\x92\x03\x02\x08\x02\x62\x08\x65\x64itionsp\xe8\x07' -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(DESCRIPTOR_PROTO) - -_globals = globals() -_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) -_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'datasets.v1.example_dataset_pb2', _globals) -if not _descriptor._USE_C_DESCRIPTORS: - _globals['DESCRIPTOR']._loaded_options = None - _globals['DESCRIPTOR']._serialized_options = b'\222\003\002\010\002' - _globals['_EXAMPLEDATAPOINT']._serialized_start=152 - _globals['_EXAMPLEDATAPOINT']._serialized_end=1550 -# @@protoc_insertion_point(module_scope) - - -# fmt: on diff --git a/tilebox-datasets/tests/example_dataset/example_dataset_pb2.pyi b/tilebox-datasets/tests/example_dataset/example_dataset_pb2.pyi deleted file mode 100644 index d4aa089..0000000 --- a/tilebox-datasets/tests/example_dataset/example_dataset_pb2.pyi +++ /dev/null @@ -1,125 +0,0 @@ -from collections.abc import Iterable as _Iterable -from collections.abc import Mapping as _Mapping -from typing import ClassVar as _ClassVar - -from google.protobuf import descriptor as _descriptor -from google.protobuf import duration_pb2 as _duration_pb2 -from google.protobuf import message as _message -from google.protobuf import timestamp_pb2 as _timestamp_pb2 -from google.protobuf.internal import containers as _containers - -from tilebox.datasets.datasets.v1 import well_known_types_pb2 as _well_known_types_pb2 - -DESCRIPTOR_PROTO: bytes -DESCRIPTOR: _descriptor.FileDescriptor - -class ExampleDatapoint(_message.Message): - __slots__ = ( - "geometry", - "id", - "ingestion_time", - "some_bool", - "some_bytes", - "some_double", - "some_duration", - "some_enum", - "some_geometry", - "some_identifier", - "some_int", - "some_quaternion", - "some_repeated_bool", - "some_repeated_bytes", - "some_repeated_double", - "some_repeated_duration", - "some_repeated_geometry", - "some_repeated_identifier", - "some_repeated_int", - "some_repeated_string", - "some_repeated_time", - "some_repeated_vec3", - "some_string", - "some_time", - "some_vec3", - "time", - ) - TIME_FIELD_NUMBER: _ClassVar[int] - ID_FIELD_NUMBER: _ClassVar[int] - INGESTION_TIME_FIELD_NUMBER: _ClassVar[int] - GEOMETRY_FIELD_NUMBER: _ClassVar[int] - SOME_STRING_FIELD_NUMBER: _ClassVar[int] - SOME_INT_FIELD_NUMBER: _ClassVar[int] - SOME_DOUBLE_FIELD_NUMBER: _ClassVar[int] - SOME_TIME_FIELD_NUMBER: _ClassVar[int] - SOME_DURATION_FIELD_NUMBER: _ClassVar[int] - SOME_BYTES_FIELD_NUMBER: _ClassVar[int] - SOME_BOOL_FIELD_NUMBER: _ClassVar[int] - SOME_IDENTIFIER_FIELD_NUMBER: _ClassVar[int] - SOME_VEC3_FIELD_NUMBER: _ClassVar[int] - SOME_QUATERNION_FIELD_NUMBER: _ClassVar[int] - SOME_GEOMETRY_FIELD_NUMBER: _ClassVar[int] - SOME_ENUM_FIELD_NUMBER: _ClassVar[int] - SOME_REPEATED_STRING_FIELD_NUMBER: _ClassVar[int] - SOME_REPEATED_INT_FIELD_NUMBER: _ClassVar[int] - SOME_REPEATED_DOUBLE_FIELD_NUMBER: _ClassVar[int] - SOME_REPEATED_BYTES_FIELD_NUMBER: _ClassVar[int] - SOME_REPEATED_BOOL_FIELD_NUMBER: _ClassVar[int] - SOME_REPEATED_TIME_FIELD_NUMBER: _ClassVar[int] - SOME_REPEATED_DURATION_FIELD_NUMBER: _ClassVar[int] - SOME_REPEATED_IDENTIFIER_FIELD_NUMBER: _ClassVar[int] - SOME_REPEATED_VEC3_FIELD_NUMBER: _ClassVar[int] - SOME_REPEATED_GEOMETRY_FIELD_NUMBER: _ClassVar[int] - time: _timestamp_pb2.Timestamp - id: _well_known_types_pb2.UUID - ingestion_time: _timestamp_pb2.Timestamp - geometry: _well_known_types_pb2.Geometry - some_string: str - some_int: int - some_double: float - some_time: _timestamp_pb2.Timestamp - some_duration: _duration_pb2.Duration - some_bytes: bytes - some_bool: bool - some_identifier: _well_known_types_pb2.UUID - some_vec3: _well_known_types_pb2.Vec3 - some_quaternion: _well_known_types_pb2.Quaternion - some_geometry: _well_known_types_pb2.Geometry - some_enum: _well_known_types_pb2.ProcessingLevel - some_repeated_string: _containers.RepeatedScalarFieldContainer[str] - some_repeated_int: _containers.RepeatedScalarFieldContainer[int] - some_repeated_double: _containers.RepeatedScalarFieldContainer[float] - some_repeated_bytes: _containers.RepeatedScalarFieldContainer[bytes] - some_repeated_bool: _containers.RepeatedScalarFieldContainer[bool] - some_repeated_time: _containers.RepeatedCompositeFieldContainer[_timestamp_pb2.Timestamp] - some_repeated_duration: _containers.RepeatedCompositeFieldContainer[_duration_pb2.Duration] - some_repeated_identifier: _containers.RepeatedCompositeFieldContainer[_well_known_types_pb2.UUID] - some_repeated_vec3: _containers.RepeatedCompositeFieldContainer[_well_known_types_pb2.Vec3] - some_repeated_geometry: _containers.RepeatedCompositeFieldContainer[_well_known_types_pb2.Geometry] - def __init__( - self, - time: _timestamp_pb2.Timestamp | _Mapping | None = ..., - id: _well_known_types_pb2.UUID | _Mapping | None = ..., # noqa: A002 - ingestion_time: _timestamp_pb2.Timestamp | _Mapping | None = ..., - geometry: _well_known_types_pb2.Geometry | _Mapping | None = ..., - some_string: str | None = ..., - some_int: int | None = ..., - some_double: float | None = ..., - some_time: _timestamp_pb2.Timestamp | _Mapping | None = ..., - some_duration: _duration_pb2.Duration | _Mapping | None = ..., - some_bytes: bytes | None = ..., - some_bool: bool = ..., - some_identifier: _well_known_types_pb2.UUID | _Mapping | None = ..., - some_vec3: _well_known_types_pb2.Vec3 | _Mapping | None = ..., - some_quaternion: _well_known_types_pb2.Quaternion | _Mapping | None = ..., - some_geometry: _well_known_types_pb2.Geometry | _Mapping | None = ..., - some_enum: _well_known_types_pb2.ProcessingLevel | str | None = ..., - some_repeated_string: _Iterable[str] | None = ..., - some_repeated_int: _Iterable[int] | None = ..., - some_repeated_double: _Iterable[float] | None = ..., - some_repeated_bytes: _Iterable[bytes] | None = ..., - some_repeated_bool: _Iterable[bool] | None = ..., - some_repeated_time: _Iterable[_timestamp_pb2.Timestamp | _Mapping] | None = ..., - some_repeated_duration: _Iterable[_duration_pb2.Duration | _Mapping] | None = ..., - some_repeated_identifier: _Iterable[_well_known_types_pb2.UUID | _Mapping] | None = ..., - some_repeated_vec3: _Iterable[_well_known_types_pb2.Vec3 | _Mapping] | None = ..., - some_repeated_geometry: _Iterable[_well_known_types_pb2.Geometry | _Mapping] | None = ..., - ) -> None: ... diff --git a/tilebox-datasets/tests/example_dataset/schema.py b/tilebox-datasets/tests/example_dataset/schema.py new file mode 100644 index 0000000..70a3d50 --- /dev/null +++ b/tilebox-datasets/tests/example_dataset/schema.py @@ -0,0 +1,86 @@ +"""Native descriptors for scientific conversion properties.""" + +from typing import TYPE_CHECKING, Any + +from protobuf.wkt.google.protobuf.descriptor_pb import ( + DescriptorProto, + Edition, + FieldDescriptorProto, + FileDescriptorProto, + FileDescriptorSet, +) + +from tilebox.datasets.data.datasets import AnnotatedType +from tilebox.datasets.message_pool import get_message_type + +_T = FieldDescriptorProto.Type +_L = FieldDescriptorProto.Label +_FIELDS = [ + ("time", _T.MESSAGE, ".google.protobuf.Timestamp"), + ("id", _T.MESSAGE, ".datasets.v1.UUID"), + ("ingestion_time", _T.MESSAGE, ".google.protobuf.Timestamp"), + ("geometry", _T.MESSAGE, ".datasets.v1.Geometry"), + ("some_string", _T.STRING, None), + ("some_int", _T.INT64, None), + ("some_double", _T.DOUBLE, None), + ("some_time", _T.MESSAGE, ".google.protobuf.Timestamp"), + ("some_duration", _T.MESSAGE, ".google.protobuf.Duration"), + ("some_bytes", _T.BYTES, None), + ("some_bool", _T.BOOL, None), + ("some_identifier", _T.MESSAGE, ".datasets.v1.UUID"), + ("some_vec3", _T.MESSAGE, ".datasets.v1.Vec3"), + ("some_quaternion", _T.MESSAGE, ".datasets.v1.Quaternion"), + ("some_geometry", _T.MESSAGE, ".datasets.v1.Geometry"), + ("some_enum", _T.ENUM, ".datasets.v1.ProcessingLevel"), + ("some_repeated_string", _T.STRING, None), + ("some_repeated_int", _T.INT64, None), + ("some_repeated_double", _T.DOUBLE, None), + ("some_repeated_bytes", _T.BYTES, None), + ("some_repeated_bool", _T.BOOL, None), + ("some_repeated_time", _T.MESSAGE, ".google.protobuf.Timestamp"), + ("some_repeated_duration", _T.MESSAGE, ".google.protobuf.Duration"), + ("some_repeated_identifier", _T.MESSAGE, ".datasets.v1.UUID"), + ("some_repeated_vec3", _T.MESSAGE, ".datasets.v1.Vec3"), + ("some_repeated_geometry", _T.MESSAGE, ".datasets.v1.Geometry"), +] + +DESCRIPTOR = FileDescriptorProto( + name="tests/example_dataset.proto", + package="tests.scientific", + syntax="editions", + edition=Edition.EDITION_2023, + dependency=[ + "datasets/v1/well_known_types.proto", + "google/protobuf/timestamp.proto", + "google/protobuf/duration.proto", + ], + message_type=[ + DescriptorProto( + name="Datapoint", + field=[ + FieldDescriptorProto( + name=name, + number=index, + type=kind, + type_name=type_name, + label=_L.REPEATED if "repeated" in name else _L.OPTIONAL, + ) + for index, (name, kind, type_name) in enumerate(_FIELDS, start=1) + ], + ) + ], +) +DESCRIPTOR_PROTO = DESCRIPTOR.to_binary() +if TYPE_CHECKING: + from protobuf import Message + + class ExampleDatapoint(Message): + """Static shape for the descriptor-defined scientific fixture.""" + + def __init__(self, **fields: Any) -> None: ... + + def __getattr__(self, name: str) -> Any: ... +else: + ExampleDatapoint = get_message_type( + AnnotatedType(FileDescriptorSet(file=[DESCRIPTOR]), "tests.scientific.Datapoint", []) + ) diff --git a/tilebox-datasets/tests/protobuf_conversion/test_protobuf_xarray.py b/tilebox-datasets/tests/protobuf_conversion/test_protobuf_xarray.py index bb4bf47..807fea0 100644 --- a/tilebox-datasets/tests/protobuf_conversion/test_protobuf_xarray.py +++ b/tilebox-datasets/tests/protobuf_conversion/test_protobuf_xarray.py @@ -1,43 +1,124 @@ from enum import Enum from uuid import UUID +import numpy as np 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 from pandas import to_datetime +from protobuf import Oneof +from protobuf.wkt.google.protobuf.descriptor_pb import ( + DescriptorProto, + EnumDescriptorProto, + EnumValueDescriptorProto, + FieldDescriptorProto, + FileDescriptorProto, + FileDescriptorSet, + OneofDescriptorProto, +) from shapely import MultiPolygon, Polygon, from_wkb from xarray.testing import assert_equal from tests.data.datapoint import example_datapoints -from tests.example_dataset.example_dataset_pb2 import ExampleDatapoint -from tilebox.datasets.datasets.stac.v1.asset_pb import Assets -from tilebox.datasets.datasets.stac.v1.asset_pb2 import Asset as AssetPB2 -from tilebox.datasets.datasets.stac.v1.asset_pb2 import Assets as AssetsPB2 -from tilebox.datasets.datasets.stac.v1.authentication_pb import Authentication -from tilebox.datasets.datasets.stac.v1.authentication_pb2 import Authentication as AuthenticationPB2 -from tilebox.datasets.datasets.stac.v1.core_pb import Links, Provider -from tilebox.datasets.datasets.stac.v1.core_pb2 import Link as LinkPB2 -from tilebox.datasets.datasets.stac.v1.core_pb2 import Links as LinksPB2 -from tilebox.datasets.datasets.stac.v1.core_pb2 import Provider as ProviderPB2 -from tilebox.datasets.datasets.stac.v1.processing_pb import ProcessingSoftware -from tilebox.datasets.datasets.stac.v1.processing_pb2 import ProcessingSoftware as ProcessingSoftwarePB2 -from tilebox.datasets.datasets.stac.v1.sar_pb2 import ( - SAR_POLARIZATION_HH, - SAR_POLARIZATION_VV, - SARProperties, -) -from tilebox.datasets.datasets.stac.v1.storage_pb import Storage -from tilebox.datasets.datasets.stac.v1.storage_pb2 import Storage as StoragePB2 -from tilebox.datasets.datasets.v1.well_known_types_pb2 import ProcessingLevel +from tests.example_dataset.schema import DESCRIPTOR, ExampleDatapoint +from tilebox.datasets.data.datasets import AnnotatedType +from tilebox.datasets.message_pool import get_message_type from tilebox.datasets.protobuf_conversion.field_types import _AssetsDisplay from tilebox.datasets.protobuf_conversion.protobuf_xarray import MessageToXarrayConverter from tilebox.datasets.protobuf_conversion.to_protobuf import to_messages from tilebox.datasets.query.time_interval import timestamp_to_datetime, us_to_datetime +from tilebox.datasets.stac.v1.asset_pb import Asset, Assets +from tilebox.datasets.stac.v1.authentication_pb import Authentication +from tilebox.datasets.stac.v1.core_pb import Link, Links, Provider +from tilebox.datasets.stac.v1.processing_pb import ProcessingSoftware +from tilebox.datasets.stac.v1.sar_pb import ( + SARPolarization, + SARProperties, +) +from tilebox.datasets.stac.v1.storage_pb import Storage +from tilebox.datasets.v1 import well_known_types_pb +from tilebox.datasets.v1.well_known_types_pb import ProcessingLevel + + +def test_projected_batches_preserve_scalar_and_oneof_presence() -> None: + """Keep explicit zero and false values through projection, batching, and buffer growth.""" + schema = FileDescriptorProto( + name="tests/presence.proto", + package="tests.presence", + syntax="proto2", + message_type=[ + DescriptorProto( + name="Row", + oneof_decl=[OneofDescriptorProto(name="choice")], + field=[ + FieldDescriptorProto( + name=name, + number=number, + label=FieldDescriptorProto.Label.OPTIONAL, + type=kind, + oneof_index=0 if name in {"count", "enabled"} else None, + ) + for number, (name, kind) in enumerate( + [ + ("score", FieldDescriptorProto.Type.DOUBLE), + ("count", FieldDescriptorProto.Type.DOUBLE), + ("enabled", FieldDescriptorProto.Type.BOOL), + ("excluded", FieldDescriptorProto.Type.STRING), + ("never", FieldDescriptorProto.Type.DOUBLE), + ], + start=1, + ) + ], + ) + ], + ) + row = get_message_type(AnnotatedType(FileDescriptorSet(file=[schema]), "tests.presence.Row", [])) + messages = [ + row(excluded="not projected"), + row(score=0.0, choice=Oneof("count", 0.0)), + row(score=7.0, choice=Oneof("enabled", False)), + ] + converter = MessageToXarrayConverter(initial_capacity=1, fields=("score", "count", "enabled", "never")) + converter.convert(messages[0]) + converter.convert_all(messages[1:]) + result = converter.finalize("row", skip_empty_fields=True) + + assert set(result.data_vars) == {"score", "count", "enabled"} + assert_array_equal(result["score"], [np.nan, 0.0, 7.0]) + assert_array_equal(result["count"], [np.nan, 0.0, np.nan]) + assert_array_equal(result["enabled"], [False, False, False]) + + +@pytest.mark.parametrize("nanos", [-1_000_000_001, -1, 0, 1_000_000_001]) +def test_supplied_imports_preserve_nanoseconds_and_message_values(nanos: int) -> None: + """Convert isolated nested types and preserve exact signed nanoseconds through xarray ingestion.""" + schema = AnnotatedType( + FileDescriptorSet(file=[well_known_types_pb.desc().proto, DESCRIPTOR]), + "tests.scientific.Datapoint", + [], + ) + message_type = get_message_type(schema) + messages = to_messages( + [ + { + "time": np.datetime64(nanos, "ns"), + "some_duration": np.timedelta64(nanos, "ns"), + "some_identifier": UUID(int=1), + "some_vec3": (1.0, 2.0, 3.0), + "some_repeated_vec3": [(1.0, 2.0, 3.0)], + } + ], + message_type, + ) + messages = [message_type.from_binary(message.to_binary()) for message in messages] + converter = MessageToXarrayConverter() + converter.convert_all(messages) + dataset = converter.finalize("time", skip_empty_fields=True) + assert dataset.time.item() == nanos + assert dataset.some_duration.item() == nanos + assert to_messages(dataset, message_type) == messages @given(example_datapoints(generated_fields=True, missing_fields=False)) @@ -94,7 +175,7 @@ def test_convert_datapoint(datapoint: ExampleDatapoint) -> None: # noqa: PLR091 ) assert isinstance(dataset.some_geometry.item(), Polygon | MultiPolygon) - assert dataset.some_enum.item() == ProcessingLevel.Name(datapoint.some_enum).removeprefix("PROCESSING_LEVEL_") + assert dataset.some_enum.item() == ProcessingLevel(datapoint.some_enum).name assert list(dataset.some_repeated_string.to_numpy()) == list(datapoint.some_repeated_string) assert_array_equal(dataset.some_repeated_int.to_numpy(), datapoint.some_repeated_int) @@ -126,18 +207,18 @@ def test_convert_datapoint(datapoint: ExampleDatapoint) -> None: # noqa: PLR091 def test_convert_stac_messages_to_protobuf_py() -> None: message_types = { - "assets": ("datasets.stac.v1.Assets", AssetsPB2(assets=[AssetPB2(key="preview")]), _AssetsDisplay), - "authentication": ("datasets.stac.v1.Authentication", AuthenticationPB2(), Authentication), - "links": ("datasets.stac.v1.Links", LinksPB2(links=[LinkPB2(href="https://example.com")]), Links), - "provider": ("datasets.stac.v1.Provider", ProviderPB2(name="Tilebox"), Provider), + "assets": ("datasets.stac.v1.Assets", Assets(assets=[Asset(key="preview")]), _AssetsDisplay), + "authentication": ("datasets.stac.v1.Authentication", Authentication(), Authentication), + "links": ("datasets.stac.v1.Links", Links(links=[Link(href="https://example.com")]), Links), + "provider": ("datasets.stac.v1.Provider", Provider(name="Tilebox"), Provider), "processing_software": ( "datasets.stac.v1.ProcessingSoftware", - ProcessingSoftwarePB2(versions={"processor": "1.0"}), + ProcessingSoftware(versions={"processor": "1.0"}), ProcessingSoftware, ), - "storage": ("datasets.stac.v1.Storage", StoragePB2(), Storage), + "storage": ("datasets.stac.v1.Storage", Storage(), Storage), } - file_descriptor = descriptor_pb2.FileDescriptorProto( + file_descriptor = FileDescriptorProto( name="tests/protobuf_conversion/stac_datapoint.proto", package="tests.protobuf_conversion", dependency=[ @@ -148,24 +229,30 @@ def test_convert_stac_messages_to_protobuf_py() -> None: "datasets/stac/v1/storage.proto", ], ) - message_descriptor = file_descriptor.message_type.add(name="StacDatapoint") + message_descriptor = DescriptorProto(name="StacDatapoint") + file_descriptor.message_type.append(message_descriptor) for index, (field_name, (message_name, _, _)) in enumerate(message_types.items()): - message_descriptor.field.add( - name=field_name, - number=index * 2 + 1, - label=descriptor_pb2.FieldDescriptorProto.LABEL_OPTIONAL, - type=descriptor_pb2.FieldDescriptorProto.TYPE_MESSAGE, - type_name=f".{message_name}", + message_descriptor.field.append( + FieldDescriptorProto( + name=field_name, + number=index * 2 + 1, + label=FieldDescriptorProto.Label.OPTIONAL, + type=FieldDescriptorProto.Type.MESSAGE, + type_name=f".{message_name}", + ) ) - message_descriptor.field.add( - name=f"related_{field_name}", - number=index * 2 + 2, - label=descriptor_pb2.FieldDescriptorProto.LABEL_REPEATED, - type=descriptor_pb2.FieldDescriptorProto.TYPE_MESSAGE, - type_name=f".{message_name}", + message_descriptor.field.append( + FieldDescriptorProto( + name=f"related_{field_name}", + number=index * 2 + 2, + label=FieldDescriptorProto.Label.REPEATED, + type=FieldDescriptorProto.Type.MESSAGE, + type_name=f".{message_name}", + ) ) - descriptor = Default().AddSerializedFile(file_descriptor.SerializeToString()) - message_type = GetMessageClass(descriptor.message_types_by_name["StacDatapoint"]) + message_type = get_message_type( + AnnotatedType(FileDescriptorSet(file=[file_descriptor]), "tests.protobuf_conversion.StacDatapoint", []) + ) values = {} for field_name, (_, source_value, _) in message_types.items(): @@ -182,7 +269,7 @@ def test_convert_stac_messages_to_protobuf_py() -> None: assert dataset[field_name][0].item() is None converted_value = dataset[field_name][1].item() assert isinstance(converted_value, target_type) - assert converted_value.to_binary() == source_value.SerializeToString() + assert converted_value.to_binary() == source_value.to_binary() if field_name == "assets": assert isinstance(converted_value, Assets) assert str(converted_value) == "1 assets" @@ -192,7 +279,7 @@ def test_convert_stac_messages_to_protobuf_py() -> None: assert dataset[repeated_field_name].dtype == object converted_repeated_value = dataset[repeated_field_name][1, 0].item() assert isinstance(converted_repeated_value, target_type) - assert converted_repeated_value.to_binary() == source_value.SerializeToString() + assert converted_repeated_value.to_binary() == source_value.to_binary() converted_assets = dataset["assets"][1].item() assert isinstance(converted_assets, Assets) @@ -207,7 +294,7 @@ def test_convert_stac_messages_to_protobuf_py() -> None: record = {} for field_name, (_, source_value, target_type) in message_types.items(): value_type = Assets if field_name == "assets" else target_type - target_value = value_type.from_binary(source_value.SerializeToString()) + target_value = value_type.from_binary(source_value.to_binary()) input_data[field_name] = [target_value] input_data[f"related_{field_name}"] = [repeated_container([target_value])] record[field_name] = target_value @@ -225,30 +312,37 @@ class Polarization(Enum): HH = "HH" VV = "VV" - file_descriptor = descriptor_pb2.FileDescriptorProto( + file_descriptor = FileDescriptorProto( name="tests/protobuf_conversion/enum_datapoint.proto", package="tests.protobuf_conversion.enums", ) - enum_descriptor = file_descriptor.enum_type.add(name="SARPolarization") + enum_descriptor = EnumDescriptorProto(name="SARPolarization") + file_descriptor.enum_type.append(enum_descriptor) for name, number in (("SAR_POLARIZATION_UNSPECIFIED", 0), ("SAR_POLARIZATION_HH", 1), ("SAR_POLARIZATION_VV", 2)): - enum_descriptor.value.add(name=name, number=number) - message_descriptor = file_descriptor.message_type.add(name="EnumDatapoint") - message_descriptor.field.add( - name="primary_polarization", - number=1, - label=descriptor_pb2.FieldDescriptorProto.LABEL_OPTIONAL, - type=descriptor_pb2.FieldDescriptorProto.TYPE_ENUM, - type_name=".tests.protobuf_conversion.enums.SARPolarization", + enum_descriptor.value.append(EnumValueDescriptorProto(name=name, number=number)) + message_descriptor = DescriptorProto(name="EnumDatapoint") + file_descriptor.message_type.append(message_descriptor) + message_descriptor.field.append( + FieldDescriptorProto( + name="primary_polarization", + number=1, + label=FieldDescriptorProto.Label.OPTIONAL, + type=FieldDescriptorProto.Type.ENUM, + type_name=".tests.protobuf_conversion.enums.SARPolarization", + ) + ) + message_descriptor.field.append( + FieldDescriptorProto( + name="polarizations", + number=2, + label=FieldDescriptorProto.Label.REPEATED, + type=FieldDescriptorProto.Type.ENUM, + type_name=".tests.protobuf_conversion.enums.SARPolarization", + ) ) - message_descriptor.field.add( - name="polarizations", - number=2, - label=descriptor_pb2.FieldDescriptorProto.LABEL_REPEATED, - type=descriptor_pb2.FieldDescriptorProto.TYPE_ENUM, - type_name=".tests.protobuf_conversion.enums.SARPolarization", + message_type = get_message_type( + AnnotatedType(FileDescriptorSet(file=[file_descriptor]), "tests.protobuf_conversion.enums.EnumDatapoint", []) ) - descriptor = Default().AddSerializedFile(file_descriptor.SerializeToString()) - message_type = GetMessageClass(descriptor.message_types_by_name["EnumDatapoint"]) messages = [ message_type(primary_polarization=1, polarizations=[0, 1]), message_type(primary_polarization=2, polarizations=[2]), @@ -284,8 +378,8 @@ class Polarization(Enum): def test_convert_sar_polarizations_with_short_names_in_both_directions() -> None: messages = [ - SARProperties(polarizations=[SAR_POLARIZATION_HH, SAR_POLARIZATION_VV]), - SARProperties(polarizations=[SAR_POLARIZATION_VV]), + SARProperties(polarizations=[SARPolarization.HH, SARPolarization.VV]), + SARProperties(polarizations=[SARPolarization.VV]), SARProperties(), ] converter = MessageToXarrayConverter() @@ -298,7 +392,7 @@ def test_convert_sar_polarizations_with_short_names_in_both_directions() -> None assert pd.isna(dataset.polarizations[1, 1].item()) assert pd.isna(dataset.polarizations[2].to_numpy()).all() - other_fields = [field.name for field in SARProperties.DESCRIPTOR.fields if field.name != "polarizations"] + other_fields = [field.name for field in SARProperties.desc().fields if field.name != "polarizations"] assert to_messages(dataset, SARProperties, ignore_fields=other_fields) == messages assert to_messages([{"polarizations": ["HH", "VV"]}], SARProperties) == [messages[0]] diff --git a/tilebox-datasets/tests/protobuf_conversion/test_to_protobuf.py b/tilebox-datasets/tests/protobuf_conversion/test_to_protobuf.py index 7ea3297..f1302e9 100644 --- a/tilebox-datasets/tests/protobuf_conversion/test_to_protobuf.py +++ b/tilebox-datasets/tests/protobuf_conversion/test_to_protobuf.py @@ -6,13 +6,50 @@ import xarray as xr from hypothesis import given from hypothesis.strategies import lists +from protobuf.wkt.google.protobuf.descriptor_pb import ( + DescriptorProto, + FieldDescriptorProto, + FileDescriptorProto, + FileDescriptorSet, +) from tests.data.datapoint import example_datapoints, example_pandas_datapoints -from tests.example_dataset.example_dataset_pb2 import ExampleDatapoint +from tests.example_dataset.schema import ExampleDatapoint +from tilebox.datasets.data.datasets import AnnotatedType +from tilebox.datasets.message_pool import get_message_type from tilebox.datasets.protobuf_conversion.protobuf_xarray import MessageToXarrayConverter from tilebox.datasets.protobuf_conversion.to_protobuf import to_messages +def test_ingestion_preserves_fields_with_escaped_python_names() -> None: + record = {"normal": 3, "class": 5, "from": 7, "desc": 11, "to_binary": 13} + schema = FileDescriptorProto( + name="tests/escaped_names.proto", + package="tests.escaped_names", + syntax="proto3", + message_type=[ + DescriptorProto( + name="Row", + field=[ + FieldDescriptorProto( + name=name, + number=number, + type=FieldDescriptorProto.Type.INT64, + label=FieldDescriptorProto.Label.OPTIONAL, + ) + for number, name in enumerate(record, start=1) + ], + ) + ], + ) + row = get_message_type(AnnotatedType(FileDescriptorSet(file=[schema]), "tests.escaped_names.Row", [])) + + message = to_messages([record], row)[0] + + # Wire field numbers and values must not depend on Python's escaped attribute names. + assert message.to_binary() == b"\x08\x03\x10\x05\x18\x07\x20\x0b\x28\x0d" + + @given(lists(example_datapoints(generated_fields=True, missing_fields=False), min_size=1, max_size=5)) def test_xarray_dataset_to_protobuf_messages(messages: list[ExampleDatapoint]) -> None: dataset = _to_dataset(messages) @@ -22,9 +59,9 @@ def test_xarray_dataset_to_protobuf_messages(messages: list[ExampleDatapoint]) - assert len(converted) == len(messages) for converted_message, expected_message in zip(converted, messages, strict=True): - for field in expected_message.DESCRIPTOR.fields: + for field in expected_message.desc().fields: if field.name in ignore_fields: - assert not converted_message.HasField(field.name) + assert not converted_message.has_field(field.name) continue assert getattr(expected_message, field.name) == getattr(converted_message, field.name), ( diff --git a/tilebox-datasets/tests/query/test_expression.py b/tilebox-datasets/tests/query/test_expression.py index 401d071..fb3b215 100644 --- a/tilebox-datasets/tests/query/test_expression.py +++ b/tilebox-datasets/tests/query/test_expression.py @@ -1,11 +1,12 @@ from datetime import UTC, datetime, timedelta from enum import Enum, IntEnum +from typing import Literal import numpy as np import pytest -from tilebox.datasets.datasets.v1 import data_access_pb2 from tilebox.datasets.query import Expression, field +from tilebox.datasets.v1 import data_access_pb class Quality(Enum): @@ -29,9 +30,18 @@ class NumericQuality(IntEnum): (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) +def test_query_value_types( + value: object, + kind: Literal[ + "bool_value", "int64_value", "uint64_value", "double_value", "string_value", "bytes_value", "enum_name" + ], + expected: object, +) -> None: + comparison = (field("quality") == value).to_message().comparison + assert comparison is not None + message = comparison.value + assert message is not None + assert message.has_field(kind) assert getattr(message, kind) == expected @@ -58,7 +68,11 @@ def test_well_known_query_values_round_trip(value: object) -> None: ], ) def test_negative_duration_values(value: object, seconds: int, nanos: int) -> None: - duration = (field("value") == value).to_message().comparison.value.duration_value + comparison = (field("value") == value).to_message().comparison + assert comparison is not None + assert comparison.value is not None + duration = comparison.value.duration_value + assert duration is not None assert (duration.seconds, duration.nanos) == (seconds, nanos) @@ -74,10 +88,14 @@ def test_negative_duration_values(value: object, seconds: int, nanos: int) -> No ], ) def test_numpy_query_values_preserve_type_when_reflected(value: object) -> None: - field_first = (field("value") == value).to_message().comparison.value + comparison = (field("value") == value).to_message().comparison + assert comparison is not None + field_first = comparison.value reflected = value == field("value") assert isinstance(reflected, Expression) - value_first = reflected.to_message().comparison.value + reflected_comparison = reflected.to_message().comparison + assert reflected_comparison is not None + value_first = reflected_comparison.value assert value_first == field_first @@ -90,10 +108,15 @@ def test_logical_expression_shape_and_round_trip() -> None: ) message = expression.to_message() - assert message.logical.operator == data_access_pb2.LOGICAL_OPERATOR_AND + assert message.logical is not None + assert message.logical.operator == data_access_pb.LogicalOperator.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 + disjunction = message.logical.operands[2].logical + negation = message.logical.operands[3].logical + assert disjunction is not None + assert negation is not None + assert disjunction.operator == data_access_pb.LogicalOperator.OR + assert negation.operator == data_access_pb.LogicalOperator.NOT assert Expression.from_message(message) == expression @@ -127,10 +150,10 @@ def test_invalid_query_operators() -> None: def test_invalid_expression_message() -> None: with pytest.raises(ValueError, match="exactly one expression node"): - Expression.from_message(data_access_pb2.FilterExpression()) + Expression.from_message(data_access_pb.FilterExpression()) - malformed_not = data_access_pb2.FilterExpression( - logical=data_access_pb2.LogicalExpression(operator=data_access_pb2.LOGICAL_OPERATOR_NOT) + malformed_not = data_access_pb.FilterExpression( + logical=data_access_pb.LogicalExpression(operator=data_access_pb.LogicalOperator.NOT) ) with pytest.raises(ValueError, match="NOT requires exactly one operand"): Expression.from_message(malformed_not) @@ -139,24 +162,24 @@ def test_invalid_expression_message() -> None: @pytest.mark.parametrize( "message", [ - data_access_pb2.FilterExpression( - comparison=data_access_pb2.FieldComparison( + data_access_pb.FilterExpression( + comparison=data_access_pb.FieldComparison( field_name="quality", - operator=data_access_pb2.FIELD_COMPARISON_OPERATOR_LESS_THAN, - value=data_access_pb2.FieldQueryValue(bool_value=True), + operator=data_access_pb.FieldComparisonOperator.LESS_THAN, + value=data_access_pb.FieldQueryValue(bool_value=True), ) ), - data_access_pb2.FilterExpression( - comparison=data_access_pb2.FieldComparison( + data_access_pb.FilterExpression( + comparison=data_access_pb.FieldComparison( field_name="quality", - operator=data_access_pb2.FIELD_COMPARISON_OPERATOR_EQUAL, - value=data_access_pb2.FieldQueryValue(double_value=float("nan")), + operator=data_access_pb.FieldComparisonOperator.EQUAL, + value=data_access_pb.FieldQueryValue(double_value=float("nan")), ) ), ], ) def test_expression_message_cannot_bypass_comparison_validation( - message: data_access_pb2.FilterExpression, + message: data_access_pb.FilterExpression, ) -> None: with pytest.raises((TypeError, ValueError)): Expression.from_message(message) diff --git a/tilebox-datasets/tests/query/test_pagination.py b/tilebox-datasets/tests/query/test_pagination.py index af500cb..ecf4cbf 100644 --- a/tilebox-datasets/tests/query/test_pagination.py +++ b/tilebox-datasets/tests/query/test_pagination.py @@ -1,9 +1,51 @@ +from collections.abc import AsyncIterator +from functools import partial +from io import StringIO +from unittest.mock import patch +from uuid import UUID + +import pytest from hypothesis import given +from hypothesis.strategies import integers, lists from tests.query.pagination import paginations +from tqdm import tqdm +from tilebox.datasets.aio.pagination import with_progressbar as async_with_progressbar +from tilebox.datasets.data.datapoint import QueryResultPage, RepeatedAny from tilebox.datasets.query.pagination import Pagination +from tilebox.datasets.sync.pagination import with_progressbar @given(paginations()) def test_pages_to_message_and_back(page: Pagination) -> None: assert Pagination.from_message(page.to_message()) == page + + +@pytest.mark.asyncio +@given(page_sizes=lists(integers(min_value=1, max_value=100), min_size=2, max_size=5)) +async def test_progress_counts_actual_datapoints_in_sync_and_async_pages(page_sizes: list[int]) -> None: + pages = [ + QueryResultPage( + RepeatedAny("", [b""] * size), + Pagination(starting_after=UUID(int=index + 1) if index < len(page_sizes) - 1 else None), + byte_size=0, + ) + for index, size in enumerate(page_sizes) + ] + + async def async_pages() -> AsyncIterator[QueryResultPage]: + for page in pages: + yield page + + for module in ("sync", "aio"): + output = StringIO() + with patch( + f"tilebox.datasets.{module}.pagination.tqdm", + partial(tqdm, file=output, bar_format="{n}", mininterval=0), + ): + if module == "sync": + result = list(with_progressbar(iter(pages), "query")) + else: + result = [page async for page in async_with_progressbar(async_pages(), "query")] + assert result == pages + assert int(output.getvalue().strip().rsplit("\r", maxsplit=1)[-1]) == sum(page_sizes) diff --git a/tilebox-datasets/tests/query/test_time_interval.py b/tilebox-datasets/tests/query/test_time_interval.py index 53a6301..793e287 100644 --- a/tilebox-datasets/tests/query/test_time_interval.py +++ b/tilebox-datasets/tests/query/test_time_interval.py @@ -1,12 +1,16 @@ from datetime import UTC, datetime, timedelta +import numpy as np +import pandas as pd import pytest -from google.protobuf.timestamp_pb2 import Timestamp +import xarray as xr from hypothesis import given from hypothesis.strategies import datetimes from pandas.core.tools.datetimes import DatetimeScalar +from protobuf.wkt.google.protobuf.timestamp_pb import Timestamp from tests.query.time_interval import datetime_scalars, time_intervals +from _tilebox.proto.tilebox.v1 import query_pb from tilebox.datasets.query.time_interval import ( _SMALLEST_POSSIBLE_TIMEDELTA, TimeInterval, @@ -16,7 +20,6 @@ timedelta_to_duration, timestamp_to_datetime, ) -from tilebox.datasets.tilebox.v1 import query_pb2 _TIME_FORMAT = "%Y-%m-%dT%H:%M:%S" @@ -88,7 +91,7 @@ class CustomInterval(TimeInterval): pass interval = CustomInterval.from_message( - query_pb2.TimeInterval( + query_pb.TimeInterval( start_time=Timestamp(seconds=start_seconds), end_time=Timestamp(seconds=end_seconds), end_inclusive=end_inclusive, @@ -159,3 +162,24 @@ def test_parse_time_interval_from_tuple( assert interval.end == end_dt, f"Failed parsing interval for ({start_scalar}, {end_scalar})" assert not interval.start_exclusive assert not interval.end_inclusive + + +def test_pandas_timestamp_keeps_conversion_and_precision_behavior() -> None: + value = pd.Timestamp("2026-03-04T02:30:00.123456789+02:00") + assert isinstance(value, pd.Timestamp) + with pytest.warns(UserWarning, match="Discarding nonzero nanoseconds"): + interval = TimeInterval.parse(value) + assert type(interval.start) is datetime + assert interval.start == interval.end == datetime(2026, 3, 4, 0, 30, 0, 123456, tzinfo=UTC) + assert interval.end_inclusive + + +@pytest.mark.parametrize("as_dataset", [False, True]) +def test_xarray_time_interval_preserves_notebook_inputs(as_dataset: bool) -> None: + values = np.array(["2026-03-04T00:30", "2026-03-05T01:15"], dtype="datetime64[ns]") + data = xr.Dataset(coords={"time": values}) if as_dataset else xr.DataArray(values) + interval = TimeInterval.parse(data) + assert interval.start == datetime(2026, 3, 4, 0, 30, tzinfo=UTC) + assert interval.end == datetime(2026, 3, 5, 1, 15, tzinfo=UTC) + assert not interval.start_exclusive + assert interval.end_inclusive diff --git a/tilebox-datasets/tests/test_aio.py b/tilebox-datasets/tests/test_aio.py index b08ab3b..3193d82 100644 --- a/tilebox-datasets/tests/test_aio.py +++ b/tilebox-datasets/tests/test_aio.py @@ -7,22 +7,22 @@ from tilebox.datasets.client import _TILEBOX_API_URL, _TILEBOX_DEV_API_URL -@patch("tilebox.datasets.aio.client.open_channel") -def test_tilebox_client_init_opens_channel(open_channel_mock: MagicMock) -> None: +@patch("tilebox.datasets.aio.client.AsyncConnection") +def test_tilebox_client_init_opens_channel(connection_mock: MagicMock) -> None: Client(url="some-url", token="some-token") # noqa: S106 - open_channel_mock.assert_called_once_with("some-url", "some-token", rpc_method_prefix=None) + connection_mock.assert_called_once_with("some-url", "some-token", None, transport="http2", interceptors=()) @pytest.mark.parametrize("url", [_TILEBOX_API_URL, _TILEBOX_DEV_API_URL, f"{_TILEBOX_API_URL}/"]) @patch.dict(os.environ, {}, clear=True) -@patch("tilebox.datasets.aio.client.open_channel") -def test_tilebox_client_init_uses_public_rpc_prefix_for_tilebox_urls(open_channel_mock: MagicMock, url: str) -> None: +@patch("tilebox.datasets.aio.client.AsyncConnection") +def test_tilebox_client_init_uses_public_rpc_prefix_for_tilebox_urls(connection_mock: MagicMock, url: str) -> None: Client(url=url, token=None) - open_channel_mock.assert_called_once_with(url.removesuffix("/"), None, rpc_method_prefix="/public") + connection_mock.assert_called_once_with(url.removesuffix("/"), None, "/public", transport="http2", interceptors=()) @patch.dict(os.environ, {}, clear=True) -@patch("tilebox.datasets.aio.client.open_channel") -def test_tilebox_client_init_skips_public_rpc_prefix_for_custom_urls(open_channel_mock: MagicMock) -> None: +@patch("tilebox.datasets.aio.client.AsyncConnection") +def test_tilebox_client_init_skips_public_rpc_prefix_for_custom_urls(connection_mock: MagicMock) -> None: Client(url="some-url", token=None) - open_channel_mock.assert_called_once_with("some-url", None, rpc_method_prefix=None) + connection_mock.assert_called_once_with("some-url", None, None, transport="http2", interceptors=()) diff --git a/tilebox-datasets/tests/test_assets.py b/tilebox-datasets/tests/test_assets.py index b2ed728..e089969 100644 --- a/tilebox-datasets/tests/test_assets.py +++ b/tilebox-datasets/tests/test_assets.py @@ -12,44 +12,43 @@ Band, MediaType, ) -from tilebox.datasets.datasets.stac.v1.asset_metadata_pb import ( +from tilebox.datasets.stac.v1.asset_metadata_pb import ( ClassificationBitField, ClassificationClass, EOCommonName, EOProperties, RasterProperties, ) -from tilebox.datasets.datasets.stac.v1.asset_pb import ( +from tilebox.datasets.stac.v1.asset_pb import ( Asset as ProtoAsset, ) -from tilebox.datasets.datasets.stac.v1.asset_pb import ( +from tilebox.datasets.stac.v1.asset_pb import ( AssetAccessProfile, Assets, DataType, KnownAssetRole, ) -from tilebox.datasets.datasets.stac.v1.asset_pb import AssetLocation as ProtoAssetLocation -from tilebox.datasets.datasets.stac.v1.asset_pb import Band as ProtoBand -from tilebox.datasets.datasets.stac.v1.asset_pb2 import Assets as AssetsWire -from tilebox.datasets.datasets.stac.v1.authentication_pb import ( +from tilebox.datasets.stac.v1.asset_pb import AssetLocation as ProtoAssetLocation +from tilebox.datasets.stac.v1.asset_pb import Band as ProtoBand +from tilebox.datasets.stac.v1.authentication_pb import ( Authentication, AuthenticationFlow, AuthenticationParameter, OAuth2Flow, SignedURLFlow, ) -from tilebox.datasets.datasets.stac.v1.authentication_pb import ( +from tilebox.datasets.stac.v1.authentication_pb import ( AuthenticationScheme as AuthenticationSchemeProto, ) -from tilebox.datasets.datasets.stac.v1.core_pb import KnownMediaType -from tilebox.datasets.datasets.stac.v1.core_pb import MediaType as ProtoMediaType -from tilebox.datasets.datasets.stac.v1.sar_pb import ( +from tilebox.datasets.stac.v1.core_pb import KnownMediaType +from tilebox.datasets.stac.v1.core_pb import MediaType as ProtoMediaType +from tilebox.datasets.stac.v1.sar_pb import ( SARFrequencyBand, SARObservationDirection, SARPolarization, SARProperties, ) -from tilebox.datasets.datasets.stac.v1.storage_pb import KnownStorageType, Storage, StorageScheme +from tilebox.datasets.stac.v1.storage_pb import KnownStorageType, Storage, StorageScheme TESTDATA = Path(__file__).parent / "testdata" @@ -364,12 +363,12 @@ def test_classification_bitfields_round_trip(placement: str) -> None: sparse_bitfields = [compiled.band_profiles[index].bitfields for index in compiled.assets[0].band_profile_indices] assert sparse_bitfields == [list(bits) if bits != expected_asset else [] for bits in expected_bands] - wire = AssetsWire.FromString(compiled.to_binary()) + wire = Assets.from_binary(compiled.to_binary()) if placement != "distinct": - assert wire.assets[0].bitfields[0].HasField("offset") + assert wire.assets[0].bitfields[0].has_field("offset") assert wire.assets[0].bitfields[0].offset == 0 - assert wire.assets[0].bitfields[0].classes[0].HasField("value") - decoded = AssetCollection.from_datapoint(_scalar(assets=Assets.from_binary(wire.SerializeToString()))) + assert wire.assets[0].bitfields[0].classes[0].has_field("value") + decoded = AssetCollection.from_datapoint(_scalar(assets=wire)) assert decoded["qa"].bitfields == expected_asset assert tuple(band.bitfields for band in decoded["qa"].bands) == expected_bands assert decoded == authored diff --git a/tilebox-datasets/tests/test_client.py b/tilebox-datasets/tests/test_client.py index 7b1c3ee..f9e9003 100644 --- a/tilebox-datasets/tests/test_client.py +++ b/tilebox-datasets/tests/test_client.py @@ -9,8 +9,8 @@ import xarray as xr from shapely import Polygon -from _tilebox.grpc.error import NotFoundError -from _tilebox.grpc.replay import open_recording_channel, open_replay_channel +from _tilebox.errors import ArgumentError, AuthenticationError, InternalServerError, NotFoundError +from _tilebox.replay import Record, Replay from tilebox.datasets import Client, DatasetClient from tilebox.datasets.aio.client import Client as AsyncClient from tilebox.datasets.client import _TILEBOX_API_URL, _TILEBOX_DEV_API_URL @@ -18,17 +18,6 @@ from tilebox.datasets.query.time_interval import us_to_datetime -@pytest.mark.parametrize( - ("client_type", "transport_type"), [(Client, "SyncHTTPTransport"), (AsyncClient, "HTTPTransport")] -) -def test_http1_trusts_system_certificates(client_type: type[Client] | type[AsyncClient], transport_type: str) -> None: - from pyqwest import HTTPVersion # noqa: PLC0415 - - with patch(f"pyqwest.{transport_type}") as transport: - client_type(url="https://api.tilebox.com", transport="http1") - transport.assert_called_once_with(http_version=HTTPVersion.HTTP1, tls_include_system_certs=True) - - @pytest.mark.parametrize("client_type", [Client, AsyncClient]) @pytest.mark.parametrize( ("environment_url", "explicit_url", "expected_url"), @@ -51,25 +40,19 @@ def test_client_url_environment( if environment_url is not None: monkeypatch.setenv("TILEBOX_API_URL", environment_url) monkeypatch.setenv("TILEBOX_API_KEY", "runner-key") - with patch(f"{client_type.__module__}.open_channel") as open_channel_mock: + connection_type = "AsyncConnection" if client_type is AsyncClient else "Connection" + with patch(f"{client_type.__module__}.{connection_type}") as connection_mock: if explicit_url is None: client_type() else: client_type(url=explicit_url) - open_channel_mock.assert_called_once_with(expected_url, "runner-key", rpc_method_prefix=None) + connection_mock.assert_called_once_with(expected_url, "runner-key", None, transport="http2", interceptors=()) -@pytest.mark.parametrize("grpc_verbosity", [None, "DEBUG"]) -def test_heavy_imports_are_lazy(monkeypatch: pytest.MonkeyPatch, grpc_verbosity: str | None) -> None: - monkeypatch.delenv("GRPC_VERBOSITY", raising=False) - if grpc_verbosity is not None: - monkeypatch.setenv("GRPC_VERBOSITY", grpc_verbosity) +def test_heavy_imports_are_lazy() -> None: code = ( - "import os\n" "import sys\n" "import tilebox.datasets as datasets\n" - f"assert os.environ['GRPC_VERBOSITY'] == {grpc_verbosity or 'ERROR'!r}\n" - "assert 'grpc' not in sys.modules\n" "assert not {'pandas', 'xarray'} & sys.modules.keys()\n" "assert set(datasets.__all__) <= set(dir(datasets))\n" "from tilebox.datasets.query.time_interval import TimeInterval\n" @@ -84,53 +67,53 @@ def test_heavy_imports_are_lazy(monkeypatch: pytest.MonkeyPatch, grpc_verbosity: subprocess.run([sys.executable, "-c", code], check=True) # noqa: S603 +def test_legacy_root_timeseries_aliases_warn_and_preserve_async_identity() -> None: + code = """ +import warnings +import tilebox.datasets as datasets +from tilebox.datasets import aio + +for legacy, current in (("TimeseriesDataset", "DatasetClient"), ("TimeseriesCollection", "CollectionClient")): + with warnings.catch_warnings(record=True) as caught: + warnings.simplefilter("always") + alias = getattr(datasets, legacy) + assert alias is getattr(aio, current) + assert alias is not getattr(datasets, current) + assert len(caught) == 1 + assert caught[0].category is DeprecationWarning + assert f"tilebox.datasets.aio.{current}" in str(caught[0].message) +""" + subprocess.run([sys.executable, "-c", code], check=True) # noqa: S603 + + @pytest.mark.parametrize("url", [_TILEBOX_API_URL, _TILEBOX_DEV_API_URL, f"{_TILEBOX_API_URL}/"]) @patch.dict(os.environ, {}, clear=True) -@patch("tilebox.datasets.sync.client.open_channel") -def test_tilebox_client_init_uses_public_rpc_prefix_for_tilebox_urls(open_channel_mock: MagicMock, url: str) -> None: +@patch("tilebox.datasets.sync.client.Connection") +def test_tilebox_client_init_uses_public_rpc_prefix_for_tilebox_urls(connection_mock: MagicMock, url: str) -> None: Client(url=url, token=None) - open_channel_mock.assert_called_once_with(url.removesuffix("/"), None, rpc_method_prefix="/public") + connection_mock.assert_called_once_with(url.removesuffix("/"), None, "/public", transport="http2", interceptors=()) @patch.dict(os.environ, {}, clear=True) -@patch("tilebox.datasets.sync.client.open_channel") -def test_tilebox_client_init_skips_public_rpc_prefix_for_custom_urls(open_channel_mock: MagicMock) -> None: +@patch("tilebox.datasets.sync.client.Connection") +def test_tilebox_client_init_skips_public_rpc_prefix_for_custom_urls(connection_mock: MagicMock) -> None: Client(url="some-url", token=None) - open_channel_mock.assert_called_once_with("some-url", None, rpc_method_prefix=None) + connection_mock.assert_called_once_with("some-url", None, None, transport="http2", interceptors=()) def replay_client(replay_file: str, assert_request_matches: bool = True) -> Client: replay = Path(__file__).parent / "testdata" / "recordings" / replay_file - replay_channel = open_replay_channel(replay, assert_request_matches) - - with patch("tilebox.datasets.sync.client.open_channel") as open_channel_mock: - open_channel_mock.return_value = replay_channel - # url/token doesn't matter since its a mocked channel - client = Client( - url="https://api.tilebox.com", - token="token", # noqa: S106 - ) - open_channel_mock.assert_called_once() - - return client + return Client(url="https://api.tilebox.com", interceptors=[Replay(replay, assert_request_matches)]) def record_client(recording_file: str) -> Client: recording = Path(__file__).parent / "testdata" / "recordings" / recording_file - # this will open a channel to api.tilebox.com, which will send real requests to the server, and record them - # for later offline replay - recording_channel = open_recording_channel( - "https://api.tilebox.com", os.environ["TILEBOX_OPENDATA_ONLY_API_KEY"], recording + return Client( + url="https://api.tilebox.com", + token=os.environ["TILEBOX_OPENDATA_ONLY_API_KEY"], + interceptors=[Record(recording)], ) - with patch("tilebox.datasets.sync.client.open_channel") as open_channel_mock: - open_channel_mock.return_value = recording_channel - # url/token doesn't matter since its a mocked channel - client = Client(url="https://api.tilebox.com", token="token") # noqa: S106 - open_channel_mock.assert_called_once() - - return client - def test_list_datasets() -> None: # we send our package version as client_info, so the outgoing request changes over time, so let's not check it @@ -179,6 +162,43 @@ def test_dataset_not_found() -> None: client.dataset("this.dataset.does.not.exist") +@pytest.mark.asyncio +@pytest.mark.parametrize("asynchronous", [False, True]) +@pytest.mark.parametrize( + "error", + [ + (16, AuthenticationError, "Unauthenticated: denied"), + (7, AuthenticationError, "Unauthorized: denied"), + (5, NotFoundError, "denied"), + (3, ArgumentError, "denied"), + (13, InternalServerError, "Oops, something went wrong: denied"), + (12, AuthenticationError, "ListDatasets is only available for authenticated users"), + ], +) +async def test_public_client_translates_native_errors( + tmp_path: Path, + monkeypatch: pytest.MonkeyPatch, + asynchronous: bool, + error: tuple[int, type[Exception], str], +) -> None: + """Both public clients translate native failures through the shared connection.""" + monkeypatch.delenv("TILEBOX_API_KEY", raising=False) + status, error_type, message = error + recording = tmp_path / "error.bin" + recording.write_bytes(f"/datasets.v1.DatasetService/ListDatasets\n\n{status}\ndenied\n".encode()) + replay = Replay(recording) + if asynchronous: + async with AsyncClient(url=_TILEBOX_API_URL, token=None, interceptors=[replay]) as client: + with pytest.raises(error_type, match=message): + await client.datasets() + else: + with ( + Client(url=_TILEBOX_API_URL, token=None, interceptors=[replay]) as sync_client, + pytest.raises(error_type, match=message), + ): + sync_client.datasets() + + def test_find_datapoint() -> None: client = replay_client("find_s2_datapoint.rpcs.bin") diff --git a/tilebox-datasets/tests/test_notices.py b/tilebox-datasets/tests/test_notices.py index 108563c..1f689f4 100644 --- a/tilebox-datasets/tests/test_notices.py +++ b/tilebox-datasets/tests/test_notices.py @@ -77,7 +77,8 @@ def test_terminal_color_opt_out( def test_anonymous_notice_can_be_disabled( capsys: pytest.CaptureFixture[str], client_type: type[Client] | type[AsyncClient] ) -> None: - with patch(f"{client_type.__module__}.open_channel"): + connection_type = "AsyncConnection" if client_type is AsyncClient else "Connection" + with patch(f"{client_type.__module__}.{connection_type}"): client_type(warn_if_unauthenticated=False) assert capsys.readouterr() == ("", "") @@ -88,7 +89,8 @@ def test_anonymous_notice_can_be_disabled( def test_anonymous_notice_is_printed( capsys: pytest.CaptureFixture[str], client_type: type[Client] | type[AsyncClient] ) -> None: - with patch(f"{client_type.__module__}.open_channel"): + connection_type = "AsyncConnection" if client_type is AsyncClient else "Connection" + with patch(f"{client_type.__module__}.{connection_type}"): client_type() captured = capsys.readouterr() @@ -99,5 +101,5 @@ def test_anonymous_notice_is_printed( def test_server_message_is_printed(capsys: pytest.CaptureFixture[str]) -> None: response = ListDatasetsResponse([], [], "A message from the server") - assert _log_server_message(response) is response + _log_server_message(response) assert capsys.readouterr() == ("", "A message from the server\n\n") diff --git a/tilebox-datasets/tests/test_timeseries.py b/tilebox-datasets/tests/test_timeseries.py index 8fcb950..755aeb1 100644 --- a/tilebox-datasets/tests/test_timeseries.py +++ b/tilebox-datasets/tests/test_timeseries.py @@ -8,37 +8,34 @@ from hypothesis import assume, given, settings from hypothesis.stateful import Bundle, RuleBasedStateMachine, consumes, invariant, rule from hypothesis.strategies import lists -from promise import Promise -from _tilebox.grpc.error import ArgumentError, NotFoundError +from _tilebox.errors import ArgumentError, NotFoundError from tests.data.collection import collection_infos, collection_names 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 tests.example_dataset.schema import ExampleDatapoint 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 from tilebox.datasets.data.timeseries import TimeseriesDatasetChunk -from tilebox.datasets.datasets.v1.collections_pb2 import ( - CreateCollectionRequest, - DeleteCollectionRequest, - GetCollectionByNameRequest, - ListCollectionsRequest, - ListCollectionsResponse, -) -from tilebox.datasets.datasets.v1.collections_pb2_grpc import CollectionServiceStub -from tilebox.datasets.datasets.v1.core_pb2 import Collection as CollectionMessage -from tilebox.datasets.datasets.v1.core_pb2 import CollectionInfo as CollectionInfoMessage -from tilebox.datasets.datasets.v1.timeseries_pb2 import TimeseriesDatasetChunk as TimeseriesDatasetChunkMessage from tilebox.datasets.query.time_interval import ( _EMPTY_TIME_INTERVAL, TimeInterval, _convert_to_datetime, timestamp_to_datetime, ) -from tilebox.datasets.service import TileboxDatasetService from tilebox.datasets.uuid import uuid_message_to_uuid, uuid_to_uuid_message +from tilebox.datasets.v1.collections_pb import ( + CreateCollectionRequest, + DeleteCollectionRequest, + GetCollectionByNameRequest, + ListCollectionsRequest, + ListCollectionsResponse, +) +from tilebox.datasets.v1.core_pb import Collection as CollectionMessage +from tilebox.datasets.v1.core_pb import CollectionInfo as CollectionInfoMessage +from tilebox.datasets.v1.timeseries_pb import TimeseriesDatasetChunk as TimeseriesDatasetChunkMessage @pytest.mark.parametrize(("start", "end"), [(0, 86400), (-86400, 0), (0.5, 1)]) @@ -50,7 +47,9 @@ def test_chunk_preserves_zero_second_timestamps(start: float, end: float) -> Non time_interval=interval.to_message(), ) - assert TimeseriesDatasetChunk.from_message(message).time_interval == interval + chunk = TimeseriesDatasetChunk.from_message(message) + assert chunk.time_interval == interval + assert TimeseriesDatasetChunk.from_binary(chunk.to_binary()) == chunk @pytest.mark.parametrize("missing_field", ["time_interval", "start_time", "end_time"]) @@ -62,21 +61,23 @@ def test_chunk_missing_time_interval_fields(missing_field: str) -> None: time_interval=interval.to_message(), ) if missing_field == "time_interval": - message.ClearField(missing_field) + setattr(message, missing_field, None) else: - message.time_interval.ClearField(missing_field) + setattr(message.time_interval, missing_field, None) assert TimeseriesDatasetChunk.from_message(message).time_interval is None def _mocked_dataset() -> tuple[DatasetClient, MagicMock]: service = MagicMock() + connection = MagicMock() + connection.client.return_value = service # we do not sample/draw from datasets() here, because the values themselves are irrelevant for the tests # (we are not testing properties, but rather writing conventional unit tests here, so it doesn't make sense to # run them multiple times) dataset = DatasetClient( - service, + connection, Dataset( id=uuid4(), group_id=uuid4(), @@ -107,12 +108,15 @@ def test_timeseries_dataset_list_collections(infos: list[CollectionInfo]) -> Non for info in infos ] dataset, service = _mocked_dataset() - # mock a protobuf message response value for the gRPC endpoint: - service.get_collections.return_value = Promise.resolve(infos) + service.list_collections.return_value = ListCollectionsResponse(data=[info.to_message() for info in infos]) collections = dataset.collections() - service.get_collections.assert_called_once_with(dataset._dataset.id, True, True) + service.list_collections.assert_called_once_with( + ListCollectionsRequest( + dataset_id=uuid_to_uuid_message(dataset._dataset.id), with_availability=True, with_count=True + ) + ) assert len(collections) == len(infos) for info in infos: @@ -130,11 +134,18 @@ def test_timeseries_dataset_get_collection(collection_name: str) -> None: mocked = _mocked_collection() info = mocked.collection_info info.collection.name = collection_name - service.get_collection_by_name.return_value = Promise.resolve(info) + service.get_collection_by_name.return_value = info.to_message() collection = dataset.collection(collection_name) - service.get_collection_by_name.assert_called_once_with(dataset._dataset.id, collection_name, True, True) + service.get_collection_by_name.assert_called_once_with( + GetCollectionByNameRequest( + dataset_id=uuid_to_uuid_message(dataset._dataset.id), + collection_name=collection_name, + with_availability=True, + with_count=True, + ) + ) assert collection_name in repr(collection), "Expected collection name to be in repr" @@ -162,6 +173,7 @@ def _mocked_collection() -> MockedCollection: count=None, ) + service.get_collection_by_name.return_value = collection_info.to_message() collection = dataset.collection(collection_info.collection.name) collection._info = collection_info return MockedCollection(dataset, dataset._dataset, collection, collection_info, service) @@ -183,7 +195,7 @@ def test_timeseries_dataset_collection_info_cache() -> None: collection = mocked.collection for _ in range(3): # call three times to ensure caching works - mocked.service.get_collection_by_name.return_value = Promise.resolve(info) + mocked.service.get_collection_by_name.return_value = info.to_message() assert collection.info() == info assert mocked.service.get_collection_by_name.call_count == 1, "Expected the info endpoint responses to be cached" @@ -196,9 +208,9 @@ def test_timeseries_dataset_collection_find(expected_datapoint: ExampleDatapoint mocked = _mocked_collection() collection = mocked.collection - message = AnyMessage(example_dataset_type_url(), expected_datapoint.SerializeToString()) + message = AnyMessage(example_dataset_type_url(), expected_datapoint.to_binary()) - mocked.service.query_by_id.return_value = Promise.resolve(message) + mocked.service.query_by_id.return_value = message.to_message() datapoint = collection.find(uuid_message_to_uuid(expected_datapoint.id)) assert isinstance(datapoint, xr.Dataset) @@ -237,9 +249,11 @@ def test_timeseries_dataset_find_multiple_collections(expected_datapoint: Exampl named_collection = CollectionInfo(Collection(uuid4(), "named-collection"), None, None) other_collection = CollectionInfo(Collection(uuid4(), "other-collection"), None, None) - service.get_collections.return_value = Promise.resolve([named_collection, other_collection]) - message = AnyMessage(example_dataset_type_url(), expected_datapoint.SerializeToString()) - service.query_by_id.return_value = Promise.resolve(message) + service.list_collections.return_value = ListCollectionsResponse( + data=[named_collection.to_message(), other_collection.to_message()] + ) + message = AnyMessage(example_dataset_type_url(), expected_datapoint.to_binary()) + service.query_by_id.return_value = message.to_message() datapoint_id = uuid_message_to_uuid(expected_datapoint.id) datapoint = dataset.find( @@ -250,15 +264,16 @@ def test_timeseries_dataset_find_multiple_collections(expected_datapoint: Exampl ) assert isinstance(datapoint, xr.Dataset) - service.get_collections.assert_called_once_with(dataset._dataset.id, True, True) - service.query_by_id.assert_called_once_with( - dataset._dataset.id, - [ - named_collection.collection.id, - ], - datapoint_id, - False, + service.list_collections.assert_called_once_with( + ListCollectionsRequest( + dataset_id=uuid_to_uuid_message(dataset._dataset.id), with_availability=True, with_count=True + ) ) + request = service.query_by_id.call_args.args[0] + assert request.dataset_id == uuid_to_uuid_message(dataset._dataset.id) + assert request.collection_ids == [uuid_to_uuid_message(named_collection.collection.id)] + assert request.id == uuid_to_uuid_message(datapoint_id) + assert not request.skip_data @settings(max_examples=1) @@ -270,8 +285,10 @@ def test_timeseries_dataset_query_multiple_collections(pages: list[QueryResultPa named_collection = CollectionInfo(Collection(uuid4(), "named-collection"), None, None) other_collection = CollectionInfo(Collection(uuid4(), "other-collection"), None, None) - service.get_collections.return_value = Promise.resolve([named_collection, other_collection]) - service.query.side_effect = [Promise.resolve(page) for page in pages] + service.list_collections.return_value = ListCollectionsResponse( + data=[named_collection.to_message(), other_collection.to_message()] + ) + service.query.side_effect = [page.to_message() for page in pages] interval = TimeInterval(datetime.now(), datetime.now() + timedelta(days=1)) queried = dataset.query( @@ -282,20 +299,22 @@ def test_timeseries_dataset_query_multiple_collections(pages: list[QueryResultPa ) _assert_datapoints_match(queried, pages) - service.get_collections.assert_called_once_with(dataset._dataset.id, True, True) - first_call_args = service.query.call_args_list[0][0] - assert first_call_args[0] == dataset._dataset.id - assert first_call_args[1] == [ - named_collection.collection.id, - ] + service.list_collections.assert_called_once_with( + ListCollectionsRequest( + dataset_id=uuid_to_uuid_message(dataset._dataset.id), with_availability=True, with_count=True + ) + ) + request = service.query.call_args_list[0].args[0] + assert request.dataset_id == uuid_to_uuid_message(dataset._dataset.id) + assert request.collection_ids == [uuid_to_uuid_message(named_collection.collection.id)] @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] + service.list_collections.return_value = ListCollectionsResponse() + service.query.side_effect = [page.to_message() for page in pages] interval = TimeInterval(datetime.now(), datetime.now() + timedelta(days=1)) dataset.query( @@ -303,7 +322,7 @@ def test_timeseries_dataset_query_filter(pages: list[QueryResultPage]) -> None: filter=(field("quality") >= 80) | field("quality").is_null(), ) - filters = service.query.call_args_list[0][0][2].to_message() + filters = service.query.call_args_list[0].args[0].filters assert len(filters.expressions) == 1 assert filters.expressions[0].logical.operands[0].comparison.field_name == "quality" @@ -319,7 +338,7 @@ def test_timeseries_dataset_query_rejects_invalid_filter_before_request() -> Non filter="quality > 80", # ty: ignore[invalid-argument-type] ) - service.get_collections.assert_not_called() + service.list_collections.assert_not_called() service.query.assert_not_called() @@ -342,7 +361,7 @@ def test_timeseries_dataset_collection_query( mocked = _mocked_collection() # each call will return the next page in the list # the last page will have an empty next_page set, indicating that there are no more pages - mocked.service.query.side_effect = [Promise.resolve(page) for page in pages] + mocked.service.query.side_effect = [page.to_message() for page in pages] # the interval doesn't actually matter here, since we mock the response interval = TimeInterval(datetime.now(), datetime.now() + timedelta(days=1)) dataset = mocked.collection.query(temporal_extent=interval, show_progress=show_progress) @@ -370,7 +389,7 @@ def test_timeseries_dataset_collection_find_interval( mocked = _mocked_collection() # each call will return the next page in the list # the last page will have an empty next_page set, indicating that there are no more pages - mocked.service.query.side_effect = [Promise.resolve(page) for page in pages] + mocked.service.query.side_effect = [page.to_message() for page in pages] # the interval doesn't actually matter here, since we mock the response dataset = mocked.collection._find_interval((uuid4(), uuid4()), show_progress=show_progress) _assert_datapoints_match(dataset, pages) @@ -399,31 +418,32 @@ def _assert_datapoints_match(dataset: xr.Dataset, pages: list[QueryResultPage]) assert str(uuid_message_to_uuid(datapoint_message.id)) == datapoint["id"] -class MockCollectionService(CollectionServiceStub): - """A mock implementation of the gRPC collection service, that stores collections in memory as a dict.""" +class MockCollectionService: + """A native collection service double that stores collections in memory.""" def __init__(self) -> None: self.collections: dict[str, CollectionInfoMessage] = {} - def CreateCollection(self, req: CreateCollectionRequest) -> CollectionInfoMessage: # noqa: N802 + def create_collection(self, req: CreateCollectionRequest) -> CollectionInfoMessage: collection = CollectionInfoMessage( collection=CollectionMessage(id=uuid_to_uuid_message(uuid4()), name=req.name), availability=None, count=None ) self.collections[req.name] = collection return collection - def GetCollectionByName(self, req: GetCollectionByNameRequest) -> CollectionInfoMessage: # noqa: N802 + def get_collection_by_name(self, req: GetCollectionByNameRequest) -> CollectionInfoMessage: if req.collection_name in self.collections: return self.collections[req.collection_name] raise NotFoundError(f"Collection {req.collection_name} not found") - def DeleteCollection(self, req: DeleteCollectionRequest) -> None: # noqa: N802 + def delete_collection(self, req: DeleteCollectionRequest) -> None: for collection in self.collections.values(): + assert collection.collection is not None if collection.collection.id == req.collection_id: del self.collections[collection.collection.name] return - def ListCollections(self, req: ListCollectionsRequest) -> ListCollectionsResponse: # noqa: N802 + def list_collections(self, req: ListCollectionsRequest) -> ListCollectionsResponse: _ = req return ListCollectionsResponse(data=list(self.collections.values()), owned_collections=len(self.collections)) @@ -443,10 +463,12 @@ class CollectionCRUDOperations(RuleBasedStateMachine): def __init__(self) -> None: super().__init__() - dataset_client, _ = _mocked_dataset() - dataset_client._service = TileboxDatasetService( - MagicMock(), MockCollectionService(), MagicMock(), MagicMock() - ) # mock the gRPC service + dataset_client, service = _mocked_dataset() + collections = MockCollectionService() + service.create_collection.side_effect = collections.create_collection + service.get_collection_by_name.side_effect = collections.get_collection_by_name + service.delete_collection.side_effect = collections.delete_collection + service.list_collections.side_effect = collections.list_collections self.dataset_client = dataset_client self.count_collections = 0 diff --git a/tilebox-datasets/tilebox/datasets/__init__.py b/tilebox-datasets/tilebox/datasets/__init__.py index 9ea4ae5..c3e3d60 100644 --- a/tilebox-datasets/tilebox/datasets/__init__.py +++ b/tilebox-datasets/tilebox/datasets/__init__.py @@ -1,7 +1,5 @@ from typing import TYPE_CHECKING, Any - -# Apply native logging defaults before any submodule can initialize gRPC. -import _tilebox.grpc # noqa: F401 +from warnings import warn if TYPE_CHECKING: from tilebox.datasets.aio.timeseries import TimeseriesCollection, TimeseriesDataset @@ -25,6 +23,15 @@ def __getattr__(name: str) -> Any: # PEP 562 module __getattr__ is supported since Python 3.7. Keep these aliases lazy so importing a focused # submodule like tilebox.datasets.query.id_interval does not also import the sync/aio clients and their data-model # dependencies. + if name in {"TimeseriesCollection", "TimeseriesDataset"}: + replacement = "CollectionClient" if name == "TimeseriesCollection" else "DatasetClient" + warn( + f"tilebox.datasets.{name} is deprecated; this legacy alias is asynchronous. " + f"Use tilebox.datasets.aio.{replacement} for the same class, " + f"or tilebox.datasets.{replacement} for synchronous code.", + DeprecationWarning, + stacklevel=2, + ) match name: case "Client": from tilebox.datasets.sync.client import Client # noqa: PLC0415 diff --git a/tilebox-datasets/tilebox/datasets/aio/client.py b/tilebox-datasets/tilebox/datasets/aio/client.py index db73af8..add76db 100644 --- a/tilebox-datasets/tilebox/datasets/aio/client.py +++ b/tilebox-datasets/tilebox/datasets/aio/client.py @@ -1,163 +1,101 @@ -import os +"""Asynchronous datasets API backed by native Connect clients.""" + +from collections.abc import Sequence +from typing import Any, Self from uuid import UUID -from _tilebox.grpc.aio.channel import open_channel -from _tilebox.grpc.aio.error import with_pythonic_errors -from _tilebox.grpc.channel import ( - AsyncConnectStubAdapter, - Transport, - connect_address, -) -from _tilebox.grpc.error import NotFoundError -from _tilebox.grpc.public import _PUBLIC_RPC_METHOD_PREFIX +from _tilebox.client import AsyncConnection, Transport +from _tilebox.errors import NotFoundError from tilebox.datasets.aio.dataset import DatasetClient -from tilebox.datasets.client import _TILEBOX_API_KEY_ENV_VAR, _TILEBOX_API_URL, _TILEBOX_DEV_API_URL -from tilebox.datasets.client import Client as BaseClient -from tilebox.datasets.data.datasets import DatasetKind, FieldDict -from tilebox.datasets.datasets.v1.collections_pb2_grpc import CollectionServiceStub -from tilebox.datasets.datasets.v1.data_access_pb2_grpc import DataAccessServiceStub -from tilebox.datasets.datasets.v1.data_ingestion_pb2_grpc import DataIngestionServiceStub -from tilebox.datasets.datasets.v1.datasets_pb2_grpc import DatasetServiceStub +from tilebox.datasets.client import _construct_root_group, _log_server_message, connection_options +from tilebox.datasets.data.datasets import Dataset, DatasetKind, DatasetType, FieldDict, ListDatasetsResponse from tilebox.datasets.group import Group -from tilebox.datasets.notices import print_notice -from tilebox.datasets.service import TileboxDatasetService +from tilebox.datasets.uuid import as_uuid, uuid_to_uuid_message +from tilebox.datasets.v1.datasets_connect import DatasetServiceClient +from tilebox.datasets.v1.datasets_pb import ( + CreateDatasetRequest, + GetDatasetRequest, + ListDatasetsRequest, + UpdateDatasetRequest, +) class Client: + """Access datasets using asynchronous Connect RPCs.""" + def __init__( self, *, url: str | None = None, token: str | None = None, warn_if_unauthenticated: bool = True, - transport: Transport = "grpc", + transport: Transport = "http2", + interceptors: Sequence[Any] = (), ) -> None: - """ - Create a Tilebox datasets client. + """Use Connect over HTTP/2 by default, or force HTTP/1 with transport="http1". - Args: - url: Tilebox API URL. If not set, uses the `TILEBOX_API_URL` environment variable, - or defaulting to "https://api.tilebox.com". - token: The API Key to authenticate with. If not set the `TILEBOX_API_KEY` environment variable will be used. - If no token is provided or found, anonymous open data access will be used. - warn_if_unauthenticated: Whether to warn if no API key is provided and the client is used with the default - Tilebox API URL. Defaults to True. - transport: Network transport to use for API requests. Defaults to "grpc". Use "http1" to force - the Connect protocol over HTTP/1.1 for networks that do not support gRPC over HTTP/2 correctly. + The legacy transport="grpc" spelling is an alias for "http2". """ - if url is None: - url = os.environ.get("TILEBOX_API_URL") or _TILEBOX_API_URL - url = url.removesuffix("/") - - if token is None: - token = os.environ.get(_TILEBOX_API_KEY_ENV_VAR, None) - - is_tilebox_deployment = url in (_TILEBOX_API_URL, _TILEBOX_DEV_API_URL) - if token is None and is_tilebox_deployment and warn_if_unauthenticated: - print_notice( - "[yellow]" - "No Tilebox API key detected. Using [bold]anonymous open data access[/bold] without authentication. " - "For higher throughput and rate limits, sign up for a free account at https://console.tilebox.com." - "[/yellow]" - ) + url, token, prefix = connection_options(url, token, warn_if_unauthenticated) + self._connection = AsyncConnection(url, token, prefix, transport=transport, interceptors=interceptors) + self._datasets = self._connection.client(DatasetServiceClient) - rpc_method_prefix = _PUBLIC_RPC_METHOD_PREFIX if (is_tilebox_deployment and token is None) else None - match transport: - case "grpc": - channel = open_channel(url, token, rpc_method_prefix=rpc_method_prefix) - dataset_service_stub = with_pythonic_errors(DatasetServiceStub(channel)) - collection_service_stub = with_pythonic_errors(CollectionServiceStub(channel)) - data_access_service_stub = with_pythonic_errors(DataAccessServiceStub(channel)) - data_ingestion_service_stub = with_pythonic_errors(DataIngestionServiceStub(channel)) - case "http1": - from pyqwest import Client as HTTPClient # noqa: PLC0415 - from pyqwest import HTTPTransport, HTTPVersion # noqa: PLC0415 + async def close(self) -> None: + """Release the connection's HTTP resources.""" + await self._connection.close() - from tilebox.datasets.datasets.v1.collections_connect import CollectionServiceClient # noqa: PLC0415 - from tilebox.datasets.datasets.v1.data_access_connect import DataAccessServiceClient # noqa: PLC0415 - from tilebox.datasets.datasets.v1.data_ingestion_connect import ( # noqa: PLC0415 - DataIngestionServiceClient, - ) - from tilebox.datasets.datasets.v1.datasets_connect import DatasetServiceClient # noqa: PLC0415 + async def __aenter__(self) -> Self: + return self - address = connect_address(url, rpc_method_prefix) - http_client = HTTPClient(HTTPTransport(http_version=HTTPVersion.HTTP1, tls_include_system_certs=True)) - headers = None if token is None else {"authorization": f"Bearer {token}"} - dataset_service_stub = AsyncConnectStubAdapter( - DatasetServiceClient(address, http_client=http_client), - headers, - rpc_method_prefix, - ) - collection_service_stub = AsyncConnectStubAdapter( - CollectionServiceClient(address, http_client=http_client), - headers, - rpc_method_prefix, - ) - data_access_service_stub = AsyncConnectStubAdapter( - DataAccessServiceClient(address, http_client=http_client), - headers, - rpc_method_prefix, - ) - data_ingestion_service_stub = AsyncConnectStubAdapter( - DataIngestionServiceClient(address, http_client=http_client), - headers, - rpc_method_prefix, - ) - case _: - raise ValueError(f"Unsupported transport: {transport}") - service = TileboxDatasetService( - dataset_service_stub, collection_service_stub, data_access_service_stub, data_ingestion_service_stub - ) - self._client = BaseClient(service) + async def __aexit__(self, *_: object) -> None: + await self.close() async def create_or_update_dataset( - self, - kind: DatasetKind, - code_name: str, - fields: list[FieldDict] | None = None, - *, - name: str | None = None, + self, kind: DatasetKind, code_name: str, fields: list[FieldDict] | None = None, *, name: str | None = None ) -> DatasetClient: - """Create a new dataset. - - Args: - kind: The kind of the dataset. - code_name: The code name of the dataset. - fields: The custom fields of the dataset. Supported generated - message field types are available from :mod:`tilebox.datasets.schema`. - name: The name of the dataset. Defaults to the code name. - - Returns: - The created dataset. - """ - + """Create a dataset, or update it if the slug already exists.""" try: - dataset = await self.dataset(code_name) + existing = Dataset.from_message(await self._datasets.get_dataset(GetDatasetRequest(slug=code_name))) except NotFoundError: - return await self._client.create_dataset(kind, code_name, fields or [], name or code_name, DatasetClient) - - return await self._client.update_dataset( - kind, - dataset._dataset.id, # noqa: SLF001 - fields or [], - name or dataset._dataset.name, # noqa: SLF001 - DatasetClient, - ) + dataset = Dataset.from_message( + await self._datasets.create_dataset( + CreateDatasetRequest( + code_name=code_name, + name=name or code_name, + type=DatasetType.from_fields(kind, fields or []).to_message(), + ) + ) + ) + else: + dataset = Dataset.from_message( + await self._datasets.update_dataset( + UpdateDatasetRequest( + id=uuid_to_uuid_message(existing.id), + name=name or existing.name, + type=DatasetType.from_fields(kind, fields or []).to_message(), + ) + ) + ) + return DatasetClient(self._connection, dataset) async def datasets(self) -> Group: - """Fetch all available datasets.""" - return await self._client.datasets(DatasetClient) + """Fetch the hierarchy of accessible datasets.""" + response = ListDatasetsResponse.from_message(await self._datasets.list_datasets(ListDatasetsRequest())) + _log_server_message(response) + return _construct_root_group( + [DatasetClient(self._connection, dataset) for dataset in response.datasets], response.groups + ) async def dataset(self, slug: str) -> DatasetClient: - """Get a dataset by its slug, e.g. `open_data.copernicus.sentinel1_sar`. - - Args: - slug: The slug of the dataset. - - Returns: - The dataset if it exists. - """ - return await self._client.dataset(slug, DatasetClient) + """Fetch a dataset by slug.""" + return DatasetClient( + self._connection, Dataset.from_message(await self._datasets.get_dataset(GetDatasetRequest(slug=slug))) + ) async def _dataset_by_id(self, dataset_id: str | UUID) -> DatasetClient: - return await self._client._dataset_by_id(dataset_id, DatasetClient) # noqa: SLF001 + return DatasetClient( + self._connection, + Dataset.from_message( + await self._datasets.get_dataset(GetDatasetRequest(id=uuid_to_uuid_message(as_uuid(dataset_id)))) + ), + ) diff --git a/tilebox-datasets/tilebox/datasets/aio/dataset.py b/tilebox-datasets/tilebox/datasets/aio/dataset.py index 6de23fd..631746e 100644 --- a/tilebox-datasets/tilebox/datasets/aio/dataset.py +++ b/tilebox-datasets/tilebox/datasets/aio/dataset.py @@ -1,20 +1,23 @@ from collections.abc import AsyncIterator +from dataclasses import replace from functools import partial -from typing import cast from uuid import UUID from warnings import warn import xarray as xr +from protobuf import Message from tqdm.auto import tqdm -from _tilebox.grpc.aio.pagination import Pagination as PaginationProtocol -from _tilebox.grpc.aio.pagination import paginated_request -from _tilebox.grpc.aio.producer_consumer import async_producer_consumer -from _tilebox.grpc.error import ArgumentError, NotFoundError +from _tilebox.aio.concurrency import async_producer_consumer +from _tilebox.aio.pagination import Pagination as PaginationProtocol +from _tilebox.aio.pagination import paginated_request +from _tilebox.client import AsyncConnection +from _tilebox.errors import ArgumentError, NotFoundError +from _tilebox.proto.tilebox.v1.id_pb import ID from tilebox.datasets.aio.pagination import with_progressbar, with_time_progress_callback, with_time_progressbar from tilebox.datasets.data.collection import Collection, CollectionInfo from tilebox.datasets.data.data_access import QueryFilters, SpatialFilter, SpatialFilterLike -from tilebox.datasets.data.datapoint import QueryResultPage +from tilebox.datasets.data.datapoint import DATAPOINT_METADATA_FIELDS, AnyMessage, IngestResponse, QueryResultPage from tilebox.datasets.data.datasets import Dataset from tilebox.datasets.message_pool import get_message_type from tilebox.datasets.progress import ProgressCallback @@ -30,8 +33,18 @@ 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 -from tilebox.datasets.service import TileboxDatasetService -from tilebox.datasets.uuid import as_uuid +from tilebox.datasets.uuid import as_uuid, must_uuid_to_uuid_message, uuid_to_uuid_message +from tilebox.datasets.v1.collections_connect import CollectionServiceClient +from tilebox.datasets.v1.collections_pb import ( + CreateCollectionRequest, + DeleteCollectionRequest, + GetCollectionByNameRequest, + ListCollectionsRequest, +) +from tilebox.datasets.v1.data_access_connect import DataAccessServiceClient +from tilebox.datasets.v1.data_access_pb import QueryByIDRequest, QueryRequest +from tilebox.datasets.v1.data_ingestion_connect import DataIngestionServiceClient +from tilebox.datasets.v1.data_ingestion_pb import DeleteRequest, IngestRequest # allow private member access: we allow it here because we want to make as much private as possible so that we can # minimize the publicly facing API (which allows us to change internals later, and also limits to auto-completion) @@ -43,12 +56,15 @@ class DatasetClient: def __init__( self, - service: TileboxDatasetService, + connection: AsyncConnection, dataset: Dataset, ) -> None: - self._service = service + self._collections = connection.client(CollectionServiceClient) + self._data_access = connection.client(DataAccessServiceClient) + self._data_ingestion = connection.client(DataIngestionServiceClient) self.name = dataset.name self._dataset = dataset + self._message_type = get_message_type(dataset.type) async def collections( self, availability: bool | None = None, count: bool | None = None @@ -78,7 +94,12 @@ async def collections( stacklevel=2, ) - collections = await self._service.get_collections(self._dataset.id, True, True) + response = await self._collections.list_collections( + ListCollectionsRequest( + dataset_id=uuid_to_uuid_message(self._dataset.id), with_availability=True, with_count=True + ) + ) + collections = [CollectionInfo.from_message(collection) for collection in response.data] return {collection.collection.name: CollectionClient(self, collection) for collection in collections} @@ -106,7 +127,11 @@ async def create_collection(self, name: str) -> "CollectionClient": Returns: The created collection. """ - info = await self._service.create_collection(self._dataset.id, name) + info = CollectionInfo.from_message( + await self._collections.create_collection( + CreateCollectionRequest(dataset_id=uuid_to_uuid_message(self._dataset.id), name=name) + ) + ) return CollectionClient(self, info) async def collection(self, name: str) -> "CollectionClient": @@ -119,7 +144,16 @@ async def collection(self, name: str) -> "CollectionClient": The collection with the given name. """ try: - info = await self._service.get_collection_by_name(self._dataset.id, name, True, True) + info = CollectionInfo.from_message( + await self._collections.get_collection_by_name( + GetCollectionByNameRequest( + dataset_id=uuid_to_uuid_message(self._dataset.id), + collection_name=name, + with_availability=True, + with_count=True, + ) + ) + ) except NotFoundError: raise NotFoundError(f"No such collection {name}") from None @@ -138,7 +172,11 @@ async def delete_collection(self, collection: "str | UUID | CollectionClient") - else: # str collection_id = (await self.collection(collection))._collection.id - await self._service.delete_collection(self._dataset.id, collection_id) + await self._collections.delete_collection( + DeleteCollectionRequest( + dataset_id=uuid_to_uuid_message(self._dataset.id), collection_id=uuid_to_uuid_message(collection_id) + ) + ) async def find( self, @@ -161,21 +199,26 @@ async def find( """ collection_ids = await self._collection_ids(collections) try: - datapoint = await self._service.query_by_id( - self._dataset.id, - collection_ids, - as_uuid(datapoint_id), - skip_data, + datapoint = AnyMessage.from_message( + await self._data_access.query_by_id( + QueryByIDRequest( + dataset_id=must_uuid_to_uuid_message(self._dataset.id), + collection_ids=list(map(must_uuid_to_uuid_message, collection_ids)), + id=must_uuid_to_uuid_message(as_uuid(datapoint_id)), + skip_data=skip_data, + ) + ) ) except ArgumentError: raise ValueError(f"Invalid datapoint id: {datapoint_id} is not a valid UUID") from None except NotFoundError: raise NotFoundError(f"No such datapoint {datapoint_id}") from None - message_type = get_message_type(datapoint.type_url) - data = message_type.FromString(datapoint.value) + data = self._message_type.from_binary(datapoint.value) - converter = MessageToXarrayConverter(initial_capacity=1) + converter = MessageToXarrayConverter( + initial_capacity=1, fields=DATAPOINT_METADATA_FIELDS if skip_data else None + ) converter.convert(data) return converter.finalize("time", skip_empty_fields=skip_data).isel(time=0) @@ -213,7 +256,7 @@ async def query( # noqa: PLR0913 collection_ids = await self._collection_ids(collections) pages = _iter_query_pages( - self._service, + self._data_access, self._dataset.id, collection_ids, temporal_extent, @@ -221,6 +264,7 @@ async def query( # noqa: PLR0913 skip_data, filter_expression=filter, dataset_name=self.name, + message_type=self._message_type, show_progress=show_progress, ) return await _convert_to_dataset(pages, skip_empty_fields=skip_data) @@ -241,7 +285,7 @@ async def _collection_ids( if collections is None: return [] - all_collections: list[CollectionInfo] = await self._service.get_collections(self._dataset.id, True, True) + all_collections = [await collection.info() for collection in (await self.collections()).values()] # find all valid collection names and ids collections_by_name = {c.collection.name: c.collection.id for c in all_collections} valid_collection_ids = {c.collection.id for c in all_collections} @@ -321,11 +365,15 @@ async def info(self, availability: bool | None = None, count: bool | None = None if self._info is None: # only load collection info if it hasn't been loaded yet (or it has been invalidated) try: - self._info = cast( - CollectionInfo, - await self._dataset._service.get_collection_by_name( - self._dataset._dataset.id, self.name, True, True - ), + self._info = CollectionInfo.from_message( + await self._dataset._collections.get_collection_by_name( + GetCollectionByNameRequest( + dataset_id=uuid_to_uuid_message(self._dataset._dataset.id), + collection_name=self.name, + with_availability=True, + with_count=True, + ) + ) ) except NotFoundError: raise NotFoundError(f"No such collection {self.name}") from None @@ -344,18 +392,26 @@ async def find(self, datapoint_id: str | UUID, skip_data: bool = False) -> xr.Da The datapoint as an xarray dataset """ try: - datapoint = await self._dataset._service.query_by_id( - self._dataset._dataset.id, [self._collection.id], as_uuid(datapoint_id), skip_data + datapoint = AnyMessage.from_message( + await self._dataset._data_access.query_by_id( + QueryByIDRequest( + dataset_id=must_uuid_to_uuid_message(self._dataset._dataset.id), + collection_ids=[must_uuid_to_uuid_message(self._collection.id)], + id=must_uuid_to_uuid_message(as_uuid(datapoint_id)), + skip_data=skip_data, + ) + ) ) except ArgumentError: raise ValueError(f"Invalid datapoint id: {datapoint_id} is not a valid UUID") from None except NotFoundError: raise NotFoundError(f"No such datapoint {datapoint_id}") from None - message_type = get_message_type(datapoint.type_url) - data = message_type.FromString(datapoint.value) + data = self._dataset._message_type.from_binary(datapoint.value) - converter = MessageToXarrayConverter(initial_capacity=1) + converter = MessageToXarrayConverter( + initial_capacity=1, fields=DATAPOINT_METADATA_FIELDS if skip_data else None + ) converter.convert(data) return converter.finalize("time", skip_empty_fields=skip_data).isel(time=0) @@ -384,12 +440,13 @@ async def _find_interval( async def request(page: PaginationProtocol) -> QueryResultPage: return await _query_page( - self._dataset._service, + self._dataset._data_access, self._dataset._dataset.id, [self._collection.id], filters, skip_data, page, + message_type=self._dataset._message_type, ) initial_page = Pagination() @@ -485,7 +542,7 @@ async def query( raise ValueError("A temporal_extent for your query must be specified") pages = _iter_query_pages( - self._dataset._service, + self._dataset._data_access, self._dataset._dataset.id, [self._collection.id], temporal_extent, @@ -493,6 +550,7 @@ async def query( skip_data, filter_expression=filter, dataset_name=self._dataset.name, + message_type=self._dataset._message_type, show_progress=show_progress, ) return await _convert_to_dataset(pages, skip_empty_fields=skip_data) @@ -506,13 +564,14 @@ async def _iter_pages( page_size: int | None = None, ) -> AsyncIterator[QueryResultPage]: async for page in _iter_query_pages( - self._dataset._service, + self._dataset._data_access, self._dataset._dataset.id, [self._collection.id], temporal_extent, spatial_extent, skip_data, dataset_name=self._dataset.name, + message_type=self._dataset._message_type, show_progress=show_progress, page_size=page_size, ): @@ -543,7 +602,7 @@ async def ingest( Returns: List of datapoint ids that were ingested. """ - message_type = get_message_type(self._dataset._dataset.type.type_url) + message_type = self._dataset._message_type messages = marshal_messages( to_messages(data, message_type, required_fields=["time"], ignore_fields=["id", "ingestion_time"]) ) @@ -559,7 +618,15 @@ async def ingest( ) as progress_bar: for chunk_start in range(0, len(messages), _INGEST_CHUNK_SIZE): chunk = messages[chunk_start : chunk_start + _INGEST_CHUNK_SIZE] - response = await self._dataset._service.ingest(self._collection.id, chunk, allow_existing) + response = IngestResponse.from_message( + await self._dataset._data_ingestion.ingest( + IngestRequest( + collection_id=uuid_to_uuid_message(self._collection.id), + values=chunk, + allow_existing=allow_existing, + ) + ) + ) ingested_ids.extend(response.datapoint_ids) progress_bar.update(len(chunk)) @@ -602,7 +669,14 @@ async def delete(self, datapoints: DatapointIDs, *, show_progress: bool | Progre ) as progress_bar: for chunk_start in range(0, len(datapoint_ids), _DELETE_CHUNK_SIZE): chunk = datapoint_ids[chunk_start : chunk_start + _DELETE_CHUNK_SIZE] - num_deleted += await self._dataset._service.delete(self._collection.id, chunk) + num_deleted += ( + await self._dataset._data_ingestion.delete( + DeleteRequest( + collection_id=uuid_to_uuid_message(self._collection.id), + datapoint_ids=[ID(uuid=value.bytes) for value in chunk], + ) + ) + ).num_deleted progress_bar.update(len(chunk)) if callable(show_progress): @@ -612,19 +686,34 @@ async def delete(self, datapoints: DatapointIDs, *, show_progress: bool | Progre async def _query_page( # noqa: PLR0913, PLR0917 - service: TileboxDatasetService, + service: DataAccessServiceClient, dataset_id: UUID, collection_ids: list[UUID] | None, filters: QueryFilters, skip_data: bool, page: PaginationProtocol | None = None, + *, + message_type: type[Message], ) -> QueryResultPage: query_page = Pagination(page.limit, page.starting_after) if page else Pagination() - return await service.query(dataset_id, collection_ids or [], filters, skip_data, query_page) + return replace( + QueryResultPage.from_message( + await service.query( + QueryRequest( + dataset_id=must_uuid_to_uuid_message(dataset_id), + collection_ids=list(map(must_uuid_to_uuid_message, collection_ids or [])), + filters=filters.to_message(), + skip_data=skip_data, + page=query_page.to_message(), + ) + ) + ), + message_type=message_type, + ) async def _iter_query_pages( # noqa: PLR0913, PLR0917 - service: TileboxDatasetService, + service: DataAccessServiceClient, dataset_id: UUID, collection_ids: list[UUID] | None, temporal_extent: TimeIntervalLike, @@ -633,6 +722,7 @@ async def _iter_query_pages( # noqa: PLR0913, PLR0917 *, filter_expression: Expression | None = None, dataset_name: str, + message_type: type[Message], show_progress: bool | ProgressCallback = False, page_size: int | None = None, ) -> AsyncIterator[QueryResultPage]: @@ -643,7 +733,7 @@ async def _iter_query_pages( # noqa: PLR0913, PLR0917 filter_expression, ) - request = partial(_query_page, service, dataset_id, collection_ids, filters, skip_data) + request = partial(_query_page, service, dataset_id, collection_ids, filters, skip_data, message_type=message_type) initial_page = Pagination(limit=page_size) pages = paginated_request(request, initial_page) @@ -670,11 +760,14 @@ async def _convert_to_dataset(pages: AsyncIterator[QueryResultPage], skip_empty_ Returns: The datapoints from the individual pages converted and combined into a single xarray dataset """ - converter = MessageToXarrayConverter() + converter = MessageToXarrayConverter(fields=DATAPOINT_METADATA_FIELDS if skip_empty_fields else None) def convert_page(page: QueryResultPage) -> None: - message_type = get_message_type(page.data.type_url) - messages = [message_type.FromString(v) for v in page.data.value] + if not page.data.value: + return + if page.message_type is None: + raise ValueError("Query page is not bound to a dataset schema") + messages = [page.message_type.from_binary(v) for v in page.data.value] converter.convert_all(messages) # lets parse the incoming pages already while we wait for the next page from the server diff --git a/tilebox-datasets/tilebox/datasets/aio/pagination.py b/tilebox-datasets/tilebox/datasets/aio/pagination.py index 9965508..b2292bc 100644 --- a/tilebox-datasets/tilebox/datasets/aio/pagination.py +++ b/tilebox-datasets/tilebox/datasets/aio/pagination.py @@ -16,7 +16,7 @@ async def with_progressbar( paginated_request: AsyncIterator[ResultPage], progress_description: str, ) -> AsyncIterator[ResultPage]: - """Make a paginated request to a gRPC service endpoint while displaying the progress in a tqdm progress bar. + """Consume paginated RPC results while displaying progress in a tqdm progress bar. We don't know the total amount of work beforehand, so the progress bar will just show the number of data points that have been downloaded so far. @@ -54,7 +54,7 @@ async def with_time_progressbar( interval: TimeInterval, progress_description: str, ) -> AsyncIterator[ResultPage]: - """Make a paginated request to a gRPC service endpoint while displaying the progress in a tqdm progress bar. + """Consume paginated RPC results while displaying progress in a tqdm progress bar. The given interval is used to estimate a total amount of work for the progress bar. Then the event_time of the latest data point returned in each page is used to update the progress bar accordingly. @@ -108,7 +108,7 @@ async def with_time_progress_callback( interval: TimeInterval, progress_callback: ProgressCallback, ) -> AsyncIterator[ResultPage]: - """Make a paginated request to a gRPC service endpoint, and reporting progress percentage to a callback function. + """Consume paginated RPC results and report progress percentages to a callback. The given interval is used to estimate a total amount of work for the progress bar. Then the event_time of the latest data point returned in each page is used to calculate progress percentage. diff --git a/tilebox-datasets/tilebox/datasets/assets/assets.py b/tilebox-datasets/tilebox/datasets/assets/assets.py index 9990428..a29d740 100644 --- a/tilebox-datasets/tilebox/datasets/assets/assets.py +++ b/tilebox-datasets/tilebox/datasets/assets/assets.py @@ -13,7 +13,7 @@ import xarray as xr from protobuf import Message -from tilebox.datasets.datasets.stac.v1.asset_metadata_pb import ( +from tilebox.datasets.stac.v1.asset_metadata_pb import ( ClassificationBitField, ClassificationClass, EOCommonName, @@ -24,33 +24,33 @@ RasterSampling, View, ) -from tilebox.datasets.datasets.stac.v1.asset_pb import ( +from tilebox.datasets.stac.v1.asset_pb import ( Asset as ProtoAsset, ) -from tilebox.datasets.datasets.stac.v1.asset_pb import ( +from tilebox.datasets.stac.v1.asset_pb import ( AssetAccessProfile, Assets, DataType, KnownAssetRole, Statistics, ) -from tilebox.datasets.datasets.stac.v1.asset_pb import ( +from tilebox.datasets.stac.v1.asset_pb import ( AssetLocation as ProtoAssetLocation, ) -from tilebox.datasets.datasets.stac.v1.asset_pb import ( +from tilebox.datasets.stac.v1.asset_pb import ( Band as ProtoBand, ) -from tilebox.datasets.datasets.stac.v1.authentication_pb import Authentication, AuthenticationScheme -from tilebox.datasets.datasets.stac.v1.core_pb import KnownMediaType -from tilebox.datasets.datasets.stac.v1.core_pb import MediaType as ProtoMediaType -from tilebox.datasets.datasets.stac.v1.product_pb import ProductProperties -from tilebox.datasets.datasets.stac.v1.sar_pb import ( +from tilebox.datasets.stac.v1.authentication_pb import Authentication, AuthenticationScheme +from tilebox.datasets.stac.v1.core_pb import KnownMediaType +from tilebox.datasets.stac.v1.core_pb import MediaType as ProtoMediaType +from tilebox.datasets.stac.v1.product_pb import ProductProperties +from tilebox.datasets.stac.v1.sar_pb import ( SARFrequencyBand, SARObservationDirection, SARProperties, ) -from tilebox.datasets.datasets.stac.v1.satellite_pb import SatelliteProperties -from tilebox.datasets.datasets.stac.v1.storage_pb import Storage, StorageScheme +from tilebox.datasets.stac.v1.satellite_pb import SatelliteProperties +from tilebox.datasets.stac.v1.storage_pb import Storage, StorageScheme _ASSETS_MESSAGE_NAME = "datasets.stac.v1.Assets" _STORAGE_MESSAGE_NAME = "datasets.stac.v1.Storage" diff --git a/tilebox-datasets/tilebox/datasets/client.py b/tilebox-datasets/tilebox/datasets/client.py index 07abfe0..0b3f18b 100644 --- a/tilebox-datasets/tilebox/datasets/client.py +++ b/tilebox-datasets/tilebox/datasets/client.py @@ -1,123 +1,45 @@ -from typing import Any, Protocol, TypeVar -from uuid import UUID +"""Configuration and result assembly shared by synchronous and asynchronous clients.""" -from promise import Promise +import os +from typing import Any -from tilebox.datasets.data.datasets import Dataset, DatasetGroup, DatasetKind, FieldDict, ListDatasetsResponse +from tilebox.datasets.data.datasets import DatasetGroup, ListDatasetsResponse from tilebox.datasets.group import Group -from tilebox.datasets.message_pool import register_once from tilebox.datasets.notices import print_notice -from tilebox.datasets.service import TileboxDatasetService -from tilebox.datasets.uuid import as_uuid _TILEBOX_API_URL = "https://api.tilebox.com" _TILEBOX_DEV_API_URL = "https://api.tilebox.dev" _TILEBOX_API_KEY_ENV_VAR = "TILEBOX_API_KEY" -class TimeseriesDatasetLike(Protocol): - def __init__(self, service: TileboxDatasetService, dataset: Dataset) -> None: - pass - - -T = TypeVar("T", bound=TimeseriesDatasetLike) - - -class Client: - def __init__(self, service: TileboxDatasetService) -> None: - self._service = service - - def create_dataset( - self, - kind: DatasetKind, - code_name: str, - fields: list[FieldDict] | None, - name: str | None, - py_dataset_class: type[T], - ) -> Promise[T]: - return ( - self._service.create_dataset(kind, code_name, name or code_name, fields or []) - .then(_ensure_registered) - .then(lambda dataset: py_dataset_class(self._service, dataset)) - ) - - def update_dataset( - self, - kind: DatasetKind, - dataset_id: UUID, - fields: list[FieldDict] | None, - name: str | None, - py_dataset_class: type[T], - ) -> Promise[T]: - return ( - self._service.update_dataset(kind, dataset_id, name, fields or []) - .then(_ensure_registered) - .then(lambda dataset: py_dataset_class(self._service, dataset)) - ) - - def datasets(self, dataset_type: type[T]) -> Promise[Group]: - return ( - self._service.list_datasets() - .then(_log_server_message) - .then(_ensure_all_registered) - .then( - lambda response: _construct_root_group( - [dataset_type(self._service, dataset) for dataset in response.datasets], response.groups - ) - ) - ) - - def dataset(self, slug: str, dataset_type: type[T]) -> Promise[T]: - return ( - self._service.get_dataset_by_slug(slug) - .then(_ensure_registered) - .then(lambda dataset: dataset_type(self._service, dataset)) - ) - - def _dataset_by_id(self, dataset_id: str | UUID, dataset_type: type[T]) -> Promise[T]: - return ( - self._service.get_dataset_by_id(as_uuid(dataset_id)) - .then(_ensure_registered) - .then(lambda dataset: dataset_type(self._service, dataset)) +def connection_options( + url: str | None, token: str | None, warn_if_unauthenticated: bool +) -> tuple[str, str | None, str | None]: + """Resolve credentials and anonymous public routing for Tilebox deployments.""" + url = (url or os.environ.get("TILEBOX_API_URL") or _TILEBOX_API_URL).rstrip("/") + if token is None: + token = os.environ.get(_TILEBOX_API_KEY_ENV_VAR) + public = url in (_TILEBOX_API_URL, _TILEBOX_DEV_API_URL) and token is None + if public and warn_if_unauthenticated: + print_notice( + "[yellow]No Tilebox API key detected. Using [bold]anonymous open data access[/bold] without authentication. " + "For higher throughput and rate limits, sign up for a free account at https://console.tilebox.com.[/yellow]" ) + return url, token, "/public" if public else None -def _log_server_message(response: ListDatasetsResponse) -> ListDatasetsResponse: +def _log_server_message(response: ListDatasetsResponse) -> None: if response.server_message: print_notice(response.server_message + "\n") - return response - - -def _ensure_registered(dataset: Dataset) -> Dataset: - register_once(dataset.type) - return dataset - - -def _ensure_all_registered(response: ListDatasetsResponse) -> ListDatasetsResponse: - for dataset in response.datasets: - register_once(dataset.type) - return response -def _construct_root_group( - datasets: list[Any], - groups: list[DatasetGroup], -) -> Group: +def _construct_root_group(datasets: list[Any], groups: list[DatasetGroup]) -> Group: root = Group() group_lookup = {g.id: Group() for g in groups} - - # recursively nest groups based on their parent_id - for g in groups: - parent = group_lookup[g.parent_id] if g.parent_id is not None else root - parent._add(g.code_name, group_lookup[g.id]) # noqa: SLF001 - - # add datasets to their respective groups - for d in datasets: - dataset = d._dataset # noqa: SLF001 - group = group_lookup[dataset.group_id] - group._add(dataset.code_name, d) # noqa: SLF001 - + for group in groups: + parent = group_lookup[group.parent_id] if group.parent_id is not None else root + parent._add(group.code_name, group_lookup[group.id]) # noqa: SLF001 + for client in datasets: + dataset = client._dataset # noqa: SLF001 + group_lookup.get(dataset.group_id, root)._add(dataset.code_name, client) # noqa: SLF001 return root - - -__all__ = ["Client"] diff --git a/tilebox-datasets/tilebox/datasets/data/collection.py b/tilebox-datasets/tilebox/datasets/data/collection.py index 34fdde1..37d6478 100644 --- a/tilebox-datasets/tilebox/datasets/data/collection.py +++ b/tilebox-datasets/tilebox/datasets/data/collection.py @@ -1,9 +1,9 @@ from dataclasses import dataclass from uuid import UUID -from tilebox.datasets.datasets.v1 import core_pb2 from tilebox.datasets.query.time_interval import TimeInterval from tilebox.datasets.uuid import uuid_message_to_uuid, uuid_to_uuid_message +from tilebox.datasets.v1 import core_pb @dataclass @@ -14,11 +14,11 @@ class Collection: name: str @classmethod - def from_message(cls, collection: core_pb2.Collection) -> "Collection": + def from_message(cls, collection: core_pb.Collection) -> "Collection": return Collection(id=uuid_message_to_uuid(collection.id), name=collection.name) - def to_message(self) -> core_pb2.Collection: - return core_pb2.Collection(id=uuid_to_uuid_message(self.id), name=self.name) + def to_message(self) -> core_pb.Collection: + return core_pb.Collection(id=uuid_to_uuid_message(self.id), name=self.name) @dataclass @@ -30,7 +30,7 @@ class CollectionInfo: count: int | None @classmethod - def from_message(cls, info: core_pb2.CollectionInfo) -> "CollectionInfo": + def from_message(cls, info: core_pb.CollectionInfo) -> "CollectionInfo": """ Convert a CollectionInfo protobuf message to a CollectionInfo object. @@ -41,16 +41,18 @@ def from_message(cls, info: core_pb2.CollectionInfo) -> "CollectionInfo": don't know whether it has data points or not. Because the protobuf message availability field is None for both of those cases. """ + if info.collection is None: + raise ValueError("Collection info is missing its collection") return CollectionInfo( collection=Collection.from_message(info.collection), - availability=TimeInterval.from_message(info.availability) if info.HasField("availability") else None, - count=info.count if info.HasField("count") else None, + availability=TimeInterval.from_message(info.availability) if info.availability is not None else None, + count=info.count if info.has_field("count") else None, ) - def to_message(self) -> core_pb2.CollectionInfo: - info = core_pb2.CollectionInfo(collection=self.collection.to_message()) + def to_message(self) -> core_pb.CollectionInfo: + info = core_pb.CollectionInfo(collection=self.collection.to_message()) if isinstance(self.availability, TimeInterval): - info.availability.CopyFrom(self.availability.to_message()) + info.availability = self.availability.to_message() if self.count is not None: info.count = self.count return info diff --git a/tilebox-datasets/tilebox/datasets/data/data_access.py b/tilebox-datasets/tilebox/datasets/data/data_access.py index 2a6b177..8882034 100644 --- a/tilebox-datasets/tilebox/datasets/data/data_access.py +++ b/tilebox-datasets/tilebox/datasets/data/data_access.py @@ -5,16 +5,16 @@ from shapely import Geometry, from_wkb, to_wkb -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 +from tilebox.datasets.v1 import data_access_pb, well_known_types_pb class SpatialFilterMode(Enum): - INTERSECTS = data_access_pb2.SPATIAL_FILTER_MODE_INTERSECTS - FILTER_CONTAINS_GEOMETRY = data_access_pb2.SPATIAL_FILTER_MODE_FILTER_CONTAINS_GEOMETRY - GEOMETRY_CONTAINS_FILTER = data_access_pb2.SPATIAL_FILTER_MODE_GEOMETRY_CONTAINS_FILTER + INTERSECTS = data_access_pb.SpatialFilterMode.INTERSECTS + FILTER_CONTAINS_GEOMETRY = data_access_pb.SpatialFilterMode.FILTER_CONTAINS_GEOMETRY + GEOMETRY_CONTAINS_FILTER = data_access_pb.SpatialFilterMode.GEOMETRY_CONTAINS_FILTER _filter_modes_from_string = {mode.name.lower(): mode for mode in SpatialFilterMode} @@ -22,8 +22,8 @@ class SpatialFilterMode(Enum): class SpatialCoordinateSystem(Enum): - CARTESIAN = data_access_pb2.SPATIAL_COORDINATE_SYSTEM_CARTESIAN - SPHERICAL = data_access_pb2.SPATIAL_COORDINATE_SYSTEM_SPHERICAL + CARTESIAN = data_access_pb.SpatialCoordinateSystem.CARTESIAN + SPHERICAL = data_access_pb.SpatialCoordinateSystem.SPHERICAL _coordinate_systems_from_string = {system.name.lower(): system for system in SpatialCoordinateSystem} @@ -62,23 +62,25 @@ class SpatialFilter: coordinate_system: SpatialCoordinateSystem | None = None @classmethod - def from_message(cls, filter_message: data_access_pb2.SpatialFilter) -> "SpatialFilter": + def from_message(cls, filter_message: data_access_pb.SpatialFilter) -> "SpatialFilter": + if filter_message.geometry is None: + raise ValueError("Spatial filter is missing its geometry") return SpatialFilter( geometry=from_wkb(filter_message.geometry.wkb), mode=_filter_mode_int_to_enum.get(filter_message.mode), coordinate_system=_coordinate_system_int_to_enum.get(filter_message.coordinate_system), ) - def to_message(self) -> data_access_pb2.SpatialFilter: - filter_mode = self.mode.value if self.mode else data_access_pb2.SPATIAL_FILTER_MODE_UNSPECIFIED + def to_message(self) -> data_access_pb.SpatialFilter: + filter_mode = self.mode.value if self.mode else data_access_pb.SpatialFilterMode.UNSPECIFIED coordinate_system = ( self.coordinate_system.value if self.coordinate_system - else data_access_pb2.SPATIAL_COORDINATE_SYSTEM_UNSPECIFIED + else data_access_pb.SpatialCoordinateSystem.UNSPECIFIED ) - return data_access_pb2.SpatialFilter( - geometry=well_known_types_pb2.Geometry(wkb=to_wkb(self.geometry)), + return data_access_pb.SpatialFilter( + geometry=well_known_types_pb.Geometry(wkb=to_wkb(self.geometry)), mode=filter_mode, coordinate_system=coordinate_system, ) @@ -123,18 +125,20 @@ def __post_init__(self) -> None: raise TypeError(f"Expected a query expression, got {type(self.filter).__name__}") @classmethod - def from_message(cls, filters: data_access_pb2.QueryFilters) -> "QueryFilters": - interval_variants = [name for name in ("time_interval", "datapoint_interval") if filters.HasField(name)] + def from_message(cls, filters: data_access_pb.QueryFilters) -> "QueryFilters": + interval_variants = [name for name in ("time_interval", "datapoint_interval") if filters.has_field(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": + assert filters.time_interval is not None temporal_extent = TimeInterval.from_message(filters.time_interval) else: + assert filters.datapoint_interval is not None temporal_extent = IDInterval.from_message(filters.datapoint_interval) spatial_extent = None - if filters.HasField("spatial_extent"): + if filters.spatial_extent is not None: spatial_extent = SpatialFilter.from_message(filters.spatial_extent) expressions = tuple(Expression.from_message(expression) for expression in filters.expressions) @@ -150,17 +154,17 @@ def from_message(cls, filters: data_access_pb2.QueryFilters) -> "QueryFilters": filter=filter_expression, ) - def to_message(self) -> data_access_pb2.QueryFilters: + def to_message(self) -> data_access_pb.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( + return data_access_pb.QueryFilters( time_interval=self.temporal_extent.to_message(), spatial_extent=spatial_extent, expressions=expressions, ) - return data_access_pb2.QueryFilters( + return data_access_pb.QueryFilters( datapoint_interval=self.temporal_extent.to_message(), spatial_extent=spatial_extent, expressions=expressions, diff --git a/tilebox-datasets/tilebox/datasets/data/datapoint.py b/tilebox-datasets/tilebox/datasets/data/datapoint.py index 225091b..30df26b 100644 --- a/tilebox-datasets/tilebox/datasets/data/datapoint.py +++ b/tilebox-datasets/tilebox/datasets/data/datapoint.py @@ -3,12 +3,15 @@ from typing import Any from uuid import UUID -from tilebox.datasets.datasets.v1 import core_pb2, data_access_pb2, data_ingestion_pb2 -from tilebox.datasets.message_pool import get_message_type +from protobuf import Message + +from _tilebox.proto.tilebox.v1 import id_pb from tilebox.datasets.query.pagination import Pagination from tilebox.datasets.query.time_interval import timestamp_to_datetime -from tilebox.datasets.tilebox.v1 import id_pb2 from tilebox.datasets.uuid import uuid_message_to_uuid +from tilebox.datasets.v1 import core_pb, data_access_pb, data_ingestion_pb + +DATAPOINT_METADATA_FIELDS = ("time", "id", "ingestion_time") @dataclass(frozen=True) @@ -20,11 +23,11 @@ class AnyMessage: value: bytes @classmethod - def from_message(cls, a: core_pb2.Any) -> "AnyMessage": + def from_message(cls, a: core_pb.Any) -> "AnyMessage": return cls(type_url=a.type_url, value=a.value) - def to_message(self) -> core_pb2.Any: - return core_pb2.Any(type_url=self.type_url, value=self.value) + def to_message(self) -> core_pb.Any: + return core_pb.Any(type_url=self.type_url, value=self.value) @dataclass(frozen=True) @@ -36,11 +39,11 @@ class RepeatedAny: value: list[bytes] @classmethod - def from_message(cls, a: core_pb2.RepeatedAny) -> "RepeatedAny": + def from_message(cls, a: core_pb.RepeatedAny) -> "RepeatedAny": return cls(type_url=a.type_url, value=list(a.value)) - def to_message(self) -> core_pb2.RepeatedAny: - return core_pb2.RepeatedAny(type_url=self.type_url, value=self.value) + def to_message(self) -> core_pb.RepeatedAny: + return core_pb.RepeatedAny(type_url=self.type_url, value=self.value) @dataclass(frozen=True) @@ -48,17 +51,18 @@ class QueryResultPage: data: RepeatedAny next_page: Pagination byte_size: int = field(compare=False) + message_type: type[Message] | None = field(default=None, compare=False, repr=False) @classmethod - def from_message(cls, page: data_access_pb2.QueryResultPage) -> "QueryResultPage": + def from_message(cls, page: data_access_pb.QueryResultPage) -> "QueryResultPage": return cls( - data=RepeatedAny.from_message(page.data), + data=RepeatedAny.from_message(page.data) if page.data is not None else RepeatedAny("", []), next_page=Pagination.from_message(page.next_page), - byte_size=page.ByteSize(), + byte_size=len(page.to_binary()), ) - def to_message(self) -> data_access_pb2.QueryResultPage: - return data_access_pb2.QueryResultPage( + def to_message(self) -> data_access_pb.QueryResultPage: + return data_access_pb.QueryResultPage( data=self.data.to_message(), next_page=self.next_page.to_message(), ) @@ -84,8 +88,9 @@ def max_time(self) -> datetime: return timestamp_to_datetime(self._parse_message(-1).time) def _parse_message(self, index: int) -> Any: - message_type = get_message_type(self.data.type_url) - return message_type.FromString(self.data.value[index]) + if self.message_type is None: + raise ValueError("Query page is not bound to a dataset schema") + return self.message_type.from_binary(self.data.value[index]) @dataclass(frozen=True) @@ -95,16 +100,16 @@ class IngestResponse: datapoint_ids: list[UUID] @classmethod - def from_message(cls, response: data_ingestion_pb2.IngestResponse) -> "IngestResponse": + def from_message(cls, response: data_ingestion_pb.IngestResponse) -> "IngestResponse": return cls( num_created=response.num_created, num_existing=response.num_existing, datapoint_ids=[uuid_message_to_uuid(datapoint_id) for datapoint_id in response.datapoint_ids], ) - def to_message(self) -> data_ingestion_pb2.IngestResponse: - return data_ingestion_pb2.IngestResponse( + def to_message(self) -> data_ingestion_pb.IngestResponse: + return data_ingestion_pb.IngestResponse( num_created=self.num_created, num_existing=self.num_existing, - datapoint_ids=[id_pb2.ID(uuid=datapoint_id.bytes) for datapoint_id in self.datapoint_ids], + datapoint_ids=[id_pb.ID(uuid=datapoint_id.bytes) for datapoint_id in self.datapoint_ids], ) diff --git a/tilebox-datasets/tilebox/datasets/data/datasets.py b/tilebox-datasets/tilebox/datasets/data/datasets.py index 6aaa7a8..aca8c3b 100644 --- a/tilebox-datasets/tilebox/datasets/data/datasets.py +++ b/tilebox-datasets/tilebox/datasets/data/datasets.py @@ -5,18 +5,18 @@ from typing import Literal, NotRequired, Required, TypeAlias, TypedDict, get_args, get_origin import numpy as np -from google.protobuf import duration_pb2, timestamp_pb2 -from google.protobuf.descriptor_pb2 import FieldDescriptorProto, FileDescriptorSet +from protobuf.wkt.google.protobuf import duration_pb, timestamp_pb +from protobuf.wkt.google.protobuf.descriptor_pb import FieldDescriptorProto, FileDescriptorSet -from tilebox.datasets.datasets.v1 import core_pb2, dataset_type_pb2, datasets_pb2, well_known_types_pb2 -from tilebox.datasets.schema import UUID, Geometry, MessageFieldType, _wire_message_type +from tilebox.datasets.schema import UUID, Geometry, MessageFieldType, _message_field_type from tilebox.datasets.uuid import uuid_message_to_optional_uuid, uuid_message_to_uuid, uuid_to_uuid_message +from tilebox.datasets.v1 import core_pb, dataset_type_pb, datasets_pb, well_known_types_pb class DatasetKind(Enum): - TEMPORAL = dataset_type_pb2.DATASET_KIND_TEMPORAL + TEMPORAL = dataset_type_pb.DatasetKind.TEMPORAL """A dataset that contains a timestamp field.""" - SPATIOTEMPORAL = dataset_type_pb2.DATASET_KIND_SPATIOTEMPORAL + SPATIOTEMPORAL = dataset_type_pb.DatasetKind.SPATIOTEMPORAL """A dataset that contains a timestamp field and a geometry field.""" @@ -24,7 +24,7 @@ class DatasetKind(Enum): class FieldRole(Enum): - PRIMARY_TITLE = dataset_type_pb2.FIELD_ROLE_PRIMARY_TITLE + PRIMARY_TITLE = dataset_type_pb.FieldRole.PRIMARY_TITLE """The primary human-readable title or name used when displaying a datapoint.""" @@ -49,20 +49,20 @@ class FieldAnnotation: """Semantic display roles fulfilled by the field.""" @classmethod - def from_message(cls, annotation: dataset_type_pb2.FieldAnnotation) -> "FieldAnnotation": + def from_message(cls, annotation: dataset_type_pb.FieldAnnotation | None) -> "FieldAnnotation": + if annotation is None: + return cls("", "") return cls( description=annotation.description, example_value=annotation.example_value, - source_json_pointer=( - annotation.source_json_pointer if annotation.HasField("source_json_pointer") else None - ), + source_json_pointer=annotation.source_json_pointer if annotation.has_field("source_json_pointer") else None, queryable=annotation.queryable, - json_schema_ref=annotation.json_schema_ref if annotation.HasField("json_schema_ref") else None, + json_schema_ref=annotation.json_schema_ref if annotation.has_field("json_schema_ref") else None, roles=[_field_role_int_to_enum[role] for role in annotation.roles], ) - def to_message(self) -> dataset_type_pb2.FieldAnnotation: - annotation = dataset_type_pb2.FieldAnnotation( + def to_message(self) -> dataset_type_pb.FieldAnnotation: + annotation = dataset_type_pb.FieldAnnotation( description=self.description, example_value=self.example_value, queryable=self.queryable, @@ -108,17 +108,17 @@ class FieldDict(TypedDict): roles: NotRequired[FieldRolesLike] -_TYPE_INFO: dict[type, tuple[FieldDescriptorProto.Type.ValueType, str | None]] = { - str: (FieldDescriptorProto.TYPE_STRING, None), - bytes: (FieldDescriptorProto.TYPE_BYTES, None), - bool: (FieldDescriptorProto.TYPE_BOOL, None), - int: (FieldDescriptorProto.TYPE_INT64, None), - np.uint64: (FieldDescriptorProto.TYPE_UINT64, None), - float: (FieldDescriptorProto.TYPE_DOUBLE, None), - timedelta: (FieldDescriptorProto.TYPE_MESSAGE, f".{duration_pb2.Duration.DESCRIPTOR.full_name}"), - datetime: (FieldDescriptorProto.TYPE_MESSAGE, f".{timestamp_pb2.Timestamp.DESCRIPTOR.full_name}"), - UUID: (FieldDescriptorProto.TYPE_MESSAGE, f".{well_known_types_pb2.UUID.DESCRIPTOR.full_name}"), - Geometry: (FieldDescriptorProto.TYPE_MESSAGE, f".{well_known_types_pb2.Geometry.DESCRIPTOR.full_name}"), +_TYPE_INFO: dict[type, tuple[FieldDescriptorProto.Type, str | None]] = { + str: (FieldDescriptorProto.Type.STRING, None), + bytes: (FieldDescriptorProto.Type.BYTES, None), + bool: (FieldDescriptorProto.Type.BOOL, None), + int: (FieldDescriptorProto.Type.INT64, None), + np.uint64: (FieldDescriptorProto.Type.UINT64, None), + float: (FieldDescriptorProto.Type.DOUBLE, None), + timedelta: (FieldDescriptorProto.Type.MESSAGE, f".{duration_pb.Duration.desc().type_name}"), + datetime: (FieldDescriptorProto.Type.MESSAGE, f".{timestamp_pb.Timestamp.desc().type_name}"), + UUID: (FieldDescriptorProto.Type.MESSAGE, f".{well_known_types_pb.UUID.desc().type_name}"), + Geometry: (FieldDescriptorProto.Type.MESSAGE, f".{well_known_types_pb.Geometry.desc().type_name}"), } @@ -128,7 +128,9 @@ class Field: annotation: FieldAnnotation @classmethod - def from_message(cls, field: dataset_type_pb2.Field) -> "Field": + def from_message(cls, field: dataset_type_pb.Field) -> "Field": + if field.descriptor is None: + raise ValueError("Dataset field is missing its descriptor") return cls( descriptor=field.descriptor, annotation=FieldAnnotation.from_message(field.annotation), @@ -138,17 +140,17 @@ def from_message(cls, field: dataset_type_pb2.Field) -> "Field": def from_dict(cls, field: FieldDict) -> "Field": origin = get_origin(field["type"]) if origin is list: - label = FieldDescriptorProto.Label.LABEL_REPEATED + label = FieldDescriptorProto.Label.REPEATED args = get_args(field["type"]) inner_type = args[0] if args else field["type"] else: - label = FieldDescriptorProto.Label.LABEL_OPTIONAL + label = FieldDescriptorProto.Label.OPTIONAL inner_type = field["type"] - wire_message_type = _wire_message_type(inner_type) - if wire_message_type is not None: - field_type = FieldDescriptorProto.TYPE_MESSAGE - field_type_name = f".{wire_message_type.DESCRIPTOR.full_name}" + message_type = _message_field_type(inner_type) + if message_type is not None: + field_type = FieldDescriptorProto.Type.MESSAGE + field_type_name = f".{message_type.desc().type_name}" else: (field_type, field_type_name) = _TYPE_INFO[inner_type] @@ -171,8 +173,8 @@ def from_dict(cls, field: FieldDict) -> "Field": ), ) - def to_message(self) -> dataset_type_pb2.Field: - return dataset_type_pb2.Field( + def to_message(self) -> dataset_type_pb.Field: + return dataset_type_pb.Field( descriptor=self.descriptor, annotation=self.annotation.to_message(), ) @@ -184,19 +186,84 @@ class DatasetType: fields: list[Field] @classmethod - def from_message(cls, dataset_type: dataset_type_pb2.DatasetType) -> "DatasetType": + def from_fields(cls, kind: DatasetKind, custom_fields: list[FieldDict]) -> "DatasetType": + """Build a dataset schema with required metadata before custom fields.""" + return cls(kind, _REQUIRED_FIELDS_PER_DATASET_KIND[kind] + [Field.from_dict(field) for field in custom_fields]) + + @classmethod + def from_message(cls, dataset_type: dataset_type_pb.DatasetType) -> "DatasetType": return cls( kind=_dataset_kind_int_to_enum.get(dataset_type.kind), fields=[Field.from_message(f) for f in dataset_type.fields], ) - def to_message(self) -> dataset_type_pb2.DatasetType: - return dataset_type_pb2.DatasetType( - kind=self.kind.value if self.kind else dataset_type_pb2.DATASET_KIND_UNSPECIFIED, + def to_message(self) -> dataset_type_pb.DatasetType: + return dataset_type_pb.DatasetType( + kind=self.kind.value if self.kind else dataset_type_pb.DatasetKind.UNSPECIFIED, fields=[f.to_message() for f in self.fields], ) +_time_field = Field( + descriptor=FieldDescriptorProto( + name="time", + label=FieldDescriptorProto.Label.OPTIONAL, + type=FieldDescriptorProto.Type.MESSAGE, + type_name=".google.protobuf.Timestamp", + ), + annotation=FieldAnnotation( + description="The timestamp associated with each data point.", + example_value="2022-10-17T14:35:28Z", + source_json_pointer="/properties/datetime", + json_schema_ref="https://schemas.stacspec.org/v1.1.0/item-spec/json-schema/datetime.json#/properties/datetime", + ), +) +_id_field = Field( + descriptor=FieldDescriptorProto( + name="id", + label=FieldDescriptorProto.Label.OPTIONAL, + type=FieldDescriptorProto.Type.MESSAGE, + type_name=".datasets.v1.UUID", + ), + 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", + ), +) +_ingestion_time_field = Field( + descriptor=FieldDescriptorProto( + name="ingestion_time", + label=FieldDescriptorProto.Label.OPTIONAL, + type=FieldDescriptorProto.Type.MESSAGE, + type_name=".google.protobuf.Timestamp", + ), + 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/tilebox:ingestion_time", + ), +) +_geometry_field = Field( + descriptor=FieldDescriptorProto( + name="geometry", + label=FieldDescriptorProto.Label.OPTIONAL, + type=FieldDescriptorProto.Type.MESSAGE, + type_name=".datasets.v1.Geometry", + ), + annotation=FieldAnnotation( + description="The geometry associated with each data point.", + example_value="POLYGON ((112.345 -36.789, ...))", + source_json_pointer="/geometry", + json_schema_ref="https://geojson.org/schema/Geometry.json", + ), +) +_REQUIRED_FIELDS_PER_DATASET_KIND: dict[DatasetKind, list[Field]] = { + DatasetKind.TEMPORAL: [_time_field, _id_field, _ingestion_time_field], + DatasetKind.SPATIOTEMPORAL: [_time_field, _id_field, _ingestion_time_field, _geometry_field], +} + + @dataclass(frozen=True) class AnnotatedType: descriptor_set: FileDescriptorSet @@ -205,7 +272,9 @@ class AnnotatedType: kind: DatasetKind | None = None @classmethod - def from_message(cls, annotated_type: dataset_type_pb2.AnnotatedType) -> "AnnotatedType": + def from_message(cls, annotated_type: dataset_type_pb.AnnotatedType) -> "AnnotatedType": + if annotated_type.descriptor_set is None: + raise ValueError("Dataset type is missing its descriptor set") return cls( descriptor_set=annotated_type.descriptor_set, type_url=annotated_type.type_url, @@ -213,12 +282,12 @@ def from_message(cls, annotated_type: dataset_type_pb2.AnnotatedType) -> "Annota kind=_dataset_kind_int_to_enum.get(annotated_type.kind), ) - def to_message(self) -> dataset_type_pb2.AnnotatedType: - return dataset_type_pb2.AnnotatedType( + def to_message(self) -> dataset_type_pb.AnnotatedType: + return dataset_type_pb.AnnotatedType( descriptor_set=self.descriptor_set, type_url=self.type_url, field_annotations=[a.to_message() for a in self.field_annotations], - kind=self.kind.value if self.kind else dataset_type_pb2.DATASET_KIND_UNSPECIFIED, + kind=self.kind.value if self.kind else dataset_type_pb.DatasetKind.UNSPECIFIED, ) @@ -236,7 +305,9 @@ class Dataset: description: str @classmethod - def from_message(cls, dataset: core_pb2.Dataset) -> "Dataset": + def from_message(cls, dataset: core_pb.Dataset) -> "Dataset": + if dataset.type is None: + raise ValueError("Dataset is missing its type") return cls( id=uuid_message_to_uuid(dataset.id), group_id=uuid_message_to_uuid(dataset.group_id), @@ -248,8 +319,8 @@ def from_message(cls, dataset: core_pb2.Dataset) -> "Dataset": description=dataset.description, ) - def to_message(self) -> core_pb2.Dataset: - return core_pb2.Dataset( + def to_message(self) -> core_pb.Dataset: + return core_pb.Dataset( id=uuid_to_uuid_message(self.id), group_id=uuid_to_uuid_message(self.group_id), type=self.type.to_message(), @@ -270,7 +341,7 @@ class DatasetGroup: icon: str @classmethod - def from_message(cls, group: core_pb2.DatasetGroup) -> "DatasetGroup": + def from_message(cls, group: core_pb.DatasetGroup) -> "DatasetGroup": return cls( id=uuid_message_to_uuid(group.id), parent_id=uuid_message_to_optional_uuid(group.parent_id), @@ -279,8 +350,8 @@ def from_message(cls, group: core_pb2.DatasetGroup) -> "DatasetGroup": icon=group.icon, ) - def to_message(self) -> core_pb2.DatasetGroup: - return core_pb2.DatasetGroup( + def to_message(self) -> core_pb.DatasetGroup: + return core_pb.DatasetGroup( id=uuid_to_uuid_message(self.id), parent_id=uuid_to_uuid_message(self.parent_id), code_name=self.code_name, @@ -296,16 +367,16 @@ class ListDatasetsResponse: server_message: str | None @classmethod - def from_message(cls, response: datasets_pb2.ListDatasetsResponse) -> "ListDatasetsResponse": + def from_message(cls, response: datasets_pb.ListDatasetsResponse) -> "ListDatasetsResponse": return cls( datasets=[Dataset.from_message(dataset) for dataset in response.datasets], groups=[DatasetGroup.from_message(group) for group in response.groups], server_message=response.server_message, ) - def to_message(self) -> datasets_pb2.ListDatasetsResponse: - return datasets_pb2.ListDatasetsResponse( + def to_message(self) -> datasets_pb.ListDatasetsResponse: + return datasets_pb.ListDatasetsResponse( datasets=[dataset.to_message() for dataset in self.datasets], groups=[group.to_message() for group in self.groups], - server_message=self.server_message, + server_message=self.server_message or "", ) diff --git a/tilebox-datasets/tilebox/datasets/data/timeseries.py b/tilebox-datasets/tilebox/datasets/data/timeseries.py index a774874..dcb6032 100644 --- a/tilebox-datasets/tilebox/datasets/data/timeseries.py +++ b/tilebox-datasets/tilebox/datasets/data/timeseries.py @@ -2,10 +2,10 @@ from datetime import timedelta from uuid import UUID -from tilebox.datasets.datasets.v1 import timeseries_pb2 from tilebox.datasets.query.id_interval import IDInterval from tilebox.datasets.query.time_interval import TimeInterval, duration_to_timedelta, timedelta_to_duration from tilebox.datasets.uuid import uuid_message_to_uuid, uuid_to_uuid_message +from tilebox.datasets.v1 import timeseries_pb @dataclass @@ -19,7 +19,7 @@ class TimeseriesDatasetChunk: datapoints_per_365_days: int @classmethod - def from_message(cls, chunk: timeseries_pb2.TimeseriesDatasetChunk) -> "TimeseriesDatasetChunk": + def from_message(cls, chunk: timeseries_pb.TimeseriesDatasetChunk) -> "TimeseriesDatasetChunk": datapoint_interval = None if ( chunk.datapoint_interval @@ -32,9 +32,9 @@ def from_message(cls, chunk: timeseries_pb2.TimeseriesDatasetChunk) -> "Timeseri time_interval = None if ( - chunk.HasField("time_interval") - and chunk.time_interval.HasField("start_time") - and chunk.time_interval.HasField("end_time") + chunk.time_interval is not None + and chunk.time_interval.start_time is not None + and chunk.time_interval.end_time is not None ): time_interval = TimeInterval.from_message(chunk.time_interval) @@ -48,8 +48,8 @@ def from_message(cls, chunk: timeseries_pb2.TimeseriesDatasetChunk) -> "Timeseri datapoints_per_365_days=chunk.datapoints_per_365_days, ) - def to_message(self) -> timeseries_pb2.TimeseriesDatasetChunk: - return timeseries_pb2.TimeseriesDatasetChunk( + def to_message(self) -> timeseries_pb.TimeseriesDatasetChunk: + return timeseries_pb.TimeseriesDatasetChunk( dataset_id=uuid_to_uuid_message(self.dataset_id), collection_id=uuid_to_uuid_message(self.collection_id), time_interval=self.time_interval.to_message() if self.time_interval else None, @@ -59,13 +59,14 @@ def to_message(self) -> timeseries_pb2.TimeseriesDatasetChunk: datapoints_per_365_days=self.datapoints_per_365_days, ) - # needed to serialize as protobuf in tasks - def SerializeToString(self) -> bytes: # noqa: N802 - return self.to_message().SerializeToString() + def to_binary(self) -> bytes: + """Serialize the chunk in native protobuf wire format.""" + return self.to_message().to_binary() @classmethod - def FromString(cls, s: bytes) -> "TimeseriesDatasetChunk": # noqa: N802 - return TimeseriesDatasetChunk.from_message(timeseries_pb2.TimeseriesDatasetChunk.FromString(s)) + def from_binary(cls, s: bytes) -> "TimeseriesDatasetChunk": + """Parse a chunk from native protobuf wire format.""" + return TimeseriesDatasetChunk.from_message(timeseries_pb.TimeseriesDatasetChunk.from_binary(s)) @dataclass @@ -74,22 +75,25 @@ class TimeChunk: chunk_size: timedelta @classmethod - def from_message(cls, chunk: timeseries_pb2.TimeChunk) -> "TimeChunk": + def from_message(cls, chunk: timeseries_pb.TimeChunk) -> "TimeChunk": + if chunk.time_interval is None or chunk.chunk_size is None: + raise ValueError("Time chunk requires a time interval and chunk size") return cls( time_interval=TimeInterval.from_message(chunk.time_interval), chunk_size=duration_to_timedelta(chunk.chunk_size), ) - def to_message(self) -> timeseries_pb2.TimeChunk: - return timeseries_pb2.TimeChunk( + def to_message(self) -> timeseries_pb.TimeChunk: + return timeseries_pb.TimeChunk( time_interval=self.time_interval.to_message(), chunk_size=timedelta_to_duration(self.chunk_size), ) - # needed to serialize as protobuf in tasks - def SerializeToString(self) -> bytes: # noqa: N802 - return self.to_message().SerializeToString() + def to_binary(self) -> bytes: + """Serialize the chunk in native protobuf wire format.""" + return self.to_message().to_binary() @classmethod - def FromString(cls, s: bytes) -> "TimeChunk": # noqa: N802 - return TimeChunk.from_message(timeseries_pb2.TimeChunk.FromString(s)) + def from_binary(cls, s: bytes) -> "TimeChunk": + """Parse a chunk from native protobuf wire format.""" + return TimeChunk.from_message(timeseries_pb.TimeChunk.from_binary(s)) diff --git a/tilebox-datasets/tilebox/datasets/datasets/__init__.py b/tilebox-datasets/tilebox/datasets/datasets/__init__.py deleted file mode 100644 index 9d48db4..0000000 --- a/tilebox-datasets/tilebox/datasets/datasets/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from __future__ import annotations diff --git a/tilebox-datasets/tilebox/datasets/datasets/stac/__init__.py b/tilebox-datasets/tilebox/datasets/datasets/stac/__init__.py deleted file mode 100644 index 9d48db4..0000000 --- a/tilebox-datasets/tilebox/datasets/datasets/stac/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from __future__ import annotations diff --git a/tilebox-datasets/tilebox/datasets/datasets/stac/v1/__init__.py b/tilebox-datasets/tilebox/datasets/datasets/stac/v1/__init__.py deleted file mode 100644 index 9d48db4..0000000 --- a/tilebox-datasets/tilebox/datasets/datasets/stac/v1/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from __future__ import annotations diff --git a/tilebox-datasets/tilebox/datasets/datasets/stac/v1/asset_metadata_pb2.py b/tilebox-datasets/tilebox/datasets/datasets/stac/v1/asset_metadata_pb2.py deleted file mode 100644 index 7e922c7..0000000 --- a/tilebox-datasets/tilebox/datasets/datasets/stac/v1/asset_metadata_pb2.py +++ /dev/null @@ -1,101 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by the protocol buffer compiler. DO NOT EDIT! -# NO CHECKED-IN PROTOBUF GENCODE -# source: datasets/stac/v1/asset_metadata.proto -# Protobuf Python Version: 6.30.0 -"""Generated protocol buffer code.""" -from google.protobuf import descriptor as _descriptor -from google.protobuf import descriptor_pool as _descriptor_pool -from google.protobuf import runtime_version as _runtime_version -from google.protobuf import symbol_database as _symbol_database -from google.protobuf.internal import builder as _builder -_runtime_version.ValidateProtobufRuntimeVersion( - _runtime_version.Domain.PUBLIC, - 6, - 30, - 0, - '', - 'datasets/stac/v1/asset_metadata.proto' -) -# @@protoc_insertion_point(imports) - -_sym_db = _symbol_database.Default() - - - - -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n%datasets/stac/v1/asset_metadata.proto\x12\x10\x64\x61tasets.stac.v1\"\xef\x01\n\x0c\x45OProperties\x12?\n\x0b\x63ommon_name\x18\x01 \x01(\x0e\x32\x1e.datasets.stac.v1.EOCommonNameR\ncommonName\x12\x32\n\x11\x63\x65nter_wavelength\x18\x02 \x01(\x01\x42\x05\xaa\x01\x02\x08\x01R\x10\x63\x65nterWavelength\x12\x34\n\x13\x66ull_width_half_max\x18\x03 \x01(\x01\x42\x05\xaa\x01\x02\x08\x01R\x10\x66ullWidthHalfMax\x12\x34\n\x12solar_illumination\x18\x04 \x01(\x01\x42\x05\xaa\x01\x02\x08\x01R\x11solarIllumination\"\xc2\x01\n\x10RasterProperties\x12<\n\x08sampling\x18\x01 \x01(\x0e\x32 .datasets.stac.v1.RasterSamplingR\x08sampling\x12\x1b\n\x05scale\x18\x02 \x01(\x01\x42\x05\xaa\x01\x02\x08\x01R\x05scale\x12\x1d\n\x06offset\x18\x03 \x01(\x01\x42\x05\xaa\x01\x02\x08\x01R\x06offset\x12\x34\n\x12spatial_resolution\x18\x04 \x01(\x01\x42\x05\xaa\x01\x02\x08\x01R\x11spatialResolution\"\x9c\x02\n\x13\x43lassificationClass\x12\x1b\n\x05value\x18\x01 \x01(\x03\x42\x05\xaa\x01\x02\x08\x01R\x05value\x12\'\n\x0b\x64\x65scription\x18\x02 \x01(\tB\x05\xaa\x01\x02\x08\x01R\x0b\x64\x65scription\x12\x19\n\x04name\x18\x03 \x01(\tB\x05\xaa\x01\x02\x08\x01R\x04name\x12\x1b\n\x05title\x18\x04 \x01(\tB\x05\xaa\x01\x02\x08\x01R\x05title\x12$\n\ncolor_hint\x18\x05 \x01(\tB\x05\xaa\x01\x02\x08\x01R\tcolorHint\x12\x1d\n\x06nodata\x18\x06 \x01(\x08\x42\x05\xaa\x01\x02\x08\x01R\x06nodata\x12%\n\npercentage\x18\x07 \x01(\x01\x42\x05\xaa\x01\x02\x08\x01R\npercentage\x12\x1b\n\x05\x63ount\x18\x08 \x01(\x04\x42\x05\xaa\x01\x02\x08\x01R\x05\x63ount\"\xf1\x01\n\x16\x43lassificationBitField\x12\x1d\n\x06offset\x18\x01 \x01(\rB\x05\xaa\x01\x02\x08\x01R\x06offset\x12\x1d\n\x06length\x18\x02 \x01(\rB\x05\xaa\x01\x02\x08\x01R\x06length\x12?\n\x07\x63lasses\x18\x03 \x03(\x0b\x32%.datasets.stac.v1.ClassificationClassR\x07\x63lasses\x12\x19\n\x04name\x18\x04 \x01(\tB\x05\xaa\x01\x02\x08\x01R\x04name\x12\'\n\x0b\x64\x65scription\x18\x05 \x01(\tB\x05\xaa\x01\x02\x08\x01R\x0b\x64\x65scription\x12\x14\n\x05roles\x18\x06 \x03(\tR\x05roles\"o\n\nProjection\x12\x19\n\x04\x63ode\x18\x01 \x01(\tB\x05\xaa\x01\x02\x08\x01R\x04\x63ode\x12\x12\n\x04\x62\x62ox\x18\x02 \x03(\x01R\x04\x62\x62ox\x12\x14\n\x05shape\x18\x03 \x03(\x03R\x05shape\x12\x1c\n\ttransform\x18\x04 \x03(\x01R\ttransform\"W\n\x04View\x12.\n\x0fincidence_angle\x18\x01 \x01(\x01\x42\x05\xaa\x01\x02\x08\x01R\x0eincidenceAngle\x12\x1f\n\x07\x61zimuth\x18\x02 \x01(\x01\x42\x05\xaa\x01\x02\x08\x01R\x07\x61zimuth\"j\n\x04\x46ile\x12!\n\x08\x63hecksum\x18\x01 \x01(\x0c\x42\x05\xaa\x01\x02\x08\x01R\x08\x63hecksum\x12\x19\n\x04size\x18\x02 \x01(\x04\x42\x05\xaa\x01\x02\x08\x01R\x04size\x12$\n\nlocal_path\x18\x03 \x01(\tB\x05\xaa\x01\x02\x08\x01R\tlocalPath*\xc9\x04\n\x0c\x45OCommonName\x12\x1e\n\x1a\x45O_COMMON_NAME_UNSPECIFIED\x10\x00\x12\x16\n\x12\x45O_COMMON_NAME_PAN\x10\x01\x12\x1a\n\x16\x45O_COMMON_NAME_COASTAL\x10\x02\x12\x17\n\x13\x45O_COMMON_NAME_BLUE\x10\x03\x12\x18\n\x14\x45O_COMMON_NAME_GREEN\x10\x04\x12\x1a\n\x16\x45O_COMMON_NAME_GREEN05\x10\x05\x12\x19\n\x15\x45O_COMMON_NAME_YELLOW\x10\x06\x12\x16\n\x12\x45O_COMMON_NAME_RED\x10\x07\x12\x1a\n\x16\x45O_COMMON_NAME_REDEDGE\x10\x08\x12\x1d\n\x19\x45O_COMMON_NAME_REDEDGE071\x10\t\x12\x1d\n\x19\x45O_COMMON_NAME_REDEDGE075\x10\n\x12\x1d\n\x19\x45O_COMMON_NAME_REDEDGE078\x10\x0b\x12\x16\n\x12\x45O_COMMON_NAME_NIR\x10\x0c\x12\x18\n\x14\x45O_COMMON_NAME_NIR08\x10\r\x12\x18\n\x14\x45O_COMMON_NAME_NIR09\x10\x0e\x12\x19\n\x15\x45O_COMMON_NAME_CIRRUS\x10\x0f\x12\x19\n\x15\x45O_COMMON_NAME_SWIR16\x10\x10\x12\x19\n\x15\x45O_COMMON_NAME_SWIR22\x10\x11\x12\x17\n\x13\x45O_COMMON_NAME_LWIR\x10\x12\x12\x19\n\x15\x45O_COMMON_NAME_LWIR11\x10\x13\x12\x19\n\x15\x45O_COMMON_NAME_LWIR12\x10\x14*f\n\x0eRasterSampling\x12\x1f\n\x1bRASTER_SAMPLING_UNSPECIFIED\x10\x00\x12\x18\n\x14RASTER_SAMPLING_AREA\x10\x01\x12\x19\n\x15RASTER_SAMPLING_POINT\x10\x02\x42\x91\x01\n\x14\x63om.datasets.stac.v1B\x12\x41ssetMetadataProtoP\x01\xa2\x02\x03\x44SX\xaa\x02\x10\x44\x61tasets.Stac.V1\xca\x02\x10\x44\x61tasets\\Stac\\V1\xe2\x02\x1c\x44\x61tasets\\Stac\\V1\\GPBMetadata\xea\x02\x12\x44\x61tasets::Stac::V1\x92\x03\x02\x08\x02\x62\x08\x65\x64itionsp\xe8\x07') - -_globals = globals() -_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) -_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'datasets.stac.v1.asset_metadata_pb2', _globals) -if not _descriptor._USE_C_DESCRIPTORS: - _globals['DESCRIPTOR']._loaded_options = None - _globals['DESCRIPTOR']._serialized_options = b'\n\024com.datasets.stac.v1B\022AssetMetadataProtoP\001\242\002\003DSX\252\002\020Datasets.Stac.V1\312\002\020Datasets\\Stac\\V1\342\002\034Datasets\\Stac\\V1\\GPBMetadata\352\002\022Datasets::Stac::V1\222\003\002\010\002' - _globals['_EOPROPERTIES'].fields_by_name['center_wavelength']._loaded_options = None - _globals['_EOPROPERTIES'].fields_by_name['center_wavelength']._serialized_options = b'\252\001\002\010\001' - _globals['_EOPROPERTIES'].fields_by_name['full_width_half_max']._loaded_options = None - _globals['_EOPROPERTIES'].fields_by_name['full_width_half_max']._serialized_options = b'\252\001\002\010\001' - _globals['_EOPROPERTIES'].fields_by_name['solar_illumination']._loaded_options = None - _globals['_EOPROPERTIES'].fields_by_name['solar_illumination']._serialized_options = b'\252\001\002\010\001' - _globals['_RASTERPROPERTIES'].fields_by_name['scale']._loaded_options = None - _globals['_RASTERPROPERTIES'].fields_by_name['scale']._serialized_options = b'\252\001\002\010\001' - _globals['_RASTERPROPERTIES'].fields_by_name['offset']._loaded_options = None - _globals['_RASTERPROPERTIES'].fields_by_name['offset']._serialized_options = b'\252\001\002\010\001' - _globals['_RASTERPROPERTIES'].fields_by_name['spatial_resolution']._loaded_options = None - _globals['_RASTERPROPERTIES'].fields_by_name['spatial_resolution']._serialized_options = b'\252\001\002\010\001' - _globals['_CLASSIFICATIONCLASS'].fields_by_name['value']._loaded_options = None - _globals['_CLASSIFICATIONCLASS'].fields_by_name['value']._serialized_options = b'\252\001\002\010\001' - _globals['_CLASSIFICATIONCLASS'].fields_by_name['description']._loaded_options = None - _globals['_CLASSIFICATIONCLASS'].fields_by_name['description']._serialized_options = b'\252\001\002\010\001' - _globals['_CLASSIFICATIONCLASS'].fields_by_name['name']._loaded_options = None - _globals['_CLASSIFICATIONCLASS'].fields_by_name['name']._serialized_options = b'\252\001\002\010\001' - _globals['_CLASSIFICATIONCLASS'].fields_by_name['title']._loaded_options = None - _globals['_CLASSIFICATIONCLASS'].fields_by_name['title']._serialized_options = b'\252\001\002\010\001' - _globals['_CLASSIFICATIONCLASS'].fields_by_name['color_hint']._loaded_options = None - _globals['_CLASSIFICATIONCLASS'].fields_by_name['color_hint']._serialized_options = b'\252\001\002\010\001' - _globals['_CLASSIFICATIONCLASS'].fields_by_name['nodata']._loaded_options = None - _globals['_CLASSIFICATIONCLASS'].fields_by_name['nodata']._serialized_options = b'\252\001\002\010\001' - _globals['_CLASSIFICATIONCLASS'].fields_by_name['percentage']._loaded_options = None - _globals['_CLASSIFICATIONCLASS'].fields_by_name['percentage']._serialized_options = b'\252\001\002\010\001' - _globals['_CLASSIFICATIONCLASS'].fields_by_name['count']._loaded_options = None - _globals['_CLASSIFICATIONCLASS'].fields_by_name['count']._serialized_options = b'\252\001\002\010\001' - _globals['_CLASSIFICATIONBITFIELD'].fields_by_name['offset']._loaded_options = None - _globals['_CLASSIFICATIONBITFIELD'].fields_by_name['offset']._serialized_options = b'\252\001\002\010\001' - _globals['_CLASSIFICATIONBITFIELD'].fields_by_name['length']._loaded_options = None - _globals['_CLASSIFICATIONBITFIELD'].fields_by_name['length']._serialized_options = b'\252\001\002\010\001' - _globals['_CLASSIFICATIONBITFIELD'].fields_by_name['name']._loaded_options = None - _globals['_CLASSIFICATIONBITFIELD'].fields_by_name['name']._serialized_options = b'\252\001\002\010\001' - _globals['_CLASSIFICATIONBITFIELD'].fields_by_name['description']._loaded_options = None - _globals['_CLASSIFICATIONBITFIELD'].fields_by_name['description']._serialized_options = b'\252\001\002\010\001' - _globals['_PROJECTION'].fields_by_name['code']._loaded_options = None - _globals['_PROJECTION'].fields_by_name['code']._serialized_options = b'\252\001\002\010\001' - _globals['_VIEW'].fields_by_name['incidence_angle']._loaded_options = None - _globals['_VIEW'].fields_by_name['incidence_angle']._serialized_options = b'\252\001\002\010\001' - _globals['_VIEW'].fields_by_name['azimuth']._loaded_options = None - _globals['_VIEW'].fields_by_name['azimuth']._serialized_options = b'\252\001\002\010\001' - _globals['_FILE'].fields_by_name['checksum']._loaded_options = None - _globals['_FILE'].fields_by_name['checksum']._serialized_options = b'\252\001\002\010\001' - _globals['_FILE'].fields_by_name['size']._loaded_options = None - _globals['_FILE'].fields_by_name['size']._serialized_options = b'\252\001\002\010\001' - _globals['_FILE'].fields_by_name['local_path']._loaded_options = None - _globals['_FILE'].fields_by_name['local_path']._serialized_options = b'\252\001\002\010\001' - _globals['_EOCOMMONNAME']._serialized_start=1340 - _globals['_EOCOMMONNAME']._serialized_end=1925 - _globals['_RASTERSAMPLING']._serialized_start=1927 - _globals['_RASTERSAMPLING']._serialized_end=2029 - _globals['_EOPROPERTIES']._serialized_start=60 - _globals['_EOPROPERTIES']._serialized_end=299 - _globals['_RASTERPROPERTIES']._serialized_start=302 - _globals['_RASTERPROPERTIES']._serialized_end=496 - _globals['_CLASSIFICATIONCLASS']._serialized_start=499 - _globals['_CLASSIFICATIONCLASS']._serialized_end=783 - _globals['_CLASSIFICATIONBITFIELD']._serialized_start=786 - _globals['_CLASSIFICATIONBITFIELD']._serialized_end=1027 - _globals['_PROJECTION']._serialized_start=1029 - _globals['_PROJECTION']._serialized_end=1140 - _globals['_VIEW']._serialized_start=1142 - _globals['_VIEW']._serialized_end=1229 - _globals['_FILE']._serialized_start=1231 - _globals['_FILE']._serialized_end=1337 -# @@protoc_insertion_point(module_scope) diff --git a/tilebox-datasets/tilebox/datasets/datasets/stac/v1/asset_metadata_pb2.pyi b/tilebox-datasets/tilebox/datasets/datasets/stac/v1/asset_metadata_pb2.pyi deleted file mode 100644 index a9cb2b7..0000000 --- a/tilebox-datasets/tilebox/datasets/datasets/stac/v1/asset_metadata_pb2.pyi +++ /dev/null @@ -1,152 +0,0 @@ -from google.protobuf.internal import containers as _containers -from google.protobuf.internal import enum_type_wrapper as _enum_type_wrapper -from google.protobuf import descriptor as _descriptor -from google.protobuf import message as _message -from collections.abc import Iterable as _Iterable, Mapping as _Mapping -from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union - -DESCRIPTOR: _descriptor.FileDescriptor - -class EOCommonName(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): - __slots__ = () - EO_COMMON_NAME_UNSPECIFIED: _ClassVar[EOCommonName] - EO_COMMON_NAME_PAN: _ClassVar[EOCommonName] - EO_COMMON_NAME_COASTAL: _ClassVar[EOCommonName] - EO_COMMON_NAME_BLUE: _ClassVar[EOCommonName] - EO_COMMON_NAME_GREEN: _ClassVar[EOCommonName] - EO_COMMON_NAME_GREEN05: _ClassVar[EOCommonName] - EO_COMMON_NAME_YELLOW: _ClassVar[EOCommonName] - EO_COMMON_NAME_RED: _ClassVar[EOCommonName] - EO_COMMON_NAME_REDEDGE: _ClassVar[EOCommonName] - EO_COMMON_NAME_REDEDGE071: _ClassVar[EOCommonName] - EO_COMMON_NAME_REDEDGE075: _ClassVar[EOCommonName] - EO_COMMON_NAME_REDEDGE078: _ClassVar[EOCommonName] - EO_COMMON_NAME_NIR: _ClassVar[EOCommonName] - EO_COMMON_NAME_NIR08: _ClassVar[EOCommonName] - EO_COMMON_NAME_NIR09: _ClassVar[EOCommonName] - EO_COMMON_NAME_CIRRUS: _ClassVar[EOCommonName] - EO_COMMON_NAME_SWIR16: _ClassVar[EOCommonName] - EO_COMMON_NAME_SWIR22: _ClassVar[EOCommonName] - EO_COMMON_NAME_LWIR: _ClassVar[EOCommonName] - EO_COMMON_NAME_LWIR11: _ClassVar[EOCommonName] - EO_COMMON_NAME_LWIR12: _ClassVar[EOCommonName] - -class RasterSampling(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): - __slots__ = () - RASTER_SAMPLING_UNSPECIFIED: _ClassVar[RasterSampling] - RASTER_SAMPLING_AREA: _ClassVar[RasterSampling] - RASTER_SAMPLING_POINT: _ClassVar[RasterSampling] -EO_COMMON_NAME_UNSPECIFIED: EOCommonName -EO_COMMON_NAME_PAN: EOCommonName -EO_COMMON_NAME_COASTAL: EOCommonName -EO_COMMON_NAME_BLUE: EOCommonName -EO_COMMON_NAME_GREEN: EOCommonName -EO_COMMON_NAME_GREEN05: EOCommonName -EO_COMMON_NAME_YELLOW: EOCommonName -EO_COMMON_NAME_RED: EOCommonName -EO_COMMON_NAME_REDEDGE: EOCommonName -EO_COMMON_NAME_REDEDGE071: EOCommonName -EO_COMMON_NAME_REDEDGE075: EOCommonName -EO_COMMON_NAME_REDEDGE078: EOCommonName -EO_COMMON_NAME_NIR: EOCommonName -EO_COMMON_NAME_NIR08: EOCommonName -EO_COMMON_NAME_NIR09: EOCommonName -EO_COMMON_NAME_CIRRUS: EOCommonName -EO_COMMON_NAME_SWIR16: EOCommonName -EO_COMMON_NAME_SWIR22: EOCommonName -EO_COMMON_NAME_LWIR: EOCommonName -EO_COMMON_NAME_LWIR11: EOCommonName -EO_COMMON_NAME_LWIR12: EOCommonName -RASTER_SAMPLING_UNSPECIFIED: RasterSampling -RASTER_SAMPLING_AREA: RasterSampling -RASTER_SAMPLING_POINT: RasterSampling - -class EOProperties(_message.Message): - __slots__ = ("common_name", "center_wavelength", "full_width_half_max", "solar_illumination") - COMMON_NAME_FIELD_NUMBER: _ClassVar[int] - CENTER_WAVELENGTH_FIELD_NUMBER: _ClassVar[int] - FULL_WIDTH_HALF_MAX_FIELD_NUMBER: _ClassVar[int] - SOLAR_ILLUMINATION_FIELD_NUMBER: _ClassVar[int] - common_name: EOCommonName - center_wavelength: float - full_width_half_max: float - solar_illumination: float - def __init__(self, common_name: _Optional[_Union[EOCommonName, str]] = ..., center_wavelength: _Optional[float] = ..., full_width_half_max: _Optional[float] = ..., solar_illumination: _Optional[float] = ...) -> None: ... - -class RasterProperties(_message.Message): - __slots__ = ("sampling", "scale", "offset", "spatial_resolution") - SAMPLING_FIELD_NUMBER: _ClassVar[int] - SCALE_FIELD_NUMBER: _ClassVar[int] - OFFSET_FIELD_NUMBER: _ClassVar[int] - SPATIAL_RESOLUTION_FIELD_NUMBER: _ClassVar[int] - sampling: RasterSampling - scale: float - offset: float - spatial_resolution: float - def __init__(self, sampling: _Optional[_Union[RasterSampling, str]] = ..., scale: _Optional[float] = ..., offset: _Optional[float] = ..., spatial_resolution: _Optional[float] = ...) -> None: ... - -class ClassificationClass(_message.Message): - __slots__ = ("value", "description", "name", "title", "color_hint", "nodata", "percentage", "count") - VALUE_FIELD_NUMBER: _ClassVar[int] - DESCRIPTION_FIELD_NUMBER: _ClassVar[int] - NAME_FIELD_NUMBER: _ClassVar[int] - TITLE_FIELD_NUMBER: _ClassVar[int] - COLOR_HINT_FIELD_NUMBER: _ClassVar[int] - NODATA_FIELD_NUMBER: _ClassVar[int] - PERCENTAGE_FIELD_NUMBER: _ClassVar[int] - COUNT_FIELD_NUMBER: _ClassVar[int] - value: int - description: str - name: str - title: str - color_hint: str - nodata: bool - percentage: float - count: int - def __init__(self, value: _Optional[int] = ..., description: _Optional[str] = ..., name: _Optional[str] = ..., title: _Optional[str] = ..., color_hint: _Optional[str] = ..., nodata: bool = ..., percentage: _Optional[float] = ..., count: _Optional[int] = ...) -> None: ... - -class ClassificationBitField(_message.Message): - __slots__ = ("offset", "length", "classes", "name", "description", "roles") - OFFSET_FIELD_NUMBER: _ClassVar[int] - LENGTH_FIELD_NUMBER: _ClassVar[int] - CLASSES_FIELD_NUMBER: _ClassVar[int] - NAME_FIELD_NUMBER: _ClassVar[int] - DESCRIPTION_FIELD_NUMBER: _ClassVar[int] - ROLES_FIELD_NUMBER: _ClassVar[int] - offset: int - length: int - classes: _containers.RepeatedCompositeFieldContainer[ClassificationClass] - name: str - description: str - roles: _containers.RepeatedScalarFieldContainer[str] - def __init__(self, offset: _Optional[int] = ..., length: _Optional[int] = ..., classes: _Optional[_Iterable[_Union[ClassificationClass, _Mapping]]] = ..., name: _Optional[str] = ..., description: _Optional[str] = ..., roles: _Optional[_Iterable[str]] = ...) -> None: ... - -class Projection(_message.Message): - __slots__ = ("code", "bbox", "shape", "transform") - CODE_FIELD_NUMBER: _ClassVar[int] - BBOX_FIELD_NUMBER: _ClassVar[int] - SHAPE_FIELD_NUMBER: _ClassVar[int] - TRANSFORM_FIELD_NUMBER: _ClassVar[int] - code: str - bbox: _containers.RepeatedScalarFieldContainer[float] - shape: _containers.RepeatedScalarFieldContainer[int] - transform: _containers.RepeatedScalarFieldContainer[float] - def __init__(self, code: _Optional[str] = ..., bbox: _Optional[_Iterable[float]] = ..., shape: _Optional[_Iterable[int]] = ..., transform: _Optional[_Iterable[float]] = ...) -> None: ... - -class View(_message.Message): - __slots__ = ("incidence_angle", "azimuth") - INCIDENCE_ANGLE_FIELD_NUMBER: _ClassVar[int] - AZIMUTH_FIELD_NUMBER: _ClassVar[int] - incidence_angle: float - azimuth: float - def __init__(self, incidence_angle: _Optional[float] = ..., azimuth: _Optional[float] = ...) -> None: ... - -class File(_message.Message): - __slots__ = ("checksum", "size", "local_path") - CHECKSUM_FIELD_NUMBER: _ClassVar[int] - SIZE_FIELD_NUMBER: _ClassVar[int] - LOCAL_PATH_FIELD_NUMBER: _ClassVar[int] - checksum: bytes - size: int - local_path: str - def __init__(self, checksum: _Optional[bytes] = ..., size: _Optional[int] = ..., local_path: _Optional[str] = ...) -> None: ... diff --git a/tilebox-datasets/tilebox/datasets/datasets/stac/v1/asset_metadata_pb2_grpc.py b/tilebox-datasets/tilebox/datasets/datasets/stac/v1/asset_metadata_pb2_grpc.py deleted file mode 100644 index 8a93939..0000000 --- a/tilebox-datasets/tilebox/datasets/datasets/stac/v1/asset_metadata_pb2_grpc.py +++ /dev/null @@ -1,3 +0,0 @@ -# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! -"""Client and server classes corresponding to protobuf-defined services.""" -import grpc diff --git a/tilebox-datasets/tilebox/datasets/datasets/stac/v1/asset_pb2.py b/tilebox-datasets/tilebox/datasets/datasets/stac/v1/asset_pb2.py deleted file mode 100644 index 81b0be2..0000000 --- a/tilebox-datasets/tilebox/datasets/datasets/stac/v1/asset_pb2.py +++ /dev/null @@ -1,86 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by the protocol buffer compiler. DO NOT EDIT! -# NO CHECKED-IN PROTOBUF GENCODE -# source: datasets/stac/v1/asset.proto -# Protobuf Python Version: 6.30.0 -"""Generated protocol buffer code.""" -from google.protobuf import descriptor as _descriptor -from google.protobuf import descriptor_pool as _descriptor_pool -from google.protobuf import runtime_version as _runtime_version -from google.protobuf import symbol_database as _symbol_database -from google.protobuf.internal import builder as _builder -_runtime_version.ValidateProtobufRuntimeVersion( - _runtime_version.Domain.PUBLIC, - 6, - 30, - 0, - '', - 'datasets/stac/v1/asset.proto' -) -# @@protoc_insertion_point(imports) - -_sym_db = _symbol_database.Default() - - -from tilebox.datasets.datasets.stac.v1 import asset_metadata_pb2 as datasets_dot_stac_dot_v1_dot_asset__metadata__pb2 -from tilebox.datasets.datasets.stac.v1 import core_pb2 as datasets_dot_stac_dot_v1_dot_core__pb2 -from tilebox.datasets.datasets.stac.v1 import product_pb2 as datasets_dot_stac_dot_v1_dot_product__pb2 -from tilebox.datasets.datasets.stac.v1 import sar_pb2 as datasets_dot_stac_dot_v1_dot_sar__pb2 -from tilebox.datasets.datasets.stac.v1 import satellite_pb2 as datasets_dot_stac_dot_v1_dot_satellite__pb2 - - -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1c\x64\x61tasets/stac/v1/asset.proto\x12\x10\x64\x61tasets.stac.v1\x1a%datasets/stac/v1/asset_metadata.proto\x1a\x1b\x64\x61tasets/stac/v1/core.proto\x1a\x1e\x64\x61tasets/stac/v1/product.proto\x1a\x1a\x64\x61tasets/stac/v1/sar.proto\x1a datasets/stac/v1/satellite.proto\"N\n\nStatistics\x12\x1f\n\x07minimum\x18\x01 \x01(\x01\x42\x05\xaa\x01\x02\x08\x01R\x07minimum\x12\x1f\n\x07maximum\x18\x02 \x01(\x01\x42\x05\xaa\x01\x02\x08\x01R\x07maximum\"\xd3\x01\n\x12\x41ssetAccessProfile\x12#\n\ralternate_key\x18\x01 \x01(\tR\x0c\x61lternateKey\x12;\n\x16\x64\x65\x66\x61ult_alternate_name\x18\x02 \x01(\tB\x05\xaa\x01\x02\x08\x01R\x14\x64\x65\x66\x61ultAlternateName\x12\x1b\n\tbase_href\x18\x03 \x01(\tR\x08\x62\x61seHref\x12!\n\x0cstorage_refs\x18\x04 \x03(\tR\x0bstorageRefs\x12\x1b\n\tauth_refs\x18\x05 \x03(\tR\x08\x61uthRefs\"\x91\x01\n\rAssetLocation\x12\x37\n\x14\x61\x63\x63\x65ss_profile_index\x18\x01 \x01(\rB\x05\xaa\x01\x02\x08\x01R\x12\x61\x63\x63\x65ssProfileIndex\x12\x19\n\x04href\x18\x02 \x01(\tB\x05\xaa\x01\x02\x08\x01R\x04href\x12,\n\x0e\x61lternate_name\x18\x03 \x01(\tB\x05\xaa\x01\x02\x08\x01R\ralternateName\"\xe5\x03\n\x04\x42\x61nd\x12\x19\n\x04name\x18\x01 \x01(\tB\x05\xaa\x01\x02\x08\x01R\x04name\x12\'\n\x0b\x64\x65scription\x18\x02 \x01(\tB\x05\xaa\x01\x02\x08\x01R\x0b\x64\x65scription\x12\x37\n\tdata_type\x18\x03 \x01(\x0e\x32\x1a.datasets.stac.v1.DataTypeR\x08\x64\x61taType\x12\x1d\n\x06nodata\x18\x04 \x01(\x01\x42\x05\xaa\x01\x02\x08\x01R\x06nodata\x12\x19\n\x04unit\x18\x05 \x01(\tB\x05\xaa\x01\x02\x08\x01R\x04unit\x12.\n\x02\x65o\x18\x06 \x01(\x0b\x32\x1e.datasets.stac.v1.EOPropertiesR\x02\x65o\x12:\n\x06raster\x18\x07 \x01(\x0b\x32\".datasets.stac.v1.RasterPropertiesR\x06raster\x12?\n\x07\x63lasses\x18\x08 \x03(\x0b\x32%.datasets.stac.v1.ClassificationClassR\x07\x63lasses\x12\x31\n\x03sar\x18\t \x01(\x0b\x32\x1f.datasets.stac.v1.SARPropertiesR\x03sar\x12\x46\n\tbitfields\x18\n \x03(\x0b\x32(.datasets.stac.v1.ClassificationBitFieldR\tbitfields\"\xb0\t\n\x05\x41sset\x12\x10\n\x03key\x18\x01 \x01(\tR\x03key\x12\x39\n\x07primary\x18\x02 \x01(\x0b\x32\x1f.datasets.stac.v1.AssetLocationR\x07primary\x12?\n\nalternates\x18\x03 \x03(\x0b\x32\x1f.datasets.stac.v1.AssetLocationR\nalternates\x12:\n\nmedia_type\x18\x04 \x01(\x0b\x32\x1b.datasets.stac.v1.MediaTypeR\tmediaType\x12\x1b\n\x05title\x18\x05 \x01(\tB\x05\xaa\x01\x02\x08\x01R\x05title\x12\'\n\x0b\x64\x65scription\x18\x06 \x01(\tB\x05\xaa\x01\x02\x08\x01R\x0b\x64\x65scription\x12\x36\n\x05roles\x18\x07 \x03(\x0e\x32 .datasets.stac.v1.KnownAssetRoleR\x05roles\x12!\n\x0c\x63ustom_roles\x18\x08 \x03(\tR\x0b\x63ustomRoles\x12\x17\n\x03gsd\x18\t \x01(\x01\x42\x05\xaa\x01\x02\x08\x01R\x03gsd\x12\x30\n\x14\x62\x61nd_profile_indices\x18\n \x03(\rR\x12\x62\x61ndProfileIndices\x12\x37\n\tdata_type\x18\x0b \x01(\x0e\x32\x1a.datasets.stac.v1.DataTypeR\x08\x64\x61taType\x12\x1d\n\x06nodata\x18\x0c \x01(\x01\x42\x05\xaa\x01\x02\x08\x01R\x06nodata\x12<\n\nstatistics\x18\r \x01(\x0b\x32\x1c.datasets.stac.v1.StatisticsR\nstatistics\x12\x19\n\x04unit\x18\x0e \x01(\tB\x05\xaa\x01\x02\x08\x01R\x04unit\x12.\n\x02\x65o\x18\x0f \x01(\x0b\x32\x1e.datasets.stac.v1.EOPropertiesR\x02\x65o\x12:\n\x06raster\x18\x10 \x01(\x0b\x32\".datasets.stac.v1.RasterPropertiesR\x06raster\x12<\n\nprojection\x18\x11 \x01(\x0b\x32\x1c.datasets.stac.v1.ProjectionR\nprojection\x12*\n\x04view\x18\x12 \x01(\x0b\x32\x16.datasets.stac.v1.ViewR\x04view\x12?\n\x07\x63lasses\x18\x13 \x03(\x0b\x32%.datasets.stac.v1.ClassificationClassR\x07\x63lasses\x12*\n\x04\x66ile\x18\x14 \x01(\x0b\x32\x16.datasets.stac.v1.FileR\x04\x66ile\x12\x31\n\x03sar\x18\x15 \x01(\x0b\x32\x1f.datasets.stac.v1.SARPropertiesR\x03sar\x12\x43\n\tsatellite\x18\x16 \x01(\x0b\x32%.datasets.stac.v1.SatellitePropertiesR\tsatellite\x12=\n\x07product\x18\x17 \x01(\x0b\x32#.datasets.stac.v1.ProductPropertiesR\x07product\x12\x46\n\tbitfields\x18\x18 \x03(\x0b\x32(.datasets.stac.v1.ClassificationBitFieldR\tbitfields\"\xc5\x01\n\x06\x41ssets\x12M\n\x0f\x61\x63\x63\x65ss_profiles\x18\x01 \x03(\x0b\x32$.datasets.stac.v1.AssetAccessProfileR\x0e\x61\x63\x63\x65ssProfiles\x12;\n\rband_profiles\x18\x02 \x03(\x0b\x32\x16.datasets.stac.v1.BandR\x0c\x62\x61ndProfiles\x12/\n\x06\x61ssets\x18\x03 \x03(\x0b\x32\x17.datasets.stac.v1.AssetR\x06\x61ssets*\xbe\x07\n\x0eKnownAssetRole\x12 \n\x1cKNOWN_ASSET_ROLE_UNSPECIFIED\x10\x00\x12\x19\n\x15KNOWN_ASSET_ROLE_DATA\x10\x01\x12\x1d\n\x19KNOWN_ASSET_ROLE_METADATA\x10\x02\x12\x1e\n\x1aKNOWN_ASSET_ROLE_THUMBNAIL\x10\x03\x12\x1d\n\x19KNOWN_ASSET_ROLE_OVERVIEW\x10\x04\x12\x1b\n\x17KNOWN_ASSET_ROLE_VISUAL\x10\x05\x12\x19\n\x15KNOWN_ASSET_ROLE_DATE\x10\x06\x12\x1c\n\x18KNOWN_ASSET_ROLE_GRAPHIC\x10\x07\x12\x1e\n\x1aKNOWN_ASSET_ROLE_DATA_MASK\x10\x08\x12\x1d\n\x19KNOWN_ASSET_ROLE_SNOW_ICE\x10\t\x12\x1f\n\x1bKNOWN_ASSET_ROLE_LAND_WATER\x10\n\x12\x1f\n\x1bKNOWN_ASSET_ROLE_WATER_MASK\x10\x0b\x12\x1e\n\x1aKNOWN_ASSET_ROLE_ISO_19115\x10\x0c\x12 \n\x1cKNOWN_ASSET_ROLE_REFLECTANCE\x10\x64\x12 \n\x1cKNOWN_ASSET_ROLE_TEMPERATURE\x10\x65\x12\x1f\n\x1bKNOWN_ASSET_ROLE_SATURATION\x10\x66\x12\x1a\n\x16KNOWN_ASSET_ROLE_CLOUD\x10g\x12!\n\x1dKNOWN_ASSET_ROLE_CLOUD_SHADOW\x10h\x12+\n&KNOWN_ASSET_ROLE_LOCAL_INCIDENCE_ANGLE\x10\xc8\x01\x12/\n*KNOWN_ASSET_ROLE_ELLIPSOID_INCIDENCE_ANGLE\x10\xc9\x01\x12!\n\x1cKNOWN_ASSET_ROLE_NOISE_POWER\x10\xca\x01\x12\x1f\n\x1aKNOWN_ASSET_ROLE_AMPLITUDE\x10\xcb\x01\x12\x1f\n\x1aKNOWN_ASSET_ROLE_MAGNITUDE\x10\xcc\x01\x12\x1c\n\x17KNOWN_ASSET_ROLE_SIGMA0\x10\xcd\x01\x12\x1b\n\x16KNOWN_ASSET_ROLE_BETA0\x10\xce\x01\x12\x1c\n\x17KNOWN_ASSET_ROLE_GAMMA0\x10\xcf\x01\x12!\n\x1cKNOWN_ASSET_ROLE_DATE_OFFSET\x10\xd0\x01\x12\x1c\n\x17KNOWN_ASSET_ROLE_COVMAT\x10\xd1\x01\x12\x19\n\x14KNOWN_ASSET_ROLE_PRD\x10\xd2\x01*\x85\x03\n\x08\x44\x61taType\x12\x19\n\x15\x44\x41TA_TYPE_UNSPECIFIED\x10\x00\x12\x12\n\x0e\x44\x41TA_TYPE_INT8\x10\x01\x12\x13\n\x0f\x44\x41TA_TYPE_INT16\x10\x02\x12\x13\n\x0f\x44\x41TA_TYPE_INT32\x10\x03\x12\x13\n\x0f\x44\x41TA_TYPE_INT64\x10\x04\x12\x13\n\x0f\x44\x41TA_TYPE_UINT8\x10\x05\x12\x14\n\x10\x44\x41TA_TYPE_UINT16\x10\x06\x12\x14\n\x10\x44\x41TA_TYPE_UINT32\x10\x07\x12\x14\n\x10\x44\x41TA_TYPE_UINT64\x10\x08\x12\x15\n\x11\x44\x41TA_TYPE_FLOAT16\x10\t\x12\x15\n\x11\x44\x41TA_TYPE_FLOAT32\x10\n\x12\x15\n\x11\x44\x41TA_TYPE_FLOAT64\x10\x0b\x12\x14\n\x10\x44\x41TA_TYPE_CINT16\x10\x0c\x12\x14\n\x10\x44\x41TA_TYPE_CINT32\x10\r\x12\x16\n\x12\x44\x41TA_TYPE_CFLOAT32\x10\x0e\x12\x16\n\x12\x44\x41TA_TYPE_CFLOAT64\x10\x0f\x12\x13\n\x0f\x44\x41TA_TYPE_OTHER\x10\x10\x42\x89\x01\n\x14\x63om.datasets.stac.v1B\nAssetProtoP\x01\xa2\x02\x03\x44SX\xaa\x02\x10\x44\x61tasets.Stac.V1\xca\x02\x10\x44\x61tasets\\Stac\\V1\xe2\x02\x1c\x44\x61tasets\\Stac\\V1\\GPBMetadata\xea\x02\x12\x44\x61tasets::Stac::V1\x92\x03\x02\x08\x02\x62\x08\x65\x64itionsp\xe8\x07') - -_globals = globals() -_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) -_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'datasets.stac.v1.asset_pb2', _globals) -if not _descriptor._USE_C_DESCRIPTORS: - _globals['DESCRIPTOR']._loaded_options = None - _globals['DESCRIPTOR']._serialized_options = b'\n\024com.datasets.stac.v1B\nAssetProtoP\001\242\002\003DSX\252\002\020Datasets.Stac.V1\312\002\020Datasets\\Stac\\V1\342\002\034Datasets\\Stac\\V1\\GPBMetadata\352\002\022Datasets::Stac::V1\222\003\002\010\002' - _globals['_STATISTICS'].fields_by_name['minimum']._loaded_options = None - _globals['_STATISTICS'].fields_by_name['minimum']._serialized_options = b'\252\001\002\010\001' - _globals['_STATISTICS'].fields_by_name['maximum']._loaded_options = None - _globals['_STATISTICS'].fields_by_name['maximum']._serialized_options = b'\252\001\002\010\001' - _globals['_ASSETACCESSPROFILE'].fields_by_name['default_alternate_name']._loaded_options = None - _globals['_ASSETACCESSPROFILE'].fields_by_name['default_alternate_name']._serialized_options = b'\252\001\002\010\001' - _globals['_ASSETLOCATION'].fields_by_name['access_profile_index']._loaded_options = None - _globals['_ASSETLOCATION'].fields_by_name['access_profile_index']._serialized_options = b'\252\001\002\010\001' - _globals['_ASSETLOCATION'].fields_by_name['href']._loaded_options = None - _globals['_ASSETLOCATION'].fields_by_name['href']._serialized_options = b'\252\001\002\010\001' - _globals['_ASSETLOCATION'].fields_by_name['alternate_name']._loaded_options = None - _globals['_ASSETLOCATION'].fields_by_name['alternate_name']._serialized_options = b'\252\001\002\010\001' - _globals['_BAND'].fields_by_name['name']._loaded_options = None - _globals['_BAND'].fields_by_name['name']._serialized_options = b'\252\001\002\010\001' - _globals['_BAND'].fields_by_name['description']._loaded_options = None - _globals['_BAND'].fields_by_name['description']._serialized_options = b'\252\001\002\010\001' - _globals['_BAND'].fields_by_name['nodata']._loaded_options = None - _globals['_BAND'].fields_by_name['nodata']._serialized_options = b'\252\001\002\010\001' - _globals['_BAND'].fields_by_name['unit']._loaded_options = None - _globals['_BAND'].fields_by_name['unit']._serialized_options = b'\252\001\002\010\001' - _globals['_ASSET'].fields_by_name['title']._loaded_options = None - _globals['_ASSET'].fields_by_name['title']._serialized_options = b'\252\001\002\010\001' - _globals['_ASSET'].fields_by_name['description']._loaded_options = None - _globals['_ASSET'].fields_by_name['description']._serialized_options = b'\252\001\002\010\001' - _globals['_ASSET'].fields_by_name['gsd']._loaded_options = None - _globals['_ASSET'].fields_by_name['gsd']._serialized_options = b'\252\001\002\010\001' - _globals['_ASSET'].fields_by_name['nodata']._loaded_options = None - _globals['_ASSET'].fields_by_name['nodata']._serialized_options = b'\252\001\002\010\001' - _globals['_ASSET'].fields_by_name['unit']._loaded_options = None - _globals['_ASSET'].fields_by_name['unit']._serialized_options = b'\252\001\002\010\001' - _globals['_KNOWNASSETROLE']._serialized_start=2546 - _globals['_KNOWNASSETROLE']._serialized_end=3504 - _globals['_DATATYPE']._serialized_start=3507 - _globals['_DATATYPE']._serialized_end=3896 - _globals['_STATISTICS']._serialized_start=212 - _globals['_STATISTICS']._serialized_end=290 - _globals['_ASSETACCESSPROFILE']._serialized_start=293 - _globals['_ASSETACCESSPROFILE']._serialized_end=504 - _globals['_ASSETLOCATION']._serialized_start=507 - _globals['_ASSETLOCATION']._serialized_end=652 - _globals['_BAND']._serialized_start=655 - _globals['_BAND']._serialized_end=1140 - _globals['_ASSET']._serialized_start=1143 - _globals['_ASSET']._serialized_end=2343 - _globals['_ASSETS']._serialized_start=2346 - _globals['_ASSETS']._serialized_end=2543 -# @@protoc_insertion_point(module_scope) diff --git a/tilebox-datasets/tilebox/datasets/datasets/stac/v1/asset_pb2.pyi b/tilebox-datasets/tilebox/datasets/datasets/stac/v1/asset_pb2.pyi deleted file mode 100644 index 0239bc6..0000000 --- a/tilebox-datasets/tilebox/datasets/datasets/stac/v1/asset_pb2.pyi +++ /dev/null @@ -1,229 +0,0 @@ -from tilebox.datasets.datasets.stac.v1 import asset_metadata_pb2 as _asset_metadata_pb2 -from tilebox.datasets.datasets.stac.v1 import core_pb2 as _core_pb2 -from tilebox.datasets.datasets.stac.v1 import product_pb2 as _product_pb2 -from tilebox.datasets.datasets.stac.v1 import sar_pb2 as _sar_pb2 -from tilebox.datasets.datasets.stac.v1 import satellite_pb2 as _satellite_pb2 -from google.protobuf.internal import containers as _containers -from google.protobuf.internal import enum_type_wrapper as _enum_type_wrapper -from google.protobuf import descriptor as _descriptor -from google.protobuf import message as _message -from collections.abc import Iterable as _Iterable, Mapping as _Mapping -from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union - -DESCRIPTOR: _descriptor.FileDescriptor - -class KnownAssetRole(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): - __slots__ = () - KNOWN_ASSET_ROLE_UNSPECIFIED: _ClassVar[KnownAssetRole] - KNOWN_ASSET_ROLE_DATA: _ClassVar[KnownAssetRole] - KNOWN_ASSET_ROLE_METADATA: _ClassVar[KnownAssetRole] - KNOWN_ASSET_ROLE_THUMBNAIL: _ClassVar[KnownAssetRole] - KNOWN_ASSET_ROLE_OVERVIEW: _ClassVar[KnownAssetRole] - KNOWN_ASSET_ROLE_VISUAL: _ClassVar[KnownAssetRole] - KNOWN_ASSET_ROLE_DATE: _ClassVar[KnownAssetRole] - KNOWN_ASSET_ROLE_GRAPHIC: _ClassVar[KnownAssetRole] - KNOWN_ASSET_ROLE_DATA_MASK: _ClassVar[KnownAssetRole] - KNOWN_ASSET_ROLE_SNOW_ICE: _ClassVar[KnownAssetRole] - KNOWN_ASSET_ROLE_LAND_WATER: _ClassVar[KnownAssetRole] - KNOWN_ASSET_ROLE_WATER_MASK: _ClassVar[KnownAssetRole] - KNOWN_ASSET_ROLE_ISO_19115: _ClassVar[KnownAssetRole] - KNOWN_ASSET_ROLE_REFLECTANCE: _ClassVar[KnownAssetRole] - KNOWN_ASSET_ROLE_TEMPERATURE: _ClassVar[KnownAssetRole] - KNOWN_ASSET_ROLE_SATURATION: _ClassVar[KnownAssetRole] - KNOWN_ASSET_ROLE_CLOUD: _ClassVar[KnownAssetRole] - KNOWN_ASSET_ROLE_CLOUD_SHADOW: _ClassVar[KnownAssetRole] - KNOWN_ASSET_ROLE_LOCAL_INCIDENCE_ANGLE: _ClassVar[KnownAssetRole] - KNOWN_ASSET_ROLE_ELLIPSOID_INCIDENCE_ANGLE: _ClassVar[KnownAssetRole] - KNOWN_ASSET_ROLE_NOISE_POWER: _ClassVar[KnownAssetRole] - KNOWN_ASSET_ROLE_AMPLITUDE: _ClassVar[KnownAssetRole] - KNOWN_ASSET_ROLE_MAGNITUDE: _ClassVar[KnownAssetRole] - KNOWN_ASSET_ROLE_SIGMA0: _ClassVar[KnownAssetRole] - KNOWN_ASSET_ROLE_BETA0: _ClassVar[KnownAssetRole] - KNOWN_ASSET_ROLE_GAMMA0: _ClassVar[KnownAssetRole] - KNOWN_ASSET_ROLE_DATE_OFFSET: _ClassVar[KnownAssetRole] - KNOWN_ASSET_ROLE_COVMAT: _ClassVar[KnownAssetRole] - KNOWN_ASSET_ROLE_PRD: _ClassVar[KnownAssetRole] - -class DataType(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): - __slots__ = () - DATA_TYPE_UNSPECIFIED: _ClassVar[DataType] - DATA_TYPE_INT8: _ClassVar[DataType] - DATA_TYPE_INT16: _ClassVar[DataType] - DATA_TYPE_INT32: _ClassVar[DataType] - DATA_TYPE_INT64: _ClassVar[DataType] - DATA_TYPE_UINT8: _ClassVar[DataType] - DATA_TYPE_UINT16: _ClassVar[DataType] - DATA_TYPE_UINT32: _ClassVar[DataType] - DATA_TYPE_UINT64: _ClassVar[DataType] - DATA_TYPE_FLOAT16: _ClassVar[DataType] - DATA_TYPE_FLOAT32: _ClassVar[DataType] - DATA_TYPE_FLOAT64: _ClassVar[DataType] - DATA_TYPE_CINT16: _ClassVar[DataType] - DATA_TYPE_CINT32: _ClassVar[DataType] - DATA_TYPE_CFLOAT32: _ClassVar[DataType] - DATA_TYPE_CFLOAT64: _ClassVar[DataType] - DATA_TYPE_OTHER: _ClassVar[DataType] -KNOWN_ASSET_ROLE_UNSPECIFIED: KnownAssetRole -KNOWN_ASSET_ROLE_DATA: KnownAssetRole -KNOWN_ASSET_ROLE_METADATA: KnownAssetRole -KNOWN_ASSET_ROLE_THUMBNAIL: KnownAssetRole -KNOWN_ASSET_ROLE_OVERVIEW: KnownAssetRole -KNOWN_ASSET_ROLE_VISUAL: KnownAssetRole -KNOWN_ASSET_ROLE_DATE: KnownAssetRole -KNOWN_ASSET_ROLE_GRAPHIC: KnownAssetRole -KNOWN_ASSET_ROLE_DATA_MASK: KnownAssetRole -KNOWN_ASSET_ROLE_SNOW_ICE: KnownAssetRole -KNOWN_ASSET_ROLE_LAND_WATER: KnownAssetRole -KNOWN_ASSET_ROLE_WATER_MASK: KnownAssetRole -KNOWN_ASSET_ROLE_ISO_19115: KnownAssetRole -KNOWN_ASSET_ROLE_REFLECTANCE: KnownAssetRole -KNOWN_ASSET_ROLE_TEMPERATURE: KnownAssetRole -KNOWN_ASSET_ROLE_SATURATION: KnownAssetRole -KNOWN_ASSET_ROLE_CLOUD: KnownAssetRole -KNOWN_ASSET_ROLE_CLOUD_SHADOW: KnownAssetRole -KNOWN_ASSET_ROLE_LOCAL_INCIDENCE_ANGLE: KnownAssetRole -KNOWN_ASSET_ROLE_ELLIPSOID_INCIDENCE_ANGLE: KnownAssetRole -KNOWN_ASSET_ROLE_NOISE_POWER: KnownAssetRole -KNOWN_ASSET_ROLE_AMPLITUDE: KnownAssetRole -KNOWN_ASSET_ROLE_MAGNITUDE: KnownAssetRole -KNOWN_ASSET_ROLE_SIGMA0: KnownAssetRole -KNOWN_ASSET_ROLE_BETA0: KnownAssetRole -KNOWN_ASSET_ROLE_GAMMA0: KnownAssetRole -KNOWN_ASSET_ROLE_DATE_OFFSET: KnownAssetRole -KNOWN_ASSET_ROLE_COVMAT: KnownAssetRole -KNOWN_ASSET_ROLE_PRD: KnownAssetRole -DATA_TYPE_UNSPECIFIED: DataType -DATA_TYPE_INT8: DataType -DATA_TYPE_INT16: DataType -DATA_TYPE_INT32: DataType -DATA_TYPE_INT64: DataType -DATA_TYPE_UINT8: DataType -DATA_TYPE_UINT16: DataType -DATA_TYPE_UINT32: DataType -DATA_TYPE_UINT64: DataType -DATA_TYPE_FLOAT16: DataType -DATA_TYPE_FLOAT32: DataType -DATA_TYPE_FLOAT64: DataType -DATA_TYPE_CINT16: DataType -DATA_TYPE_CINT32: DataType -DATA_TYPE_CFLOAT32: DataType -DATA_TYPE_CFLOAT64: DataType -DATA_TYPE_OTHER: DataType - -class Statistics(_message.Message): - __slots__ = ("minimum", "maximum") - MINIMUM_FIELD_NUMBER: _ClassVar[int] - MAXIMUM_FIELD_NUMBER: _ClassVar[int] - minimum: float - maximum: float - def __init__(self, minimum: _Optional[float] = ..., maximum: _Optional[float] = ...) -> None: ... - -class AssetAccessProfile(_message.Message): - __slots__ = ("alternate_key", "default_alternate_name", "base_href", "storage_refs", "auth_refs") - ALTERNATE_KEY_FIELD_NUMBER: _ClassVar[int] - DEFAULT_ALTERNATE_NAME_FIELD_NUMBER: _ClassVar[int] - BASE_HREF_FIELD_NUMBER: _ClassVar[int] - STORAGE_REFS_FIELD_NUMBER: _ClassVar[int] - AUTH_REFS_FIELD_NUMBER: _ClassVar[int] - alternate_key: str - default_alternate_name: str - base_href: str - storage_refs: _containers.RepeatedScalarFieldContainer[str] - auth_refs: _containers.RepeatedScalarFieldContainer[str] - def __init__(self, alternate_key: _Optional[str] = ..., default_alternate_name: _Optional[str] = ..., base_href: _Optional[str] = ..., storage_refs: _Optional[_Iterable[str]] = ..., auth_refs: _Optional[_Iterable[str]] = ...) -> None: ... - -class AssetLocation(_message.Message): - __slots__ = ("access_profile_index", "href", "alternate_name") - ACCESS_PROFILE_INDEX_FIELD_NUMBER: _ClassVar[int] - HREF_FIELD_NUMBER: _ClassVar[int] - ALTERNATE_NAME_FIELD_NUMBER: _ClassVar[int] - access_profile_index: int - href: str - alternate_name: str - def __init__(self, access_profile_index: _Optional[int] = ..., href: _Optional[str] = ..., alternate_name: _Optional[str] = ...) -> None: ... - -class Band(_message.Message): - __slots__ = ("name", "description", "data_type", "nodata", "unit", "eo", "raster", "classes", "sar", "bitfields") - NAME_FIELD_NUMBER: _ClassVar[int] - DESCRIPTION_FIELD_NUMBER: _ClassVar[int] - DATA_TYPE_FIELD_NUMBER: _ClassVar[int] - NODATA_FIELD_NUMBER: _ClassVar[int] - UNIT_FIELD_NUMBER: _ClassVar[int] - EO_FIELD_NUMBER: _ClassVar[int] - RASTER_FIELD_NUMBER: _ClassVar[int] - CLASSES_FIELD_NUMBER: _ClassVar[int] - SAR_FIELD_NUMBER: _ClassVar[int] - BITFIELDS_FIELD_NUMBER: _ClassVar[int] - name: str - description: str - data_type: DataType - nodata: float - unit: str - eo: _asset_metadata_pb2.EOProperties - raster: _asset_metadata_pb2.RasterProperties - classes: _containers.RepeatedCompositeFieldContainer[_asset_metadata_pb2.ClassificationClass] - sar: _sar_pb2.SARProperties - bitfields: _containers.RepeatedCompositeFieldContainer[_asset_metadata_pb2.ClassificationBitField] - def __init__(self, name: _Optional[str] = ..., description: _Optional[str] = ..., data_type: _Optional[_Union[DataType, str]] = ..., nodata: _Optional[float] = ..., unit: _Optional[str] = ..., eo: _Optional[_Union[_asset_metadata_pb2.EOProperties, _Mapping]] = ..., raster: _Optional[_Union[_asset_metadata_pb2.RasterProperties, _Mapping]] = ..., classes: _Optional[_Iterable[_Union[_asset_metadata_pb2.ClassificationClass, _Mapping]]] = ..., sar: _Optional[_Union[_sar_pb2.SARProperties, _Mapping]] = ..., bitfields: _Optional[_Iterable[_Union[_asset_metadata_pb2.ClassificationBitField, _Mapping]]] = ...) -> None: ... - -class Asset(_message.Message): - __slots__ = ("key", "primary", "alternates", "media_type", "title", "description", "roles", "custom_roles", "gsd", "band_profile_indices", "data_type", "nodata", "statistics", "unit", "eo", "raster", "projection", "view", "classes", "file", "sar", "satellite", "product", "bitfields") - KEY_FIELD_NUMBER: _ClassVar[int] - PRIMARY_FIELD_NUMBER: _ClassVar[int] - ALTERNATES_FIELD_NUMBER: _ClassVar[int] - MEDIA_TYPE_FIELD_NUMBER: _ClassVar[int] - TITLE_FIELD_NUMBER: _ClassVar[int] - DESCRIPTION_FIELD_NUMBER: _ClassVar[int] - ROLES_FIELD_NUMBER: _ClassVar[int] - CUSTOM_ROLES_FIELD_NUMBER: _ClassVar[int] - GSD_FIELD_NUMBER: _ClassVar[int] - BAND_PROFILE_INDICES_FIELD_NUMBER: _ClassVar[int] - DATA_TYPE_FIELD_NUMBER: _ClassVar[int] - NODATA_FIELD_NUMBER: _ClassVar[int] - STATISTICS_FIELD_NUMBER: _ClassVar[int] - UNIT_FIELD_NUMBER: _ClassVar[int] - EO_FIELD_NUMBER: _ClassVar[int] - RASTER_FIELD_NUMBER: _ClassVar[int] - PROJECTION_FIELD_NUMBER: _ClassVar[int] - VIEW_FIELD_NUMBER: _ClassVar[int] - CLASSES_FIELD_NUMBER: _ClassVar[int] - FILE_FIELD_NUMBER: _ClassVar[int] - SAR_FIELD_NUMBER: _ClassVar[int] - SATELLITE_FIELD_NUMBER: _ClassVar[int] - PRODUCT_FIELD_NUMBER: _ClassVar[int] - BITFIELDS_FIELD_NUMBER: _ClassVar[int] - key: str - primary: AssetLocation - alternates: _containers.RepeatedCompositeFieldContainer[AssetLocation] - media_type: _core_pb2.MediaType - title: str - description: str - roles: _containers.RepeatedScalarFieldContainer[KnownAssetRole] - custom_roles: _containers.RepeatedScalarFieldContainer[str] - gsd: float - band_profile_indices: _containers.RepeatedScalarFieldContainer[int] - data_type: DataType - nodata: float - statistics: Statistics - unit: str - eo: _asset_metadata_pb2.EOProperties - raster: _asset_metadata_pb2.RasterProperties - projection: _asset_metadata_pb2.Projection - view: _asset_metadata_pb2.View - classes: _containers.RepeatedCompositeFieldContainer[_asset_metadata_pb2.ClassificationClass] - file: _asset_metadata_pb2.File - sar: _sar_pb2.SARProperties - satellite: _satellite_pb2.SatelliteProperties - product: _product_pb2.ProductProperties - bitfields: _containers.RepeatedCompositeFieldContainer[_asset_metadata_pb2.ClassificationBitField] - def __init__(self, key: _Optional[str] = ..., primary: _Optional[_Union[AssetLocation, _Mapping]] = ..., alternates: _Optional[_Iterable[_Union[AssetLocation, _Mapping]]] = ..., media_type: _Optional[_Union[_core_pb2.MediaType, _Mapping]] = ..., title: _Optional[str] = ..., description: _Optional[str] = ..., roles: _Optional[_Iterable[_Union[KnownAssetRole, str]]] = ..., custom_roles: _Optional[_Iterable[str]] = ..., gsd: _Optional[float] = ..., band_profile_indices: _Optional[_Iterable[int]] = ..., data_type: _Optional[_Union[DataType, str]] = ..., nodata: _Optional[float] = ..., statistics: _Optional[_Union[Statistics, _Mapping]] = ..., unit: _Optional[str] = ..., eo: _Optional[_Union[_asset_metadata_pb2.EOProperties, _Mapping]] = ..., raster: _Optional[_Union[_asset_metadata_pb2.RasterProperties, _Mapping]] = ..., projection: _Optional[_Union[_asset_metadata_pb2.Projection, _Mapping]] = ..., view: _Optional[_Union[_asset_metadata_pb2.View, _Mapping]] = ..., classes: _Optional[_Iterable[_Union[_asset_metadata_pb2.ClassificationClass, _Mapping]]] = ..., file: _Optional[_Union[_asset_metadata_pb2.File, _Mapping]] = ..., sar: _Optional[_Union[_sar_pb2.SARProperties, _Mapping]] = ..., satellite: _Optional[_Union[_satellite_pb2.SatelliteProperties, _Mapping]] = ..., product: _Optional[_Union[_product_pb2.ProductProperties, _Mapping]] = ..., bitfields: _Optional[_Iterable[_Union[_asset_metadata_pb2.ClassificationBitField, _Mapping]]] = ...) -> None: ... - -class Assets(_message.Message): - __slots__ = ("access_profiles", "band_profiles", "assets") - ACCESS_PROFILES_FIELD_NUMBER: _ClassVar[int] - BAND_PROFILES_FIELD_NUMBER: _ClassVar[int] - ASSETS_FIELD_NUMBER: _ClassVar[int] - access_profiles: _containers.RepeatedCompositeFieldContainer[AssetAccessProfile] - band_profiles: _containers.RepeatedCompositeFieldContainer[Band] - assets: _containers.RepeatedCompositeFieldContainer[Asset] - def __init__(self, access_profiles: _Optional[_Iterable[_Union[AssetAccessProfile, _Mapping]]] = ..., band_profiles: _Optional[_Iterable[_Union[Band, _Mapping]]] = ..., assets: _Optional[_Iterable[_Union[Asset, _Mapping]]] = ...) -> None: ... diff --git a/tilebox-datasets/tilebox/datasets/datasets/stac/v1/asset_pb2_grpc.py b/tilebox-datasets/tilebox/datasets/datasets/stac/v1/asset_pb2_grpc.py deleted file mode 100644 index 8a93939..0000000 --- a/tilebox-datasets/tilebox/datasets/datasets/stac/v1/asset_pb2_grpc.py +++ /dev/null @@ -1,3 +0,0 @@ -# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! -"""Client and server classes corresponding to protobuf-defined services.""" -import grpc diff --git a/tilebox-datasets/tilebox/datasets/datasets/stac/v1/authentication_pb2.py b/tilebox-datasets/tilebox/datasets/datasets/stac/v1/authentication_pb2.py deleted file mode 100644 index 2d7a6ca..0000000 --- a/tilebox-datasets/tilebox/datasets/datasets/stac/v1/authentication_pb2.py +++ /dev/null @@ -1,88 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by the protocol buffer compiler. DO NOT EDIT! -# NO CHECKED-IN PROTOBUF GENCODE -# source: datasets/stac/v1/authentication.proto -# Protobuf Python Version: 6.30.0 -"""Generated protocol buffer code.""" -from google.protobuf import descriptor as _descriptor -from google.protobuf import descriptor_pool as _descriptor_pool -from google.protobuf import runtime_version as _runtime_version -from google.protobuf import symbol_database as _symbol_database -from google.protobuf.internal import builder as _builder -_runtime_version.ValidateProtobufRuntimeVersion( - _runtime_version.Domain.PUBLIC, - 6, - 30, - 0, - '', - 'datasets/stac/v1/authentication.proto' -) -# @@protoc_insertion_point(imports) - -_sym_db = _symbol_database.Default() - - -from google.protobuf import struct_pb2 as google_dot_protobuf_dot_struct__pb2 - - -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n%datasets/stac/v1/authentication.proto\x12\x10\x64\x61tasets.stac.v1\x1a\x1cgoogle/protobuf/struct.proto\"\xbd\x01\n\x0e\x41uthentication\x12G\n\x07schemes\x18\x01 \x03(\x0b\x32-.datasets.stac.v1.Authentication.SchemesEntryR\x07schemes\x1a\x62\n\x0cSchemesEntry\x12\x10\n\x03key\x18\x01 \x01(\tR\x03key\x12<\n\x05value\x18\x02 \x01(\x0b\x32&.datasets.stac.v1.AuthenticationSchemeR\x05value:\x02\x38\x01\"\x81\x03\n\x14\x41uthenticationScheme\x12O\n\nknown_type\x18\x01 \x01(\x0e\x32).datasets.stac.v1.KnownAuthenticationTypeB\x05\xaa\x01\x02\x08\x01R\tknownType\x12&\n\x0b\x63ustom_type\x18\x02 \x01(\tB\x05\xaa\x01\x02\x08\x01R\ncustomType\x12\'\n\x0b\x64\x65scription\x18\x03 \x01(\tB\x05\xaa\x01\x02\x08\x01R\x0b\x64\x65scription\x12\x19\n\x04name\x18\x04 \x01(\tB\x05\xaa\x01\x02\x08\x01R\x04name\x12\x1b\n\x08location\x18\x05 \x01(\tB\x05\xaa\x01\x02\x08\x01R\x02in\x12\x1d\n\x06scheme\x18\x06 \x01(\tB\x05\xaa\x01\x02\x08\x01R\x06scheme\x12:\n\x05\x66lows\x18\x07 \x03(\x0b\x32$.datasets.stac.v1.AuthenticationFlowR\x05\x66lows\x12\x34\n\x13open_id_connect_url\x18\x08 \x01(\tB\x05\xaa\x01\x02\x08\x01R\x10openIdConnectUrl\"\x9c\x01\n\x12\x41uthenticationFlow\x12\x10\n\x03key\x18\x01 \x01(\tR\x03key\x12\x34\n\x06oauth2\x18\x02 \x01(\x0b\x32\x1c.datasets.stac.v1.OAuth2FlowR\x06oauth2\x12>\n\nsigned_url\x18\x03 \x01(\x0b\x32\x1f.datasets.stac.v1.SignedURLFlowR\tsignedUrl\"\x89\x02\n\nOAuth2Flow\x12\x32\n\x11\x61uthorization_url\x18\x01 \x01(\tB\x05\xaa\x01\x02\x08\x01R\x10\x61uthorizationUrl\x12\"\n\ttoken_url\x18\x02 \x01(\tB\x05\xaa\x01\x02\x08\x01R\x08tokenUrl\x12@\n\x06scopes\x18\x03 \x03(\x0b\x32(.datasets.stac.v1.OAuth2Flow.ScopesEntryR\x06scopes\x12&\n\x0brefresh_url\x18\x04 \x01(\tB\x05\xaa\x01\x02\x08\x01R\nrefreshUrl\x1a\x39\n\x0bScopesEntry\x12\x10\n\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n\x05value\x18\x02 \x01(\tR\x05value:\x02\x38\x01\"\xbd\x02\n\rSignedURLFlow\x12\x16\n\x06method\x18\x01 \x01(\tR\x06method\x12+\n\x11\x61uthorization_api\x18\x02 \x01(\tR\x10\x61uthorizationApi\x12O\n\nparameters\x18\x03 \x03(\x0b\x32/.datasets.stac.v1.SignedURLFlow.ParametersEntryR\nparameters\x12,\n\x0eresponse_field\x18\x04 \x01(\tB\x05\xaa\x01\x02\x08\x01R\rresponseField\x1ah\n\x0fParametersEntry\x12\x10\n\x03key\x18\x01 \x01(\tR\x03key\x12?\n\x05value\x18\x02 \x01(\x0b\x32).datasets.stac.v1.AuthenticationParameterR\x05value:\x02\x38\x01\"\xac\x01\n\x17\x41uthenticationParameter\x12\x14\n\x08location\x18\x01 \x01(\tR\x02in\x12!\n\x08required\x18\x02 \x01(\x08\x42\x05\xaa\x01\x02\x08\x01R\x08required\x12\'\n\x0b\x64\x65scription\x18\x03 \x01(\tB\x05\xaa\x01\x02\x08\x01R\x0b\x64\x65scription\x12/\n\x06schema\x18\x04 \x01(\x0b\x32\x17.google.protobuf.StructR\x06schema*\xb0\x02\n\x17KnownAuthenticationType\x12)\n%KNOWN_AUTHENTICATION_TYPE_UNSPECIFIED\x10\x00\x12\"\n\x1eKNOWN_AUTHENTICATION_TYPE_HTTP\x10\x01\x12 \n\x1cKNOWN_AUTHENTICATION_TYPE_S3\x10\x02\x12(\n$KNOWN_AUTHENTICATION_TYPE_SIGNED_URL\x10\x03\x12$\n KNOWN_AUTHENTICATION_TYPE_OAUTH2\x10\x04\x12%\n!KNOWN_AUTHENTICATION_TYPE_API_KEY\x10\x05\x12-\n)KNOWN_AUTHENTICATION_TYPE_OPEN_ID_CONNECT\x10\x06\x42\x92\x01\n\x14\x63om.datasets.stac.v1B\x13\x41uthenticationProtoP\x01\xa2\x02\x03\x44SX\xaa\x02\x10\x44\x61tasets.Stac.V1\xca\x02\x10\x44\x61tasets\\Stac\\V1\xe2\x02\x1c\x44\x61tasets\\Stac\\V1\\GPBMetadata\xea\x02\x12\x44\x61tasets::Stac::V1\x92\x03\x02\x08\x02\x62\x08\x65\x64itionsp\xe8\x07') - -_globals = globals() -_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) -_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'datasets.stac.v1.authentication_pb2', _globals) -if not _descriptor._USE_C_DESCRIPTORS: - _globals['DESCRIPTOR']._loaded_options = None - _globals['DESCRIPTOR']._serialized_options = b'\n\024com.datasets.stac.v1B\023AuthenticationProtoP\001\242\002\003DSX\252\002\020Datasets.Stac.V1\312\002\020Datasets\\Stac\\V1\342\002\034Datasets\\Stac\\V1\\GPBMetadata\352\002\022Datasets::Stac::V1\222\003\002\010\002' - _globals['_AUTHENTICATION_SCHEMESENTRY']._loaded_options = None - _globals['_AUTHENTICATION_SCHEMESENTRY']._serialized_options = b'8\001' - _globals['_AUTHENTICATIONSCHEME'].fields_by_name['known_type']._loaded_options = None - _globals['_AUTHENTICATIONSCHEME'].fields_by_name['known_type']._serialized_options = b'\252\001\002\010\001' - _globals['_AUTHENTICATIONSCHEME'].fields_by_name['custom_type']._loaded_options = None - _globals['_AUTHENTICATIONSCHEME'].fields_by_name['custom_type']._serialized_options = b'\252\001\002\010\001' - _globals['_AUTHENTICATIONSCHEME'].fields_by_name['description']._loaded_options = None - _globals['_AUTHENTICATIONSCHEME'].fields_by_name['description']._serialized_options = b'\252\001\002\010\001' - _globals['_AUTHENTICATIONSCHEME'].fields_by_name['name']._loaded_options = None - _globals['_AUTHENTICATIONSCHEME'].fields_by_name['name']._serialized_options = b'\252\001\002\010\001' - _globals['_AUTHENTICATIONSCHEME'].fields_by_name['location']._loaded_options = None - _globals['_AUTHENTICATIONSCHEME'].fields_by_name['location']._serialized_options = b'\252\001\002\010\001' - _globals['_AUTHENTICATIONSCHEME'].fields_by_name['scheme']._loaded_options = None - _globals['_AUTHENTICATIONSCHEME'].fields_by_name['scheme']._serialized_options = b'\252\001\002\010\001' - _globals['_AUTHENTICATIONSCHEME'].fields_by_name['open_id_connect_url']._loaded_options = None - _globals['_AUTHENTICATIONSCHEME'].fields_by_name['open_id_connect_url']._serialized_options = b'\252\001\002\010\001' - _globals['_OAUTH2FLOW_SCOPESENTRY']._loaded_options = None - _globals['_OAUTH2FLOW_SCOPESENTRY']._serialized_options = b'8\001' - _globals['_OAUTH2FLOW'].fields_by_name['authorization_url']._loaded_options = None - _globals['_OAUTH2FLOW'].fields_by_name['authorization_url']._serialized_options = b'\252\001\002\010\001' - _globals['_OAUTH2FLOW'].fields_by_name['token_url']._loaded_options = None - _globals['_OAUTH2FLOW'].fields_by_name['token_url']._serialized_options = b'\252\001\002\010\001' - _globals['_OAUTH2FLOW'].fields_by_name['refresh_url']._loaded_options = None - _globals['_OAUTH2FLOW'].fields_by_name['refresh_url']._serialized_options = b'\252\001\002\010\001' - _globals['_SIGNEDURLFLOW_PARAMETERSENTRY']._loaded_options = None - _globals['_SIGNEDURLFLOW_PARAMETERSENTRY']._serialized_options = b'8\001' - _globals['_SIGNEDURLFLOW'].fields_by_name['response_field']._loaded_options = None - _globals['_SIGNEDURLFLOW'].fields_by_name['response_field']._serialized_options = b'\252\001\002\010\001' - _globals['_AUTHENTICATIONPARAMETER'].fields_by_name['required']._loaded_options = None - _globals['_AUTHENTICATIONPARAMETER'].fields_by_name['required']._serialized_options = b'\252\001\002\010\001' - _globals['_AUTHENTICATIONPARAMETER'].fields_by_name['description']._loaded_options = None - _globals['_AUTHENTICATIONPARAMETER'].fields_by_name['description']._serialized_options = b'\252\001\002\010\001' - _globals['_KNOWNAUTHENTICATIONTYPE']._serialized_start=1592 - _globals['_KNOWNAUTHENTICATIONTYPE']._serialized_end=1896 - _globals['_AUTHENTICATION']._serialized_start=90 - _globals['_AUTHENTICATION']._serialized_end=279 - _globals['_AUTHENTICATION_SCHEMESENTRY']._serialized_start=181 - _globals['_AUTHENTICATION_SCHEMESENTRY']._serialized_end=279 - _globals['_AUTHENTICATIONSCHEME']._serialized_start=282 - _globals['_AUTHENTICATIONSCHEME']._serialized_end=667 - _globals['_AUTHENTICATIONFLOW']._serialized_start=670 - _globals['_AUTHENTICATIONFLOW']._serialized_end=826 - _globals['_OAUTH2FLOW']._serialized_start=829 - _globals['_OAUTH2FLOW']._serialized_end=1094 - _globals['_OAUTH2FLOW_SCOPESENTRY']._serialized_start=1037 - _globals['_OAUTH2FLOW_SCOPESENTRY']._serialized_end=1094 - _globals['_SIGNEDURLFLOW']._serialized_start=1097 - _globals['_SIGNEDURLFLOW']._serialized_end=1414 - _globals['_SIGNEDURLFLOW_PARAMETERSENTRY']._serialized_start=1310 - _globals['_SIGNEDURLFLOW_PARAMETERSENTRY']._serialized_end=1414 - _globals['_AUTHENTICATIONPARAMETER']._serialized_start=1417 - _globals['_AUTHENTICATIONPARAMETER']._serialized_end=1589 -# @@protoc_insertion_point(module_scope) diff --git a/tilebox-datasets/tilebox/datasets/datasets/stac/v1/authentication_pb2.pyi b/tilebox-datasets/tilebox/datasets/datasets/stac/v1/authentication_pb2.pyi deleted file mode 100644 index e8e04bd..0000000 --- a/tilebox-datasets/tilebox/datasets/datasets/stac/v1/authentication_pb2.pyi +++ /dev/null @@ -1,119 +0,0 @@ -from google.protobuf import struct_pb2 as _struct_pb2 -from google.protobuf.internal import containers as _containers -from google.protobuf.internal import enum_type_wrapper as _enum_type_wrapper -from google.protobuf import descriptor as _descriptor -from google.protobuf import message as _message -from collections.abc import Iterable as _Iterable, Mapping as _Mapping -from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union - -DESCRIPTOR: _descriptor.FileDescriptor - -class KnownAuthenticationType(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): - __slots__ = () - KNOWN_AUTHENTICATION_TYPE_UNSPECIFIED: _ClassVar[KnownAuthenticationType] - KNOWN_AUTHENTICATION_TYPE_HTTP: _ClassVar[KnownAuthenticationType] - KNOWN_AUTHENTICATION_TYPE_S3: _ClassVar[KnownAuthenticationType] - KNOWN_AUTHENTICATION_TYPE_SIGNED_URL: _ClassVar[KnownAuthenticationType] - KNOWN_AUTHENTICATION_TYPE_OAUTH2: _ClassVar[KnownAuthenticationType] - KNOWN_AUTHENTICATION_TYPE_API_KEY: _ClassVar[KnownAuthenticationType] - KNOWN_AUTHENTICATION_TYPE_OPEN_ID_CONNECT: _ClassVar[KnownAuthenticationType] -KNOWN_AUTHENTICATION_TYPE_UNSPECIFIED: KnownAuthenticationType -KNOWN_AUTHENTICATION_TYPE_HTTP: KnownAuthenticationType -KNOWN_AUTHENTICATION_TYPE_S3: KnownAuthenticationType -KNOWN_AUTHENTICATION_TYPE_SIGNED_URL: KnownAuthenticationType -KNOWN_AUTHENTICATION_TYPE_OAUTH2: KnownAuthenticationType -KNOWN_AUTHENTICATION_TYPE_API_KEY: KnownAuthenticationType -KNOWN_AUTHENTICATION_TYPE_OPEN_ID_CONNECT: KnownAuthenticationType - -class Authentication(_message.Message): - __slots__ = ("schemes",) - class SchemesEntry(_message.Message): - __slots__ = ("key", "value") - KEY_FIELD_NUMBER: _ClassVar[int] - VALUE_FIELD_NUMBER: _ClassVar[int] - key: str - value: AuthenticationScheme - def __init__(self, key: _Optional[str] = ..., value: _Optional[_Union[AuthenticationScheme, _Mapping]] = ...) -> None: ... - SCHEMES_FIELD_NUMBER: _ClassVar[int] - schemes: _containers.MessageMap[str, AuthenticationScheme] - def __init__(self, schemes: _Optional[_Mapping[str, AuthenticationScheme]] = ...) -> None: ... - -class AuthenticationScheme(_message.Message): - __slots__ = ("known_type", "custom_type", "description", "name", "location", "scheme", "flows", "open_id_connect_url") - KNOWN_TYPE_FIELD_NUMBER: _ClassVar[int] - CUSTOM_TYPE_FIELD_NUMBER: _ClassVar[int] - DESCRIPTION_FIELD_NUMBER: _ClassVar[int] - NAME_FIELD_NUMBER: _ClassVar[int] - LOCATION_FIELD_NUMBER: _ClassVar[int] - SCHEME_FIELD_NUMBER: _ClassVar[int] - FLOWS_FIELD_NUMBER: _ClassVar[int] - OPEN_ID_CONNECT_URL_FIELD_NUMBER: _ClassVar[int] - known_type: KnownAuthenticationType - custom_type: str - description: str - name: str - location: str - scheme: str - flows: _containers.RepeatedCompositeFieldContainer[AuthenticationFlow] - open_id_connect_url: str - def __init__(self, known_type: _Optional[_Union[KnownAuthenticationType, str]] = ..., custom_type: _Optional[str] = ..., description: _Optional[str] = ..., name: _Optional[str] = ..., location: _Optional[str] = ..., scheme: _Optional[str] = ..., flows: _Optional[_Iterable[_Union[AuthenticationFlow, _Mapping]]] = ..., open_id_connect_url: _Optional[str] = ...) -> None: ... - -class AuthenticationFlow(_message.Message): - __slots__ = ("key", "oauth2", "signed_url") - KEY_FIELD_NUMBER: _ClassVar[int] - OAUTH2_FIELD_NUMBER: _ClassVar[int] - SIGNED_URL_FIELD_NUMBER: _ClassVar[int] - key: str - oauth2: OAuth2Flow - signed_url: SignedURLFlow - def __init__(self, key: _Optional[str] = ..., oauth2: _Optional[_Union[OAuth2Flow, _Mapping]] = ..., signed_url: _Optional[_Union[SignedURLFlow, _Mapping]] = ...) -> None: ... - -class OAuth2Flow(_message.Message): - __slots__ = ("authorization_url", "token_url", "scopes", "refresh_url") - class ScopesEntry(_message.Message): - __slots__ = ("key", "value") - KEY_FIELD_NUMBER: _ClassVar[int] - VALUE_FIELD_NUMBER: _ClassVar[int] - key: str - value: str - def __init__(self, key: _Optional[str] = ..., value: _Optional[str] = ...) -> None: ... - AUTHORIZATION_URL_FIELD_NUMBER: _ClassVar[int] - TOKEN_URL_FIELD_NUMBER: _ClassVar[int] - SCOPES_FIELD_NUMBER: _ClassVar[int] - REFRESH_URL_FIELD_NUMBER: _ClassVar[int] - authorization_url: str - token_url: str - scopes: _containers.ScalarMap[str, str] - refresh_url: str - def __init__(self, authorization_url: _Optional[str] = ..., token_url: _Optional[str] = ..., scopes: _Optional[_Mapping[str, str]] = ..., refresh_url: _Optional[str] = ...) -> None: ... - -class SignedURLFlow(_message.Message): - __slots__ = ("method", "authorization_api", "parameters", "response_field") - class ParametersEntry(_message.Message): - __slots__ = ("key", "value") - KEY_FIELD_NUMBER: _ClassVar[int] - VALUE_FIELD_NUMBER: _ClassVar[int] - key: str - value: AuthenticationParameter - def __init__(self, key: _Optional[str] = ..., value: _Optional[_Union[AuthenticationParameter, _Mapping]] = ...) -> None: ... - METHOD_FIELD_NUMBER: _ClassVar[int] - AUTHORIZATION_API_FIELD_NUMBER: _ClassVar[int] - PARAMETERS_FIELD_NUMBER: _ClassVar[int] - RESPONSE_FIELD_FIELD_NUMBER: _ClassVar[int] - method: str - authorization_api: str - parameters: _containers.MessageMap[str, AuthenticationParameter] - response_field: str - def __init__(self, method: _Optional[str] = ..., authorization_api: _Optional[str] = ..., parameters: _Optional[_Mapping[str, AuthenticationParameter]] = ..., response_field: _Optional[str] = ...) -> None: ... - -class AuthenticationParameter(_message.Message): - __slots__ = ("location", "required", "description", "schema") - LOCATION_FIELD_NUMBER: _ClassVar[int] - REQUIRED_FIELD_NUMBER: _ClassVar[int] - DESCRIPTION_FIELD_NUMBER: _ClassVar[int] - SCHEMA_FIELD_NUMBER: _ClassVar[int] - location: str - required: bool - description: str - schema: _struct_pb2.Struct - def __init__(self, location: _Optional[str] = ..., required: bool = ..., description: _Optional[str] = ..., schema: _Optional[_Union[_struct_pb2.Struct, _Mapping]] = ...) -> None: ... diff --git a/tilebox-datasets/tilebox/datasets/datasets/stac/v1/authentication_pb2_grpc.py b/tilebox-datasets/tilebox/datasets/datasets/stac/v1/authentication_pb2_grpc.py deleted file mode 100644 index 8a93939..0000000 --- a/tilebox-datasets/tilebox/datasets/datasets/stac/v1/authentication_pb2_grpc.py +++ /dev/null @@ -1,3 +0,0 @@ -# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! -"""Client and server classes corresponding to protobuf-defined services.""" -import grpc diff --git a/tilebox-datasets/tilebox/datasets/datasets/stac/v1/core_pb2.py b/tilebox-datasets/tilebox/datasets/datasets/stac/v1/core_pb2.py deleted file mode 100644 index 3ad77ce..0000000 --- a/tilebox-datasets/tilebox/datasets/datasets/stac/v1/core_pb2.py +++ /dev/null @@ -1,59 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by the protocol buffer compiler. DO NOT EDIT! -# NO CHECKED-IN PROTOBUF GENCODE -# source: datasets/stac/v1/core.proto -# Protobuf Python Version: 6.30.0 -"""Generated protocol buffer code.""" -from google.protobuf import descriptor as _descriptor -from google.protobuf import descriptor_pool as _descriptor_pool -from google.protobuf import runtime_version as _runtime_version -from google.protobuf import symbol_database as _symbol_database -from google.protobuf.internal import builder as _builder -_runtime_version.ValidateProtobufRuntimeVersion( - _runtime_version.Domain.PUBLIC, - 6, - 30, - 0, - '', - 'datasets/stac/v1/core.proto' -) -# @@protoc_insertion_point(imports) - -_sym_db = _symbol_database.Default() - - - - -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1b\x64\x61tasets/stac/v1/core.proto\x12\x10\x64\x61tasets.stac.v1\"i\n\tMediaType\x12=\n\x05known\x18\x01 \x01(\x0e\x32 .datasets.stac.v1.KnownMediaTypeB\x05\xaa\x01\x02\x08\x01R\x05known\x12\x1d\n\x06\x63ustom\x18\x02 \x01(\tB\x05\xaa\x01\x02\x08\x01R\x06\x63ustom\"\x9d\x01\n\x08Provider\x12\x19\n\x04name\x18\x01 \x01(\tB\x05\xaa\x01\x02\x08\x01R\x04name\x12\'\n\x0b\x64\x65scription\x18\x02 \x01(\tB\x05\xaa\x01\x02\x08\x01R\x0b\x64\x65scription\x12\x34\n\x05roles\x18\x03 \x03(\x0e\x32\x1e.datasets.stac.v1.ProviderRoleR\x05roles\x12\x17\n\x03url\x18\x04 \x01(\tB\x05\xaa\x01\x02\x08\x01R\x03url\"\xc5\x01\n\x04Link\x12\x12\n\x04href\x18\x01 \x01(\tR\x04href\x12\x10\n\x03rel\x18\x02 \x01(\tR\x03rel\x12:\n\nmedia_type\x18\x03 \x01(\x0b\x32\x1b.datasets.stac.v1.MediaTypeR\tmediaType\x12\x1b\n\x05title\x18\x04 \x01(\tB\x05\xaa\x01\x02\x08\x01R\x05title\x12!\n\x0cstorage_refs\x18\x05 \x03(\tR\x0bstorageRefs\x12\x1b\n\tauth_refs\x18\x06 \x03(\tR\x08\x61uthRefs\"5\n\x05Links\x12,\n\x05links\x18\x01 \x03(\x0b\x32\x16.datasets.stac.v1.LinkR\x05links*\xc1\x06\n\x0eKnownMediaType\x12 \n\x1cKNOWN_MEDIA_TYPE_UNSPECIFIED\x10\x00\x12\x1c\n\x18KNOWN_MEDIA_TYPE_GEOJSON\x10\x01\x12\x19\n\x15KNOWN_MEDIA_TYPE_JSON\x10\x02\x12,\n(KNOWN_MEDIA_TYPE_CLOUD_OPTIMIZED_GEOTIFF\x10\x03\x12\x1e\n\x1aKNOWN_MEDIA_TYPE_JPEG_2000\x10\x04\x12\x19\n\x15KNOWN_MEDIA_TYPE_JPEG\x10\x05\x12\x18\n\x14KNOWN_MEDIA_TYPE_PNG\x10\x06\x12$\n KNOWN_MEDIA_TYPE_APPLICATION_XML\x10\x07\x12\x18\n\x14KNOWN_MEDIA_TYPE_ZIP\x10\x08\x12\x1e\n\x1aKNOWN_MEDIA_TYPE_DIRECTORY\x10\t\x12\x1c\n\x18KNOWN_MEDIA_TYPE_GEOTIFF\x10\n\x12\x19\n\x15KNOWN_MEDIA_TYPE_TIFF\x10\x0b\x12\x19\n\x15KNOWN_MEDIA_TYPE_HDF5\x10\x0c\x12\x18\n\x14KNOWN_MEDIA_TYPE_HDF\x10\r\x12\x1b\n\x17KNOWN_MEDIA_TYPE_NETCDF\x10\x0e\x12\x1c\n\x18KNOWN_MEDIA_TYPE_ZARR_V2\x10\x0f\x12\x1c\n\x18KNOWN_MEDIA_TYPE_ZARR_V3\x10\x10\x12\x1c\n\x18KNOWN_MEDIA_TYPE_PARQUET\x10\x11\x12\x1f\n\x1bKNOWN_MEDIA_TYPE_GEOPACKAGE\x10\x12\x12\x19\n\x15KNOWN_MEDIA_TYPE_COPC\x10\x13\x12\x19\n\x15KNOWN_MEDIA_TYPE_HTML\x10\x14\x12\x19\n\x15KNOWN_MEDIA_TYPE_TEXT\x10\x15\x12\x1d\n\x19KNOWN_MEDIA_TYPE_TEXT_XML\x10\x16\x12\x1f\n\x1bKNOWN_MEDIA_TYPE_FLATGEOBUF\x10\x17\x12\x1c\n\x18KNOWN_MEDIA_TYPE_PMTILES\x10\x18\x12\x19\n\x15KNOWN_MEDIA_TYPE_NITF\x10\x19\x12!\n\x1dKNOWN_MEDIA_TYPE_OCTET_STREAM\x10\x1a*\x9a\x01\n\x0cProviderRole\x12\x1d\n\x19PROVIDER_ROLE_UNSPECIFIED\x10\x00\x12\x1a\n\x16PROVIDER_ROLE_PRODUCER\x10\x01\x12\x1a\n\x16PROVIDER_ROLE_LICENSOR\x10\x02\x12\x1b\n\x17PROVIDER_ROLE_PROCESSOR\x10\x03\x12\x16\n\x12PROVIDER_ROLE_HOST\x10\x04\x42\x88\x01\n\x14\x63om.datasets.stac.v1B\tCoreProtoP\x01\xa2\x02\x03\x44SX\xaa\x02\x10\x44\x61tasets.Stac.V1\xca\x02\x10\x44\x61tasets\\Stac\\V1\xe2\x02\x1c\x44\x61tasets\\Stac\\V1\\GPBMetadata\xea\x02\x12\x44\x61tasets::Stac::V1\x92\x03\x02\x08\x02\x62\x08\x65\x64itionsp\xe8\x07') - -_globals = globals() -_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) -_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'datasets.stac.v1.core_pb2', _globals) -if not _descriptor._USE_C_DESCRIPTORS: - _globals['DESCRIPTOR']._loaded_options = None - _globals['DESCRIPTOR']._serialized_options = b'\n\024com.datasets.stac.v1B\tCoreProtoP\001\242\002\003DSX\252\002\020Datasets.Stac.V1\312\002\020Datasets\\Stac\\V1\342\002\034Datasets\\Stac\\V1\\GPBMetadata\352\002\022Datasets::Stac::V1\222\003\002\010\002' - _globals['_MEDIATYPE'].fields_by_name['known']._loaded_options = None - _globals['_MEDIATYPE'].fields_by_name['known']._serialized_options = b'\252\001\002\010\001' - _globals['_MEDIATYPE'].fields_by_name['custom']._loaded_options = None - _globals['_MEDIATYPE'].fields_by_name['custom']._serialized_options = b'\252\001\002\010\001' - _globals['_PROVIDER'].fields_by_name['name']._loaded_options = None - _globals['_PROVIDER'].fields_by_name['name']._serialized_options = b'\252\001\002\010\001' - _globals['_PROVIDER'].fields_by_name['description']._loaded_options = None - _globals['_PROVIDER'].fields_by_name['description']._serialized_options = b'\252\001\002\010\001' - _globals['_PROVIDER'].fields_by_name['url']._loaded_options = None - _globals['_PROVIDER'].fields_by_name['url']._serialized_options = b'\252\001\002\010\001' - _globals['_LINK'].fields_by_name['title']._loaded_options = None - _globals['_LINK'].fields_by_name['title']._serialized_options = b'\252\001\002\010\001' - _globals['_KNOWNMEDIATYPE']._serialized_start=572 - _globals['_KNOWNMEDIATYPE']._serialized_end=1405 - _globals['_PROVIDERROLE']._serialized_start=1408 - _globals['_PROVIDERROLE']._serialized_end=1562 - _globals['_MEDIATYPE']._serialized_start=49 - _globals['_MEDIATYPE']._serialized_end=154 - _globals['_PROVIDER']._serialized_start=157 - _globals['_PROVIDER']._serialized_end=314 - _globals['_LINK']._serialized_start=317 - _globals['_LINK']._serialized_end=514 - _globals['_LINKS']._serialized_start=516 - _globals['_LINKS']._serialized_end=569 -# @@protoc_insertion_point(module_scope) diff --git a/tilebox-datasets/tilebox/datasets/datasets/stac/v1/core_pb2.pyi b/tilebox-datasets/tilebox/datasets/datasets/stac/v1/core_pb2.pyi deleted file mode 100644 index 8e1f293..0000000 --- a/tilebox-datasets/tilebox/datasets/datasets/stac/v1/core_pb2.pyi +++ /dev/null @@ -1,120 +0,0 @@ -from google.protobuf.internal import containers as _containers -from google.protobuf.internal import enum_type_wrapper as _enum_type_wrapper -from google.protobuf import descriptor as _descriptor -from google.protobuf import message as _message -from collections.abc import Iterable as _Iterable, Mapping as _Mapping -from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union - -DESCRIPTOR: _descriptor.FileDescriptor - -class KnownMediaType(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): - __slots__ = () - KNOWN_MEDIA_TYPE_UNSPECIFIED: _ClassVar[KnownMediaType] - KNOWN_MEDIA_TYPE_GEOJSON: _ClassVar[KnownMediaType] - KNOWN_MEDIA_TYPE_JSON: _ClassVar[KnownMediaType] - KNOWN_MEDIA_TYPE_CLOUD_OPTIMIZED_GEOTIFF: _ClassVar[KnownMediaType] - KNOWN_MEDIA_TYPE_JPEG_2000: _ClassVar[KnownMediaType] - KNOWN_MEDIA_TYPE_JPEG: _ClassVar[KnownMediaType] - KNOWN_MEDIA_TYPE_PNG: _ClassVar[KnownMediaType] - KNOWN_MEDIA_TYPE_APPLICATION_XML: _ClassVar[KnownMediaType] - KNOWN_MEDIA_TYPE_ZIP: _ClassVar[KnownMediaType] - KNOWN_MEDIA_TYPE_DIRECTORY: _ClassVar[KnownMediaType] - KNOWN_MEDIA_TYPE_GEOTIFF: _ClassVar[KnownMediaType] - KNOWN_MEDIA_TYPE_TIFF: _ClassVar[KnownMediaType] - KNOWN_MEDIA_TYPE_HDF5: _ClassVar[KnownMediaType] - KNOWN_MEDIA_TYPE_HDF: _ClassVar[KnownMediaType] - KNOWN_MEDIA_TYPE_NETCDF: _ClassVar[KnownMediaType] - KNOWN_MEDIA_TYPE_ZARR_V2: _ClassVar[KnownMediaType] - KNOWN_MEDIA_TYPE_ZARR_V3: _ClassVar[KnownMediaType] - KNOWN_MEDIA_TYPE_PARQUET: _ClassVar[KnownMediaType] - KNOWN_MEDIA_TYPE_GEOPACKAGE: _ClassVar[KnownMediaType] - KNOWN_MEDIA_TYPE_COPC: _ClassVar[KnownMediaType] - KNOWN_MEDIA_TYPE_HTML: _ClassVar[KnownMediaType] - KNOWN_MEDIA_TYPE_TEXT: _ClassVar[KnownMediaType] - KNOWN_MEDIA_TYPE_TEXT_XML: _ClassVar[KnownMediaType] - KNOWN_MEDIA_TYPE_FLATGEOBUF: _ClassVar[KnownMediaType] - KNOWN_MEDIA_TYPE_PMTILES: _ClassVar[KnownMediaType] - KNOWN_MEDIA_TYPE_NITF: _ClassVar[KnownMediaType] - KNOWN_MEDIA_TYPE_OCTET_STREAM: _ClassVar[KnownMediaType] - -class ProviderRole(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): - __slots__ = () - PROVIDER_ROLE_UNSPECIFIED: _ClassVar[ProviderRole] - PROVIDER_ROLE_PRODUCER: _ClassVar[ProviderRole] - PROVIDER_ROLE_LICENSOR: _ClassVar[ProviderRole] - PROVIDER_ROLE_PROCESSOR: _ClassVar[ProviderRole] - PROVIDER_ROLE_HOST: _ClassVar[ProviderRole] -KNOWN_MEDIA_TYPE_UNSPECIFIED: KnownMediaType -KNOWN_MEDIA_TYPE_GEOJSON: KnownMediaType -KNOWN_MEDIA_TYPE_JSON: KnownMediaType -KNOWN_MEDIA_TYPE_CLOUD_OPTIMIZED_GEOTIFF: KnownMediaType -KNOWN_MEDIA_TYPE_JPEG_2000: KnownMediaType -KNOWN_MEDIA_TYPE_JPEG: KnownMediaType -KNOWN_MEDIA_TYPE_PNG: KnownMediaType -KNOWN_MEDIA_TYPE_APPLICATION_XML: KnownMediaType -KNOWN_MEDIA_TYPE_ZIP: KnownMediaType -KNOWN_MEDIA_TYPE_DIRECTORY: KnownMediaType -KNOWN_MEDIA_TYPE_GEOTIFF: KnownMediaType -KNOWN_MEDIA_TYPE_TIFF: KnownMediaType -KNOWN_MEDIA_TYPE_HDF5: KnownMediaType -KNOWN_MEDIA_TYPE_HDF: KnownMediaType -KNOWN_MEDIA_TYPE_NETCDF: KnownMediaType -KNOWN_MEDIA_TYPE_ZARR_V2: KnownMediaType -KNOWN_MEDIA_TYPE_ZARR_V3: KnownMediaType -KNOWN_MEDIA_TYPE_PARQUET: KnownMediaType -KNOWN_MEDIA_TYPE_GEOPACKAGE: KnownMediaType -KNOWN_MEDIA_TYPE_COPC: KnownMediaType -KNOWN_MEDIA_TYPE_HTML: KnownMediaType -KNOWN_MEDIA_TYPE_TEXT: KnownMediaType -KNOWN_MEDIA_TYPE_TEXT_XML: KnownMediaType -KNOWN_MEDIA_TYPE_FLATGEOBUF: KnownMediaType -KNOWN_MEDIA_TYPE_PMTILES: KnownMediaType -KNOWN_MEDIA_TYPE_NITF: KnownMediaType -KNOWN_MEDIA_TYPE_OCTET_STREAM: KnownMediaType -PROVIDER_ROLE_UNSPECIFIED: ProviderRole -PROVIDER_ROLE_PRODUCER: ProviderRole -PROVIDER_ROLE_LICENSOR: ProviderRole -PROVIDER_ROLE_PROCESSOR: ProviderRole -PROVIDER_ROLE_HOST: ProviderRole - -class MediaType(_message.Message): - __slots__ = ("known", "custom") - KNOWN_FIELD_NUMBER: _ClassVar[int] - CUSTOM_FIELD_NUMBER: _ClassVar[int] - known: KnownMediaType - custom: str - def __init__(self, known: _Optional[_Union[KnownMediaType, str]] = ..., custom: _Optional[str] = ...) -> None: ... - -class Provider(_message.Message): - __slots__ = ("name", "description", "roles", "url") - NAME_FIELD_NUMBER: _ClassVar[int] - DESCRIPTION_FIELD_NUMBER: _ClassVar[int] - ROLES_FIELD_NUMBER: _ClassVar[int] - URL_FIELD_NUMBER: _ClassVar[int] - name: str - description: str - roles: _containers.RepeatedScalarFieldContainer[ProviderRole] - url: str - def __init__(self, name: _Optional[str] = ..., description: _Optional[str] = ..., roles: _Optional[_Iterable[_Union[ProviderRole, str]]] = ..., url: _Optional[str] = ...) -> None: ... - -class Link(_message.Message): - __slots__ = ("href", "rel", "media_type", "title", "storage_refs", "auth_refs") - HREF_FIELD_NUMBER: _ClassVar[int] - REL_FIELD_NUMBER: _ClassVar[int] - MEDIA_TYPE_FIELD_NUMBER: _ClassVar[int] - TITLE_FIELD_NUMBER: _ClassVar[int] - STORAGE_REFS_FIELD_NUMBER: _ClassVar[int] - AUTH_REFS_FIELD_NUMBER: _ClassVar[int] - href: str - rel: str - media_type: MediaType - title: str - storage_refs: _containers.RepeatedScalarFieldContainer[str] - auth_refs: _containers.RepeatedScalarFieldContainer[str] - def __init__(self, href: _Optional[str] = ..., rel: _Optional[str] = ..., media_type: _Optional[_Union[MediaType, _Mapping]] = ..., title: _Optional[str] = ..., storage_refs: _Optional[_Iterable[str]] = ..., auth_refs: _Optional[_Iterable[str]] = ...) -> None: ... - -class Links(_message.Message): - __slots__ = ("links",) - LINKS_FIELD_NUMBER: _ClassVar[int] - links: _containers.RepeatedCompositeFieldContainer[Link] - def __init__(self, links: _Optional[_Iterable[_Union[Link, _Mapping]]] = ...) -> None: ... diff --git a/tilebox-datasets/tilebox/datasets/datasets/stac/v1/core_pb2_grpc.py b/tilebox-datasets/tilebox/datasets/datasets/stac/v1/core_pb2_grpc.py deleted file mode 100644 index 8a93939..0000000 --- a/tilebox-datasets/tilebox/datasets/datasets/stac/v1/core_pb2_grpc.py +++ /dev/null @@ -1,3 +0,0 @@ -# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! -"""Client and server classes corresponding to protobuf-defined services.""" -import grpc diff --git a/tilebox-datasets/tilebox/datasets/datasets/stac/v1/processing_pb2.py b/tilebox-datasets/tilebox/datasets/datasets/stac/v1/processing_pb2.py deleted file mode 100644 index f54973d..0000000 --- a/tilebox-datasets/tilebox/datasets/datasets/stac/v1/processing_pb2.py +++ /dev/null @@ -1,41 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by the protocol buffer compiler. DO NOT EDIT! -# NO CHECKED-IN PROTOBUF GENCODE -# source: datasets/stac/v1/processing.proto -# Protobuf Python Version: 6.30.0 -"""Generated protocol buffer code.""" -from google.protobuf import descriptor as _descriptor -from google.protobuf import descriptor_pool as _descriptor_pool -from google.protobuf import runtime_version as _runtime_version -from google.protobuf import symbol_database as _symbol_database -from google.protobuf.internal import builder as _builder -_runtime_version.ValidateProtobufRuntimeVersion( - _runtime_version.Domain.PUBLIC, - 6, - 30, - 0, - '', - 'datasets/stac/v1/processing.proto' -) -# @@protoc_insertion_point(imports) - -_sym_db = _symbol_database.Default() - - - - -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n!datasets/stac/v1/processing.proto\x12\x10\x64\x61tasets.stac.v1\"\xa1\x01\n\x12ProcessingSoftware\x12N\n\x08versions\x18\x01 \x03(\x0b\x32\x32.datasets.stac.v1.ProcessingSoftware.VersionsEntryR\x08versions\x1a;\n\rVersionsEntry\x12\x10\n\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n\x05value\x18\x02 \x01(\tR\x05value:\x02\x38\x01\x42\x8e\x01\n\x14\x63om.datasets.stac.v1B\x0fProcessingProtoP\x01\xa2\x02\x03\x44SX\xaa\x02\x10\x44\x61tasets.Stac.V1\xca\x02\x10\x44\x61tasets\\Stac\\V1\xe2\x02\x1c\x44\x61tasets\\Stac\\V1\\GPBMetadata\xea\x02\x12\x44\x61tasets::Stac::V1\x92\x03\x02\x08\x02\x62\x08\x65\x64itionsp\xe8\x07') - -_globals = globals() -_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) -_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'datasets.stac.v1.processing_pb2', _globals) -if not _descriptor._USE_C_DESCRIPTORS: - _globals['DESCRIPTOR']._loaded_options = None - _globals['DESCRIPTOR']._serialized_options = b'\n\024com.datasets.stac.v1B\017ProcessingProtoP\001\242\002\003DSX\252\002\020Datasets.Stac.V1\312\002\020Datasets\\Stac\\V1\342\002\034Datasets\\Stac\\V1\\GPBMetadata\352\002\022Datasets::Stac::V1\222\003\002\010\002' - _globals['_PROCESSINGSOFTWARE_VERSIONSENTRY']._loaded_options = None - _globals['_PROCESSINGSOFTWARE_VERSIONSENTRY']._serialized_options = b'8\001' - _globals['_PROCESSINGSOFTWARE']._serialized_start=56 - _globals['_PROCESSINGSOFTWARE']._serialized_end=217 - _globals['_PROCESSINGSOFTWARE_VERSIONSENTRY']._serialized_start=158 - _globals['_PROCESSINGSOFTWARE_VERSIONSENTRY']._serialized_end=217 -# @@protoc_insertion_point(module_scope) diff --git a/tilebox-datasets/tilebox/datasets/datasets/stac/v1/processing_pb2.pyi b/tilebox-datasets/tilebox/datasets/datasets/stac/v1/processing_pb2.pyi deleted file mode 100644 index 523056d..0000000 --- a/tilebox-datasets/tilebox/datasets/datasets/stac/v1/processing_pb2.pyi +++ /dev/null @@ -1,20 +0,0 @@ -from google.protobuf.internal import containers as _containers -from google.protobuf import descriptor as _descriptor -from google.protobuf import message as _message -from collections.abc import Mapping as _Mapping -from typing import ClassVar as _ClassVar, Optional as _Optional - -DESCRIPTOR: _descriptor.FileDescriptor - -class ProcessingSoftware(_message.Message): - __slots__ = ("versions",) - class VersionsEntry(_message.Message): - __slots__ = ("key", "value") - KEY_FIELD_NUMBER: _ClassVar[int] - VALUE_FIELD_NUMBER: _ClassVar[int] - key: str - value: str - def __init__(self, key: _Optional[str] = ..., value: _Optional[str] = ...) -> None: ... - VERSIONS_FIELD_NUMBER: _ClassVar[int] - versions: _containers.ScalarMap[str, str] - def __init__(self, versions: _Optional[_Mapping[str, str]] = ...) -> None: ... diff --git a/tilebox-datasets/tilebox/datasets/datasets/stac/v1/processing_pb2_grpc.py b/tilebox-datasets/tilebox/datasets/datasets/stac/v1/processing_pb2_grpc.py deleted file mode 100644 index 8a93939..0000000 --- a/tilebox-datasets/tilebox/datasets/datasets/stac/v1/processing_pb2_grpc.py +++ /dev/null @@ -1,3 +0,0 @@ -# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! -"""Client and server classes corresponding to protobuf-defined services.""" -import grpc diff --git a/tilebox-datasets/tilebox/datasets/datasets/stac/v1/product_pb2.py b/tilebox-datasets/tilebox/datasets/datasets/stac/v1/product_pb2.py deleted file mode 100644 index 6cc07b2..0000000 --- a/tilebox-datasets/tilebox/datasets/datasets/stac/v1/product_pb2.py +++ /dev/null @@ -1,45 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by the protocol buffer compiler. DO NOT EDIT! -# NO CHECKED-IN PROTOBUF GENCODE -# source: datasets/stac/v1/product.proto -# Protobuf Python Version: 6.30.0 -"""Generated protocol buffer code.""" -from google.protobuf import descriptor as _descriptor -from google.protobuf import descriptor_pool as _descriptor_pool -from google.protobuf import runtime_version as _runtime_version -from google.protobuf import symbol_database as _symbol_database -from google.protobuf.internal import builder as _builder -_runtime_version.ValidateProtobufRuntimeVersion( - _runtime_version.Domain.PUBLIC, - 6, - 30, - 0, - '', - 'datasets/stac/v1/product.proto' -) -# @@protoc_insertion_point(imports) - -_sym_db = _symbol_database.Default() - - - - -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1e\x64\x61tasets/stac/v1/product.proto\x12\x10\x64\x61tasets.stac.v1\"\xe2\x01\n\x11ProductProperties\x12\x19\n\x04type\x18\x01 \x01(\tB\x05\xaa\x01\x02\x08\x01R\x04type\x12%\n\ntimeliness\x18\x02 \x01(\tB\x05\xaa\x01\x02\x08\x01R\ntimeliness\x12\x36\n\x13timeliness_category\x18\x03 \x01(\tB\x05\xaa\x01\x02\x08\x01R\x12timelinessCategory\x12S\n\x10\x61\x63quisition_type\x18\x04 \x01(\x0e\x32(.datasets.stac.v1.ProductAcquisitionTypeR\x0f\x61\x63quisitionType*\xb6\x01\n\x16ProductAcquisitionType\x12(\n$PRODUCT_ACQUISITION_TYPE_UNSPECIFIED\x10\x00\x12$\n PRODUCT_ACQUISITION_TYPE_NOMINAL\x10\x01\x12(\n$PRODUCT_ACQUISITION_TYPE_CALIBRATION\x10\x02\x12\"\n\x1ePRODUCT_ACQUISITION_TYPE_OTHER\x10\x03\x42\x8b\x01\n\x14\x63om.datasets.stac.v1B\x0cProductProtoP\x01\xa2\x02\x03\x44SX\xaa\x02\x10\x44\x61tasets.Stac.V1\xca\x02\x10\x44\x61tasets\\Stac\\V1\xe2\x02\x1c\x44\x61tasets\\Stac\\V1\\GPBMetadata\xea\x02\x12\x44\x61tasets::Stac::V1\x92\x03\x02\x08\x02\x62\x08\x65\x64itionsp\xe8\x07') - -_globals = globals() -_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) -_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'datasets.stac.v1.product_pb2', _globals) -if not _descriptor._USE_C_DESCRIPTORS: - _globals['DESCRIPTOR']._loaded_options = None - _globals['DESCRIPTOR']._serialized_options = b'\n\024com.datasets.stac.v1B\014ProductProtoP\001\242\002\003DSX\252\002\020Datasets.Stac.V1\312\002\020Datasets\\Stac\\V1\342\002\034Datasets\\Stac\\V1\\GPBMetadata\352\002\022Datasets::Stac::V1\222\003\002\010\002' - _globals['_PRODUCTPROPERTIES'].fields_by_name['type']._loaded_options = None - _globals['_PRODUCTPROPERTIES'].fields_by_name['type']._serialized_options = b'\252\001\002\010\001' - _globals['_PRODUCTPROPERTIES'].fields_by_name['timeliness']._loaded_options = None - _globals['_PRODUCTPROPERTIES'].fields_by_name['timeliness']._serialized_options = b'\252\001\002\010\001' - _globals['_PRODUCTPROPERTIES'].fields_by_name['timeliness_category']._loaded_options = None - _globals['_PRODUCTPROPERTIES'].fields_by_name['timeliness_category']._serialized_options = b'\252\001\002\010\001' - _globals['_PRODUCTACQUISITIONTYPE']._serialized_start=282 - _globals['_PRODUCTACQUISITIONTYPE']._serialized_end=464 - _globals['_PRODUCTPROPERTIES']._serialized_start=53 - _globals['_PRODUCTPROPERTIES']._serialized_end=279 -# @@protoc_insertion_point(module_scope) diff --git a/tilebox-datasets/tilebox/datasets/datasets/stac/v1/product_pb2.pyi b/tilebox-datasets/tilebox/datasets/datasets/stac/v1/product_pb2.pyi deleted file mode 100644 index c0adec6..0000000 --- a/tilebox-datasets/tilebox/datasets/datasets/stac/v1/product_pb2.pyi +++ /dev/null @@ -1,29 +0,0 @@ -from google.protobuf.internal import enum_type_wrapper as _enum_type_wrapper -from google.protobuf import descriptor as _descriptor -from google.protobuf import message as _message -from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union - -DESCRIPTOR: _descriptor.FileDescriptor - -class ProductAcquisitionType(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): - __slots__ = () - PRODUCT_ACQUISITION_TYPE_UNSPECIFIED: _ClassVar[ProductAcquisitionType] - PRODUCT_ACQUISITION_TYPE_NOMINAL: _ClassVar[ProductAcquisitionType] - PRODUCT_ACQUISITION_TYPE_CALIBRATION: _ClassVar[ProductAcquisitionType] - PRODUCT_ACQUISITION_TYPE_OTHER: _ClassVar[ProductAcquisitionType] -PRODUCT_ACQUISITION_TYPE_UNSPECIFIED: ProductAcquisitionType -PRODUCT_ACQUISITION_TYPE_NOMINAL: ProductAcquisitionType -PRODUCT_ACQUISITION_TYPE_CALIBRATION: ProductAcquisitionType -PRODUCT_ACQUISITION_TYPE_OTHER: ProductAcquisitionType - -class ProductProperties(_message.Message): - __slots__ = ("type", "timeliness", "timeliness_category", "acquisition_type") - TYPE_FIELD_NUMBER: _ClassVar[int] - TIMELINESS_FIELD_NUMBER: _ClassVar[int] - TIMELINESS_CATEGORY_FIELD_NUMBER: _ClassVar[int] - ACQUISITION_TYPE_FIELD_NUMBER: _ClassVar[int] - type: str - timeliness: str - timeliness_category: str - acquisition_type: ProductAcquisitionType - def __init__(self, type: _Optional[str] = ..., timeliness: _Optional[str] = ..., timeliness_category: _Optional[str] = ..., acquisition_type: _Optional[_Union[ProductAcquisitionType, str]] = ...) -> None: ... diff --git a/tilebox-datasets/tilebox/datasets/datasets/stac/v1/product_pb2_grpc.py b/tilebox-datasets/tilebox/datasets/datasets/stac/v1/product_pb2_grpc.py deleted file mode 100644 index 8a93939..0000000 --- a/tilebox-datasets/tilebox/datasets/datasets/stac/v1/product_pb2_grpc.py +++ /dev/null @@ -1,3 +0,0 @@ -# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! -"""Client and server classes corresponding to protobuf-defined services.""" -import grpc diff --git a/tilebox-datasets/tilebox/datasets/datasets/stac/v1/sar_pb2.py b/tilebox-datasets/tilebox/datasets/datasets/stac/v1/sar_pb2.py deleted file mode 100644 index 029cc04..0000000 --- a/tilebox-datasets/tilebox/datasets/datasets/stac/v1/sar_pb2.py +++ /dev/null @@ -1,65 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by the protocol buffer compiler. DO NOT EDIT! -# NO CHECKED-IN PROTOBUF GENCODE -# source: datasets/stac/v1/sar.proto -# Protobuf Python Version: 6.30.0 -"""Generated protocol buffer code.""" -from google.protobuf import descriptor as _descriptor -from google.protobuf import descriptor_pool as _descriptor_pool -from google.protobuf import runtime_version as _runtime_version -from google.protobuf import symbol_database as _symbol_database -from google.protobuf.internal import builder as _builder -_runtime_version.ValidateProtobufRuntimeVersion( - _runtime_version.Domain.PUBLIC, - 6, - 30, - 0, - '', - 'datasets/stac/v1/sar.proto' -) -# @@protoc_insertion_point(imports) - -_sym_db = _symbol_database.Default() - - - - -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1a\x64\x61tasets/stac/v1/sar.proto\x12\x10\x64\x61tasets.stac.v1\"\xc0\x06\n\rSARProperties\x12G\n\rpolarizations\x18\x01 \x03(\x0e\x32!.datasets.stac.v1.SARPolarizationR\rpolarizations\x12.\n\x0finstrument_mode\x18\x02 \x01(\tB\x05\xaa\x01\x02\x08\x01R\x0einstrumentMode\x12I\n\x0e\x66requency_band\x18\x03 \x01(\x0e\x32\".datasets.stac.v1.SARFrequencyBandR\rfrequencyBand\x12\x30\n\x10\x63\x65nter_frequency\x18\x04 \x01(\x01\x42\x05\xaa\x01\x02\x08\x01R\x0f\x63\x65nterFrequency\x12#\n\tbandwidth\x18\x05 \x01(\x01\x42\x05\xaa\x01\x02\x08\x01R\tbandwidth\x12\x30\n\x10resolution_range\x18\x06 \x01(\x01\x42\x05\xaa\x01\x02\x08\x01R\x0fresolutionRange\x12\x34\n\x12resolution_azimuth\x18\x07 \x01(\x01\x42\x05\xaa\x01\x02\x08\x01R\x11resolutionAzimuth\x12\x35\n\x13pixel_spacing_range\x18\x08 \x01(\x01\x42\x05\xaa\x01\x02\x08\x01R\x11pixelSpacingRange\x12\x39\n\x15pixel_spacing_azimuth\x18\t \x01(\x01\x42\x05\xaa\x01\x02\x08\x01R\x13pixelSpacingAzimuth\x12&\n\x0blooks_range\x18\n \x01(\x04\x42\x05\xaa\x01\x02\x08\x01R\nlooksRange\x12*\n\rlooks_azimuth\x18\x0b \x01(\x04\x42\x05\xaa\x01\x02\x08\x01R\x0clooksAzimuth\x12=\n\x17looks_equivalent_number\x18\x0c \x01(\x01\x42\x05\xaa\x01\x02\x08\x01R\x15looksEquivalentNumber\x12^\n\x15observation_direction\x18\r \x01(\x0e\x32).datasets.stac.v1.SARObservationDirectionR\x14observationDirection\x12,\n\x0erelative_burst\x18\x0e \x01(\x04\x42\x05\xaa\x01\x02\x08\x01R\rrelativeBurst\x12\x19\n\x08\x62\x65\x61m_ids\x18\x0f \x03(\tR\x07\x62\x65\x61mIds*\x88\x02\n\x10SARFrequencyBand\x12\"\n\x1eSAR_FREQUENCY_BAND_UNSPECIFIED\x10\x00\x12\x18\n\x14SAR_FREQUENCY_BAND_P\x10\x01\x12\x18\n\x14SAR_FREQUENCY_BAND_L\x10\x02\x12\x18\n\x14SAR_FREQUENCY_BAND_S\x10\x03\x12\x18\n\x14SAR_FREQUENCY_BAND_C\x10\x04\x12\x18\n\x14SAR_FREQUENCY_BAND_X\x10\x05\x12\x19\n\x15SAR_FREQUENCY_BAND_KU\x10\x06\x12\x18\n\x14SAR_FREQUENCY_BAND_K\x10\x07\x12\x19\n\x15SAR_FREQUENCY_BAND_KA\x10\x08*\xad\x02\n\x0fSARPolarization\x12 \n\x1cSAR_POLARIZATION_UNSPECIFIED\x10\x00\x12\x17\n\x13SAR_POLARIZATION_HH\x10\x01\x12\x17\n\x13SAR_POLARIZATION_VV\x10\x02\x12\x17\n\x13SAR_POLARIZATION_HV\x10\x03\x12\x17\n\x13SAR_POLARIZATION_VH\x10\x04\x12\x17\n\x13SAR_POLARIZATION_LH\x10\x05\x12\x17\n\x13SAR_POLARIZATION_LV\x10\x06\x12\x17\n\x13SAR_POLARIZATION_RH\x10\x07\x12\x17\n\x13SAR_POLARIZATION_RV\x10\x08\x12\x17\n\x13SAR_POLARIZATION_CH\x10\t\x12\x17\n\x13SAR_POLARIZATION_CV\x10\n*\x8d\x01\n\x17SARObservationDirection\x12)\n%SAR_OBSERVATION_DIRECTION_UNSPECIFIED\x10\x00\x12\"\n\x1eSAR_OBSERVATION_DIRECTION_LEFT\x10\x01\x12#\n\x1fSAR_OBSERVATION_DIRECTION_RIGHT\x10\x02\x42\x87\x01\n\x14\x63om.datasets.stac.v1B\x08SarProtoP\x01\xa2\x02\x03\x44SX\xaa\x02\x10\x44\x61tasets.Stac.V1\xca\x02\x10\x44\x61tasets\\Stac\\V1\xe2\x02\x1c\x44\x61tasets\\Stac\\V1\\GPBMetadata\xea\x02\x12\x44\x61tasets::Stac::V1\x92\x03\x02\x08\x02\x62\x08\x65\x64itionsp\xe8\x07') - -_globals = globals() -_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) -_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'datasets.stac.v1.sar_pb2', _globals) -if not _descriptor._USE_C_DESCRIPTORS: - _globals['DESCRIPTOR']._loaded_options = None - _globals['DESCRIPTOR']._serialized_options = b'\n\024com.datasets.stac.v1B\010SarProtoP\001\242\002\003DSX\252\002\020Datasets.Stac.V1\312\002\020Datasets\\Stac\\V1\342\002\034Datasets\\Stac\\V1\\GPBMetadata\352\002\022Datasets::Stac::V1\222\003\002\010\002' - _globals['_SARPROPERTIES'].fields_by_name['instrument_mode']._loaded_options = None - _globals['_SARPROPERTIES'].fields_by_name['instrument_mode']._serialized_options = b'\252\001\002\010\001' - _globals['_SARPROPERTIES'].fields_by_name['center_frequency']._loaded_options = None - _globals['_SARPROPERTIES'].fields_by_name['center_frequency']._serialized_options = b'\252\001\002\010\001' - _globals['_SARPROPERTIES'].fields_by_name['bandwidth']._loaded_options = None - _globals['_SARPROPERTIES'].fields_by_name['bandwidth']._serialized_options = b'\252\001\002\010\001' - _globals['_SARPROPERTIES'].fields_by_name['resolution_range']._loaded_options = None - _globals['_SARPROPERTIES'].fields_by_name['resolution_range']._serialized_options = b'\252\001\002\010\001' - _globals['_SARPROPERTIES'].fields_by_name['resolution_azimuth']._loaded_options = None - _globals['_SARPROPERTIES'].fields_by_name['resolution_azimuth']._serialized_options = b'\252\001\002\010\001' - _globals['_SARPROPERTIES'].fields_by_name['pixel_spacing_range']._loaded_options = None - _globals['_SARPROPERTIES'].fields_by_name['pixel_spacing_range']._serialized_options = b'\252\001\002\010\001' - _globals['_SARPROPERTIES'].fields_by_name['pixel_spacing_azimuth']._loaded_options = None - _globals['_SARPROPERTIES'].fields_by_name['pixel_spacing_azimuth']._serialized_options = b'\252\001\002\010\001' - _globals['_SARPROPERTIES'].fields_by_name['looks_range']._loaded_options = None - _globals['_SARPROPERTIES'].fields_by_name['looks_range']._serialized_options = b'\252\001\002\010\001' - _globals['_SARPROPERTIES'].fields_by_name['looks_azimuth']._loaded_options = None - _globals['_SARPROPERTIES'].fields_by_name['looks_azimuth']._serialized_options = b'\252\001\002\010\001' - _globals['_SARPROPERTIES'].fields_by_name['looks_equivalent_number']._loaded_options = None - _globals['_SARPROPERTIES'].fields_by_name['looks_equivalent_number']._serialized_options = b'\252\001\002\010\001' - _globals['_SARPROPERTIES'].fields_by_name['relative_burst']._loaded_options = None - _globals['_SARPROPERTIES'].fields_by_name['relative_burst']._serialized_options = b'\252\001\002\010\001' - _globals['_SARFREQUENCYBAND']._serialized_start=884 - _globals['_SARFREQUENCYBAND']._serialized_end=1148 - _globals['_SARPOLARIZATION']._serialized_start=1151 - _globals['_SARPOLARIZATION']._serialized_end=1452 - _globals['_SAROBSERVATIONDIRECTION']._serialized_start=1455 - _globals['_SAROBSERVATIONDIRECTION']._serialized_end=1596 - _globals['_SARPROPERTIES']._serialized_start=49 - _globals['_SARPROPERTIES']._serialized_end=881 -# @@protoc_insertion_point(module_scope) diff --git a/tilebox-datasets/tilebox/datasets/datasets/stac/v1/sar_pb2.pyi b/tilebox-datasets/tilebox/datasets/datasets/stac/v1/sar_pb2.pyi deleted file mode 100644 index 5014050..0000000 --- a/tilebox-datasets/tilebox/datasets/datasets/stac/v1/sar_pb2.pyi +++ /dev/null @@ -1,97 +0,0 @@ -from google.protobuf.internal import containers as _containers -from google.protobuf.internal import enum_type_wrapper as _enum_type_wrapper -from google.protobuf import descriptor as _descriptor -from google.protobuf import message as _message -from collections.abc import Iterable as _Iterable -from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union - -DESCRIPTOR: _descriptor.FileDescriptor - -class SARFrequencyBand(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): - __slots__ = () - SAR_FREQUENCY_BAND_UNSPECIFIED: _ClassVar[SARFrequencyBand] - SAR_FREQUENCY_BAND_P: _ClassVar[SARFrequencyBand] - SAR_FREQUENCY_BAND_L: _ClassVar[SARFrequencyBand] - SAR_FREQUENCY_BAND_S: _ClassVar[SARFrequencyBand] - SAR_FREQUENCY_BAND_C: _ClassVar[SARFrequencyBand] - SAR_FREQUENCY_BAND_X: _ClassVar[SARFrequencyBand] - SAR_FREQUENCY_BAND_KU: _ClassVar[SARFrequencyBand] - SAR_FREQUENCY_BAND_K: _ClassVar[SARFrequencyBand] - SAR_FREQUENCY_BAND_KA: _ClassVar[SARFrequencyBand] - -class SARPolarization(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): - __slots__ = () - SAR_POLARIZATION_UNSPECIFIED: _ClassVar[SARPolarization] - SAR_POLARIZATION_HH: _ClassVar[SARPolarization] - SAR_POLARIZATION_VV: _ClassVar[SARPolarization] - SAR_POLARIZATION_HV: _ClassVar[SARPolarization] - SAR_POLARIZATION_VH: _ClassVar[SARPolarization] - SAR_POLARIZATION_LH: _ClassVar[SARPolarization] - SAR_POLARIZATION_LV: _ClassVar[SARPolarization] - SAR_POLARIZATION_RH: _ClassVar[SARPolarization] - SAR_POLARIZATION_RV: _ClassVar[SARPolarization] - SAR_POLARIZATION_CH: _ClassVar[SARPolarization] - SAR_POLARIZATION_CV: _ClassVar[SARPolarization] - -class SARObservationDirection(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): - __slots__ = () - SAR_OBSERVATION_DIRECTION_UNSPECIFIED: _ClassVar[SARObservationDirection] - SAR_OBSERVATION_DIRECTION_LEFT: _ClassVar[SARObservationDirection] - SAR_OBSERVATION_DIRECTION_RIGHT: _ClassVar[SARObservationDirection] -SAR_FREQUENCY_BAND_UNSPECIFIED: SARFrequencyBand -SAR_FREQUENCY_BAND_P: SARFrequencyBand -SAR_FREQUENCY_BAND_L: SARFrequencyBand -SAR_FREQUENCY_BAND_S: SARFrequencyBand -SAR_FREQUENCY_BAND_C: SARFrequencyBand -SAR_FREQUENCY_BAND_X: SARFrequencyBand -SAR_FREQUENCY_BAND_KU: SARFrequencyBand -SAR_FREQUENCY_BAND_K: SARFrequencyBand -SAR_FREQUENCY_BAND_KA: SARFrequencyBand -SAR_POLARIZATION_UNSPECIFIED: SARPolarization -SAR_POLARIZATION_HH: SARPolarization -SAR_POLARIZATION_VV: SARPolarization -SAR_POLARIZATION_HV: SARPolarization -SAR_POLARIZATION_VH: SARPolarization -SAR_POLARIZATION_LH: SARPolarization -SAR_POLARIZATION_LV: SARPolarization -SAR_POLARIZATION_RH: SARPolarization -SAR_POLARIZATION_RV: SARPolarization -SAR_POLARIZATION_CH: SARPolarization -SAR_POLARIZATION_CV: SARPolarization -SAR_OBSERVATION_DIRECTION_UNSPECIFIED: SARObservationDirection -SAR_OBSERVATION_DIRECTION_LEFT: SARObservationDirection -SAR_OBSERVATION_DIRECTION_RIGHT: SARObservationDirection - -class SARProperties(_message.Message): - __slots__ = ("polarizations", "instrument_mode", "frequency_band", "center_frequency", "bandwidth", "resolution_range", "resolution_azimuth", "pixel_spacing_range", "pixel_spacing_azimuth", "looks_range", "looks_azimuth", "looks_equivalent_number", "observation_direction", "relative_burst", "beam_ids") - POLARIZATIONS_FIELD_NUMBER: _ClassVar[int] - INSTRUMENT_MODE_FIELD_NUMBER: _ClassVar[int] - FREQUENCY_BAND_FIELD_NUMBER: _ClassVar[int] - CENTER_FREQUENCY_FIELD_NUMBER: _ClassVar[int] - BANDWIDTH_FIELD_NUMBER: _ClassVar[int] - RESOLUTION_RANGE_FIELD_NUMBER: _ClassVar[int] - RESOLUTION_AZIMUTH_FIELD_NUMBER: _ClassVar[int] - PIXEL_SPACING_RANGE_FIELD_NUMBER: _ClassVar[int] - PIXEL_SPACING_AZIMUTH_FIELD_NUMBER: _ClassVar[int] - LOOKS_RANGE_FIELD_NUMBER: _ClassVar[int] - LOOKS_AZIMUTH_FIELD_NUMBER: _ClassVar[int] - LOOKS_EQUIVALENT_NUMBER_FIELD_NUMBER: _ClassVar[int] - OBSERVATION_DIRECTION_FIELD_NUMBER: _ClassVar[int] - RELATIVE_BURST_FIELD_NUMBER: _ClassVar[int] - BEAM_IDS_FIELD_NUMBER: _ClassVar[int] - polarizations: _containers.RepeatedScalarFieldContainer[SARPolarization] - instrument_mode: str - frequency_band: SARFrequencyBand - center_frequency: float - bandwidth: float - resolution_range: float - resolution_azimuth: float - pixel_spacing_range: float - pixel_spacing_azimuth: float - looks_range: int - looks_azimuth: int - looks_equivalent_number: float - observation_direction: SARObservationDirection - relative_burst: int - beam_ids: _containers.RepeatedScalarFieldContainer[str] - def __init__(self, polarizations: _Optional[_Iterable[_Union[SARPolarization, str]]] = ..., instrument_mode: _Optional[str] = ..., frequency_band: _Optional[_Union[SARFrequencyBand, str]] = ..., center_frequency: _Optional[float] = ..., bandwidth: _Optional[float] = ..., resolution_range: _Optional[float] = ..., resolution_azimuth: _Optional[float] = ..., pixel_spacing_range: _Optional[float] = ..., pixel_spacing_azimuth: _Optional[float] = ..., looks_range: _Optional[int] = ..., looks_azimuth: _Optional[int] = ..., looks_equivalent_number: _Optional[float] = ..., observation_direction: _Optional[_Union[SARObservationDirection, str]] = ..., relative_burst: _Optional[int] = ..., beam_ids: _Optional[_Iterable[str]] = ...) -> None: ... diff --git a/tilebox-datasets/tilebox/datasets/datasets/stac/v1/sar_pb2_grpc.py b/tilebox-datasets/tilebox/datasets/datasets/stac/v1/sar_pb2_grpc.py deleted file mode 100644 index 8a93939..0000000 --- a/tilebox-datasets/tilebox/datasets/datasets/stac/v1/sar_pb2_grpc.py +++ /dev/null @@ -1,3 +0,0 @@ -# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! -"""Client and server classes corresponding to protobuf-defined services.""" -import grpc diff --git a/tilebox-datasets/tilebox/datasets/datasets/stac/v1/satellite_pb2.py b/tilebox-datasets/tilebox/datasets/datasets/stac/v1/satellite_pb2.py deleted file mode 100644 index fb5009a..0000000 --- a/tilebox-datasets/tilebox/datasets/datasets/stac/v1/satellite_pb2.py +++ /dev/null @@ -1,52 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by the protocol buffer compiler. DO NOT EDIT! -# NO CHECKED-IN PROTOBUF GENCODE -# source: datasets/stac/v1/satellite.proto -# Protobuf Python Version: 6.30.0 -"""Generated protocol buffer code.""" -from google.protobuf import descriptor as _descriptor -from google.protobuf import descriptor_pool as _descriptor_pool -from google.protobuf import runtime_version as _runtime_version -from google.protobuf import symbol_database as _symbol_database -from google.protobuf.internal import builder as _builder -_runtime_version.ValidateProtobufRuntimeVersion( - _runtime_version.Domain.PUBLIC, - 6, - 30, - 0, - '', - 'datasets/stac/v1/satellite.proto' -) -# @@protoc_insertion_point(imports) - -_sym_db = _symbol_database.Default() - - -from google.protobuf import timestamp_pb2 as google_dot_protobuf_dot_timestamp__pb2 - - -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n datasets/stac/v1/satellite.proto\x12\x10\x64\x61tasets.stac.v1\x1a\x1fgoogle/protobuf/timestamp.proto\"k\n\x19SatelliteOrbitStateVector\x12\x36\n\x08\x64\x61tetime\x18\x01 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\x08\x64\x61tetime\x12\x16\n\x06values\x18\x02 \x03(\x01R\x06values\"\x88\x04\n\x13SatelliteProperties\x12Q\n!platform_international_designator\x18\x01 \x01(\tB\x05\xaa\x01\x02\x08\x01R\x1fplatformInternationalDesignator\x12\x46\n\x0borbit_state\x18\x02 \x01(\x0e\x32%.datasets.stac.v1.SatelliteOrbitStateR\norbitState\x12,\n\x0e\x61\x62solute_orbit\x18\x03 \x01(\x04\x42\x05\xaa\x01\x02\x08\x01R\rabsoluteOrbit\x12,\n\x0erelative_orbit\x18\x04 \x01(\x04\x42\x05\xaa\x01\x02\x08\x01R\rrelativeOrbit\x12&\n\x0borbit_cycle\x18\x05 \x01(\x04\x42\x05\xaa\x01\x02\x08\x01R\norbitCycle\x12[\n\x13orbit_state_vectors\x18\x06 \x03(\x0b\x32+.datasets.stac.v1.SatelliteOrbitStateVectorR\x11orbitStateVectors\x12=\n\x0c\x61nx_datetime\x18\x07 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\x0b\x61nxDatetime\x12\x36\n\x13\x61\x63quisition_station\x18\x08 \x01(\tB\x05\xaa\x01\x02\x08\x01R\x12\x61\x63quisitionStation*\xd4\x01\n\x13SatelliteOrbitState\x12%\n!SATELLITE_ORBIT_STATE_UNSPECIFIED\x10\x00\x12#\n\x1fSATELLITE_ORBIT_STATE_ASCENDING\x10\x01\x12$\n SATELLITE_ORBIT_STATE_DESCENDING\x10\x02\x12\'\n#SATELLITE_ORBIT_STATE_GEOSTATIONARY\x10\x03\x12\"\n\x1eSATELLITE_ORBIT_STATE_CROSSING\x10\x04\x42\x8d\x01\n\x14\x63om.datasets.stac.v1B\x0eSatelliteProtoP\x01\xa2\x02\x03\x44SX\xaa\x02\x10\x44\x61tasets.Stac.V1\xca\x02\x10\x44\x61tasets\\Stac\\V1\xe2\x02\x1c\x44\x61tasets\\Stac\\V1\\GPBMetadata\xea\x02\x12\x44\x61tasets::Stac::V1\x92\x03\x02\x08\x02\x62\x08\x65\x64itionsp\xe8\x07') - -_globals = globals() -_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) -_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'datasets.stac.v1.satellite_pb2', _globals) -if not _descriptor._USE_C_DESCRIPTORS: - _globals['DESCRIPTOR']._loaded_options = None - _globals['DESCRIPTOR']._serialized_options = b'\n\024com.datasets.stac.v1B\016SatelliteProtoP\001\242\002\003DSX\252\002\020Datasets.Stac.V1\312\002\020Datasets\\Stac\\V1\342\002\034Datasets\\Stac\\V1\\GPBMetadata\352\002\022Datasets::Stac::V1\222\003\002\010\002' - _globals['_SATELLITEPROPERTIES'].fields_by_name['platform_international_designator']._loaded_options = None - _globals['_SATELLITEPROPERTIES'].fields_by_name['platform_international_designator']._serialized_options = b'\252\001\002\010\001' - _globals['_SATELLITEPROPERTIES'].fields_by_name['absolute_orbit']._loaded_options = None - _globals['_SATELLITEPROPERTIES'].fields_by_name['absolute_orbit']._serialized_options = b'\252\001\002\010\001' - _globals['_SATELLITEPROPERTIES'].fields_by_name['relative_orbit']._loaded_options = None - _globals['_SATELLITEPROPERTIES'].fields_by_name['relative_orbit']._serialized_options = b'\252\001\002\010\001' - _globals['_SATELLITEPROPERTIES'].fields_by_name['orbit_cycle']._loaded_options = None - _globals['_SATELLITEPROPERTIES'].fields_by_name['orbit_cycle']._serialized_options = b'\252\001\002\010\001' - _globals['_SATELLITEPROPERTIES'].fields_by_name['acquisition_station']._loaded_options = None - _globals['_SATELLITEPROPERTIES'].fields_by_name['acquisition_station']._serialized_options = b'\252\001\002\010\001' - _globals['_SATELLITEORBITSTATE']._serialized_start=720 - _globals['_SATELLITEORBITSTATE']._serialized_end=932 - _globals['_SATELLITEORBITSTATEVECTOR']._serialized_start=87 - _globals['_SATELLITEORBITSTATEVECTOR']._serialized_end=194 - _globals['_SATELLITEPROPERTIES']._serialized_start=197 - _globals['_SATELLITEPROPERTIES']._serialized_end=717 -# @@protoc_insertion_point(module_scope) diff --git a/tilebox-datasets/tilebox/datasets/datasets/stac/v1/satellite_pb2.pyi b/tilebox-datasets/tilebox/datasets/datasets/stac/v1/satellite_pb2.pyi deleted file mode 100644 index 51a2971..0000000 --- a/tilebox-datasets/tilebox/datasets/datasets/stac/v1/satellite_pb2.pyi +++ /dev/null @@ -1,50 +0,0 @@ -from google.protobuf import timestamp_pb2 as _timestamp_pb2 -from google.protobuf.internal import containers as _containers -from google.protobuf.internal import enum_type_wrapper as _enum_type_wrapper -from google.protobuf import descriptor as _descriptor -from google.protobuf import message as _message -from collections.abc import Iterable as _Iterable, Mapping as _Mapping -from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union - -DESCRIPTOR: _descriptor.FileDescriptor - -class SatelliteOrbitState(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): - __slots__ = () - SATELLITE_ORBIT_STATE_UNSPECIFIED: _ClassVar[SatelliteOrbitState] - SATELLITE_ORBIT_STATE_ASCENDING: _ClassVar[SatelliteOrbitState] - SATELLITE_ORBIT_STATE_DESCENDING: _ClassVar[SatelliteOrbitState] - SATELLITE_ORBIT_STATE_GEOSTATIONARY: _ClassVar[SatelliteOrbitState] - SATELLITE_ORBIT_STATE_CROSSING: _ClassVar[SatelliteOrbitState] -SATELLITE_ORBIT_STATE_UNSPECIFIED: SatelliteOrbitState -SATELLITE_ORBIT_STATE_ASCENDING: SatelliteOrbitState -SATELLITE_ORBIT_STATE_DESCENDING: SatelliteOrbitState -SATELLITE_ORBIT_STATE_GEOSTATIONARY: SatelliteOrbitState -SATELLITE_ORBIT_STATE_CROSSING: SatelliteOrbitState - -class SatelliteOrbitStateVector(_message.Message): - __slots__ = ("datetime", "values") - DATETIME_FIELD_NUMBER: _ClassVar[int] - VALUES_FIELD_NUMBER: _ClassVar[int] - datetime: _timestamp_pb2.Timestamp - values: _containers.RepeatedScalarFieldContainer[float] - def __init__(self, datetime: _Optional[_Union[_timestamp_pb2.Timestamp, _Mapping]] = ..., values: _Optional[_Iterable[float]] = ...) -> None: ... - -class SatelliteProperties(_message.Message): - __slots__ = ("platform_international_designator", "orbit_state", "absolute_orbit", "relative_orbit", "orbit_cycle", "orbit_state_vectors", "anx_datetime", "acquisition_station") - PLATFORM_INTERNATIONAL_DESIGNATOR_FIELD_NUMBER: _ClassVar[int] - ORBIT_STATE_FIELD_NUMBER: _ClassVar[int] - ABSOLUTE_ORBIT_FIELD_NUMBER: _ClassVar[int] - RELATIVE_ORBIT_FIELD_NUMBER: _ClassVar[int] - ORBIT_CYCLE_FIELD_NUMBER: _ClassVar[int] - ORBIT_STATE_VECTORS_FIELD_NUMBER: _ClassVar[int] - ANX_DATETIME_FIELD_NUMBER: _ClassVar[int] - ACQUISITION_STATION_FIELD_NUMBER: _ClassVar[int] - platform_international_designator: str - orbit_state: SatelliteOrbitState - absolute_orbit: int - relative_orbit: int - orbit_cycle: int - orbit_state_vectors: _containers.RepeatedCompositeFieldContainer[SatelliteOrbitStateVector] - anx_datetime: _timestamp_pb2.Timestamp - acquisition_station: str - def __init__(self, platform_international_designator: _Optional[str] = ..., orbit_state: _Optional[_Union[SatelliteOrbitState, str]] = ..., absolute_orbit: _Optional[int] = ..., relative_orbit: _Optional[int] = ..., orbit_cycle: _Optional[int] = ..., orbit_state_vectors: _Optional[_Iterable[_Union[SatelliteOrbitStateVector, _Mapping]]] = ..., anx_datetime: _Optional[_Union[_timestamp_pb2.Timestamp, _Mapping]] = ..., acquisition_station: _Optional[str] = ...) -> None: ... diff --git a/tilebox-datasets/tilebox/datasets/datasets/stac/v1/satellite_pb2_grpc.py b/tilebox-datasets/tilebox/datasets/datasets/stac/v1/satellite_pb2_grpc.py deleted file mode 100644 index 8a93939..0000000 --- a/tilebox-datasets/tilebox/datasets/datasets/stac/v1/satellite_pb2_grpc.py +++ /dev/null @@ -1,3 +0,0 @@ -# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! -"""Client and server classes corresponding to protobuf-defined services.""" -import grpc diff --git a/tilebox-datasets/tilebox/datasets/datasets/stac/v1/storage_pb2.py b/tilebox-datasets/tilebox/datasets/datasets/stac/v1/storage_pb2.py deleted file mode 100644 index 2b2b7e9..0000000 --- a/tilebox-datasets/tilebox/datasets/datasets/stac/v1/storage_pb2.py +++ /dev/null @@ -1,68 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by the protocol buffer compiler. DO NOT EDIT! -# NO CHECKED-IN PROTOBUF GENCODE -# source: datasets/stac/v1/storage.proto -# Protobuf Python Version: 6.30.0 -"""Generated protocol buffer code.""" -from google.protobuf import descriptor as _descriptor -from google.protobuf import descriptor_pool as _descriptor_pool -from google.protobuf import runtime_version as _runtime_version -from google.protobuf import symbol_database as _symbol_database -from google.protobuf.internal import builder as _builder -_runtime_version.ValidateProtobufRuntimeVersion( - _runtime_version.Domain.PUBLIC, - 6, - 30, - 0, - '', - 'datasets/stac/v1/storage.proto' -) -# @@protoc_insertion_point(imports) - -_sym_db = _symbol_database.Default() - - -from google.protobuf import struct_pb2 as google_dot_protobuf_dot_struct__pb2 - - -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1e\x64\x61tasets/stac/v1/storage.proto\x12\x10\x64\x61tasets.stac.v1\x1a\x1cgoogle/protobuf/struct.proto\"\xa8\x01\n\x07Storage\x12@\n\x07schemes\x18\x01 \x03(\x0b\x32&.datasets.stac.v1.Storage.SchemesEntryR\x07schemes\x1a[\n\x0cSchemesEntry\x12\x10\n\x03key\x18\x01 \x01(\tR\x03key\x12\x35\n\x05value\x18\x02 \x01(\x0b\x32\x1f.datasets.stac.v1.StorageSchemeR\x05value:\x02\x38\x01\"\xed\x04\n\rStorageScheme\x12H\n\nknown_type\x18\x01 \x01(\x0e\x32\".datasets.stac.v1.KnownStorageTypeB\x05\xaa\x01\x02\x08\x01R\tknownType\x12&\n\x0b\x63ustom_type\x18\x02 \x01(\tB\x05\xaa\x01\x02\x08\x01R\ncustomType\x12\x1a\n\x08platform\x18\x03 \x01(\tR\x08platform\x12\x1b\n\x05title\x18\x04 \x01(\tB\x05\xaa\x01\x02\x08\x01R\x05title\x12\'\n\x0b\x64\x65scription\x18\x05 \x01(\tB\x05\xaa\x01\x02\x08\x01R\x0b\x64\x65scription\x12\x1d\n\x06region\x18\x06 \x01(\tB\x05\xaa\x01\x02\x08\x01R\x06region\x12,\n\x0erequester_pays\x18\x07 \x01(\x08\x42\x05\xaa\x01\x02\x08\x01R\rrequesterPays\x12*\n\rstorage_class\x18\x08 \x01(\tB\x05\xaa\x01\x02\x08\x01R\x0cstorageClass\x12\x1d\n\x06\x62ucket\x18\t \x01(\tB\x05\xaa\x01\x02\x08\x01R\x06\x62ucket\x12\x1f\n\x07\x61\x63\x63ount\x18\n \x01(\tB\x05\xaa\x01\x02\x08\x01R\x07\x61\x63\x63ount\x12n\n\x15\x61\x64\x64itional_properties\x18\x0b \x03(\x0b\x32\x39.datasets.stac.v1.StorageScheme.AdditionalPropertiesEntryR\x14\x61\x64\x64itionalProperties\x1a_\n\x19\x41\x64\x64itionalPropertiesEntry\x12\x10\n\x03key\x18\x01 \x01(\tR\x03key\x12,\n\x05value\x18\x02 \x01(\x0b\x32\x16.google.protobuf.ValueR\x05value:\x02\x38\x01*\xcc\x01\n\x10KnownStorageType\x12\"\n\x1eKNOWN_STORAGE_TYPE_UNSPECIFIED\x10\x00\x12\x1d\n\x19KNOWN_STORAGE_TYPE_AWS_S3\x10\x01\x12 \n\x1cKNOWN_STORAGE_TYPE_CUSTOM_S3\x10\x02\x12&\n\"KNOWN_STORAGE_TYPE_MICROSOFT_AZURE\x10\x03\x12+\n\'KNOWN_STORAGE_TYPE_GOOGLE_CLOUD_STORAGE\x10\x04\x42\x8b\x01\n\x14\x63om.datasets.stac.v1B\x0cStorageProtoP\x01\xa2\x02\x03\x44SX\xaa\x02\x10\x44\x61tasets.Stac.V1\xca\x02\x10\x44\x61tasets\\Stac\\V1\xe2\x02\x1c\x44\x61tasets\\Stac\\V1\\GPBMetadata\xea\x02\x12\x44\x61tasets::Stac::V1\x92\x03\x02\x08\x02\x62\x08\x65\x64itionsp\xe8\x07') - -_globals = globals() -_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) -_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'datasets.stac.v1.storage_pb2', _globals) -if not _descriptor._USE_C_DESCRIPTORS: - _globals['DESCRIPTOR']._loaded_options = None - _globals['DESCRIPTOR']._serialized_options = b'\n\024com.datasets.stac.v1B\014StorageProtoP\001\242\002\003DSX\252\002\020Datasets.Stac.V1\312\002\020Datasets\\Stac\\V1\342\002\034Datasets\\Stac\\V1\\GPBMetadata\352\002\022Datasets::Stac::V1\222\003\002\010\002' - _globals['_STORAGE_SCHEMESENTRY']._loaded_options = None - _globals['_STORAGE_SCHEMESENTRY']._serialized_options = b'8\001' - _globals['_STORAGESCHEME_ADDITIONALPROPERTIESENTRY']._loaded_options = None - _globals['_STORAGESCHEME_ADDITIONALPROPERTIESENTRY']._serialized_options = b'8\001' - _globals['_STORAGESCHEME'].fields_by_name['known_type']._loaded_options = None - _globals['_STORAGESCHEME'].fields_by_name['known_type']._serialized_options = b'\252\001\002\010\001' - _globals['_STORAGESCHEME'].fields_by_name['custom_type']._loaded_options = None - _globals['_STORAGESCHEME'].fields_by_name['custom_type']._serialized_options = b'\252\001\002\010\001' - _globals['_STORAGESCHEME'].fields_by_name['title']._loaded_options = None - _globals['_STORAGESCHEME'].fields_by_name['title']._serialized_options = b'\252\001\002\010\001' - _globals['_STORAGESCHEME'].fields_by_name['description']._loaded_options = None - _globals['_STORAGESCHEME'].fields_by_name['description']._serialized_options = b'\252\001\002\010\001' - _globals['_STORAGESCHEME'].fields_by_name['region']._loaded_options = None - _globals['_STORAGESCHEME'].fields_by_name['region']._serialized_options = b'\252\001\002\010\001' - _globals['_STORAGESCHEME'].fields_by_name['requester_pays']._loaded_options = None - _globals['_STORAGESCHEME'].fields_by_name['requester_pays']._serialized_options = b'\252\001\002\010\001' - _globals['_STORAGESCHEME'].fields_by_name['storage_class']._loaded_options = None - _globals['_STORAGESCHEME'].fields_by_name['storage_class']._serialized_options = b'\252\001\002\010\001' - _globals['_STORAGESCHEME'].fields_by_name['bucket']._loaded_options = None - _globals['_STORAGESCHEME'].fields_by_name['bucket']._serialized_options = b'\252\001\002\010\001' - _globals['_STORAGESCHEME'].fields_by_name['account']._loaded_options = None - _globals['_STORAGESCHEME'].fields_by_name['account']._serialized_options = b'\252\001\002\010\001' - _globals['_KNOWNSTORAGETYPE']._serialized_start=878 - _globals['_KNOWNSTORAGETYPE']._serialized_end=1082 - _globals['_STORAGE']._serialized_start=83 - _globals['_STORAGE']._serialized_end=251 - _globals['_STORAGE_SCHEMESENTRY']._serialized_start=160 - _globals['_STORAGE_SCHEMESENTRY']._serialized_end=251 - _globals['_STORAGESCHEME']._serialized_start=254 - _globals['_STORAGESCHEME']._serialized_end=875 - _globals['_STORAGESCHEME_ADDITIONALPROPERTIESENTRY']._serialized_start=780 - _globals['_STORAGESCHEME_ADDITIONALPROPERTIESENTRY']._serialized_end=875 -# @@protoc_insertion_point(module_scope) diff --git a/tilebox-datasets/tilebox/datasets/datasets/stac/v1/storage_pb2.pyi b/tilebox-datasets/tilebox/datasets/datasets/stac/v1/storage_pb2.pyi deleted file mode 100644 index 50c38bd..0000000 --- a/tilebox-datasets/tilebox/datasets/datasets/stac/v1/storage_pb2.pyi +++ /dev/null @@ -1,68 +0,0 @@ -from google.protobuf import struct_pb2 as _struct_pb2 -from google.protobuf.internal import containers as _containers -from google.protobuf.internal import enum_type_wrapper as _enum_type_wrapper -from google.protobuf import descriptor as _descriptor -from google.protobuf import message as _message -from collections.abc import Mapping as _Mapping -from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union - -DESCRIPTOR: _descriptor.FileDescriptor - -class KnownStorageType(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): - __slots__ = () - KNOWN_STORAGE_TYPE_UNSPECIFIED: _ClassVar[KnownStorageType] - KNOWN_STORAGE_TYPE_AWS_S3: _ClassVar[KnownStorageType] - KNOWN_STORAGE_TYPE_CUSTOM_S3: _ClassVar[KnownStorageType] - KNOWN_STORAGE_TYPE_MICROSOFT_AZURE: _ClassVar[KnownStorageType] - KNOWN_STORAGE_TYPE_GOOGLE_CLOUD_STORAGE: _ClassVar[KnownStorageType] -KNOWN_STORAGE_TYPE_UNSPECIFIED: KnownStorageType -KNOWN_STORAGE_TYPE_AWS_S3: KnownStorageType -KNOWN_STORAGE_TYPE_CUSTOM_S3: KnownStorageType -KNOWN_STORAGE_TYPE_MICROSOFT_AZURE: KnownStorageType -KNOWN_STORAGE_TYPE_GOOGLE_CLOUD_STORAGE: KnownStorageType - -class Storage(_message.Message): - __slots__ = ("schemes",) - class SchemesEntry(_message.Message): - __slots__ = ("key", "value") - KEY_FIELD_NUMBER: _ClassVar[int] - VALUE_FIELD_NUMBER: _ClassVar[int] - key: str - value: StorageScheme - def __init__(self, key: _Optional[str] = ..., value: _Optional[_Union[StorageScheme, _Mapping]] = ...) -> None: ... - SCHEMES_FIELD_NUMBER: _ClassVar[int] - schemes: _containers.MessageMap[str, StorageScheme] - def __init__(self, schemes: _Optional[_Mapping[str, StorageScheme]] = ...) -> None: ... - -class StorageScheme(_message.Message): - __slots__ = ("known_type", "custom_type", "platform", "title", "description", "region", "requester_pays", "storage_class", "bucket", "account", "additional_properties") - class AdditionalPropertiesEntry(_message.Message): - __slots__ = ("key", "value") - KEY_FIELD_NUMBER: _ClassVar[int] - VALUE_FIELD_NUMBER: _ClassVar[int] - key: str - value: _struct_pb2.Value - def __init__(self, key: _Optional[str] = ..., value: _Optional[_Union[_struct_pb2.Value, _Mapping]] = ...) -> None: ... - KNOWN_TYPE_FIELD_NUMBER: _ClassVar[int] - CUSTOM_TYPE_FIELD_NUMBER: _ClassVar[int] - PLATFORM_FIELD_NUMBER: _ClassVar[int] - TITLE_FIELD_NUMBER: _ClassVar[int] - DESCRIPTION_FIELD_NUMBER: _ClassVar[int] - REGION_FIELD_NUMBER: _ClassVar[int] - REQUESTER_PAYS_FIELD_NUMBER: _ClassVar[int] - STORAGE_CLASS_FIELD_NUMBER: _ClassVar[int] - BUCKET_FIELD_NUMBER: _ClassVar[int] - ACCOUNT_FIELD_NUMBER: _ClassVar[int] - ADDITIONAL_PROPERTIES_FIELD_NUMBER: _ClassVar[int] - known_type: KnownStorageType - custom_type: str - platform: str - title: str - description: str - region: str - requester_pays: bool - storage_class: str - bucket: str - account: str - additional_properties: _containers.MessageMap[str, _struct_pb2.Value] - def __init__(self, known_type: _Optional[_Union[KnownStorageType, str]] = ..., custom_type: _Optional[str] = ..., platform: _Optional[str] = ..., title: _Optional[str] = ..., description: _Optional[str] = ..., region: _Optional[str] = ..., requester_pays: bool = ..., storage_class: _Optional[str] = ..., bucket: _Optional[str] = ..., account: _Optional[str] = ..., additional_properties: _Optional[_Mapping[str, _struct_pb2.Value]] = ...) -> None: ... diff --git a/tilebox-datasets/tilebox/datasets/datasets/stac/v1/storage_pb2_grpc.py b/tilebox-datasets/tilebox/datasets/datasets/stac/v1/storage_pb2_grpc.py deleted file mode 100644 index 8a93939..0000000 --- a/tilebox-datasets/tilebox/datasets/datasets/stac/v1/storage_pb2_grpc.py +++ /dev/null @@ -1,3 +0,0 @@ -# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! -"""Client and server classes corresponding to protobuf-defined services.""" -import grpc diff --git a/tilebox-datasets/tilebox/datasets/datasets/v1/collections_pb2.py b/tilebox-datasets/tilebox/datasets/datasets/v1/collections_pb2.py deleted file mode 100644 index 4bbba93..0000000 --- a/tilebox-datasets/tilebox/datasets/datasets/v1/collections_pb2.py +++ /dev/null @@ -1,53 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by the protocol buffer compiler. DO NOT EDIT! -# NO CHECKED-IN PROTOBUF GENCODE -# source: datasets/v1/collections.proto -# Protobuf Python Version: 6.30.0 -"""Generated protocol buffer code.""" -from google.protobuf import descriptor as _descriptor -from google.protobuf import descriptor_pool as _descriptor_pool -from google.protobuf import runtime_version as _runtime_version -from google.protobuf import symbol_database as _symbol_database -from google.protobuf.internal import builder as _builder -_runtime_version.ValidateProtobufRuntimeVersion( - _runtime_version.Domain.PUBLIC, - 6, - 30, - 0, - '', - 'datasets/v1/collections.proto' -) -# @@protoc_insertion_point(imports) - -_sym_db = _symbol_database.Default() - - -from tilebox.datasets.datasets.v1 import core_pb2 as datasets_dot_v1_dot_core__pb2 -from tilebox.datasets.tilebox.v1 import id_pb2 as tilebox_dot_v1_dot_id__pb2 - - -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1d\x64\x61tasets/v1/collections.proto\x12\x0b\x64\x61tasets.v1\x1a\x16\x64\x61tasets/v1/core.proto\x1a\x13tilebox/v1/id.proto\"\\\n\x17\x43reateCollectionRequest\x12-\n\ndataset_id\x18\x01 \x01(\x0b\x32\x0e.tilebox.v1.IDR\tdatasetId\x12\x12\n\x04name\x18\x02 \x01(\tR\x04name\"\xc0\x01\n\x1aGetCollectionByNameRequest\x12\'\n\x0f\x63ollection_name\x18\x01 \x01(\tR\x0e\x63ollectionName\x12+\n\x11with_availability\x18\x02 \x01(\x08R\x10withAvailability\x12\x1d\n\nwith_count\x18\x03 \x01(\x08R\twithCount\x12-\n\ndataset_id\x18\x04 \x01(\x0b\x32\x0e.tilebox.v1.IDR\tdatasetId\"}\n\x17\x44\x65leteCollectionRequest\x12\x33\n\rcollection_id\x18\x01 \x01(\x0b\x32\x0e.tilebox.v1.IDR\x0c\x63ollectionId\x12-\n\ndataset_id\x18\x02 \x01(\x0b\x32\x0e.tilebox.v1.IDR\tdatasetId\"\x1a\n\x18\x44\x65leteCollectionResponse\"\x93\x01\n\x16ListCollectionsRequest\x12-\n\ndataset_id\x18\x01 \x01(\x0b\x32\x0e.tilebox.v1.IDR\tdatasetId\x12+\n\x11with_availability\x18\x02 \x01(\x08R\x10withAvailability\x12\x1d\n\nwith_count\x18\x03 \x01(\x08R\twithCount\"\xba\x01\n\x17ListCollectionsResponse\x12/\n\x04\x64\x61ta\x18\x01 \x03(\x0b\x32\x1b.datasets.v1.CollectionInfoR\x04\x64\x61ta\x12+\n\x11owned_collections\x18\x02 \x01(\x03R\x10ownedCollections\x12\x41\n\x19maximum_owned_collections\x18\x03 \x01(\x03\x42\x05\xaa\x01\x02\x08\x01R\x17maximumOwnedCollections2\x86\x03\n\x11\x43ollectionService\x12U\n\x10\x43reateCollection\x12$.datasets.v1.CreateCollectionRequest\x1a\x1b.datasets.v1.CollectionInfo\x12[\n\x13GetCollectionByName\x12\'.datasets.v1.GetCollectionByNameRequest\x1a\x1b.datasets.v1.CollectionInfo\x12_\n\x10\x44\x65leteCollection\x12$.datasets.v1.DeleteCollectionRequest\x1a%.datasets.v1.DeleteCollectionResponse\x12\\\n\x0fListCollections\x12#.datasets.v1.ListCollectionsRequest\x1a$.datasets.v1.ListCollectionsResponseBu\n\x0f\x63om.datasets.v1B\x10\x43ollectionsProtoP\x01\xa2\x02\x03\x44XX\xaa\x02\x0b\x44\x61tasets.V1\xca\x02\x0b\x44\x61tasets\\V1\xe2\x02\x17\x44\x61tasets\\V1\\GPBMetadata\xea\x02\x0c\x44\x61tasets::V1\x92\x03\x02\x08\x02\x62\x08\x65\x64itionsp\xe8\x07') - -_globals = globals() -_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) -_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'datasets.v1.collections_pb2', _globals) -if not _descriptor._USE_C_DESCRIPTORS: - _globals['DESCRIPTOR']._loaded_options = None - _globals['DESCRIPTOR']._serialized_options = b'\n\017com.datasets.v1B\020CollectionsProtoP\001\242\002\003DXX\252\002\013Datasets.V1\312\002\013Datasets\\V1\342\002\027Datasets\\V1\\GPBMetadata\352\002\014Datasets::V1\222\003\002\010\002' - _globals['_LISTCOLLECTIONSRESPONSE'].fields_by_name['maximum_owned_collections']._loaded_options = None - _globals['_LISTCOLLECTIONSRESPONSE'].fields_by_name['maximum_owned_collections']._serialized_options = b'\252\001\002\010\001' - _globals['_CREATECOLLECTIONREQUEST']._serialized_start=91 - _globals['_CREATECOLLECTIONREQUEST']._serialized_end=183 - _globals['_GETCOLLECTIONBYNAMEREQUEST']._serialized_start=186 - _globals['_GETCOLLECTIONBYNAMEREQUEST']._serialized_end=378 - _globals['_DELETECOLLECTIONREQUEST']._serialized_start=380 - _globals['_DELETECOLLECTIONREQUEST']._serialized_end=505 - _globals['_DELETECOLLECTIONRESPONSE']._serialized_start=507 - _globals['_DELETECOLLECTIONRESPONSE']._serialized_end=533 - _globals['_LISTCOLLECTIONSREQUEST']._serialized_start=536 - _globals['_LISTCOLLECTIONSREQUEST']._serialized_end=683 - _globals['_LISTCOLLECTIONSRESPONSE']._serialized_start=686 - _globals['_LISTCOLLECTIONSRESPONSE']._serialized_end=872 - _globals['_COLLECTIONSERVICE']._serialized_start=875 - _globals['_COLLECTIONSERVICE']._serialized_end=1265 -# @@protoc_insertion_point(module_scope) diff --git a/tilebox-datasets/tilebox/datasets/datasets/v1/collections_pb2.pyi b/tilebox-datasets/tilebox/datasets/datasets/v1/collections_pb2.pyi deleted file mode 100644 index aca6709..0000000 --- a/tilebox-datasets/tilebox/datasets/datasets/v1/collections_pb2.pyi +++ /dev/null @@ -1,61 +0,0 @@ -from tilebox.datasets.datasets.v1 import core_pb2 as _core_pb2 -from tilebox.datasets.tilebox.v1 import id_pb2 as _id_pb2 -from google.protobuf.internal import containers as _containers -from google.protobuf import descriptor as _descriptor -from google.protobuf import message as _message -from collections.abc import Iterable as _Iterable, Mapping as _Mapping -from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union - -DESCRIPTOR: _descriptor.FileDescriptor - -class CreateCollectionRequest(_message.Message): - __slots__ = ("dataset_id", "name") - DATASET_ID_FIELD_NUMBER: _ClassVar[int] - NAME_FIELD_NUMBER: _ClassVar[int] - dataset_id: _id_pb2.ID - name: str - def __init__(self, dataset_id: _Optional[_Union[_id_pb2.ID, _Mapping]] = ..., name: _Optional[str] = ...) -> None: ... - -class GetCollectionByNameRequest(_message.Message): - __slots__ = ("collection_name", "with_availability", "with_count", "dataset_id") - COLLECTION_NAME_FIELD_NUMBER: _ClassVar[int] - WITH_AVAILABILITY_FIELD_NUMBER: _ClassVar[int] - WITH_COUNT_FIELD_NUMBER: _ClassVar[int] - DATASET_ID_FIELD_NUMBER: _ClassVar[int] - collection_name: str - with_availability: bool - with_count: bool - dataset_id: _id_pb2.ID - def __init__(self, collection_name: _Optional[str] = ..., with_availability: bool = ..., with_count: bool = ..., dataset_id: _Optional[_Union[_id_pb2.ID, _Mapping]] = ...) -> None: ... - -class DeleteCollectionRequest(_message.Message): - __slots__ = ("collection_id", "dataset_id") - COLLECTION_ID_FIELD_NUMBER: _ClassVar[int] - DATASET_ID_FIELD_NUMBER: _ClassVar[int] - collection_id: _id_pb2.ID - dataset_id: _id_pb2.ID - def __init__(self, collection_id: _Optional[_Union[_id_pb2.ID, _Mapping]] = ..., dataset_id: _Optional[_Union[_id_pb2.ID, _Mapping]] = ...) -> None: ... - -class DeleteCollectionResponse(_message.Message): - __slots__ = () - def __init__(self) -> None: ... - -class ListCollectionsRequest(_message.Message): - __slots__ = ("dataset_id", "with_availability", "with_count") - DATASET_ID_FIELD_NUMBER: _ClassVar[int] - WITH_AVAILABILITY_FIELD_NUMBER: _ClassVar[int] - WITH_COUNT_FIELD_NUMBER: _ClassVar[int] - dataset_id: _id_pb2.ID - with_availability: bool - with_count: bool - def __init__(self, dataset_id: _Optional[_Union[_id_pb2.ID, _Mapping]] = ..., with_availability: bool = ..., with_count: bool = ...) -> None: ... - -class ListCollectionsResponse(_message.Message): - __slots__ = ("data", "owned_collections", "maximum_owned_collections") - DATA_FIELD_NUMBER: _ClassVar[int] - OWNED_COLLECTIONS_FIELD_NUMBER: _ClassVar[int] - MAXIMUM_OWNED_COLLECTIONS_FIELD_NUMBER: _ClassVar[int] - data: _containers.RepeatedCompositeFieldContainer[_core_pb2.CollectionInfo] - owned_collections: int - maximum_owned_collections: int - def __init__(self, data: _Optional[_Iterable[_Union[_core_pb2.CollectionInfo, _Mapping]]] = ..., owned_collections: _Optional[int] = ..., maximum_owned_collections: _Optional[int] = ...) -> None: ... diff --git a/tilebox-datasets/tilebox/datasets/datasets/v1/collections_pb2_grpc.py b/tilebox-datasets/tilebox/datasets/datasets/v1/collections_pb2_grpc.py deleted file mode 100644 index e385b8b..0000000 --- a/tilebox-datasets/tilebox/datasets/datasets/v1/collections_pb2_grpc.py +++ /dev/null @@ -1,210 +0,0 @@ -# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! -"""Client and server classes corresponding to protobuf-defined services.""" -import grpc - -from tilebox.datasets.datasets.v1 import collections_pb2 as datasets_dot_v1_dot_collections__pb2 -from tilebox.datasets.datasets.v1 import core_pb2 as datasets_dot_v1_dot_core__pb2 - - -class CollectionServiceStub: - """CollectionService is the service definition for the Tilebox datasets service, which provides access to datasets - """ - - def __init__(self, channel): - """Constructor. - - Args: - channel: A grpc.Channel. - """ - self.CreateCollection = channel.unary_unary( - '/datasets.v1.CollectionService/CreateCollection', - request_serializer=datasets_dot_v1_dot_collections__pb2.CreateCollectionRequest.SerializeToString, - response_deserializer=datasets_dot_v1_dot_core__pb2.CollectionInfo.FromString, - _registered_method=True) - self.GetCollectionByName = channel.unary_unary( - '/datasets.v1.CollectionService/GetCollectionByName', - request_serializer=datasets_dot_v1_dot_collections__pb2.GetCollectionByNameRequest.SerializeToString, - response_deserializer=datasets_dot_v1_dot_core__pb2.CollectionInfo.FromString, - _registered_method=True) - self.DeleteCollection = channel.unary_unary( - '/datasets.v1.CollectionService/DeleteCollection', - request_serializer=datasets_dot_v1_dot_collections__pb2.DeleteCollectionRequest.SerializeToString, - response_deserializer=datasets_dot_v1_dot_collections__pb2.DeleteCollectionResponse.FromString, - _registered_method=True) - self.ListCollections = channel.unary_unary( - '/datasets.v1.CollectionService/ListCollections', - request_serializer=datasets_dot_v1_dot_collections__pb2.ListCollectionsRequest.SerializeToString, - response_deserializer=datasets_dot_v1_dot_collections__pb2.ListCollectionsResponse.FromString, - _registered_method=True) - - -class CollectionServiceServicer: - """CollectionService is the service definition for the Tilebox datasets service, which provides access to datasets - """ - - def CreateCollection(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def GetCollectionByName(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def DeleteCollection(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def ListCollections(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - -def add_CollectionServiceServicer_to_server(servicer, server): - rpc_method_handlers = { - 'CreateCollection': grpc.unary_unary_rpc_method_handler( - servicer.CreateCollection, - request_deserializer=datasets_dot_v1_dot_collections__pb2.CreateCollectionRequest.FromString, - response_serializer=datasets_dot_v1_dot_core__pb2.CollectionInfo.SerializeToString, - ), - 'GetCollectionByName': grpc.unary_unary_rpc_method_handler( - servicer.GetCollectionByName, - request_deserializer=datasets_dot_v1_dot_collections__pb2.GetCollectionByNameRequest.FromString, - response_serializer=datasets_dot_v1_dot_core__pb2.CollectionInfo.SerializeToString, - ), - 'DeleteCollection': grpc.unary_unary_rpc_method_handler( - servicer.DeleteCollection, - request_deserializer=datasets_dot_v1_dot_collections__pb2.DeleteCollectionRequest.FromString, - response_serializer=datasets_dot_v1_dot_collections__pb2.DeleteCollectionResponse.SerializeToString, - ), - 'ListCollections': grpc.unary_unary_rpc_method_handler( - servicer.ListCollections, - request_deserializer=datasets_dot_v1_dot_collections__pb2.ListCollectionsRequest.FromString, - response_serializer=datasets_dot_v1_dot_collections__pb2.ListCollectionsResponse.SerializeToString, - ), - } - generic_handler = grpc.method_handlers_generic_handler( - 'datasets.v1.CollectionService', rpc_method_handlers) - server.add_generic_rpc_handlers((generic_handler,)) - server.add_registered_method_handlers('datasets.v1.CollectionService', rpc_method_handlers) - - - # This class is part of an EXPERIMENTAL API. -class CollectionService: - """CollectionService is the service definition for the Tilebox datasets service, which provides access to datasets - """ - - @staticmethod - def CreateCollection(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary( - request, - target, - '/datasets.v1.CollectionService/CreateCollection', - datasets_dot_v1_dot_collections__pb2.CreateCollectionRequest.SerializeToString, - datasets_dot_v1_dot_core__pb2.CollectionInfo.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) - - @staticmethod - def GetCollectionByName(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary( - request, - target, - '/datasets.v1.CollectionService/GetCollectionByName', - datasets_dot_v1_dot_collections__pb2.GetCollectionByNameRequest.SerializeToString, - datasets_dot_v1_dot_core__pb2.CollectionInfo.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) - - @staticmethod - def DeleteCollection(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary( - request, - target, - '/datasets.v1.CollectionService/DeleteCollection', - datasets_dot_v1_dot_collections__pb2.DeleteCollectionRequest.SerializeToString, - datasets_dot_v1_dot_collections__pb2.DeleteCollectionResponse.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) - - @staticmethod - def ListCollections(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary( - request, - target, - '/datasets.v1.CollectionService/ListCollections', - datasets_dot_v1_dot_collections__pb2.ListCollectionsRequest.SerializeToString, - datasets_dot_v1_dot_collections__pb2.ListCollectionsResponse.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) diff --git a/tilebox-datasets/tilebox/datasets/datasets/v1/core_pb2.py b/tilebox-datasets/tilebox/datasets/datasets/v1/core_pb2.py deleted file mode 100644 index a8f9bf8..0000000 --- a/tilebox-datasets/tilebox/datasets/datasets/v1/core_pb2.py +++ /dev/null @@ -1,58 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by the protocol buffer compiler. DO NOT EDIT! -# NO CHECKED-IN PROTOBUF GENCODE -# source: datasets/v1/core.proto -# Protobuf Python Version: 6.30.0 -"""Generated protocol buffer code.""" -from google.protobuf import descriptor as _descriptor -from google.protobuf import descriptor_pool as _descriptor_pool -from google.protobuf import runtime_version as _runtime_version -from google.protobuf import symbol_database as _symbol_database -from google.protobuf.internal import builder as _builder -_runtime_version.ValidateProtobufRuntimeVersion( - _runtime_version.Domain.PUBLIC, - 6, - 30, - 0, - '', - 'datasets/v1/core.proto' -) -# @@protoc_insertion_point(imports) - -_sym_db = _symbol_database.Default() - - -from tilebox.datasets.datasets.v1 import dataset_type_pb2 as datasets_dot_v1_dot_dataset__type__pb2 -from tilebox.datasets.tilebox.v1 import id_pb2 as tilebox_dot_v1_dot_id__pb2 -from tilebox.datasets.tilebox.v1 import query_pb2 as tilebox_dot_v1_dot_query__pb2 - - -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x16\x64\x61tasets/v1/core.proto\x12\x0b\x64\x61tasets.v1\x1a\x1e\x64\x61tasets/v1/dataset_type.proto\x1a\x13tilebox/v1/id.proto\x1a\x16tilebox/v1/query.proto\"6\n\x03\x41ny\x12\x19\n\x08type_url\x18\x01 \x01(\tR\x07typeUrl\x12\x14\n\x05value\x18\x02 \x01(\x0cR\x05value\">\n\x0bRepeatedAny\x12\x19\n\x08type_url\x18\x01 \x01(\tR\x07typeUrl\x12\x14\n\x05value\x18\x02 \x03(\x0cR\x05value\"F\n\nCollection\x12\x12\n\x04name\x18\x02 \x01(\tR\x04name\x12\x1e\n\x02id\x18\x03 \x01(\x0b\x32\x0e.tilebox.v1.IDR\x02idJ\x04\x08\x01\x10\x02\"\xab\x01\n\x0e\x43ollectionInfo\x12\x37\n\ncollection\x18\x01 \x01(\x0b\x32\x17.datasets.v1.CollectionR\ncollection\x12\x43\n\x0c\x61vailability\x18\x02 \x01(\x0b\x32\x18.tilebox.v1.TimeIntervalB\x05\xaa\x01\x02\x08\x01R\x0c\x61vailability\x12\x1b\n\x05\x63ount\x18\x03 \x01(\x04\x42\x05\xaa\x01\x02\x08\x01R\x05\x63ount\"\xf8\x03\n\x07\x44\x61taset\x12\x1e\n\x02id\x18\x01 \x01(\x0b\x32\x0e.tilebox.v1.IDR\x02id\x12)\n\x08group_id\x18\x02 \x01(\x0b\x32\x0e.tilebox.v1.IDR\x07groupId\x12.\n\x04type\x18\x03 \x01(\x0b\x32\x1a.datasets.v1.AnnotatedTypeR\x04type\x12\x1b\n\tcode_name\x18\x04 \x01(\tR\x08\x63odeName\x12\x12\n\x04name\x18\x05 \x01(\tR\x04name\x12\x18\n\x07summary\x18\x06 \x01(\tR\x07summary\x12\x12\n\x04icon\x18\x07 \x01(\tR\x04icon\x12 \n\x0b\x64\x65scription\x18\x08 \x01(\tR\x0b\x64\x65scription\x12@\n\x0bpermissions\x18\n \x03(\x0e\x32\x1e.datasets.v1.DatasetPermissionR\x0bpermissions\x12\x37\n\nvisibility\x18\x0b \x01(\x0e\x32\x17.datasets.v1.VisibilityR\nvisibility\x12\x12\n\x04slug\x18\x0c \x01(\tR\x04slug\x12#\n\rtype_editable\x18\r \x01(\x08R\x0ctypeEditable\x12=\n\x0b\x63ollections\x18\x0e \x03(\x0b\x32\x1b.datasets.v1.CollectionInfoR\x0b\x63ollections\"\xa0\x01\n\x0c\x44\x61tasetGroup\x12\x1e\n\x02id\x18\x01 \x01(\x0b\x32\x0e.tilebox.v1.IDR\x02id\x12+\n\tparent_id\x18\x02 \x01(\x0b\x32\x0e.tilebox.v1.IDR\x08parentId\x12\x1b\n\tcode_name\x18\x03 \x01(\tR\x08\x63odeName\x12\x12\n\x04name\x18\x04 \x01(\tR\x04name\x12\x12\n\x04icon\x18\x05 \x01(\tR\x04icon*\x9b\x01\n\x11\x44\x61tasetPermission\x12\"\n\x1e\x44\x41TASET_PERMISSION_UNSPECIFIED\x10\x00\x12\"\n\x1e\x44\x41TASET_PERMISSION_ACCESS_DATA\x10\x01\x12!\n\x1d\x44\x41TASET_PERMISSION_WRITE_DATA\x10\x02\x12\x1b\n\x17\x44\x41TASET_PERMISSION_EDIT\x10\x03*v\n\nVisibility\x12\x1a\n\x16VISIBILITY_UNSPECIFIED\x10\x00\x12\x16\n\x12VISIBILITY_PRIVATE\x10\x01\x12\x1d\n\x19VISIBILITY_SHARED_WITH_ME\x10\x02\x12\x15\n\x11VISIBILITY_PUBLIC\x10\x03\x42n\n\x0f\x63om.datasets.v1B\tCoreProtoP\x01\xa2\x02\x03\x44XX\xaa\x02\x0b\x44\x61tasets.V1\xca\x02\x0b\x44\x61tasets\\V1\xe2\x02\x17\x44\x61tasets\\V1\\GPBMetadata\xea\x02\x0c\x44\x61tasets::V1\x92\x03\x02\x08\x02\x62\x08\x65\x64itionsp\xe8\x07') - -_globals = globals() -_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) -_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'datasets.v1.core_pb2', _globals) -if not _descriptor._USE_C_DESCRIPTORS: - _globals['DESCRIPTOR']._loaded_options = None - _globals['DESCRIPTOR']._serialized_options = b'\n\017com.datasets.v1B\tCoreProtoP\001\242\002\003DXX\252\002\013Datasets.V1\312\002\013Datasets\\V1\342\002\027Datasets\\V1\\GPBMetadata\352\002\014Datasets::V1\222\003\002\010\002' - _globals['_COLLECTIONINFO'].fields_by_name['availability']._loaded_options = None - _globals['_COLLECTIONINFO'].fields_by_name['availability']._serialized_options = b'\252\001\002\010\001' - _globals['_COLLECTIONINFO'].fields_by_name['count']._loaded_options = None - _globals['_COLLECTIONINFO'].fields_by_name['count']._serialized_options = b'\252\001\002\010\001' - _globals['_DATASETPERMISSION']._serialized_start=1153 - _globals['_DATASETPERMISSION']._serialized_end=1308 - _globals['_VISIBILITY']._serialized_start=1310 - _globals['_VISIBILITY']._serialized_end=1428 - _globals['_ANY']._serialized_start=116 - _globals['_ANY']._serialized_end=170 - _globals['_REPEATEDANY']._serialized_start=172 - _globals['_REPEATEDANY']._serialized_end=234 - _globals['_COLLECTION']._serialized_start=236 - _globals['_COLLECTION']._serialized_end=306 - _globals['_COLLECTIONINFO']._serialized_start=309 - _globals['_COLLECTIONINFO']._serialized_end=480 - _globals['_DATASET']._serialized_start=483 - _globals['_DATASET']._serialized_end=987 - _globals['_DATASETGROUP']._serialized_start=990 - _globals['_DATASETGROUP']._serialized_end=1150 -# @@protoc_insertion_point(module_scope) diff --git a/tilebox-datasets/tilebox/datasets/datasets/v1/core_pb2.pyi b/tilebox-datasets/tilebox/datasets/datasets/v1/core_pb2.pyi deleted file mode 100644 index 75baf49..0000000 --- a/tilebox-datasets/tilebox/datasets/datasets/v1/core_pb2.pyi +++ /dev/null @@ -1,111 +0,0 @@ -from tilebox.datasets.datasets.v1 import dataset_type_pb2 as _dataset_type_pb2 -from tilebox.datasets.tilebox.v1 import id_pb2 as _id_pb2 -from tilebox.datasets.tilebox.v1 import query_pb2 as _query_pb2 -from google.protobuf.internal import containers as _containers -from google.protobuf.internal import enum_type_wrapper as _enum_type_wrapper -from google.protobuf import descriptor as _descriptor -from google.protobuf import message as _message -from collections.abc import Iterable as _Iterable, Mapping as _Mapping -from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union - -DESCRIPTOR: _descriptor.FileDescriptor - -class DatasetPermission(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): - __slots__ = () - DATASET_PERMISSION_UNSPECIFIED: _ClassVar[DatasetPermission] - DATASET_PERMISSION_ACCESS_DATA: _ClassVar[DatasetPermission] - DATASET_PERMISSION_WRITE_DATA: _ClassVar[DatasetPermission] - DATASET_PERMISSION_EDIT: _ClassVar[DatasetPermission] - -class Visibility(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): - __slots__ = () - VISIBILITY_UNSPECIFIED: _ClassVar[Visibility] - VISIBILITY_PRIVATE: _ClassVar[Visibility] - VISIBILITY_SHARED_WITH_ME: _ClassVar[Visibility] - VISIBILITY_PUBLIC: _ClassVar[Visibility] -DATASET_PERMISSION_UNSPECIFIED: DatasetPermission -DATASET_PERMISSION_ACCESS_DATA: DatasetPermission -DATASET_PERMISSION_WRITE_DATA: DatasetPermission -DATASET_PERMISSION_EDIT: DatasetPermission -VISIBILITY_UNSPECIFIED: Visibility -VISIBILITY_PRIVATE: Visibility -VISIBILITY_SHARED_WITH_ME: Visibility -VISIBILITY_PUBLIC: Visibility - -class Any(_message.Message): - __slots__ = ("type_url", "value") - TYPE_URL_FIELD_NUMBER: _ClassVar[int] - VALUE_FIELD_NUMBER: _ClassVar[int] - type_url: str - value: bytes - def __init__(self, type_url: _Optional[str] = ..., value: _Optional[bytes] = ...) -> None: ... - -class RepeatedAny(_message.Message): - __slots__ = ("type_url", "value") - TYPE_URL_FIELD_NUMBER: _ClassVar[int] - VALUE_FIELD_NUMBER: _ClassVar[int] - type_url: str - value: _containers.RepeatedScalarFieldContainer[bytes] - def __init__(self, type_url: _Optional[str] = ..., value: _Optional[_Iterable[bytes]] = ...) -> None: ... - -class Collection(_message.Message): - __slots__ = ("name", "id") - NAME_FIELD_NUMBER: _ClassVar[int] - ID_FIELD_NUMBER: _ClassVar[int] - name: str - id: _id_pb2.ID - def __init__(self, name: _Optional[str] = ..., id: _Optional[_Union[_id_pb2.ID, _Mapping]] = ...) -> None: ... - -class CollectionInfo(_message.Message): - __slots__ = ("collection", "availability", "count") - COLLECTION_FIELD_NUMBER: _ClassVar[int] - AVAILABILITY_FIELD_NUMBER: _ClassVar[int] - COUNT_FIELD_NUMBER: _ClassVar[int] - collection: Collection - availability: _query_pb2.TimeInterval - count: int - def __init__(self, collection: _Optional[_Union[Collection, _Mapping]] = ..., availability: _Optional[_Union[_query_pb2.TimeInterval, _Mapping]] = ..., count: _Optional[int] = ...) -> None: ... - -class Dataset(_message.Message): - __slots__ = ("id", "group_id", "type", "code_name", "name", "summary", "icon", "description", "permissions", "visibility", "slug", "type_editable", "collections") - ID_FIELD_NUMBER: _ClassVar[int] - GROUP_ID_FIELD_NUMBER: _ClassVar[int] - TYPE_FIELD_NUMBER: _ClassVar[int] - CODE_NAME_FIELD_NUMBER: _ClassVar[int] - NAME_FIELD_NUMBER: _ClassVar[int] - SUMMARY_FIELD_NUMBER: _ClassVar[int] - ICON_FIELD_NUMBER: _ClassVar[int] - DESCRIPTION_FIELD_NUMBER: _ClassVar[int] - PERMISSIONS_FIELD_NUMBER: _ClassVar[int] - VISIBILITY_FIELD_NUMBER: _ClassVar[int] - SLUG_FIELD_NUMBER: _ClassVar[int] - TYPE_EDITABLE_FIELD_NUMBER: _ClassVar[int] - COLLECTIONS_FIELD_NUMBER: _ClassVar[int] - id: _id_pb2.ID - group_id: _id_pb2.ID - type: _dataset_type_pb2.AnnotatedType - code_name: str - name: str - summary: str - icon: str - description: str - permissions: _containers.RepeatedScalarFieldContainer[DatasetPermission] - visibility: Visibility - slug: str - type_editable: bool - collections: _containers.RepeatedCompositeFieldContainer[CollectionInfo] - def __init__(self, id: _Optional[_Union[_id_pb2.ID, _Mapping]] = ..., group_id: _Optional[_Union[_id_pb2.ID, _Mapping]] = ..., type: _Optional[_Union[_dataset_type_pb2.AnnotatedType, _Mapping]] = ..., code_name: _Optional[str] = ..., name: _Optional[str] = ..., summary: _Optional[str] = ..., icon: _Optional[str] = ..., description: _Optional[str] = ..., permissions: _Optional[_Iterable[_Union[DatasetPermission, str]]] = ..., visibility: _Optional[_Union[Visibility, str]] = ..., slug: _Optional[str] = ..., type_editable: bool = ..., collections: _Optional[_Iterable[_Union[CollectionInfo, _Mapping]]] = ...) -> None: ... - -class DatasetGroup(_message.Message): - __slots__ = ("id", "parent_id", "code_name", "name", "icon") - ID_FIELD_NUMBER: _ClassVar[int] - PARENT_ID_FIELD_NUMBER: _ClassVar[int] - CODE_NAME_FIELD_NUMBER: _ClassVar[int] - NAME_FIELD_NUMBER: _ClassVar[int] - ICON_FIELD_NUMBER: _ClassVar[int] - id: _id_pb2.ID - parent_id: _id_pb2.ID - code_name: str - name: str - icon: str - def __init__(self, id: _Optional[_Union[_id_pb2.ID, _Mapping]] = ..., parent_id: _Optional[_Union[_id_pb2.ID, _Mapping]] = ..., code_name: _Optional[str] = ..., name: _Optional[str] = ..., icon: _Optional[str] = ...) -> None: ... diff --git a/tilebox-datasets/tilebox/datasets/datasets/v1/core_pb2_grpc.py b/tilebox-datasets/tilebox/datasets/datasets/v1/core_pb2_grpc.py deleted file mode 100644 index 8a93939..0000000 --- a/tilebox-datasets/tilebox/datasets/datasets/v1/core_pb2_grpc.py +++ /dev/null @@ -1,3 +0,0 @@ -# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! -"""Client and server classes corresponding to protobuf-defined services.""" -import grpc diff --git a/tilebox-datasets/tilebox/datasets/datasets/v1/data_access_connect.py b/tilebox-datasets/tilebox/datasets/datasets/v1/data_access_connect.py deleted file mode 100644 index d69233e..0000000 --- a/tilebox-datasets/tilebox/datasets/datasets/v1/data_access_connect.py +++ /dev/null @@ -1,195 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by https://github.com/connectrpc/connect-python. DO NOT EDIT! -# source: datasets/v1/data_access.proto - -from collections.abc import AsyncGenerator, AsyncIterator, Iterable, Iterator, Mapping -from typing import Protocol - -from connectrpc.client import ConnectClient, ConnectClientSync -from connectrpc.code import Code -from connectrpc.codec import Codec -from connectrpc.compression import Compression -from connectrpc.errors import ConnectError -from connectrpc.interceptor import Interceptor, InterceptorSync -from connectrpc.method import IdempotencyLevel, MethodInfo -from connectrpc.request import Headers, RequestContext -from connectrpc.server import ConnectASGIApplication, ConnectWSGIApplication, Endpoint, EndpointSync -import tilebox.datasets.datasets.v1.core_pb2 as datasets_dot_v1_dot_core__pb2 -import tilebox.datasets.datasets.v1.data_access_pb2 as datasets_dot_v1_dot_data__access__pb2 - - -class DataAccessService(Protocol): - async def query_by_i_d(self, request: datasets_dot_v1_dot_data__access__pb2.QueryByIDRequest, ctx: RequestContext) -> datasets_dot_v1_dot_core__pb2.Any: - raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") - - async def query(self, request: datasets_dot_v1_dot_data__access__pb2.QueryRequest, ctx: RequestContext) -> datasets_dot_v1_dot_data__access__pb2.QueryResultPage: - raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") - - -class DataAccessServiceASGIApplication(ConnectASGIApplication[DataAccessService]): - def __init__(self, service: DataAccessService | AsyncGenerator[DataAccessService], *, interceptors: Iterable[Interceptor]=(), read_max_bytes: int | None = None, compressions: Iterable[Compression] | None = None, codecs: Iterable[Codec] | None = None) -> None: - super().__init__( - service=service, - endpoints=lambda svc: { - "/datasets.v1.DataAccessService/QueryByID": Endpoint.unary( - method=MethodInfo( - name="QueryByID", - service_name="datasets.v1.DataAccessService", - input=datasets_dot_v1_dot_data__access__pb2.QueryByIDRequest, - output=datasets_dot_v1_dot_core__pb2.Any, - idempotency_level=IdempotencyLevel.UNKNOWN, - ), - function=svc.query_by_i_d, - ), - "/datasets.v1.DataAccessService/Query": Endpoint.unary( - method=MethodInfo( - name="Query", - service_name="datasets.v1.DataAccessService", - input=datasets_dot_v1_dot_data__access__pb2.QueryRequest, - output=datasets_dot_v1_dot_data__access__pb2.QueryResultPage, - idempotency_level=IdempotencyLevel.UNKNOWN, - ), - function=svc.query, - ), - }, - interceptors=interceptors, - read_max_bytes=read_max_bytes, - compressions=compressions, - codecs=codecs, - ) - - @property - def path(self) -> str: - """Returns the URL path to mount the application to when serving multiple applications.""" - return "/datasets.v1.DataAccessService" - - -class DataAccessServiceClient(ConnectClient): - async def query_by_i_d( - self, - request: datasets_dot_v1_dot_data__access__pb2.QueryByIDRequest, - *, - headers: Headers | Mapping[str, str] | None = None, - timeout_ms: int | None = None, - ) -> datasets_dot_v1_dot_core__pb2.Any: - return await self.execute_unary( - request=request, - method=MethodInfo( - name="QueryByID", - service_name="datasets.v1.DataAccessService", - input=datasets_dot_v1_dot_data__access__pb2.QueryByIDRequest, - output=datasets_dot_v1_dot_core__pb2.Any, - idempotency_level=IdempotencyLevel.UNKNOWN, - ), - headers=headers, - timeout_ms=timeout_ms, - ) - - async def query( - self, - request: datasets_dot_v1_dot_data__access__pb2.QueryRequest, - *, - headers: Headers | Mapping[str, str] | None = None, - timeout_ms: int | None = None, - ) -> datasets_dot_v1_dot_data__access__pb2.QueryResultPage: - return await self.execute_unary( - request=request, - method=MethodInfo( - name="Query", - service_name="datasets.v1.DataAccessService", - input=datasets_dot_v1_dot_data__access__pb2.QueryRequest, - output=datasets_dot_v1_dot_data__access__pb2.QueryResultPage, - idempotency_level=IdempotencyLevel.UNKNOWN, - ), - headers=headers, - timeout_ms=timeout_ms, - ) - - - - - -class DataAccessServiceSync(Protocol): - def query_by_i_d(self, request: datasets_dot_v1_dot_data__access__pb2.QueryByIDRequest, ctx: RequestContext) -> datasets_dot_v1_dot_core__pb2.Any: - raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") - def query(self, request: datasets_dot_v1_dot_data__access__pb2.QueryRequest, ctx: RequestContext) -> datasets_dot_v1_dot_data__access__pb2.QueryResultPage: - raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") - - -class DataAccessServiceWSGIApplication(ConnectWSGIApplication): - def __init__(self, service: DataAccessServiceSync, interceptors: Iterable[InterceptorSync]=(), read_max_bytes: int | None = None, compressions: Iterable[Compression] | None = None, codecs: Iterable[Codec] | None = None) -> None: - super().__init__( - endpoints={ - "/datasets.v1.DataAccessService/QueryByID": EndpointSync.unary( - method=MethodInfo( - name="QueryByID", - service_name="datasets.v1.DataAccessService", - input=datasets_dot_v1_dot_data__access__pb2.QueryByIDRequest, - output=datasets_dot_v1_dot_core__pb2.Any, - idempotency_level=IdempotencyLevel.UNKNOWN, - ), - function=service.query_by_i_d, - ), - "/datasets.v1.DataAccessService/Query": EndpointSync.unary( - method=MethodInfo( - name="Query", - service_name="datasets.v1.DataAccessService", - input=datasets_dot_v1_dot_data__access__pb2.QueryRequest, - output=datasets_dot_v1_dot_data__access__pb2.QueryResultPage, - idempotency_level=IdempotencyLevel.UNKNOWN, - ), - function=service.query, - ), - }, - interceptors=interceptors, - read_max_bytes=read_max_bytes, - compressions=compressions, - codecs=codecs, - ) - - @property - def path(self) -> str: - """Returns the URL path to mount the application to when serving multiple applications.""" - return "/datasets.v1.DataAccessService" - - -class DataAccessServiceClientSync(ConnectClientSync): - def query_by_i_d( - self, - request: datasets_dot_v1_dot_data__access__pb2.QueryByIDRequest, - *, - headers: Headers | Mapping[str, str] | None = None, - timeout_ms: int | None = None, - ) -> datasets_dot_v1_dot_core__pb2.Any: - return self.execute_unary( - request=request, - method=MethodInfo( - name="QueryByID", - service_name="datasets.v1.DataAccessService", - input=datasets_dot_v1_dot_data__access__pb2.QueryByIDRequest, - output=datasets_dot_v1_dot_core__pb2.Any, - idempotency_level=IdempotencyLevel.UNKNOWN, - ), - headers=headers, - timeout_ms=timeout_ms, - ) - - def query( - self, - request: datasets_dot_v1_dot_data__access__pb2.QueryRequest, - *, - headers: Headers | Mapping[str, str] | None = None, - timeout_ms: int | None = None, - ) -> datasets_dot_v1_dot_data__access__pb2.QueryResultPage: - return self.execute_unary( - request=request, - method=MethodInfo( - name="Query", - service_name="datasets.v1.DataAccessService", - input=datasets_dot_v1_dot_data__access__pb2.QueryRequest, - output=datasets_dot_v1_dot_data__access__pb2.QueryResultPage, - idempotency_level=IdempotencyLevel.UNKNOWN, - ), - headers=headers, - timeout_ms=timeout_ms, - ) diff --git a/tilebox-datasets/tilebox/datasets/datasets/v1/data_access_pb2.py b/tilebox-datasets/tilebox/datasets/datasets/v1/data_access_pb2.py deleted file mode 100644 index 99a5475..0000000 --- a/tilebox-datasets/tilebox/datasets/datasets/v1/data_access_pb2.py +++ /dev/null @@ -1,89 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by the protocol buffer compiler. DO NOT EDIT! -# NO CHECKED-IN PROTOBUF GENCODE -# source: datasets/v1/data_access.proto -# Protobuf Python Version: 6.30.0 -"""Generated protocol buffer code.""" -from google.protobuf import descriptor as _descriptor -from google.protobuf import descriptor_pool as _descriptor_pool -from google.protobuf import runtime_version as _runtime_version -from google.protobuf import symbol_database as _symbol_database -from google.protobuf.internal import builder as _builder -_runtime_version.ValidateProtobufRuntimeVersion( - _runtime_version.Domain.PUBLIC, - 6, - 30, - 0, - '', - 'datasets/v1/data_access.proto' -) -# @@protoc_insertion_point(imports) - -_sym_db = _symbol_database.Default() - - -from tilebox.datasets.datasets.v1 import core_pb2 as datasets_dot_v1_dot_core__pb2 -from tilebox.datasets.datasets.v1 import well_known_types_pb2 as datasets_dot_v1_dot_well__known__types__pb2 -from google.protobuf import duration_pb2 as google_dot_protobuf_dot_duration__pb2 -from google.protobuf import timestamp_pb2 as google_dot_protobuf_dot_timestamp__pb2 -from tilebox.datasets.tilebox.v1 import id_pb2 as tilebox_dot_v1_dot_id__pb2 -from tilebox.datasets.tilebox.v1 import query_pb2 as tilebox_dot_v1_dot_query__pb2 - - -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1d\x64\x61tasets/v1/data_access.proto\x12\x0b\x64\x61tasets.v1\x1a\x16\x64\x61tasets/v1/core.proto\x1a\"datasets/v1/well_known_types.proto\x1a\x1egoogle/protobuf/duration.proto\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\x13tilebox/v1/id.proto\x1a\x16tilebox/v1/query.proto\"\xb5\x01\n\x10QueryByIDRequest\x12-\n\ndataset_id\x18\x04 \x01(\x0b\x32\x0e.tilebox.v1.IDR\tdatasetId\x12\x35\n\x0e\x63ollection_ids\x18\x01 \x03(\x0b\x32\x0e.tilebox.v1.IDR\rcollectionIds\x12\x1e\n\x02id\x18\x02 \x01(\x0b\x32\x0e.tilebox.v1.IDR\x02id\x12\x1b\n\tskip_data\x18\x03 \x01(\x08R\x08skipData\"\x98\x02\n\x0cQueryFilters\x12=\n\rtime_interval\x18\x01 \x01(\x0b\x32\x18.tilebox.v1.TimeIntervalR\x0ctimeInterval\x12\x45\n\x12\x64\x61tapoint_interval\x18\x02 \x01(\x0b\x32\x16.tilebox.v1.IDIntervalR\x11\x64\x61tapointInterval\x12\x41\n\x0espatial_extent\x18\x03 \x01(\x0b\x32\x1a.datasets.v1.SpatialFilterR\rspatialExtent\x12?\n\x0b\x65xpressions\x18\x04 \x03(\x0b\x32\x1d.datasets.v1.FilterExpressionR\x0b\x65xpressions\"\xc0\x01\n\x10\x46ilterExpression\x12\x38\n\x07logical\x18\x01 \x01(\x0b\x32\x1e.datasets.v1.LogicalExpressionR\x07logical\x12<\n\ncomparison\x18\x02 \x01(\x0b\x32\x1c.datasets.v1.FieldComparisonR\ncomparison\x12\x34\n\x07is_null\x18\x03 \x01(\x0b\x32\x1b.datasets.v1.FieldNullCheckR\x06isNull\"\x88\x01\n\x11LogicalExpression\x12\x38\n\x08operator\x18\x01 \x01(\x0e\x32\x1c.datasets.v1.LogicalOperatorR\x08operator\x12\x39\n\x08operands\x18\x02 \x03(\x0b\x32\x1d.datasets.v1.FilterExpressionR\x08operands\"\xa6\x01\n\x0f\x46ieldComparison\x12\x1d\n\nfield_name\x18\x01 \x01(\tR\tfieldName\x12@\n\x08operator\x18\x02 \x01(\x0e\x32$.datasets.v1.FieldComparisonOperatorR\x08operator\x12\x32\n\x05value\x18\x03 \x01(\x0b\x32\x1c.datasets.v1.FieldQueryValueR\x05value\"/\n\x0e\x46ieldNullCheck\x12\x1d\n\nfield_name\x18\x01 \x01(\tR\tfieldName\"\xb0\x03\n\x0f\x46ieldQueryValue\x12$\n\nbool_value\x18\x01 \x01(\x08\x42\x05\xaa\x01\x02\x08\x01R\tboolValue\x12&\n\x0bint64_value\x18\x02 \x01(\x03\x42\x05\xaa\x01\x02\x08\x01R\nint64Value\x12(\n\x0cuint64_value\x18\x03 \x01(\x04\x42\x05\xaa\x01\x02\x08\x01R\x0buint64Value\x12(\n\x0c\x64ouble_value\x18\x04 \x01(\x01\x42\x05\xaa\x01\x02\x08\x01R\x0b\x64oubleValue\x12(\n\x0cstring_value\x18\x05 \x01(\tB\x05\xaa\x01\x02\x08\x01R\x0bstringValue\x12\x43\n\x0ftimestamp_value\x18\x06 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\x0etimestampValue\x12@\n\x0e\x64uration_value\x18\x07 \x01(\x0b\x32\x19.google.protobuf.DurationR\rdurationValue\x12\"\n\tenum_name\x18\x08 \x01(\tB\x05\xaa\x01\x02\x08\x01R\x08\x65numName\x12&\n\x0b\x62ytes_value\x18\t \x01(\x0c\x42\x05\xaa\x01\x02\x08\x01R\nbytesValue\"\xc9\x01\n\rSpatialFilter\x12\x31\n\x08geometry\x18\x01 \x01(\x0b\x32\x15.datasets.v1.GeometryR\x08geometry\x12\x32\n\x04mode\x18\x02 \x01(\x0e\x32\x1e.datasets.v1.SpatialFilterModeR\x04mode\x12Q\n\x11\x63oordinate_system\x18\x03 \x01(\x0e\x32$.datasets.v1.SpatialCoordinateSystemR\x10\x63oordinateSystem\"\xf9\x01\n\x0cQueryRequest\x12-\n\ndataset_id\x18\x05 \x01(\x0b\x32\x0e.tilebox.v1.IDR\tdatasetId\x12\x35\n\x0e\x63ollection_ids\x18\x01 \x03(\x0b\x32\x0e.tilebox.v1.IDR\rcollectionIds\x12\x33\n\x07\x66ilters\x18\x02 \x01(\x0b\x32\x19.datasets.v1.QueryFiltersR\x07\x66ilters\x12\x31\n\x04page\x18\x03 \x01(\x0b\x32\x16.tilebox.v1.PaginationB\x05\xaa\x01\x02\x08\x01R\x04page\x12\x1b\n\tskip_data\x18\x04 \x01(\x08R\x08skipData\"{\n\x0fQueryResultPage\x12,\n\x04\x64\x61ta\x18\x01 \x01(\x0b\x32\x18.datasets.v1.RepeatedAnyR\x04\x64\x61ta\x12:\n\tnext_page\x18\x02 \x01(\x0b\x32\x16.tilebox.v1.PaginationB\x05\xaa\x01\x02\x08\x01R\x08nextPage*\x80\x01\n\x0fLogicalOperator\x12 \n\x1cLOGICAL_OPERATOR_UNSPECIFIED\x10\x00\x12\x18\n\x14LOGICAL_OPERATOR_AND\x10\x01\x12\x17\n\x13LOGICAL_OPERATOR_OR\x10\x02\x12\x18\n\x14LOGICAL_OPERATOR_NOT\x10\x03*\xce\x02\n\x17\x46ieldComparisonOperator\x12)\n%FIELD_COMPARISON_OPERATOR_UNSPECIFIED\x10\x00\x12#\n\x1f\x46IELD_COMPARISON_OPERATOR_EQUAL\x10\x01\x12\'\n#FIELD_COMPARISON_OPERATOR_NOT_EQUAL\x10\x02\x12\'\n#FIELD_COMPARISON_OPERATOR_LESS_THAN\x10\x03\x12\x30\n,FIELD_COMPARISON_OPERATOR_LESS_THAN_OR_EQUAL\x10\x04\x12*\n&FIELD_COMPARISON_OPERATOR_GREATER_THAN\x10\x05\x12\x33\n/FIELD_COMPARISON_OPERATOR_GREATER_THAN_OR_EQUAL\x10\x06*\xc0\x01\n\x11SpatialFilterMode\x12#\n\x1fSPATIAL_FILTER_MODE_UNSPECIFIED\x10\x00\x12\"\n\x1eSPATIAL_FILTER_MODE_INTERSECTS\x10\x01\x12\x30\n,SPATIAL_FILTER_MODE_FILTER_CONTAINS_GEOMETRY\x10\x02\x12\x30\n,SPATIAL_FILTER_MODE_GEOMETRY_CONTAINS_FILTER\x10\x03*\x96\x01\n\x17SpatialCoordinateSystem\x12)\n%SPATIAL_COORDINATE_SYSTEM_UNSPECIFIED\x10\x00\x12\'\n#SPATIAL_COORDINATE_SYSTEM_CARTESIAN\x10\x01\x12\'\n#SPATIAL_COORDINATE_SYSTEM_SPHERICAL\x10\x02\x32\x93\x01\n\x11\x44\x61taAccessService\x12<\n\tQueryByID\x12\x1d.datasets.v1.QueryByIDRequest\x1a\x10.datasets.v1.Any\x12@\n\x05Query\x12\x19.datasets.v1.QueryRequest\x1a\x1c.datasets.v1.QueryResultPageBt\n\x0f\x63om.datasets.v1B\x0f\x44\x61taAccessProtoP\x01\xa2\x02\x03\x44XX\xaa\x02\x0b\x44\x61tasets.V1\xca\x02\x0b\x44\x61tasets\\V1\xe2\x02\x17\x44\x61tasets\\V1\\GPBMetadata\xea\x02\x0c\x44\x61tasets::V1\x92\x03\x02\x08\x02\x62\x08\x65\x64itionsp\xe8\x07') - -_globals = globals() -_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) -_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'datasets.v1.data_access_pb2', _globals) -if not _descriptor._USE_C_DESCRIPTORS: - _globals['DESCRIPTOR']._loaded_options = None - _globals['DESCRIPTOR']._serialized_options = b'\n\017com.datasets.v1B\017DataAccessProtoP\001\242\002\003DXX\252\002\013Datasets.V1\312\002\013Datasets\\V1\342\002\027Datasets\\V1\\GPBMetadata\352\002\014Datasets::V1\222\003\002\010\002' - _globals['_FIELDQUERYVALUE'].fields_by_name['bool_value']._loaded_options = None - _globals['_FIELDQUERYVALUE'].fields_by_name['bool_value']._serialized_options = b'\252\001\002\010\001' - _globals['_FIELDQUERYVALUE'].fields_by_name['int64_value']._loaded_options = None - _globals['_FIELDQUERYVALUE'].fields_by_name['int64_value']._serialized_options = b'\252\001\002\010\001' - _globals['_FIELDQUERYVALUE'].fields_by_name['uint64_value']._loaded_options = None - _globals['_FIELDQUERYVALUE'].fields_by_name['uint64_value']._serialized_options = b'\252\001\002\010\001' - _globals['_FIELDQUERYVALUE'].fields_by_name['double_value']._loaded_options = None - _globals['_FIELDQUERYVALUE'].fields_by_name['double_value']._serialized_options = b'\252\001\002\010\001' - _globals['_FIELDQUERYVALUE'].fields_by_name['string_value']._loaded_options = None - _globals['_FIELDQUERYVALUE'].fields_by_name['string_value']._serialized_options = b'\252\001\002\010\001' - _globals['_FIELDQUERYVALUE'].fields_by_name['enum_name']._loaded_options = None - _globals['_FIELDQUERYVALUE'].fields_by_name['enum_name']._serialized_options = b'\252\001\002\010\001' - _globals['_FIELDQUERYVALUE'].fields_by_name['bytes_value']._loaded_options = None - _globals['_FIELDQUERYVALUE'].fields_by_name['bytes_value']._serialized_options = b'\252\001\002\010\001' - _globals['_QUERYREQUEST'].fields_by_name['page']._loaded_options = None - _globals['_QUERYREQUEST'].fields_by_name['page']._serialized_options = b'\252\001\002\010\001' - _globals['_QUERYRESULTPAGE'].fields_by_name['next_page']._loaded_options = None - _globals['_QUERYRESULTPAGE'].fields_by_name['next_page']._serialized_options = b'\252\001\002\010\001' - _globals['_LOGICALOPERATOR']._serialized_start=2252 - _globals['_LOGICALOPERATOR']._serialized_end=2380 - _globals['_FIELDCOMPARISONOPERATOR']._serialized_start=2383 - _globals['_FIELDCOMPARISONOPERATOR']._serialized_end=2717 - _globals['_SPATIALFILTERMODE']._serialized_start=2720 - _globals['_SPATIALFILTERMODE']._serialized_end=2912 - _globals['_SPATIALCOORDINATESYSTEM']._serialized_start=2915 - _globals['_SPATIALCOORDINATESYSTEM']._serialized_end=3065 - _globals['_QUERYBYIDREQUEST']._serialized_start=217 - _globals['_QUERYBYIDREQUEST']._serialized_end=398 - _globals['_QUERYFILTERS']._serialized_start=401 - _globals['_QUERYFILTERS']._serialized_end=681 - _globals['_FILTEREXPRESSION']._serialized_start=684 - _globals['_FILTEREXPRESSION']._serialized_end=876 - _globals['_LOGICALEXPRESSION']._serialized_start=879 - _globals['_LOGICALEXPRESSION']._serialized_end=1015 - _globals['_FIELDCOMPARISON']._serialized_start=1018 - _globals['_FIELDCOMPARISON']._serialized_end=1184 - _globals['_FIELDNULLCHECK']._serialized_start=1186 - _globals['_FIELDNULLCHECK']._serialized_end=1233 - _globals['_FIELDQUERYVALUE']._serialized_start=1236 - _globals['_FIELDQUERYVALUE']._serialized_end=1668 - _globals['_SPATIALFILTER']._serialized_start=1671 - _globals['_SPATIALFILTER']._serialized_end=1872 - _globals['_QUERYREQUEST']._serialized_start=1875 - _globals['_QUERYREQUEST']._serialized_end=2124 - _globals['_QUERYRESULTPAGE']._serialized_start=2126 - _globals['_QUERYRESULTPAGE']._serialized_end=2249 - _globals['_DATAACCESSSERVICE']._serialized_start=3068 - _globals['_DATAACCESSSERVICE']._serialized_end=3215 -# @@protoc_insertion_point(module_scope) diff --git a/tilebox-datasets/tilebox/datasets/datasets/v1/data_access_pb2.pyi b/tilebox-datasets/tilebox/datasets/datasets/v1/data_access_pb2.pyi deleted file mode 100644 index 393d435..0000000 --- a/tilebox-datasets/tilebox/datasets/datasets/v1/data_access_pb2.pyi +++ /dev/null @@ -1,174 +0,0 @@ -from tilebox.datasets.datasets.v1 import core_pb2 as _core_pb2 -from tilebox.datasets.datasets.v1 import well_known_types_pb2 as _well_known_types_pb2 -from google.protobuf import duration_pb2 as _duration_pb2 -from google.protobuf import timestamp_pb2 as _timestamp_pb2 -from tilebox.datasets.tilebox.v1 import id_pb2 as _id_pb2 -from tilebox.datasets.tilebox.v1 import query_pb2 as _query_pb2 -from google.protobuf.internal import containers as _containers -from google.protobuf.internal import enum_type_wrapper as _enum_type_wrapper -from google.protobuf import descriptor as _descriptor -from google.protobuf import message as _message -from collections.abc import Iterable as _Iterable, Mapping as _Mapping -from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union - -DESCRIPTOR: _descriptor.FileDescriptor - -class LogicalOperator(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): - __slots__ = () - LOGICAL_OPERATOR_UNSPECIFIED: _ClassVar[LogicalOperator] - LOGICAL_OPERATOR_AND: _ClassVar[LogicalOperator] - LOGICAL_OPERATOR_OR: _ClassVar[LogicalOperator] - LOGICAL_OPERATOR_NOT: _ClassVar[LogicalOperator] - -class FieldComparisonOperator(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): - __slots__ = () - FIELD_COMPARISON_OPERATOR_UNSPECIFIED: _ClassVar[FieldComparisonOperator] - FIELD_COMPARISON_OPERATOR_EQUAL: _ClassVar[FieldComparisonOperator] - FIELD_COMPARISON_OPERATOR_NOT_EQUAL: _ClassVar[FieldComparisonOperator] - FIELD_COMPARISON_OPERATOR_LESS_THAN: _ClassVar[FieldComparisonOperator] - FIELD_COMPARISON_OPERATOR_LESS_THAN_OR_EQUAL: _ClassVar[FieldComparisonOperator] - FIELD_COMPARISON_OPERATOR_GREATER_THAN: _ClassVar[FieldComparisonOperator] - FIELD_COMPARISON_OPERATOR_GREATER_THAN_OR_EQUAL: _ClassVar[FieldComparisonOperator] - -class SpatialFilterMode(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): - __slots__ = () - SPATIAL_FILTER_MODE_UNSPECIFIED: _ClassVar[SpatialFilterMode] - SPATIAL_FILTER_MODE_INTERSECTS: _ClassVar[SpatialFilterMode] - SPATIAL_FILTER_MODE_FILTER_CONTAINS_GEOMETRY: _ClassVar[SpatialFilterMode] - SPATIAL_FILTER_MODE_GEOMETRY_CONTAINS_FILTER: _ClassVar[SpatialFilterMode] - -class SpatialCoordinateSystem(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): - __slots__ = () - SPATIAL_COORDINATE_SYSTEM_UNSPECIFIED: _ClassVar[SpatialCoordinateSystem] - SPATIAL_COORDINATE_SYSTEM_CARTESIAN: _ClassVar[SpatialCoordinateSystem] - SPATIAL_COORDINATE_SYSTEM_SPHERICAL: _ClassVar[SpatialCoordinateSystem] -LOGICAL_OPERATOR_UNSPECIFIED: LogicalOperator -LOGICAL_OPERATOR_AND: LogicalOperator -LOGICAL_OPERATOR_OR: LogicalOperator -LOGICAL_OPERATOR_NOT: LogicalOperator -FIELD_COMPARISON_OPERATOR_UNSPECIFIED: FieldComparisonOperator -FIELD_COMPARISON_OPERATOR_EQUAL: FieldComparisonOperator -FIELD_COMPARISON_OPERATOR_NOT_EQUAL: FieldComparisonOperator -FIELD_COMPARISON_OPERATOR_LESS_THAN: FieldComparisonOperator -FIELD_COMPARISON_OPERATOR_LESS_THAN_OR_EQUAL: FieldComparisonOperator -FIELD_COMPARISON_OPERATOR_GREATER_THAN: FieldComparisonOperator -FIELD_COMPARISON_OPERATOR_GREATER_THAN_OR_EQUAL: FieldComparisonOperator -SPATIAL_FILTER_MODE_UNSPECIFIED: SpatialFilterMode -SPATIAL_FILTER_MODE_INTERSECTS: SpatialFilterMode -SPATIAL_FILTER_MODE_FILTER_CONTAINS_GEOMETRY: SpatialFilterMode -SPATIAL_FILTER_MODE_GEOMETRY_CONTAINS_FILTER: SpatialFilterMode -SPATIAL_COORDINATE_SYSTEM_UNSPECIFIED: SpatialCoordinateSystem -SPATIAL_COORDINATE_SYSTEM_CARTESIAN: SpatialCoordinateSystem -SPATIAL_COORDINATE_SYSTEM_SPHERICAL: SpatialCoordinateSystem - -class QueryByIDRequest(_message.Message): - __slots__ = ("dataset_id", "collection_ids", "id", "skip_data") - DATASET_ID_FIELD_NUMBER: _ClassVar[int] - COLLECTION_IDS_FIELD_NUMBER: _ClassVar[int] - ID_FIELD_NUMBER: _ClassVar[int] - SKIP_DATA_FIELD_NUMBER: _ClassVar[int] - dataset_id: _id_pb2.ID - collection_ids: _containers.RepeatedCompositeFieldContainer[_id_pb2.ID] - id: _id_pb2.ID - skip_data: bool - def __init__(self, dataset_id: _Optional[_Union[_id_pb2.ID, _Mapping]] = ..., collection_ids: _Optional[_Iterable[_Union[_id_pb2.ID, _Mapping]]] = ..., id: _Optional[_Union[_id_pb2.ID, _Mapping]] = ..., skip_data: bool = ...) -> None: ... - -class QueryFilters(_message.Message): - __slots__ = ("time_interval", "datapoint_interval", "spatial_extent", "expressions") - TIME_INTERVAL_FIELD_NUMBER: _ClassVar[int] - DATAPOINT_INTERVAL_FIELD_NUMBER: _ClassVar[int] - SPATIAL_EXTENT_FIELD_NUMBER: _ClassVar[int] - EXPRESSIONS_FIELD_NUMBER: _ClassVar[int] - time_interval: _query_pb2.TimeInterval - datapoint_interval: _query_pb2.IDInterval - spatial_extent: SpatialFilter - expressions: _containers.RepeatedCompositeFieldContainer[FilterExpression] - def __init__(self, time_interval: _Optional[_Union[_query_pb2.TimeInterval, _Mapping]] = ..., datapoint_interval: _Optional[_Union[_query_pb2.IDInterval, _Mapping]] = ..., spatial_extent: _Optional[_Union[SpatialFilter, _Mapping]] = ..., expressions: _Optional[_Iterable[_Union[FilterExpression, _Mapping]]] = ...) -> None: ... - -class FilterExpression(_message.Message): - __slots__ = ("logical", "comparison", "is_null") - LOGICAL_FIELD_NUMBER: _ClassVar[int] - COMPARISON_FIELD_NUMBER: _ClassVar[int] - IS_NULL_FIELD_NUMBER: _ClassVar[int] - logical: LogicalExpression - comparison: FieldComparison - is_null: FieldNullCheck - def __init__(self, logical: _Optional[_Union[LogicalExpression, _Mapping]] = ..., comparison: _Optional[_Union[FieldComparison, _Mapping]] = ..., is_null: _Optional[_Union[FieldNullCheck, _Mapping]] = ...) -> None: ... - -class LogicalExpression(_message.Message): - __slots__ = ("operator", "operands") - OPERATOR_FIELD_NUMBER: _ClassVar[int] - OPERANDS_FIELD_NUMBER: _ClassVar[int] - operator: LogicalOperator - operands: _containers.RepeatedCompositeFieldContainer[FilterExpression] - def __init__(self, operator: _Optional[_Union[LogicalOperator, str]] = ..., operands: _Optional[_Iterable[_Union[FilterExpression, _Mapping]]] = ...) -> None: ... - -class FieldComparison(_message.Message): - __slots__ = ("field_name", "operator", "value") - FIELD_NAME_FIELD_NUMBER: _ClassVar[int] - OPERATOR_FIELD_NUMBER: _ClassVar[int] - VALUE_FIELD_NUMBER: _ClassVar[int] - field_name: str - operator: FieldComparisonOperator - value: FieldQueryValue - def __init__(self, field_name: _Optional[str] = ..., operator: _Optional[_Union[FieldComparisonOperator, str]] = ..., value: _Optional[_Union[FieldQueryValue, _Mapping]] = ...) -> None: ... - -class FieldNullCheck(_message.Message): - __slots__ = ("field_name",) - FIELD_NAME_FIELD_NUMBER: _ClassVar[int] - field_name: str - def __init__(self, field_name: _Optional[str] = ...) -> None: ... - -class FieldQueryValue(_message.Message): - __slots__ = ("bool_value", "int64_value", "uint64_value", "double_value", "string_value", "timestamp_value", "duration_value", "enum_name", "bytes_value") - BOOL_VALUE_FIELD_NUMBER: _ClassVar[int] - INT64_VALUE_FIELD_NUMBER: _ClassVar[int] - UINT64_VALUE_FIELD_NUMBER: _ClassVar[int] - DOUBLE_VALUE_FIELD_NUMBER: _ClassVar[int] - STRING_VALUE_FIELD_NUMBER: _ClassVar[int] - TIMESTAMP_VALUE_FIELD_NUMBER: _ClassVar[int] - DURATION_VALUE_FIELD_NUMBER: _ClassVar[int] - ENUM_NAME_FIELD_NUMBER: _ClassVar[int] - BYTES_VALUE_FIELD_NUMBER: _ClassVar[int] - bool_value: bool - int64_value: int - uint64_value: int - double_value: float - string_value: str - timestamp_value: _timestamp_pb2.Timestamp - duration_value: _duration_pb2.Duration - enum_name: str - bytes_value: bytes - def __init__(self, bool_value: bool = ..., int64_value: _Optional[int] = ..., uint64_value: _Optional[int] = ..., double_value: _Optional[float] = ..., string_value: _Optional[str] = ..., timestamp_value: _Optional[_Union[_timestamp_pb2.Timestamp, _Mapping]] = ..., duration_value: _Optional[_Union[_duration_pb2.Duration, _Mapping]] = ..., enum_name: _Optional[str] = ..., bytes_value: _Optional[bytes] = ...) -> None: ... - -class SpatialFilter(_message.Message): - __slots__ = ("geometry", "mode", "coordinate_system") - GEOMETRY_FIELD_NUMBER: _ClassVar[int] - MODE_FIELD_NUMBER: _ClassVar[int] - COORDINATE_SYSTEM_FIELD_NUMBER: _ClassVar[int] - geometry: _well_known_types_pb2.Geometry - mode: SpatialFilterMode - coordinate_system: SpatialCoordinateSystem - def __init__(self, geometry: _Optional[_Union[_well_known_types_pb2.Geometry, _Mapping]] = ..., mode: _Optional[_Union[SpatialFilterMode, str]] = ..., coordinate_system: _Optional[_Union[SpatialCoordinateSystem, str]] = ...) -> None: ... - -class QueryRequest(_message.Message): - __slots__ = ("dataset_id", "collection_ids", "filters", "page", "skip_data") - DATASET_ID_FIELD_NUMBER: _ClassVar[int] - COLLECTION_IDS_FIELD_NUMBER: _ClassVar[int] - FILTERS_FIELD_NUMBER: _ClassVar[int] - PAGE_FIELD_NUMBER: _ClassVar[int] - SKIP_DATA_FIELD_NUMBER: _ClassVar[int] - dataset_id: _id_pb2.ID - collection_ids: _containers.RepeatedCompositeFieldContainer[_id_pb2.ID] - filters: QueryFilters - page: _query_pb2.Pagination - skip_data: bool - def __init__(self, dataset_id: _Optional[_Union[_id_pb2.ID, _Mapping]] = ..., collection_ids: _Optional[_Iterable[_Union[_id_pb2.ID, _Mapping]]] = ..., filters: _Optional[_Union[QueryFilters, _Mapping]] = ..., page: _Optional[_Union[_query_pb2.Pagination, _Mapping]] = ..., skip_data: bool = ...) -> None: ... - -class QueryResultPage(_message.Message): - __slots__ = ("data", "next_page") - DATA_FIELD_NUMBER: _ClassVar[int] - NEXT_PAGE_FIELD_NUMBER: _ClassVar[int] - data: _core_pb2.RepeatedAny - next_page: _query_pb2.Pagination - def __init__(self, data: _Optional[_Union[_core_pb2.RepeatedAny, _Mapping]] = ..., next_page: _Optional[_Union[_query_pb2.Pagination, _Mapping]] = ...) -> None: ... diff --git a/tilebox-datasets/tilebox/datasets/datasets/v1/data_access_pb2_grpc.py b/tilebox-datasets/tilebox/datasets/datasets/v1/data_access_pb2_grpc.py deleted file mode 100644 index 016cd82..0000000 --- a/tilebox-datasets/tilebox/datasets/datasets/v1/data_access_pb2_grpc.py +++ /dev/null @@ -1,126 +0,0 @@ -# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! -"""Client and server classes corresponding to protobuf-defined services.""" -import grpc - -from tilebox.datasets.datasets.v1 import core_pb2 as datasets_dot_v1_dot_core__pb2 -from tilebox.datasets.datasets.v1 import data_access_pb2 as datasets_dot_v1_dot_data__access__pb2 - - -class DataAccessServiceStub: - """DataAccessService provides data access and querying capabilities for Tilebox datasets. - """ - - def __init__(self, channel): - """Constructor. - - Args: - channel: A grpc.Channel. - """ - self.QueryByID = channel.unary_unary( - '/datasets.v1.DataAccessService/QueryByID', - request_serializer=datasets_dot_v1_dot_data__access__pb2.QueryByIDRequest.SerializeToString, - response_deserializer=datasets_dot_v1_dot_core__pb2.Any.FromString, - _registered_method=True) - self.Query = channel.unary_unary( - '/datasets.v1.DataAccessService/Query', - request_serializer=datasets_dot_v1_dot_data__access__pb2.QueryRequest.SerializeToString, - response_deserializer=datasets_dot_v1_dot_data__access__pb2.QueryResultPage.FromString, - _registered_method=True) - - -class DataAccessServiceServicer: - """DataAccessService provides data access and querying capabilities for Tilebox datasets. - """ - - def QueryByID(self, request, context): - """QueryByID returns a single data point by its ID. - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def Query(self, request, context): - """Query returns a list of data points matching the given query filters. - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - -def add_DataAccessServiceServicer_to_server(servicer, server): - rpc_method_handlers = { - 'QueryByID': grpc.unary_unary_rpc_method_handler( - servicer.QueryByID, - request_deserializer=datasets_dot_v1_dot_data__access__pb2.QueryByIDRequest.FromString, - response_serializer=datasets_dot_v1_dot_core__pb2.Any.SerializeToString, - ), - 'Query': grpc.unary_unary_rpc_method_handler( - servicer.Query, - request_deserializer=datasets_dot_v1_dot_data__access__pb2.QueryRequest.FromString, - response_serializer=datasets_dot_v1_dot_data__access__pb2.QueryResultPage.SerializeToString, - ), - } - generic_handler = grpc.method_handlers_generic_handler( - 'datasets.v1.DataAccessService', rpc_method_handlers) - server.add_generic_rpc_handlers((generic_handler,)) - server.add_registered_method_handlers('datasets.v1.DataAccessService', rpc_method_handlers) - - - # This class is part of an EXPERIMENTAL API. -class DataAccessService: - """DataAccessService provides data access and querying capabilities for Tilebox datasets. - """ - - @staticmethod - def QueryByID(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary( - request, - target, - '/datasets.v1.DataAccessService/QueryByID', - datasets_dot_v1_dot_data__access__pb2.QueryByIDRequest.SerializeToString, - datasets_dot_v1_dot_core__pb2.Any.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) - - @staticmethod - def Query(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary( - request, - target, - '/datasets.v1.DataAccessService/Query', - datasets_dot_v1_dot_data__access__pb2.QueryRequest.SerializeToString, - datasets_dot_v1_dot_data__access__pb2.QueryResultPage.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) diff --git a/tilebox-datasets/tilebox/datasets/datasets/v1/data_ingestion_pb2.py b/tilebox-datasets/tilebox/datasets/datasets/v1/data_ingestion_pb2.py deleted file mode 100644 index 6147f61..0000000 --- a/tilebox-datasets/tilebox/datasets/datasets/v1/data_ingestion_pb2.py +++ /dev/null @@ -1,46 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by the protocol buffer compiler. DO NOT EDIT! -# NO CHECKED-IN PROTOBUF GENCODE -# source: datasets/v1/data_ingestion.proto -# Protobuf Python Version: 6.30.0 -"""Generated protocol buffer code.""" -from google.protobuf import descriptor as _descriptor -from google.protobuf import descriptor_pool as _descriptor_pool -from google.protobuf import runtime_version as _runtime_version -from google.protobuf import symbol_database as _symbol_database -from google.protobuf.internal import builder as _builder -_runtime_version.ValidateProtobufRuntimeVersion( - _runtime_version.Domain.PUBLIC, - 6, - 30, - 0, - '', - 'datasets/v1/data_ingestion.proto' -) -# @@protoc_insertion_point(imports) - -_sym_db = _symbol_database.Default() - - -from tilebox.datasets.tilebox.v1 import id_pb2 as tilebox_dot_v1_dot_id__pb2 - - -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n datasets/v1/data_ingestion.proto\x12\x0b\x64\x61tasets.v1\x1a\x13tilebox/v1/id.proto\"\x83\x01\n\rIngestRequest\x12\x33\n\rcollection_id\x18\x01 \x01(\x0b\x32\x0e.tilebox.v1.IDR\x0c\x63ollectionId\x12\x16\n\x06values\x18\x02 \x03(\x0cR\x06values\x12%\n\x0e\x61llow_existing\x18\x03 \x01(\x08R\rallowExisting\"\x89\x01\n\x0eIngestResponse\x12\x1f\n\x0bnum_created\x18\x01 \x01(\x03R\nnumCreated\x12!\n\x0cnum_existing\x18\x02 \x01(\x03R\x0bnumExisting\x12\x33\n\rdatapoint_ids\x18\x03 \x03(\x0b\x32\x0e.tilebox.v1.IDR\x0c\x64\x61tapointIds\"y\n\rDeleteRequest\x12\x33\n\rcollection_id\x18\x01 \x01(\x0b\x32\x0e.tilebox.v1.IDR\x0c\x63ollectionId\x12\x33\n\rdatapoint_ids\x18\x02 \x03(\x0b\x32\x0e.tilebox.v1.IDR\x0c\x64\x61tapointIds\"1\n\x0e\x44\x65leteResponse\x12\x1f\n\x0bnum_deleted\x18\x01 \x01(\x03R\nnumDeleted2\x9c\x01\n\x14\x44\x61taIngestionService\x12\x41\n\x06Ingest\x12\x1a.datasets.v1.IngestRequest\x1a\x1b.datasets.v1.IngestResponse\x12\x41\n\x06\x44\x65lete\x12\x1a.datasets.v1.DeleteRequest\x1a\x1b.datasets.v1.DeleteResponseBw\n\x0f\x63om.datasets.v1B\x12\x44\x61taIngestionProtoP\x01\xa2\x02\x03\x44XX\xaa\x02\x0b\x44\x61tasets.V1\xca\x02\x0b\x44\x61tasets\\V1\xe2\x02\x17\x44\x61tasets\\V1\\GPBMetadata\xea\x02\x0c\x44\x61tasets::V1\x92\x03\x02\x08\x02\x62\x08\x65\x64itionsp\xe8\x07') - -_globals = globals() -_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) -_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'datasets.v1.data_ingestion_pb2', _globals) -if not _descriptor._USE_C_DESCRIPTORS: - _globals['DESCRIPTOR']._loaded_options = None - _globals['DESCRIPTOR']._serialized_options = b'\n\017com.datasets.v1B\022DataIngestionProtoP\001\242\002\003DXX\252\002\013Datasets.V1\312\002\013Datasets\\V1\342\002\027Datasets\\V1\\GPBMetadata\352\002\014Datasets::V1\222\003\002\010\002' - _globals['_INGESTREQUEST']._serialized_start=71 - _globals['_INGESTREQUEST']._serialized_end=202 - _globals['_INGESTRESPONSE']._serialized_start=205 - _globals['_INGESTRESPONSE']._serialized_end=342 - _globals['_DELETEREQUEST']._serialized_start=344 - _globals['_DELETEREQUEST']._serialized_end=465 - _globals['_DELETERESPONSE']._serialized_start=467 - _globals['_DELETERESPONSE']._serialized_end=516 - _globals['_DATAINGESTIONSERVICE']._serialized_start=519 - _globals['_DATAINGESTIONSERVICE']._serialized_end=675 -# @@protoc_insertion_point(module_scope) diff --git a/tilebox-datasets/tilebox/datasets/datasets/v1/data_ingestion_pb2.pyi b/tilebox-datasets/tilebox/datasets/datasets/v1/data_ingestion_pb2.pyi deleted file mode 100644 index 3347767..0000000 --- a/tilebox-datasets/tilebox/datasets/datasets/v1/data_ingestion_pb2.pyi +++ /dev/null @@ -1,42 +0,0 @@ -from tilebox.datasets.tilebox.v1 import id_pb2 as _id_pb2 -from google.protobuf.internal import containers as _containers -from google.protobuf import descriptor as _descriptor -from google.protobuf import message as _message -from collections.abc import Iterable as _Iterable, Mapping as _Mapping -from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union - -DESCRIPTOR: _descriptor.FileDescriptor - -class IngestRequest(_message.Message): - __slots__ = ("collection_id", "values", "allow_existing") - COLLECTION_ID_FIELD_NUMBER: _ClassVar[int] - VALUES_FIELD_NUMBER: _ClassVar[int] - ALLOW_EXISTING_FIELD_NUMBER: _ClassVar[int] - collection_id: _id_pb2.ID - values: _containers.RepeatedScalarFieldContainer[bytes] - allow_existing: bool - def __init__(self, collection_id: _Optional[_Union[_id_pb2.ID, _Mapping]] = ..., values: _Optional[_Iterable[bytes]] = ..., allow_existing: bool = ...) -> None: ... - -class IngestResponse(_message.Message): - __slots__ = ("num_created", "num_existing", "datapoint_ids") - NUM_CREATED_FIELD_NUMBER: _ClassVar[int] - NUM_EXISTING_FIELD_NUMBER: _ClassVar[int] - DATAPOINT_IDS_FIELD_NUMBER: _ClassVar[int] - num_created: int - num_existing: int - datapoint_ids: _containers.RepeatedCompositeFieldContainer[_id_pb2.ID] - def __init__(self, num_created: _Optional[int] = ..., num_existing: _Optional[int] = ..., datapoint_ids: _Optional[_Iterable[_Union[_id_pb2.ID, _Mapping]]] = ...) -> None: ... - -class DeleteRequest(_message.Message): - __slots__ = ("collection_id", "datapoint_ids") - COLLECTION_ID_FIELD_NUMBER: _ClassVar[int] - DATAPOINT_IDS_FIELD_NUMBER: _ClassVar[int] - collection_id: _id_pb2.ID - datapoint_ids: _containers.RepeatedCompositeFieldContainer[_id_pb2.ID] - def __init__(self, collection_id: _Optional[_Union[_id_pb2.ID, _Mapping]] = ..., datapoint_ids: _Optional[_Iterable[_Union[_id_pb2.ID, _Mapping]]] = ...) -> None: ... - -class DeleteResponse(_message.Message): - __slots__ = ("num_deleted",) - NUM_DELETED_FIELD_NUMBER: _ClassVar[int] - num_deleted: int - def __init__(self, num_deleted: _Optional[int] = ...) -> None: ... diff --git a/tilebox-datasets/tilebox/datasets/datasets/v1/data_ingestion_pb2_grpc.py b/tilebox-datasets/tilebox/datasets/datasets/v1/data_ingestion_pb2_grpc.py deleted file mode 100644 index 2bec596..0000000 --- a/tilebox-datasets/tilebox/datasets/datasets/v1/data_ingestion_pb2_grpc.py +++ /dev/null @@ -1,123 +0,0 @@ -# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! -"""Client and server classes corresponding to protobuf-defined services.""" -import grpc - -from tilebox.datasets.datasets.v1 import data_ingestion_pb2 as datasets_dot_v1_dot_data__ingestion__pb2 - - -class DataIngestionServiceStub: - """DataIngestionService provides data ingestion and deletion capabilities for Tilebox datasets. - """ - - def __init__(self, channel): - """Constructor. - - Args: - channel: A grpc.Channel. - """ - self.Ingest = channel.unary_unary( - '/datasets.v1.DataIngestionService/Ingest', - request_serializer=datasets_dot_v1_dot_data__ingestion__pb2.IngestRequest.SerializeToString, - response_deserializer=datasets_dot_v1_dot_data__ingestion__pb2.IngestResponse.FromString, - _registered_method=True) - self.Delete = channel.unary_unary( - '/datasets.v1.DataIngestionService/Delete', - request_serializer=datasets_dot_v1_dot_data__ingestion__pb2.DeleteRequest.SerializeToString, - response_deserializer=datasets_dot_v1_dot_data__ingestion__pb2.DeleteResponse.FromString, - _registered_method=True) - - -class DataIngestionServiceServicer: - """DataIngestionService provides data ingestion and deletion capabilities for Tilebox datasets. - """ - - def Ingest(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def Delete(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - -def add_DataIngestionServiceServicer_to_server(servicer, server): - rpc_method_handlers = { - 'Ingest': grpc.unary_unary_rpc_method_handler( - servicer.Ingest, - request_deserializer=datasets_dot_v1_dot_data__ingestion__pb2.IngestRequest.FromString, - response_serializer=datasets_dot_v1_dot_data__ingestion__pb2.IngestResponse.SerializeToString, - ), - 'Delete': grpc.unary_unary_rpc_method_handler( - servicer.Delete, - request_deserializer=datasets_dot_v1_dot_data__ingestion__pb2.DeleteRequest.FromString, - response_serializer=datasets_dot_v1_dot_data__ingestion__pb2.DeleteResponse.SerializeToString, - ), - } - generic_handler = grpc.method_handlers_generic_handler( - 'datasets.v1.DataIngestionService', rpc_method_handlers) - server.add_generic_rpc_handlers((generic_handler,)) - server.add_registered_method_handlers('datasets.v1.DataIngestionService', rpc_method_handlers) - - - # This class is part of an EXPERIMENTAL API. -class DataIngestionService: - """DataIngestionService provides data ingestion and deletion capabilities for Tilebox datasets. - """ - - @staticmethod - def Ingest(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary( - request, - target, - '/datasets.v1.DataIngestionService/Ingest', - datasets_dot_v1_dot_data__ingestion__pb2.IngestRequest.SerializeToString, - datasets_dot_v1_dot_data__ingestion__pb2.IngestResponse.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) - - @staticmethod - def Delete(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary( - request, - target, - '/datasets.v1.DataIngestionService/Delete', - datasets_dot_v1_dot_data__ingestion__pb2.DeleteRequest.SerializeToString, - datasets_dot_v1_dot_data__ingestion__pb2.DeleteResponse.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) diff --git a/tilebox-datasets/tilebox/datasets/datasets/v1/dataset_type_pb2.py b/tilebox-datasets/tilebox/datasets/datasets/v1/dataset_type_pb2.py deleted file mode 100644 index 6ecba04..0000000 --- a/tilebox-datasets/tilebox/datasets/datasets/v1/dataset_type_pb2.py +++ /dev/null @@ -1,59 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by the protocol buffer compiler. DO NOT EDIT! -# NO CHECKED-IN PROTOBUF GENCODE -# source: datasets/v1/dataset_type.proto -# Protobuf Python Version: 6.30.0 -"""Generated protocol buffer code.""" -from google.protobuf import descriptor as _descriptor -from google.protobuf import descriptor_pool as _descriptor_pool -from google.protobuf import runtime_version as _runtime_version -from google.protobuf import symbol_database as _symbol_database -from google.protobuf.internal import builder as _builder -_runtime_version.ValidateProtobufRuntimeVersion( - _runtime_version.Domain.PUBLIC, - 6, - 30, - 0, - '', - 'datasets/v1/dataset_type.proto' -) -# @@protoc_insertion_point(imports) - -_sym_db = _symbol_database.Default() - - -from tilebox.datasets.datasets.v1 import well_known_types_pb2 as datasets_dot_v1_dot_well__known__types__pb2 -from google.protobuf import descriptor_pb2 as google_dot_protobuf_dot_descriptor__pb2 -from google.protobuf import timestamp_pb2 as google_dot_protobuf_dot_timestamp__pb2 -from tilebox.datasets.tilebox.v1 import id_pb2 as tilebox_dot_v1_dot_id__pb2 - - -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1e\x64\x61tasets/v1/dataset_type.proto\x12\x0b\x64\x61tasets.v1\x1a\"datasets/v1/well_known_types.proto\x1a google/protobuf/descriptor.proto\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\x13tilebox/v1/id.proto\"\x8c\x01\n\x05\x46ield\x12\x45\n\ndescriptor\x18\x01 \x01(\x0b\x32%.google.protobuf.FieldDescriptorProtoR\ndescriptor\x12<\n\nannotation\x18\x02 \x01(\x0b\x32\x1c.datasets.v1.FieldAnnotationR\nannotation\"\x8a\x02\n\x0f\x46ieldAnnotation\x12 \n\x0b\x64\x65scription\x18\x01 \x01(\tR\x0b\x64\x65scription\x12#\n\rexample_value\x18\x02 \x01(\tR\x0c\x65xampleValue\x12\x35\n\x13source_json_pointer\x18\x03 \x01(\tB\x05\xaa\x01\x02\x08\x01R\x11sourceJsonPointer\x12\x1c\n\tqueryable\x18\x04 \x01(\x08R\tqueryable\x12-\n\x0fjson_schema_ref\x18\x05 \x01(\tB\x05\xaa\x01\x02\x08\x01R\rjsonSchemaRef\x12,\n\x05roles\x18\x06 \x03(\x0e\x32\x16.datasets.v1.FieldRoleR\x05roles\"g\n\x0b\x44\x61tasetType\x12,\n\x04kind\x18\x01 \x01(\x0e\x32\x18.datasets.v1.DatasetKindR\x04kind\x12*\n\x06\x66ields\x18\x02 \x03(\x0b\x32\x12.datasets.v1.FieldR\x06\x66ields\"\xf4\x01\n\rAnnotatedType\x12I\n\x0e\x64\x65scriptor_set\x18\x01 \x01(\x0b\x32\".google.protobuf.FileDescriptorSetR\rdescriptorSet\x12\x19\n\x08type_url\x18\x02 \x01(\tR\x07typeUrl\x12I\n\x11\x66ield_annotations\x18\x04 \x03(\x0b\x32\x1c.datasets.v1.FieldAnnotationR\x10\x66ieldAnnotations\x12,\n\x04kind\x18\x05 \x01(\x0e\x32\x18.datasets.v1.DatasetKindR\x04kindJ\x04\x08\x03\x10\x04\"\xa6\x01\n\x11TemporalDatapoint\x12.\n\x04time\x18\x01 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\x04time\x12\x1e\n\x02id\x18\x02 \x01(\x0b\x32\x0e.tilebox.v1.IDR\x02id\x12\x41\n\x0eingestion_time\x18\x03 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\ringestionTime\"\xdf\x01\n\x17SpatioTemporalDatapoint\x12.\n\x04time\x18\x01 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\x04time\x12\x1e\n\x02id\x18\x02 \x01(\x0b\x32\x0e.tilebox.v1.IDR\x02id\x12\x41\n\x0eingestion_time\x18\x03 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\ringestionTime\x12\x31\n\x08geometry\x18\x04 \x01(\x0b\x32\x15.datasets.v1.GeometryR\x08geometry*g\n\x0b\x44\x61tasetKind\x12\x1c\n\x18\x44\x41TASET_KIND_UNSPECIFIED\x10\x00\x12\x19\n\x15\x44\x41TASET_KIND_TEMPORAL\x10\x01\x12\x1f\n\x1b\x44\x41TASET_KIND_SPATIOTEMPORAL\x10\x02*E\n\tFieldRole\x12\x1a\n\x16\x46IELD_ROLE_UNSPECIFIED\x10\x00\x12\x1c\n\x18\x46IELD_ROLE_PRIMARY_TITLE\x10\x01\x42u\n\x0f\x63om.datasets.v1B\x10\x44\x61tasetTypeProtoP\x01\xa2\x02\x03\x44XX\xaa\x02\x0b\x44\x61tasets.V1\xca\x02\x0b\x44\x61tasets\\V1\xe2\x02\x17\x44\x61tasets\\V1\\GPBMetadata\xea\x02\x0c\x44\x61tasets::V1\x92\x03\x02\x08\x02\x62\x08\x65\x64itionsp\xe8\x07') - -_globals = globals() -_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) -_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'datasets.v1.dataset_type_pb2', _globals) -if not _descriptor._USE_C_DESCRIPTORS: - _globals['DESCRIPTOR']._loaded_options = None - _globals['DESCRIPTOR']._serialized_options = b'\n\017com.datasets.v1B\020DatasetTypeProtoP\001\242\002\003DXX\252\002\013Datasets.V1\312\002\013Datasets\\V1\342\002\027Datasets\\V1\\GPBMetadata\352\002\014Datasets::V1\222\003\002\010\002' - _globals['_FIELDANNOTATION'].fields_by_name['source_json_pointer']._loaded_options = None - _globals['_FIELDANNOTATION'].fields_by_name['source_json_pointer']._serialized_options = b'\252\001\002\010\001' - _globals['_FIELDANNOTATION'].fields_by_name['json_schema_ref']._loaded_options = None - _globals['_FIELDANNOTATION'].fields_by_name['json_schema_ref']._serialized_options = b'\252\001\002\010\001' - _globals['_DATASETKIND']._serialized_start=1330 - _globals['_DATASETKIND']._serialized_end=1433 - _globals['_FIELDROLE']._serialized_start=1435 - _globals['_FIELDROLE']._serialized_end=1504 - _globals['_FIELD']._serialized_start=172 - _globals['_FIELD']._serialized_end=312 - _globals['_FIELDANNOTATION']._serialized_start=315 - _globals['_FIELDANNOTATION']._serialized_end=581 - _globals['_DATASETTYPE']._serialized_start=583 - _globals['_DATASETTYPE']._serialized_end=686 - _globals['_ANNOTATEDTYPE']._serialized_start=689 - _globals['_ANNOTATEDTYPE']._serialized_end=933 - _globals['_TEMPORALDATAPOINT']._serialized_start=936 - _globals['_TEMPORALDATAPOINT']._serialized_end=1102 - _globals['_SPATIOTEMPORALDATAPOINT']._serialized_start=1105 - _globals['_SPATIOTEMPORALDATAPOINT']._serialized_end=1328 -# @@protoc_insertion_point(module_scope) diff --git a/tilebox-datasets/tilebox/datasets/datasets/v1/dataset_type_pb2.pyi b/tilebox-datasets/tilebox/datasets/datasets/v1/dataset_type_pb2.pyi deleted file mode 100644 index 2bddeb9..0000000 --- a/tilebox-datasets/tilebox/datasets/datasets/v1/dataset_type_pb2.pyi +++ /dev/null @@ -1,94 +0,0 @@ -from tilebox.datasets.datasets.v1 import well_known_types_pb2 as _well_known_types_pb2 -from google.protobuf import descriptor_pb2 as _descriptor_pb2 -from google.protobuf import timestamp_pb2 as _timestamp_pb2 -from tilebox.datasets.tilebox.v1 import id_pb2 as _id_pb2 -from google.protobuf.internal import containers as _containers -from google.protobuf.internal import enum_type_wrapper as _enum_type_wrapper -from google.protobuf import descriptor as _descriptor -from google.protobuf import message as _message -from collections.abc import Iterable as _Iterable, Mapping as _Mapping -from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union - -DESCRIPTOR: _descriptor.FileDescriptor - -class DatasetKind(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): - __slots__ = () - DATASET_KIND_UNSPECIFIED: _ClassVar[DatasetKind] - DATASET_KIND_TEMPORAL: _ClassVar[DatasetKind] - DATASET_KIND_SPATIOTEMPORAL: _ClassVar[DatasetKind] - -class FieldRole(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): - __slots__ = () - FIELD_ROLE_UNSPECIFIED: _ClassVar[FieldRole] - FIELD_ROLE_PRIMARY_TITLE: _ClassVar[FieldRole] -DATASET_KIND_UNSPECIFIED: DatasetKind -DATASET_KIND_TEMPORAL: DatasetKind -DATASET_KIND_SPATIOTEMPORAL: DatasetKind -FIELD_ROLE_UNSPECIFIED: FieldRole -FIELD_ROLE_PRIMARY_TITLE: FieldRole - -class Field(_message.Message): - __slots__ = ("descriptor", "annotation") - DESCRIPTOR_FIELD_NUMBER: _ClassVar[int] - ANNOTATION_FIELD_NUMBER: _ClassVar[int] - descriptor: _descriptor_pb2.FieldDescriptorProto - annotation: FieldAnnotation - def __init__(self, descriptor: _Optional[_Union[_descriptor_pb2.FieldDescriptorProto, _Mapping]] = ..., annotation: _Optional[_Union[FieldAnnotation, _Mapping]] = ...) -> None: ... - -class FieldAnnotation(_message.Message): - __slots__ = ("description", "example_value", "source_json_pointer", "queryable", "json_schema_ref", "roles") - DESCRIPTION_FIELD_NUMBER: _ClassVar[int] - EXAMPLE_VALUE_FIELD_NUMBER: _ClassVar[int] - SOURCE_JSON_POINTER_FIELD_NUMBER: _ClassVar[int] - QUERYABLE_FIELD_NUMBER: _ClassVar[int] - JSON_SCHEMA_REF_FIELD_NUMBER: _ClassVar[int] - ROLES_FIELD_NUMBER: _ClassVar[int] - description: str - example_value: str - source_json_pointer: str - queryable: bool - json_schema_ref: str - roles: _containers.RepeatedScalarFieldContainer[FieldRole] - def __init__(self, description: _Optional[str] = ..., example_value: _Optional[str] = ..., source_json_pointer: _Optional[str] = ..., queryable: bool = ..., json_schema_ref: _Optional[str] = ..., roles: _Optional[_Iterable[_Union[FieldRole, str]]] = ...) -> None: ... - -class DatasetType(_message.Message): - __slots__ = ("kind", "fields") - KIND_FIELD_NUMBER: _ClassVar[int] - FIELDS_FIELD_NUMBER: _ClassVar[int] - kind: DatasetKind - fields: _containers.RepeatedCompositeFieldContainer[Field] - def __init__(self, kind: _Optional[_Union[DatasetKind, str]] = ..., fields: _Optional[_Iterable[_Union[Field, _Mapping]]] = ...) -> None: ... - -class AnnotatedType(_message.Message): - __slots__ = ("descriptor_set", "type_url", "field_annotations", "kind") - DESCRIPTOR_SET_FIELD_NUMBER: _ClassVar[int] - TYPE_URL_FIELD_NUMBER: _ClassVar[int] - FIELD_ANNOTATIONS_FIELD_NUMBER: _ClassVar[int] - KIND_FIELD_NUMBER: _ClassVar[int] - descriptor_set: _descriptor_pb2.FileDescriptorSet - type_url: str - field_annotations: _containers.RepeatedCompositeFieldContainer[FieldAnnotation] - kind: DatasetKind - def __init__(self, descriptor_set: _Optional[_Union[_descriptor_pb2.FileDescriptorSet, _Mapping]] = ..., type_url: _Optional[str] = ..., field_annotations: _Optional[_Iterable[_Union[FieldAnnotation, _Mapping]]] = ..., kind: _Optional[_Union[DatasetKind, str]] = ...) -> None: ... - -class TemporalDatapoint(_message.Message): - __slots__ = ("time", "id", "ingestion_time") - TIME_FIELD_NUMBER: _ClassVar[int] - ID_FIELD_NUMBER: _ClassVar[int] - INGESTION_TIME_FIELD_NUMBER: _ClassVar[int] - time: _timestamp_pb2.Timestamp - id: _id_pb2.ID - ingestion_time: _timestamp_pb2.Timestamp - def __init__(self, time: _Optional[_Union[_timestamp_pb2.Timestamp, _Mapping]] = ..., id: _Optional[_Union[_id_pb2.ID, _Mapping]] = ..., ingestion_time: _Optional[_Union[_timestamp_pb2.Timestamp, _Mapping]] = ...) -> None: ... - -class SpatioTemporalDatapoint(_message.Message): - __slots__ = ("time", "id", "ingestion_time", "geometry") - TIME_FIELD_NUMBER: _ClassVar[int] - ID_FIELD_NUMBER: _ClassVar[int] - INGESTION_TIME_FIELD_NUMBER: _ClassVar[int] - GEOMETRY_FIELD_NUMBER: _ClassVar[int] - time: _timestamp_pb2.Timestamp - id: _id_pb2.ID - ingestion_time: _timestamp_pb2.Timestamp - geometry: _well_known_types_pb2.Geometry - def __init__(self, time: _Optional[_Union[_timestamp_pb2.Timestamp, _Mapping]] = ..., id: _Optional[_Union[_id_pb2.ID, _Mapping]] = ..., ingestion_time: _Optional[_Union[_timestamp_pb2.Timestamp, _Mapping]] = ..., geometry: _Optional[_Union[_well_known_types_pb2.Geometry, _Mapping]] = ...) -> None: ... diff --git a/tilebox-datasets/tilebox/datasets/datasets/v1/dataset_type_pb2_grpc.py b/tilebox-datasets/tilebox/datasets/datasets/v1/dataset_type_pb2_grpc.py deleted file mode 100644 index 8a93939..0000000 --- a/tilebox-datasets/tilebox/datasets/datasets/v1/dataset_type_pb2_grpc.py +++ /dev/null @@ -1,3 +0,0 @@ -# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! -"""Client and server classes corresponding to protobuf-defined services.""" -import grpc diff --git a/tilebox-datasets/tilebox/datasets/datasets/v1/datasets_pb2.py b/tilebox-datasets/tilebox/datasets/datasets/v1/datasets_pb2.py deleted file mode 100644 index 6873965..0000000 --- a/tilebox-datasets/tilebox/datasets/datasets/v1/datasets_pb2.py +++ /dev/null @@ -1,66 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by the protocol buffer compiler. DO NOT EDIT! -# NO CHECKED-IN PROTOBUF GENCODE -# source: datasets/v1/datasets.proto -# Protobuf Python Version: 6.30.0 -"""Generated protocol buffer code.""" -from google.protobuf import descriptor as _descriptor -from google.protobuf import descriptor_pool as _descriptor_pool -from google.protobuf import runtime_version as _runtime_version -from google.protobuf import symbol_database as _symbol_database -from google.protobuf.internal import builder as _builder -_runtime_version.ValidateProtobufRuntimeVersion( - _runtime_version.Domain.PUBLIC, - 6, - 30, - 0, - '', - 'datasets/v1/datasets.proto' -) -# @@protoc_insertion_point(imports) - -_sym_db = _symbol_database.Default() - - -from tilebox.datasets.datasets.v1 import core_pb2 as datasets_dot_v1_dot_core__pb2 -from tilebox.datasets.datasets.v1 import dataset_type_pb2 as datasets_dot_v1_dot_dataset__type__pb2 -from tilebox.datasets.tilebox.v1 import id_pb2 as tilebox_dot_v1_dot_id__pb2 - - -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1a\x64\x61tasets/v1/datasets.proto\x12\x0b\x64\x61tasets.v1\x1a\x16\x64\x61tasets/v1/core.proto\x1a\x1e\x64\x61tasets/v1/dataset_type.proto\x1a\x13tilebox/v1/id.proto\"\xb1\x01\n\x14\x43reateDatasetRequest\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\x12,\n\x04type\x18\x02 \x01(\x0b\x32\x18.datasets.v1.DatasetTypeR\x04type\x12\x18\n\x07summary\x18\x03 \x01(\tR\x07summary\x12\x1b\n\tcode_name\x18\x04 \x01(\tR\x08\x63odeName\x12 \n\x0b\x64\x65scription\x18\x05 \x01(\tR\x0b\x64\x65scription\"G\n\x11GetDatasetRequest\x12\x12\n\x04slug\x18\x01 \x01(\tR\x04slug\x12\x1e\n\x02id\x18\x02 \x01(\x0b\x32\x0e.tilebox.v1.IDR\x02id\"\xf4\x01\n\x14UpdateDatasetRequest\x12\x1e\n\x02id\x18\x01 \x01(\x0b\x32\x0e.tilebox.v1.IDR\x02id\x12\x19\n\x04name\x18\x02 \x01(\tB\x05\xaa\x01\x02\x08\x01R\x04name\x12\x33\n\x04type\x18\x03 \x01(\x0b\x32\x18.datasets.v1.DatasetTypeB\x05\xaa\x01\x02\x08\x01R\x04type\x12\x1f\n\x07summary\x18\x04 \x01(\tB\x05\xaa\x01\x02\x08\x01R\x07summary\x12\"\n\tcode_name\x18\x05 \x01(\tB\x05\xaa\x01\x02\x08\x01R\x08\x63odeName\x12\'\n\x0b\x64\x65scription\x18\x06 \x01(\tB\x05\xaa\x01\x02\x08\x01R\x0b\x64\x65scription\"6\n\x14\x44\x65leteDatasetRequest\x12\x1e\n\x02id\x18\x01 \x01(\x0b\x32\x0e.tilebox.v1.IDR\x02id\"1\n\x15\x44\x65leteDatasetResponse\x12\x18\n\x07trashed\x18\x01 \x01(\x08R\x07trashed\"\x15\n\x13ListDatasetsRequest\"\x86\x02\n\x14ListDatasetsResponse\x12\x30\n\x08\x64\x61tasets\x18\x01 \x03(\x0b\x32\x14.datasets.v1.DatasetR\x08\x64\x61tasets\x12\x31\n\x06groups\x18\x02 \x03(\x0b\x32\x19.datasets.v1.DatasetGroupR\x06groups\x12%\n\x0eserver_message\x18\x03 \x01(\tR\rserverMessage\x12%\n\x0eowned_datasets\x18\x04 \x01(\x03R\rownedDatasets\x12;\n\x16maximum_owned_datasets\x18\x05 \x01(\x03\x42\x05\xaa\x01\x02\x08\x01R\x14maximumOwnedDatasets2\x95\x03\n\x0e\x44\x61tasetService\x12H\n\rCreateDataset\x12!.datasets.v1.CreateDatasetRequest\x1a\x14.datasets.v1.Dataset\x12\x42\n\nGetDataset\x12\x1e.datasets.v1.GetDatasetRequest\x1a\x14.datasets.v1.Dataset\x12H\n\rUpdateDataset\x12!.datasets.v1.UpdateDatasetRequest\x1a\x14.datasets.v1.Dataset\x12V\n\rDeleteDataset\x12!.datasets.v1.DeleteDatasetRequest\x1a\".datasets.v1.DeleteDatasetResponse\x12S\n\x0cListDatasets\x12 .datasets.v1.ListDatasetsRequest\x1a!.datasets.v1.ListDatasetsResponseBr\n\x0f\x63om.datasets.v1B\rDatasetsProtoP\x01\xa2\x02\x03\x44XX\xaa\x02\x0b\x44\x61tasets.V1\xca\x02\x0b\x44\x61tasets\\V1\xe2\x02\x17\x44\x61tasets\\V1\\GPBMetadata\xea\x02\x0c\x44\x61tasets::V1\x92\x03\x02\x08\x02\x62\x08\x65\x64itionsp\xe8\x07') - -_globals = globals() -_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) -_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'datasets.v1.datasets_pb2', _globals) -if not _descriptor._USE_C_DESCRIPTORS: - _globals['DESCRIPTOR']._loaded_options = None - _globals['DESCRIPTOR']._serialized_options = b'\n\017com.datasets.v1B\rDatasetsProtoP\001\242\002\003DXX\252\002\013Datasets.V1\312\002\013Datasets\\V1\342\002\027Datasets\\V1\\GPBMetadata\352\002\014Datasets::V1\222\003\002\010\002' - _globals['_UPDATEDATASETREQUEST'].fields_by_name['name']._loaded_options = None - _globals['_UPDATEDATASETREQUEST'].fields_by_name['name']._serialized_options = b'\252\001\002\010\001' - _globals['_UPDATEDATASETREQUEST'].fields_by_name['type']._loaded_options = None - _globals['_UPDATEDATASETREQUEST'].fields_by_name['type']._serialized_options = b'\252\001\002\010\001' - _globals['_UPDATEDATASETREQUEST'].fields_by_name['summary']._loaded_options = None - _globals['_UPDATEDATASETREQUEST'].fields_by_name['summary']._serialized_options = b'\252\001\002\010\001' - _globals['_UPDATEDATASETREQUEST'].fields_by_name['code_name']._loaded_options = None - _globals['_UPDATEDATASETREQUEST'].fields_by_name['code_name']._serialized_options = b'\252\001\002\010\001' - _globals['_UPDATEDATASETREQUEST'].fields_by_name['description']._loaded_options = None - _globals['_UPDATEDATASETREQUEST'].fields_by_name['description']._serialized_options = b'\252\001\002\010\001' - _globals['_LISTDATASETSRESPONSE'].fields_by_name['maximum_owned_datasets']._loaded_options = None - _globals['_LISTDATASETSRESPONSE'].fields_by_name['maximum_owned_datasets']._serialized_options = b'\252\001\002\010\001' - _globals['_CREATEDATASETREQUEST']._serialized_start=121 - _globals['_CREATEDATASETREQUEST']._serialized_end=298 - _globals['_GETDATASETREQUEST']._serialized_start=300 - _globals['_GETDATASETREQUEST']._serialized_end=371 - _globals['_UPDATEDATASETREQUEST']._serialized_start=374 - _globals['_UPDATEDATASETREQUEST']._serialized_end=618 - _globals['_DELETEDATASETREQUEST']._serialized_start=620 - _globals['_DELETEDATASETREQUEST']._serialized_end=674 - _globals['_DELETEDATASETRESPONSE']._serialized_start=676 - _globals['_DELETEDATASETRESPONSE']._serialized_end=725 - _globals['_LISTDATASETSREQUEST']._serialized_start=727 - _globals['_LISTDATASETSREQUEST']._serialized_end=748 - _globals['_LISTDATASETSRESPONSE']._serialized_start=751 - _globals['_LISTDATASETSRESPONSE']._serialized_end=1013 - _globals['_DATASETSERVICE']._serialized_start=1016 - _globals['_DATASETSERVICE']._serialized_end=1421 -# @@protoc_insertion_point(module_scope) diff --git a/tilebox-datasets/tilebox/datasets/datasets/v1/datasets_pb2.pyi b/tilebox-datasets/tilebox/datasets/datasets/v1/datasets_pb2.pyi deleted file mode 100644 index 6adbe60..0000000 --- a/tilebox-datasets/tilebox/datasets/datasets/v1/datasets_pb2.pyi +++ /dev/null @@ -1,78 +0,0 @@ -from tilebox.datasets.datasets.v1 import core_pb2 as _core_pb2 -from tilebox.datasets.datasets.v1 import dataset_type_pb2 as _dataset_type_pb2 -from tilebox.datasets.tilebox.v1 import id_pb2 as _id_pb2 -from google.protobuf.internal import containers as _containers -from google.protobuf import descriptor as _descriptor -from google.protobuf import message as _message -from collections.abc import Iterable as _Iterable, Mapping as _Mapping -from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union - -DESCRIPTOR: _descriptor.FileDescriptor - -class CreateDatasetRequest(_message.Message): - __slots__ = ("name", "type", "summary", "code_name", "description") - NAME_FIELD_NUMBER: _ClassVar[int] - TYPE_FIELD_NUMBER: _ClassVar[int] - SUMMARY_FIELD_NUMBER: _ClassVar[int] - CODE_NAME_FIELD_NUMBER: _ClassVar[int] - DESCRIPTION_FIELD_NUMBER: _ClassVar[int] - name: str - type: _dataset_type_pb2.DatasetType - summary: str - code_name: str - description: str - def __init__(self, name: _Optional[str] = ..., type: _Optional[_Union[_dataset_type_pb2.DatasetType, _Mapping]] = ..., summary: _Optional[str] = ..., code_name: _Optional[str] = ..., description: _Optional[str] = ...) -> None: ... - -class GetDatasetRequest(_message.Message): - __slots__ = ("slug", "id") - SLUG_FIELD_NUMBER: _ClassVar[int] - ID_FIELD_NUMBER: _ClassVar[int] - slug: str - id: _id_pb2.ID - def __init__(self, slug: _Optional[str] = ..., id: _Optional[_Union[_id_pb2.ID, _Mapping]] = ...) -> None: ... - -class UpdateDatasetRequest(_message.Message): - __slots__ = ("id", "name", "type", "summary", "code_name", "description") - ID_FIELD_NUMBER: _ClassVar[int] - NAME_FIELD_NUMBER: _ClassVar[int] - TYPE_FIELD_NUMBER: _ClassVar[int] - SUMMARY_FIELD_NUMBER: _ClassVar[int] - CODE_NAME_FIELD_NUMBER: _ClassVar[int] - DESCRIPTION_FIELD_NUMBER: _ClassVar[int] - id: _id_pb2.ID - name: str - type: _dataset_type_pb2.DatasetType - summary: str - code_name: str - description: str - def __init__(self, id: _Optional[_Union[_id_pb2.ID, _Mapping]] = ..., name: _Optional[str] = ..., type: _Optional[_Union[_dataset_type_pb2.DatasetType, _Mapping]] = ..., summary: _Optional[str] = ..., code_name: _Optional[str] = ..., description: _Optional[str] = ...) -> None: ... - -class DeleteDatasetRequest(_message.Message): - __slots__ = ("id",) - ID_FIELD_NUMBER: _ClassVar[int] - id: _id_pb2.ID - def __init__(self, id: _Optional[_Union[_id_pb2.ID, _Mapping]] = ...) -> None: ... - -class DeleteDatasetResponse(_message.Message): - __slots__ = ("trashed",) - TRASHED_FIELD_NUMBER: _ClassVar[int] - trashed: bool - def __init__(self, trashed: bool = ...) -> None: ... - -class ListDatasetsRequest(_message.Message): - __slots__ = () - def __init__(self) -> None: ... - -class ListDatasetsResponse(_message.Message): - __slots__ = ("datasets", "groups", "server_message", "owned_datasets", "maximum_owned_datasets") - DATASETS_FIELD_NUMBER: _ClassVar[int] - GROUPS_FIELD_NUMBER: _ClassVar[int] - SERVER_MESSAGE_FIELD_NUMBER: _ClassVar[int] - OWNED_DATASETS_FIELD_NUMBER: _ClassVar[int] - MAXIMUM_OWNED_DATASETS_FIELD_NUMBER: _ClassVar[int] - datasets: _containers.RepeatedCompositeFieldContainer[_core_pb2.Dataset] - groups: _containers.RepeatedCompositeFieldContainer[_core_pb2.DatasetGroup] - server_message: str - owned_datasets: int - maximum_owned_datasets: int - def __init__(self, datasets: _Optional[_Iterable[_Union[_core_pb2.Dataset, _Mapping]]] = ..., groups: _Optional[_Iterable[_Union[_core_pb2.DatasetGroup, _Mapping]]] = ..., server_message: _Optional[str] = ..., owned_datasets: _Optional[int] = ..., maximum_owned_datasets: _Optional[int] = ...) -> None: ... diff --git a/tilebox-datasets/tilebox/datasets/datasets/v1/datasets_pb2_grpc.py b/tilebox-datasets/tilebox/datasets/datasets/v1/datasets_pb2_grpc.py deleted file mode 100644 index 791b945..0000000 --- a/tilebox-datasets/tilebox/datasets/datasets/v1/datasets_pb2_grpc.py +++ /dev/null @@ -1,253 +0,0 @@ -# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! -"""Client and server classes corresponding to protobuf-defined services.""" -import grpc - -from tilebox.datasets.datasets.v1 import core_pb2 as datasets_dot_v1_dot_core__pb2 -from tilebox.datasets.datasets.v1 import datasets_pb2 as datasets_dot_v1_dot_datasets__pb2 - - -class DatasetServiceStub: - """DatasetsService is the CRUD service for Tilebox datasets. - """ - - def __init__(self, channel): - """Constructor. - - Args: - channel: A grpc.Channel. - """ - self.CreateDataset = channel.unary_unary( - '/datasets.v1.DatasetService/CreateDataset', - request_serializer=datasets_dot_v1_dot_datasets__pb2.CreateDatasetRequest.SerializeToString, - response_deserializer=datasets_dot_v1_dot_core__pb2.Dataset.FromString, - _registered_method=True) - self.GetDataset = channel.unary_unary( - '/datasets.v1.DatasetService/GetDataset', - request_serializer=datasets_dot_v1_dot_datasets__pb2.GetDatasetRequest.SerializeToString, - response_deserializer=datasets_dot_v1_dot_core__pb2.Dataset.FromString, - _registered_method=True) - self.UpdateDataset = channel.unary_unary( - '/datasets.v1.DatasetService/UpdateDataset', - request_serializer=datasets_dot_v1_dot_datasets__pb2.UpdateDatasetRequest.SerializeToString, - response_deserializer=datasets_dot_v1_dot_core__pb2.Dataset.FromString, - _registered_method=True) - self.DeleteDataset = channel.unary_unary( - '/datasets.v1.DatasetService/DeleteDataset', - request_serializer=datasets_dot_v1_dot_datasets__pb2.DeleteDatasetRequest.SerializeToString, - response_deserializer=datasets_dot_v1_dot_datasets__pb2.DeleteDatasetResponse.FromString, - _registered_method=True) - self.ListDatasets = channel.unary_unary( - '/datasets.v1.DatasetService/ListDatasets', - request_serializer=datasets_dot_v1_dot_datasets__pb2.ListDatasetsRequest.SerializeToString, - response_deserializer=datasets_dot_v1_dot_datasets__pb2.ListDatasetsResponse.FromString, - _registered_method=True) - - -class DatasetServiceServicer: - """DatasetsService is the CRUD service for Tilebox datasets. - """ - - def CreateDataset(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def GetDataset(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def UpdateDataset(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def DeleteDataset(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def ListDatasets(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - -def add_DatasetServiceServicer_to_server(servicer, server): - rpc_method_handlers = { - 'CreateDataset': grpc.unary_unary_rpc_method_handler( - servicer.CreateDataset, - request_deserializer=datasets_dot_v1_dot_datasets__pb2.CreateDatasetRequest.FromString, - response_serializer=datasets_dot_v1_dot_core__pb2.Dataset.SerializeToString, - ), - 'GetDataset': grpc.unary_unary_rpc_method_handler( - servicer.GetDataset, - request_deserializer=datasets_dot_v1_dot_datasets__pb2.GetDatasetRequest.FromString, - response_serializer=datasets_dot_v1_dot_core__pb2.Dataset.SerializeToString, - ), - 'UpdateDataset': grpc.unary_unary_rpc_method_handler( - servicer.UpdateDataset, - request_deserializer=datasets_dot_v1_dot_datasets__pb2.UpdateDatasetRequest.FromString, - response_serializer=datasets_dot_v1_dot_core__pb2.Dataset.SerializeToString, - ), - 'DeleteDataset': grpc.unary_unary_rpc_method_handler( - servicer.DeleteDataset, - request_deserializer=datasets_dot_v1_dot_datasets__pb2.DeleteDatasetRequest.FromString, - response_serializer=datasets_dot_v1_dot_datasets__pb2.DeleteDatasetResponse.SerializeToString, - ), - 'ListDatasets': grpc.unary_unary_rpc_method_handler( - servicer.ListDatasets, - request_deserializer=datasets_dot_v1_dot_datasets__pb2.ListDatasetsRequest.FromString, - response_serializer=datasets_dot_v1_dot_datasets__pb2.ListDatasetsResponse.SerializeToString, - ), - } - generic_handler = grpc.method_handlers_generic_handler( - 'datasets.v1.DatasetService', rpc_method_handlers) - server.add_generic_rpc_handlers((generic_handler,)) - server.add_registered_method_handlers('datasets.v1.DatasetService', rpc_method_handlers) - - - # This class is part of an EXPERIMENTAL API. -class DatasetService: - """DatasetsService is the CRUD service for Tilebox datasets. - """ - - @staticmethod - def CreateDataset(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary( - request, - target, - '/datasets.v1.DatasetService/CreateDataset', - datasets_dot_v1_dot_datasets__pb2.CreateDatasetRequest.SerializeToString, - datasets_dot_v1_dot_core__pb2.Dataset.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) - - @staticmethod - def GetDataset(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary( - request, - target, - '/datasets.v1.DatasetService/GetDataset', - datasets_dot_v1_dot_datasets__pb2.GetDatasetRequest.SerializeToString, - datasets_dot_v1_dot_core__pb2.Dataset.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) - - @staticmethod - def UpdateDataset(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary( - request, - target, - '/datasets.v1.DatasetService/UpdateDataset', - datasets_dot_v1_dot_datasets__pb2.UpdateDatasetRequest.SerializeToString, - datasets_dot_v1_dot_core__pb2.Dataset.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) - - @staticmethod - def DeleteDataset(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary( - request, - target, - '/datasets.v1.DatasetService/DeleteDataset', - datasets_dot_v1_dot_datasets__pb2.DeleteDatasetRequest.SerializeToString, - datasets_dot_v1_dot_datasets__pb2.DeleteDatasetResponse.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) - - @staticmethod - def ListDatasets(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary( - request, - target, - '/datasets.v1.DatasetService/ListDatasets', - datasets_dot_v1_dot_datasets__pb2.ListDatasetsRequest.SerializeToString, - datasets_dot_v1_dot_datasets__pb2.ListDatasetsResponse.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) diff --git a/tilebox-datasets/tilebox/datasets/datasets/v1/timeseries_pb2.py b/tilebox-datasets/tilebox/datasets/datasets/v1/timeseries_pb2.py deleted file mode 100644 index e00ed96..0000000 --- a/tilebox-datasets/tilebox/datasets/datasets/v1/timeseries_pb2.py +++ /dev/null @@ -1,42 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by the protocol buffer compiler. DO NOT EDIT! -# NO CHECKED-IN PROTOBUF GENCODE -# source: datasets/v1/timeseries.proto -# Protobuf Python Version: 6.30.0 -"""Generated protocol buffer code.""" -from google.protobuf import descriptor as _descriptor -from google.protobuf import descriptor_pool as _descriptor_pool -from google.protobuf import runtime_version as _runtime_version -from google.protobuf import symbol_database as _symbol_database -from google.protobuf.internal import builder as _builder -_runtime_version.ValidateProtobufRuntimeVersion( - _runtime_version.Domain.PUBLIC, - 6, - 30, - 0, - '', - 'datasets/v1/timeseries.proto' -) -# @@protoc_insertion_point(imports) - -_sym_db = _symbol_database.Default() - - -from google.protobuf import duration_pb2 as google_dot_protobuf_dot_duration__pb2 -from tilebox.datasets.tilebox.v1 import id_pb2 as tilebox_dot_v1_dot_id__pb2 -from tilebox.datasets.tilebox.v1 import query_pb2 as tilebox_dot_v1_dot_query__pb2 - - -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1c\x64\x61tasets/v1/timeseries.proto\x12\x0b\x64\x61tasets.v1\x1a\x1egoogle/protobuf/duration.proto\x1a\x13tilebox/v1/id.proto\x1a\x16tilebox/v1/query.proto\"\xfd\x02\n\x16TimeseriesDatasetChunk\x12-\n\ndataset_id\x18\x01 \x01(\x0b\x32\x0e.tilebox.v1.IDR\tdatasetId\x12\x33\n\rcollection_id\x18\x02 \x01(\x0b\x32\x0e.tilebox.v1.IDR\x0c\x63ollectionId\x12=\n\rtime_interval\x18\x03 \x01(\x0b\x32\x18.tilebox.v1.TimeIntervalR\x0ctimeInterval\x12\x45\n\x12\x64\x61tapoint_interval\x18\x04 \x01(\x0b\x32\x16.tilebox.v1.IDIntervalR\x11\x64\x61tapointInterval\x12#\n\rbranch_factor\x18\x05 \x01(\x05R\x0c\x62ranchFactor\x12\x1d\n\nchunk_size\x18\x06 \x01(\x05R\tchunkSize\x12\x35\n\x17\x64\x61tapoints_per_365_days\x18\x07 \x01(\x03R\x14\x64\x61tapointsPer365Days\"\x84\x01\n\tTimeChunk\x12=\n\rtime_interval\x18\x01 \x01(\x0b\x32\x18.tilebox.v1.TimeIntervalR\x0ctimeInterval\x12\x38\n\nchunk_size\x18\x02 \x01(\x0b\x32\x19.google.protobuf.DurationR\tchunkSizeBt\n\x0f\x63om.datasets.v1B\x0fTimeseriesProtoP\x01\xa2\x02\x03\x44XX\xaa\x02\x0b\x44\x61tasets.V1\xca\x02\x0b\x44\x61tasets\\V1\xe2\x02\x17\x44\x61tasets\\V1\\GPBMetadata\xea\x02\x0c\x44\x61tasets::V1\x92\x03\x02\x08\x02\x62\x08\x65\x64itionsp\xe8\x07') - -_globals = globals() -_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) -_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'datasets.v1.timeseries_pb2', _globals) -if not _descriptor._USE_C_DESCRIPTORS: - _globals['DESCRIPTOR']._loaded_options = None - _globals['DESCRIPTOR']._serialized_options = b'\n\017com.datasets.v1B\017TimeseriesProtoP\001\242\002\003DXX\252\002\013Datasets.V1\312\002\013Datasets\\V1\342\002\027Datasets\\V1\\GPBMetadata\352\002\014Datasets::V1\222\003\002\010\002' - _globals['_TIMESERIESDATASETCHUNK']._serialized_start=123 - _globals['_TIMESERIESDATASETCHUNK']._serialized_end=504 - _globals['_TIMECHUNK']._serialized_start=507 - _globals['_TIMECHUNK']._serialized_end=639 -# @@protoc_insertion_point(module_scope) diff --git a/tilebox-datasets/tilebox/datasets/datasets/v1/timeseries_pb2.pyi b/tilebox-datasets/tilebox/datasets/datasets/v1/timeseries_pb2.pyi deleted file mode 100644 index 2dc70a1..0000000 --- a/tilebox-datasets/tilebox/datasets/datasets/v1/timeseries_pb2.pyi +++ /dev/null @@ -1,35 +0,0 @@ -from google.protobuf import duration_pb2 as _duration_pb2 -from tilebox.datasets.tilebox.v1 import id_pb2 as _id_pb2 -from tilebox.datasets.tilebox.v1 import query_pb2 as _query_pb2 -from google.protobuf import descriptor as _descriptor -from google.protobuf import message as _message -from collections.abc import Mapping as _Mapping -from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union - -DESCRIPTOR: _descriptor.FileDescriptor - -class TimeseriesDatasetChunk(_message.Message): - __slots__ = ("dataset_id", "collection_id", "time_interval", "datapoint_interval", "branch_factor", "chunk_size", "datapoints_per_365_days") - DATASET_ID_FIELD_NUMBER: _ClassVar[int] - COLLECTION_ID_FIELD_NUMBER: _ClassVar[int] - TIME_INTERVAL_FIELD_NUMBER: _ClassVar[int] - DATAPOINT_INTERVAL_FIELD_NUMBER: _ClassVar[int] - BRANCH_FACTOR_FIELD_NUMBER: _ClassVar[int] - CHUNK_SIZE_FIELD_NUMBER: _ClassVar[int] - DATAPOINTS_PER_365_DAYS_FIELD_NUMBER: _ClassVar[int] - dataset_id: _id_pb2.ID - collection_id: _id_pb2.ID - time_interval: _query_pb2.TimeInterval - datapoint_interval: _query_pb2.IDInterval - branch_factor: int - chunk_size: int - datapoints_per_365_days: int - def __init__(self, dataset_id: _Optional[_Union[_id_pb2.ID, _Mapping]] = ..., collection_id: _Optional[_Union[_id_pb2.ID, _Mapping]] = ..., time_interval: _Optional[_Union[_query_pb2.TimeInterval, _Mapping]] = ..., datapoint_interval: _Optional[_Union[_query_pb2.IDInterval, _Mapping]] = ..., branch_factor: _Optional[int] = ..., chunk_size: _Optional[int] = ..., datapoints_per_365_days: _Optional[int] = ...) -> None: ... - -class TimeChunk(_message.Message): - __slots__ = ("time_interval", "chunk_size") - TIME_INTERVAL_FIELD_NUMBER: _ClassVar[int] - CHUNK_SIZE_FIELD_NUMBER: _ClassVar[int] - time_interval: _query_pb2.TimeInterval - chunk_size: _duration_pb2.Duration - def __init__(self, time_interval: _Optional[_Union[_query_pb2.TimeInterval, _Mapping]] = ..., chunk_size: _Optional[_Union[_duration_pb2.Duration, _Mapping]] = ...) -> None: ... diff --git a/tilebox-datasets/tilebox/datasets/datasets/v1/timeseries_pb2_grpc.py b/tilebox-datasets/tilebox/datasets/datasets/v1/timeseries_pb2_grpc.py deleted file mode 100644 index 8a93939..0000000 --- a/tilebox-datasets/tilebox/datasets/datasets/v1/timeseries_pb2_grpc.py +++ /dev/null @@ -1,3 +0,0 @@ -# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! -"""Client and server classes corresponding to protobuf-defined services.""" -import grpc diff --git a/tilebox-datasets/tilebox/datasets/datasets/v1/well_known_types_pb2.py b/tilebox-datasets/tilebox/datasets/datasets/v1/well_known_types_pb2.py deleted file mode 100644 index 40ae9eb..0000000 --- a/tilebox-datasets/tilebox/datasets/datasets/v1/well_known_types_pb2.py +++ /dev/null @@ -1,59 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by the protocol buffer compiler. DO NOT EDIT! -# NO CHECKED-IN PROTOBUF GENCODE -# source: datasets/v1/well_known_types.proto -# Protobuf Python Version: 6.30.0 -"""Generated protocol buffer code.""" -from google.protobuf import descriptor as _descriptor -from google.protobuf import descriptor_pool as _descriptor_pool -from google.protobuf import runtime_version as _runtime_version -from google.protobuf import symbol_database as _symbol_database -from google.protobuf.internal import builder as _builder -_runtime_version.ValidateProtobufRuntimeVersion( - _runtime_version.Domain.PUBLIC, - 6, - 30, - 0, - '', - 'datasets/v1/well_known_types.proto' -) -# @@protoc_insertion_point(imports) - -_sym_db = _symbol_database.Default() - - - - -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\"datasets/v1/well_known_types.proto\x12\x0b\x64\x61tasets.v1\"\x1a\n\x04UUID\x12\x12\n\x04uuid\x18\x01 \x01(\x0cR\x04uuid\"0\n\x04Vec3\x12\x0c\n\x01x\x18\x01 \x01(\x01R\x01x\x12\x0c\n\x01y\x18\x02 \x01(\x01R\x01y\x12\x0c\n\x01z\x18\x03 \x01(\x01R\x01z\"L\n\nQuaternion\x12\x0e\n\x02q1\x18\x01 \x01(\x01R\x02q1\x12\x0e\n\x02q2\x18\x02 \x01(\x01R\x02q2\x12\x0e\n\x02q3\x18\x03 \x01(\x01R\x02q3\x12\x0e\n\x02q4\x18\x04 \x01(\x01R\x02q4\"B\n\x06LatLon\x12\x1a\n\x08latitude\x18\x01 \x01(\x01R\x08latitude\x12\x1c\n\tlongitude\x18\x02 \x01(\x01R\tlongitude\"a\n\tLatLonAlt\x12\x1a\n\x08latitude\x18\x01 \x01(\x01R\x08latitude\x12\x1c\n\tlongitude\x18\x02 \x01(\x01R\tlongitude\x12\x1a\n\x08\x61ltitude\x18\x03 \x01(\x01R\x08\x61ltitude\"\x1c\n\x08Geometry\x12\x10\n\x03wkb\x18\x01 \x01(\x0cR\x03wkb*t\n\x0f\x46lightDirection\x12 \n\x1c\x46LIGHT_DIRECTION_UNSPECIFIED\x10\x00\x12\x1e\n\x1a\x46LIGHT_DIRECTION_ASCENDING\x10\x01\x12\x1f\n\x1b\x46LIGHT_DIRECTION_DESCENDING\x10\x02*~\n\x14ObservationDirection\x12%\n!OBSERVATION_DIRECTION_UNSPECIFIED\x10\x00\x12\x1e\n\x1aOBSERVATION_DIRECTION_LEFT\x10\x01\x12\x1f\n\x1bOBSERVATION_DIRECTION_RIGHT\x10\x02*\x99\x01\n\x10OpendataProvider\x12!\n\x1dOPENDATA_PROVIDER_UNSPECIFIED\x10\x00\x12\x19\n\x15OPENDATA_PROVIDER_ASF\x10\x01\x12*\n&OPENDATA_PROVIDER_COPERNICUS_DATASPACE\x10\x02\x12\x1b\n\x17OPENDATA_PROVIDER_UMBRA\x10\x03*\xf1\x02\n\x0fProcessingLevel\x12 \n\x1cPROCESSING_LEVEL_UNSPECIFIED\x10\x00\x12\x17\n\x13PROCESSING_LEVEL_L0\x10\x0c\x12\x17\n\x13PROCESSING_LEVEL_L1\x10\n\x12\x18\n\x14PROCESSING_LEVEL_L1A\x10\x01\x12\x18\n\x14PROCESSING_LEVEL_L1B\x10\x02\x12\x18\n\x14PROCESSING_LEVEL_L1C\x10\x03\x12\x17\n\x13PROCESSING_LEVEL_L2\x10\x04\x12\x18\n\x14PROCESSING_LEVEL_L2A\x10\x05\x12\x18\n\x14PROCESSING_LEVEL_L2B\x10\x06\x12\x17\n\x13PROCESSING_LEVEL_L3\x10\x07\x12\x18\n\x14PROCESSING_LEVEL_L3A\x10\x08\x12\x17\n\x13PROCESSING_LEVEL_L4\x10\t\x12#\n\x1fPROCESSING_LEVEL_NOT_APPLICABLE\x10\x0b*\x98\x02\n\x0cPolarization\x12\x1c\n\x18POLARIZATION_UNSPECIFIED\x10\x00\x12\x13\n\x0fPOLARIZATION_HH\x10\x01\x12\x13\n\x0fPOLARIZATION_HV\x10\x02\x12\x13\n\x0fPOLARIZATION_VH\x10\x03\x12\x13\n\x0fPOLARIZATION_VV\x10\x04\x12\x18\n\x14POLARIZATION_DUAL_HH\x10\x05\x12\x18\n\x14POLARIZATION_DUAL_HV\x10\x06\x12\x18\n\x14POLARIZATION_DUAL_VH\x10\x07\x12\x18\n\x14POLARIZATION_DUAL_VV\x10\x08\x12\x16\n\x12POLARIZATION_HH_HV\x10\t\x12\x16\n\x12POLARIZATION_VV_VH\x10\n*\xf1\x02\n\x0f\x41\x63quisitionMode\x12 \n\x1c\x41\x43QUISITION_MODE_UNSPECIFIED\x10\x00\x12\x17\n\x13\x41\x43QUISITION_MODE_SM\x10\x01\x12\x17\n\x13\x41\x43QUISITION_MODE_EW\x10\x02\x12\x17\n\x13\x41\x43QUISITION_MODE_IW\x10\x03\x12\x17\n\x13\x41\x43QUISITION_MODE_WV\x10\x04\x12\x1e\n\x1a\x41\x43QUISITION_MODE_SPOTLIGHT\x10\n\x12\x19\n\x15\x41\x43QUISITION_MODE_NOBS\x10\x14\x12\x19\n\x15\x41\x43QUISITION_MODE_EOBS\x10\x15\x12\x19\n\x15\x41\x43QUISITION_MODE_DASC\x10\x16\x12\x19\n\x15\x41\x43QUISITION_MODE_ABSR\x10\x17\x12\x18\n\x14\x41\x43QUISITION_MODE_VIC\x10\x18\x12\x18\n\x14\x41\x43QUISITION_MODE_RAW\x10\x19\x12\x18\n\x14\x41\x43QUISITION_MODE_TST\x10\x1a\x42x\n\x0f\x63om.datasets.v1B\x13WellKnownTypesProtoP\x01\xa2\x02\x03\x44XX\xaa\x02\x0b\x44\x61tasets.V1\xca\x02\x0b\x44\x61tasets\\V1\xe2\x02\x17\x44\x61tasets\\V1\\GPBMetadata\xea\x02\x0c\x44\x61tasets::V1\x92\x03\x02\x08\x02\x62\x08\x65\x64itionsp\xe8\x07') - -_globals = globals() -_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) -_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'datasets.v1.well_known_types_pb2', _globals) -if not _descriptor._USE_C_DESCRIPTORS: - _globals['DESCRIPTOR']._loaded_options = None - _globals['DESCRIPTOR']._serialized_options = b'\n\017com.datasets.v1B\023WellKnownTypesProtoP\001\242\002\003DXX\252\002\013Datasets.V1\312\002\013Datasets\\V1\342\002\027Datasets\\V1\\GPBMetadata\352\002\014Datasets::V1\222\003\002\010\002' - _globals['_FLIGHTDIRECTION']._serialized_start=404 - _globals['_FLIGHTDIRECTION']._serialized_end=520 - _globals['_OBSERVATIONDIRECTION']._serialized_start=522 - _globals['_OBSERVATIONDIRECTION']._serialized_end=648 - _globals['_OPENDATAPROVIDER']._serialized_start=651 - _globals['_OPENDATAPROVIDER']._serialized_end=804 - _globals['_PROCESSINGLEVEL']._serialized_start=807 - _globals['_PROCESSINGLEVEL']._serialized_end=1176 - _globals['_POLARIZATION']._serialized_start=1179 - _globals['_POLARIZATION']._serialized_end=1459 - _globals['_ACQUISITIONMODE']._serialized_start=1462 - _globals['_ACQUISITIONMODE']._serialized_end=1831 - _globals['_UUID']._serialized_start=51 - _globals['_UUID']._serialized_end=77 - _globals['_VEC3']._serialized_start=79 - _globals['_VEC3']._serialized_end=127 - _globals['_QUATERNION']._serialized_start=129 - _globals['_QUATERNION']._serialized_end=205 - _globals['_LATLON']._serialized_start=207 - _globals['_LATLON']._serialized_end=273 - _globals['_LATLONALT']._serialized_start=275 - _globals['_LATLONALT']._serialized_end=372 - _globals['_GEOMETRY']._serialized_start=374 - _globals['_GEOMETRY']._serialized_end=402 -# @@protoc_insertion_point(module_scope) diff --git a/tilebox-datasets/tilebox/datasets/datasets/v1/well_known_types_pb2.pyi b/tilebox-datasets/tilebox/datasets/datasets/v1/well_known_types_pb2.pyi deleted file mode 100644 index 779dc0d..0000000 --- a/tilebox-datasets/tilebox/datasets/datasets/v1/well_known_types_pb2.pyi +++ /dev/null @@ -1,170 +0,0 @@ -from google.protobuf.internal import enum_type_wrapper as _enum_type_wrapper -from google.protobuf import descriptor as _descriptor -from google.protobuf import message as _message -from typing import ClassVar as _ClassVar, Optional as _Optional - -DESCRIPTOR: _descriptor.FileDescriptor - -class FlightDirection(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): - __slots__ = () - FLIGHT_DIRECTION_UNSPECIFIED: _ClassVar[FlightDirection] - FLIGHT_DIRECTION_ASCENDING: _ClassVar[FlightDirection] - FLIGHT_DIRECTION_DESCENDING: _ClassVar[FlightDirection] - -class ObservationDirection(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): - __slots__ = () - OBSERVATION_DIRECTION_UNSPECIFIED: _ClassVar[ObservationDirection] - OBSERVATION_DIRECTION_LEFT: _ClassVar[ObservationDirection] - OBSERVATION_DIRECTION_RIGHT: _ClassVar[ObservationDirection] - -class OpendataProvider(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): - __slots__ = () - OPENDATA_PROVIDER_UNSPECIFIED: _ClassVar[OpendataProvider] - OPENDATA_PROVIDER_ASF: _ClassVar[OpendataProvider] - OPENDATA_PROVIDER_COPERNICUS_DATASPACE: _ClassVar[OpendataProvider] - OPENDATA_PROVIDER_UMBRA: _ClassVar[OpendataProvider] - -class ProcessingLevel(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): - __slots__ = () - PROCESSING_LEVEL_UNSPECIFIED: _ClassVar[ProcessingLevel] - PROCESSING_LEVEL_L0: _ClassVar[ProcessingLevel] - PROCESSING_LEVEL_L1: _ClassVar[ProcessingLevel] - PROCESSING_LEVEL_L1A: _ClassVar[ProcessingLevel] - PROCESSING_LEVEL_L1B: _ClassVar[ProcessingLevel] - PROCESSING_LEVEL_L1C: _ClassVar[ProcessingLevel] - PROCESSING_LEVEL_L2: _ClassVar[ProcessingLevel] - PROCESSING_LEVEL_L2A: _ClassVar[ProcessingLevel] - PROCESSING_LEVEL_L2B: _ClassVar[ProcessingLevel] - PROCESSING_LEVEL_L3: _ClassVar[ProcessingLevel] - PROCESSING_LEVEL_L3A: _ClassVar[ProcessingLevel] - PROCESSING_LEVEL_L4: _ClassVar[ProcessingLevel] - PROCESSING_LEVEL_NOT_APPLICABLE: _ClassVar[ProcessingLevel] - -class Polarization(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): - __slots__ = () - POLARIZATION_UNSPECIFIED: _ClassVar[Polarization] - POLARIZATION_HH: _ClassVar[Polarization] - POLARIZATION_HV: _ClassVar[Polarization] - POLARIZATION_VH: _ClassVar[Polarization] - POLARIZATION_VV: _ClassVar[Polarization] - POLARIZATION_DUAL_HH: _ClassVar[Polarization] - POLARIZATION_DUAL_HV: _ClassVar[Polarization] - POLARIZATION_DUAL_VH: _ClassVar[Polarization] - POLARIZATION_DUAL_VV: _ClassVar[Polarization] - POLARIZATION_HH_HV: _ClassVar[Polarization] - POLARIZATION_VV_VH: _ClassVar[Polarization] - -class AcquisitionMode(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): - __slots__ = () - ACQUISITION_MODE_UNSPECIFIED: _ClassVar[AcquisitionMode] - ACQUISITION_MODE_SM: _ClassVar[AcquisitionMode] - ACQUISITION_MODE_EW: _ClassVar[AcquisitionMode] - ACQUISITION_MODE_IW: _ClassVar[AcquisitionMode] - ACQUISITION_MODE_WV: _ClassVar[AcquisitionMode] - ACQUISITION_MODE_SPOTLIGHT: _ClassVar[AcquisitionMode] - ACQUISITION_MODE_NOBS: _ClassVar[AcquisitionMode] - ACQUISITION_MODE_EOBS: _ClassVar[AcquisitionMode] - ACQUISITION_MODE_DASC: _ClassVar[AcquisitionMode] - ACQUISITION_MODE_ABSR: _ClassVar[AcquisitionMode] - ACQUISITION_MODE_VIC: _ClassVar[AcquisitionMode] - ACQUISITION_MODE_RAW: _ClassVar[AcquisitionMode] - ACQUISITION_MODE_TST: _ClassVar[AcquisitionMode] -FLIGHT_DIRECTION_UNSPECIFIED: FlightDirection -FLIGHT_DIRECTION_ASCENDING: FlightDirection -FLIGHT_DIRECTION_DESCENDING: FlightDirection -OBSERVATION_DIRECTION_UNSPECIFIED: ObservationDirection -OBSERVATION_DIRECTION_LEFT: ObservationDirection -OBSERVATION_DIRECTION_RIGHT: ObservationDirection -OPENDATA_PROVIDER_UNSPECIFIED: OpendataProvider -OPENDATA_PROVIDER_ASF: OpendataProvider -OPENDATA_PROVIDER_COPERNICUS_DATASPACE: OpendataProvider -OPENDATA_PROVIDER_UMBRA: OpendataProvider -PROCESSING_LEVEL_UNSPECIFIED: ProcessingLevel -PROCESSING_LEVEL_L0: ProcessingLevel -PROCESSING_LEVEL_L1: ProcessingLevel -PROCESSING_LEVEL_L1A: ProcessingLevel -PROCESSING_LEVEL_L1B: ProcessingLevel -PROCESSING_LEVEL_L1C: ProcessingLevel -PROCESSING_LEVEL_L2: ProcessingLevel -PROCESSING_LEVEL_L2A: ProcessingLevel -PROCESSING_LEVEL_L2B: ProcessingLevel -PROCESSING_LEVEL_L3: ProcessingLevel -PROCESSING_LEVEL_L3A: ProcessingLevel -PROCESSING_LEVEL_L4: ProcessingLevel -PROCESSING_LEVEL_NOT_APPLICABLE: ProcessingLevel -POLARIZATION_UNSPECIFIED: Polarization -POLARIZATION_HH: Polarization -POLARIZATION_HV: Polarization -POLARIZATION_VH: Polarization -POLARIZATION_VV: Polarization -POLARIZATION_DUAL_HH: Polarization -POLARIZATION_DUAL_HV: Polarization -POLARIZATION_DUAL_VH: Polarization -POLARIZATION_DUAL_VV: Polarization -POLARIZATION_HH_HV: Polarization -POLARIZATION_VV_VH: Polarization -ACQUISITION_MODE_UNSPECIFIED: AcquisitionMode -ACQUISITION_MODE_SM: AcquisitionMode -ACQUISITION_MODE_EW: AcquisitionMode -ACQUISITION_MODE_IW: AcquisitionMode -ACQUISITION_MODE_WV: AcquisitionMode -ACQUISITION_MODE_SPOTLIGHT: AcquisitionMode -ACQUISITION_MODE_NOBS: AcquisitionMode -ACQUISITION_MODE_EOBS: AcquisitionMode -ACQUISITION_MODE_DASC: AcquisitionMode -ACQUISITION_MODE_ABSR: AcquisitionMode -ACQUISITION_MODE_VIC: AcquisitionMode -ACQUISITION_MODE_RAW: AcquisitionMode -ACQUISITION_MODE_TST: AcquisitionMode - -class UUID(_message.Message): - __slots__ = ("uuid",) - UUID_FIELD_NUMBER: _ClassVar[int] - uuid: bytes - def __init__(self, uuid: _Optional[bytes] = ...) -> None: ... - -class Vec3(_message.Message): - __slots__ = ("x", "y", "z") - X_FIELD_NUMBER: _ClassVar[int] - Y_FIELD_NUMBER: _ClassVar[int] - Z_FIELD_NUMBER: _ClassVar[int] - x: float - y: float - z: float - def __init__(self, x: _Optional[float] = ..., y: _Optional[float] = ..., z: _Optional[float] = ...) -> None: ... - -class Quaternion(_message.Message): - __slots__ = ("q1", "q2", "q3", "q4") - Q1_FIELD_NUMBER: _ClassVar[int] - Q2_FIELD_NUMBER: _ClassVar[int] - Q3_FIELD_NUMBER: _ClassVar[int] - Q4_FIELD_NUMBER: _ClassVar[int] - q1: float - q2: float - q3: float - q4: float - def __init__(self, q1: _Optional[float] = ..., q2: _Optional[float] = ..., q3: _Optional[float] = ..., q4: _Optional[float] = ...) -> None: ... - -class LatLon(_message.Message): - __slots__ = ("latitude", "longitude") - LATITUDE_FIELD_NUMBER: _ClassVar[int] - LONGITUDE_FIELD_NUMBER: _ClassVar[int] - latitude: float - longitude: float - def __init__(self, latitude: _Optional[float] = ..., longitude: _Optional[float] = ...) -> None: ... - -class LatLonAlt(_message.Message): - __slots__ = ("latitude", "longitude", "altitude") - LATITUDE_FIELD_NUMBER: _ClassVar[int] - LONGITUDE_FIELD_NUMBER: _ClassVar[int] - ALTITUDE_FIELD_NUMBER: _ClassVar[int] - latitude: float - longitude: float - altitude: float - def __init__(self, latitude: _Optional[float] = ..., longitude: _Optional[float] = ..., altitude: _Optional[float] = ...) -> None: ... - -class Geometry(_message.Message): - __slots__ = ("wkb",) - WKB_FIELD_NUMBER: _ClassVar[int] - wkb: bytes - def __init__(self, wkb: _Optional[bytes] = ...) -> None: ... diff --git a/tilebox-datasets/tilebox/datasets/datasets/v1/well_known_types_pb2_grpc.py b/tilebox-datasets/tilebox/datasets/datasets/v1/well_known_types_pb2_grpc.py deleted file mode 100644 index 8a93939..0000000 --- a/tilebox-datasets/tilebox/datasets/datasets/v1/well_known_types_pb2_grpc.py +++ /dev/null @@ -1,3 +0,0 @@ -# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! -"""Client and server classes corresponding to protobuf-defined services.""" -import grpc diff --git a/tilebox-datasets/tilebox/datasets/message_pool.py b/tilebox-datasets/tilebox/datasets/message_pool.py index 875d612..7cf597a 100644 --- a/tilebox-datasets/tilebox/datasets/message_pool.py +++ b/tilebox-datasets/tilebox/datasets/message_pool.py @@ -1,54 +1,91 @@ -from google.protobuf import descriptor_pb2, duration_pb2, timestamp_pb2 -from google.protobuf.descriptor_pool import Default -from google.protobuf.message import Message -from google.protobuf.message_factory import GetMessageClass, GetMessages +"""Load isolated native schemas, including the imports supported by datasets.""" +from functools import lru_cache + +from protobuf import Message, Registry +from protobuf._codegen import file_desc +from protobuf.wkt.google.protobuf import descriptor_pb, duration_pb, timestamp_pb + +from _tilebox.proto.tilebox.v1 import id_pb 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.stac.v1 import ( + asset_metadata_pb, + asset_pb, + authentication_pb, + core_pb, + processing_pb, + product_pb, + sar_pb, + satellite_pb, + storage_pb, ) -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__ = [ # 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: - try: - # if it was already registered, we should be able to get if from the global message pool - return get_message_type(message_type.type_url) - except KeyError: - # otherwise we need to register it - register_message_types(message_type.descriptor_set) - - # but now we should be able to get it from the global message pool - return get_message_type(message_type.type_url) - - -def register_message_types(descriptor_set: descriptor_pb2.FileDescriptorSet) -> None: - GetMessages(descriptor_set.file, pool=Default()) - - -def get_message_type(type_url: str) -> type[Message]: - return GetMessageClass(Default().FindMessageTypeByName(type_url)) +from tilebox.datasets.v1 import well_known_types_pb + + +@lru_cache(maxsize=1) +def _known_imports() -> Registry: + return Registry( + *( + module.desc() + for module in ( + descriptor_pb, + duration_pb, + timestamp_pb, + id_pb, + well_known_types_pb, + asset_metadata_pb, + asset_pb, + authentication_pb, + core_pb, + processing_pb, + product_pb, + sar_pb, + satellite_pb, + storage_pb, + ) + ) + ) + + +@lru_cache(maxsize=128) +def _load(descriptor_content: bytes, type_url: str) -> type[Message]: + supplied = descriptor_pb.FileDescriptorSet.from_binary(descriptor_content) + files = {file.name: file for file in supplied.file} + registry = Registry(_known_imports()) + visiting: set[str] = set() + visited: set[str] = set() + + def visit(name: str) -> None: + if name in visited: + return + if name in visiting: + raise ValueError(f"Cyclic descriptor dependency: {name}") + file = files.get(name) + if file is None: + if registry.file(name) is None: + raise ValueError(f"Unknown descriptor dependency: {name}") + return + visiting.add(name) + for dependency in file.dependency: + visit(dependency) + visiting.remove(name) + visited.add(name) + dependencies = [] + for dependency in file.dependency: + descriptor = registry.file(dependency) + assert descriptor is not None + dependencies.append(descriptor) + registry.add(file_desc(file.to_binary(), dependencies, {})) + + for name in files: + visit(name) + descriptor = registry.message(type_url.rsplit("/", 1)[-1].removeprefix(".")) + if descriptor is None: + raise ValueError(f"Message type not found in descriptor set: {type_url}") + return descriptor.type + + +def get_message_type(message_type: AnnotatedType) -> type[Message]: + """Resolve the native class cached by descriptor content and type name.""" + type_name = message_type.type_url.rsplit("/", 1)[-1].removeprefix(".") + return _load(message_type.descriptor_set.to_binary(), type_name) diff --git a/tilebox-datasets/tilebox/datasets/protobuf_conversion/field_types.py b/tilebox-datasets/tilebox/datasets/protobuf_conversion/field_types.py index f07810f..7a7d7ef 100644 --- a/tilebox-datasets/tilebox/datasets/protobuf_conversion/field_types.py +++ b/tilebox-datasets/tilebox/datasets/protobuf_conversion/field_types.py @@ -2,26 +2,36 @@ from collections.abc import Sequence from datetime import timedelta from enum import Enum -from typing import Any +from functools import lru_cache +from typing import Any, TypeGuard, TypeVar from uuid import UUID import numpy as np import pandas as pd -from google.protobuf.descriptor import FieldDescriptor -from google.protobuf.duration_pb2 import Duration -from google.protobuf.message import Message -from google.protobuf.timestamp_pb2 import Timestamp from numpy import dtypes as npdtypes from pandas.core.tools.datetimes import DatetimeScalar, to_datetime from pandas.core.tools.timedeltas import to_timedelta -from protobuf import Message as ProtobufPyMessage +from protobuf import ( + DescEnum, + DescField, + DescFieldValueEnum, + DescFieldValueList, + DescFieldValueMessage, + DescFieldValueScalar, + DescMessage, + Message, + ScalarType, +) +from protobuf.wkt.google.protobuf.duration_pb import Duration +from protobuf.wkt.google.protobuf.timestamp_pb import Timestamp from shapely import from_wkb -from tilebox.datasets.datasets.v1.well_known_types_pb2 import UUID as UUIDMessage # noqa: N811 -from tilebox.datasets.datasets.v1.well_known_types_pb2 import Geometry, LatLon, LatLonAlt, Quaternion, Vec3 from tilebox.datasets.schema import _MESSAGE_FIELD_TYPES, Assets +from tilebox.datasets.v1.well_known_types_pb import UUID as UUIDMessage # noqa: N811 +from tilebox.datasets.v1.well_known_types_pb import Geometry, LatLon, LatLonAlt, Quaternion, Vec3 ScalarProtoFieldValue = Message | float | str | bool | bytes +_MessageT = TypeVar("_MessageT", bound=Message) ProtoFieldValue = ScalarProtoFieldValue | Sequence[ScalarProtoFieldValue] | None @@ -71,39 +81,31 @@ def to_proto(self, value: Any) -> ProtoFieldValue | None: return value -class ProtobufPyMessageField(ProtobufFieldType): - """Convert between protobuf wire messages and protobuf-py values.""" +class MessageField(ProtobufFieldType): + """Normalize dynamically loaded values into public native message classes.""" def __init__( self, - wire_type: type[Message], - value_type: type[ProtobufPyMessage[Any]], + value_type: type[Message], *, - read_type: type[ProtobufPyMessage[Any]] | None = None, + read_type: type[Message] | None = None, ) -> None: - """Initialize a protobuf-py field converter. - - Args: - wire_type: Google protobuf message used on the ingestion wire. - value_type: Public protobuf-py message accepted during ingestion. - read_type: Optional subtype returned when decoding query results. - """ + """Initialize the public message class used for conversion.""" super().__init__(object) - self._wire_type = wire_type self._value_type = value_type self._read_type = read_type or value_type - def from_proto(self, value: ProtoFieldValue) -> ProtobufPyMessage[Any]: - if not isinstance(value, self._wire_type): - raise TypeError(f"Expected {self._wire_type.__name__} message but got {type(value)}") - return self._read_type.from_binary(value.SerializeToString()) + def from_proto(self, value: ProtoFieldValue) -> Message: + if not isinstance(value, Message) or value.desc().type_name != self._value_type.desc().type_name: + raise TypeError(f"Expected {self._value_type.__name__} message but got {type(value)}") + return self._read_type.from_binary(value.to_binary()) - def to_proto(self, value: ProtobufPyMessage[Any]) -> Message | None: + def to_proto(self, value: Message) -> Message | None: if is_missing(value): return None if not isinstance(value, self._value_type): raise TypeError(f"Expected {self._value_type.__name__} message but got {type(value)}") - return self._wire_type.FromString(value.to_binary()) + return self._value_type.from_binary(value.to_binary()) class BoolField(ProtobufFieldType): @@ -153,16 +155,15 @@ def __init__(self) -> None: super().__init__("datetime64[ns]") def from_proto(self, value: ProtoFieldValue) -> int: - if not isinstance(value, Timestamp): + if not _is_message(value, Timestamp): raise TypeError(f"Expected Timestamp message but got {type(value)}") - return value.seconds * 10**9 + value.nanos + return value.to_nanos() def to_proto(self, value: DatetimeScalar) -> Timestamp | None: if is_missing(value) or (isinstance(value, np.datetime64) and np.isnat(value)): return None # we use pandas to_datetime function to handle a variety of input types that can be coerced to datetimes - seconds, nanos = divmod(to_datetime(value, utc=True).value, 10**9) - return Timestamp(seconds=seconds, nanos=nanos) + return Timestamp.from_nanos(int(to_datetime(value, utc=True).as_unit("ns").value)) class TimeDeltaField(ProtobufFieldType): @@ -170,7 +171,7 @@ def __init__(self) -> None: super().__init__("timedelta64[ns]") def from_proto(self, value: ProtoFieldValue) -> int: - if not isinstance(value, Duration): + if not _is_message(value, Duration): raise TypeError(f"Expected Duration message but got {type(value)}") return value.seconds * 10**9 + value.nanos @@ -179,8 +180,7 @@ def to_proto(self, value: str | float | timedelta | np.timedelta64) -> Duration return None # we use pandas to_timedelta function to handle a variety of input types that can be coerced to timedeltas # pandas accepts np.timedelta64 at runtime, but its overloads omit it. - seconds, nanos = divmod(to_timedelta(value).value, 10**9) # ty: ignore[no-matching-overload] - return Duration(seconds=seconds, nanos=nanos) + return Duration.from_nanos(int(to_timedelta(value).as_unit("ns").value)) # ty: ignore[no-matching-overload] class UUIDField(ProtobufFieldType): @@ -188,7 +188,7 @@ def __init__(self) -> None: super().__init__(" str: - if not isinstance(value, UUIDMessage): + if not _is_message(value, UUIDMessage): raise TypeError(f"Expected UUID message but got {type(value)}") return str(UUID(bytes=value.uuid)) @@ -207,7 +207,7 @@ def __init__(self) -> None: super().__init__(object) def from_proto(self, value: ProtoFieldValue) -> Any: - if not isinstance(value, Geometry): + if not _is_message(value, Geometry): raise TypeError(f"Expected Geometry message but got {type(value)}") return from_wkb(value.wkb) @@ -222,7 +222,7 @@ def __init__(self) -> None: super().__init__(float, value_dim=3, value_dim_meta=("vec3", ["x", "y", "z"])) def from_proto(self, value: ProtoFieldValue) -> tuple[float, float, float]: - if not isinstance(value, Vec3): + if not _is_message(value, Vec3): raise TypeError(f"Expected Vec3 message but got {type(value)}") return value.x, value.y, value.z @@ -237,7 +237,7 @@ def __init__(self) -> None: super().__init__(float, value_dim=4, value_dim_meta=("quaternion", ["q1", "q2", "q3", "q4"])) def from_proto(self, value: ProtoFieldValue) -> tuple[float, float, float, float]: - if not isinstance(value, Quaternion): + if not _is_message(value, Quaternion): raise TypeError(f"Expected Quaternion message but got {type(value)}") return value.q1, value.q2, value.q3, value.q4 @@ -252,7 +252,7 @@ def __init__(self) -> None: super().__init__(float, value_dim=2, value_dim_meta=("latlon", ["latitude", "longitude"])) def from_proto(self, value: ProtoFieldValue) -> tuple[float, float]: - if not isinstance(value, LatLon): + if not _is_message(value, LatLon): raise TypeError(f"Expected LatLon message but got {type(value)}") return value.latitude, value.longitude @@ -267,7 +267,7 @@ def __init__(self) -> None: super().__init__(float, value_dim=3, value_dim_meta=("lat_lon_alt", ["latitude", "longitude", "altitude"])) def from_proto(self, value: ProtoFieldValue) -> tuple[float, float, float]: - if not isinstance(value, LatLonAlt): + if not _is_message(value, LatLonAlt): raise TypeError(f"Expected LatLonAlt message but got {type(value)}") return value.latitude, value.longitude, value.altitude @@ -280,13 +280,20 @@ def to_proto(self, value: tuple[float, float, float]) -> LatLonAlt | None: # A mapping from protobuf field types to the corresponding numpy data types _PROTOBUF_TYPE_TO_NUMPY_TYPE = { - FieldDescriptor.TYPE_DOUBLE: float, - FieldDescriptor.TYPE_FLOAT: np.float32, - FieldDescriptor.TYPE_INT64: int, - FieldDescriptor.TYPE_UINT64: np.uint64, - FieldDescriptor.TYPE_INT32: np.int32, - FieldDescriptor.TYPE_STRING: object, # use object to allow for variable length strings - FieldDescriptor.TYPE_BYTES: object, # use object to allow for variable length bytes + ScalarType.DOUBLE: float, + ScalarType.FLOAT: np.float32, + ScalarType.INT64: np.int64, + ScalarType.SINT64: np.int64, + ScalarType.SFIXED64: np.int64, + ScalarType.UINT64: np.uint64, + ScalarType.FIXED64: np.uint64, + ScalarType.INT32: np.int32, + ScalarType.SINT32: np.int32, + ScalarType.SFIXED32: np.int32, + ScalarType.UINT32: np.uint32, + ScalarType.FIXED32: np.uint32, + ScalarType.STRING: object, + ScalarType.BYTES: object, } @@ -304,12 +311,11 @@ def __str__(self) -> str: "google.protobuf.Timestamp": TimestampField(), "google.protobuf.Duration": TimeDeltaField(), **{ - wire_type.DESCRIPTOR.full_name: ProtobufPyMessageField( - wire_type, + value_type.desc().type_name: MessageField( value_type, read_type=_AssetsDisplay if value_type is Assets else None, ) - for value_type, wire_type in _MESSAGE_FIELD_TYPES.items() + for value_type in _MESSAGE_FIELD_TYPES }, "datasets.v1.UUID": UUIDField(), "datasets.v1.Geometry": GeometryField(), @@ -320,43 +326,60 @@ def __str__(self) -> str: } -def infer_field_type(field: FieldDescriptor) -> ProtobufFieldType: - if field.type == FieldDescriptor.TYPE_MESSAGE: - if field.message_type is None: - raise ValueError("Expected message type for field but got None") - message_name = field.message_type.full_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)) - - if field.type == FieldDescriptor.TYPE_BOOL: - return BoolField() # special handling, since we need to convert numpy bools to python bools - - if field.type not in _PROTOBUF_TYPE_TO_NUMPY_TYPE: - raise ValueError(f"Unsupported field type {field.type}") - - return ProtobufFieldType(_PROTOBUF_TYPE_TO_NUMPY_TYPE[field.type]) - - -def enum_mapping_from_field_descriptor(field: FieldDescriptor) -> dict[int, str]: +def _is_message(value: object, message_type: type[_MessageT]) -> TypeGuard[_MessageT]: + return isinstance(value, Message) and value.desc().type_name == message_type.desc().type_name + + +@lru_cache(maxsize=4096) +def infer_field_type(field: DescField) -> ProtobufFieldType: + """Cache scientific field conversion by native descriptor identity.""" + match field.value: + case DescFieldValueMessage(message=message): + element = message + case DescFieldValueEnum(enum=enum): + element = enum + case DescFieldValueScalar(scalar=scalar): + element = scalar + case DescFieldValueList(element=element): + pass + case _: + raise ValueError(f"Unsupported field: {field.name}") + if isinstance(element, DescMessage): + return _MESSAGE_NAMES_TO_FIELDS.get(element.type_name, ProtobufFieldType(object)) + if isinstance(element, DescEnum): + return EnumField(_enum_mapping(element)) + if element == ScalarType.BOOL: + return BoolField() + return ProtobufFieldType(_PROTOBUF_TYPE_TO_NUMPY_TYPE[element]) + + +def enum_mapping_from_field_descriptor(field: DescField) -> dict[int, str]: """Create a mapping from enum values to their names. Args: field: The field descriptor to create the mapping for. Must be of type FieldDescriptor.TYPE_ENUM. """ - if field.type != FieldDescriptor.TYPE_ENUM: - raise ValueError("Expected field to be of type FieldDescriptor.TYPE_ENUM") + value = field.value + enum = ( + value.element + if isinstance(value, DescFieldValueList) + else value.enum + if isinstance(value, DescFieldValueEnum) + else None + ) + if not isinstance(enum, DescEnum): + raise TypeError("Expected an enum field") + return _enum_mapping(enum) + - if field.enum_type is None: - raise ValueError("Expected enum type for field but got None") +def _enum_mapping(enum: DescEnum) -> dict[int, str]: # remove the enum type prefix from the enum values # e.g. FLIGHT_DIRECTION_ASCENDING of the FlightDirection enum will result in a value of ASCENDING - enum_type_prefix = _camel_to_uppercase(field.enum_type.name) + "_" + enum_type_prefix = _camel_to_uppercase(enum.name) + "_" return { v.number: str(v.name).removeprefix(enum_type_prefix) - for v in field.enum_type.values # noqa: PD011 # enum_type is not a numpy array, even though ruff thinks it is + for v in enum.values # noqa: PD011 } diff --git a/tilebox-datasets/tilebox/datasets/protobuf_conversion/protobuf_xarray.py b/tilebox-datasets/tilebox/datasets/protobuf_conversion/protobuf_xarray.py index e6dda31..bfafa3e 100644 --- a/tilebox-datasets/tilebox/datasets/protobuf_conversion/protobuf_xarray.py +++ b/tilebox-datasets/tilebox/datasets/protobuf_conversion/protobuf_xarray.py @@ -3,14 +3,14 @@ """ import contextlib -from collections.abc import Sequence -from typing import Any, TypeVar, cast +from collections.abc import Callable, Collection, Sequence +from operator import attrgetter +from typing import Any, TypeVar import numpy as np import xarray as xr -from google.protobuf.descriptor import FieldDescriptor -from google.protobuf.message import Message from numpy.typing import NDArray +from protobuf import DescField, DescFieldValueList, DescFieldValueMessage, DescFieldValueSingular, Message from tilebox.datasets.protobuf_conversion.field_types import ( ProtobufFieldType, @@ -22,17 +22,19 @@ class MessageToXarrayConverter: - def __init__(self, initial_capacity: int = 0) -> None: + def __init__(self, initial_capacity: int = 0, *, fields: Collection[str] | None = None) -> None: """ A converter for converting arbitrary protobuf messages to a xarray dataset Args: initial_capacity: The initial capacity (in number of messages) to reserve for the data arrays of each field. If the total number of messages is already known in advance, set it to this number + fields: Only convert these fields when the query projects a subset of the schema. """ self._capacity = initial_capacity self.count = 0 # number of messages already converted - self._converters: dict[str, _FieldConverter] | None = None + self._fields = fields + self._converters: list[tuple[Callable[[Message], ProtoFieldValue], _FieldConverter]] | None = None def convert(self, message: Message) -> None: """ @@ -43,7 +45,7 @@ def convert(self, message: Message) -> None: """ self._ensure_capacity(self.count + 1) if self._converters is None: - self._converters = _create_field_converters(message, buffer_size=self._capacity) + self._converters = _create_field_converters(message, self._capacity, self._fields) self._convert(message) def convert_all(self, messages: list[AnyMessage]) -> None: @@ -58,9 +60,10 @@ def convert_all(self, messages: list[AnyMessage]) -> None: self._ensure_capacity(self.count + len(messages)) if self._converters is None: - self._converters = _create_field_converters(messages[0], buffer_size=self._capacity) - for message in messages: - self._convert(message) + self._converters = _create_field_converters(messages[0], self._capacity, self._fields) + for read, converter in self._converters: + converter.convert_all(self.count, messages, read) + self.count += len(messages) def finalize( self, @@ -88,12 +91,12 @@ def finalize( array_dims = { f"n_{converter._field_name}": converter._array_dim or 0 # noqa: SLF001 - for converter in self._converters.values() + for _, converter in self._converters if isinstance(converter, _ArrayFieldConverter) } array_dim_mapping = _combine_dimension_names(array_dims) - for converter in self._converters.values(): + for _, converter in self._converters: if isinstance(converter, _ArrayFieldConverter): array_dim_name = array_dim_mapping[f"n_{converter._field_name}"][0] # noqa: SLF001 converter.finalize(dataset, self.count, (dim_name, array_dim_name), skip_empty_fields) @@ -116,8 +119,10 @@ def _convert(self, message: Message) -> None: assert self._converters is not None assert self.count < self._capacity - for field, value in message.ListFields(): - self._converters[field.name](self.count, value) + for read, convert in self._converters: + value = read(message) + if value is not None: + convert(self.count, value) self.count += 1 def _ensure_capacity(self, min_capacity: int) -> None: @@ -131,7 +136,7 @@ def _ensure_capacity(self, min_capacity: int) -> None: new_capacity *= 2 # double the buffer size until we are large enough if new_capacity > self._capacity and self._converters is not None: - for converter in self._converters.values(): + for _, converter in self._converters: converter.resize(new_capacity) self._capacity = new_capacity @@ -145,7 +150,7 @@ class _FieldConverter: to a variable in a xarray dataset. """ - def __call__(self, index: int, value: float | str | bool | Message) -> None: + def __call__(self, index: int, value: ProtoFieldValue) -> None: """ Assign the given value coming from a protobuf message to the data array constructed by this field converter. @@ -154,6 +159,13 @@ def __call__(self, index: int, value: float | str | bool | Message) -> None: value: value of the field in the protobuf message """ + def convert_all(self, index: int, messages: Sequence[Message], read: Callable[[Message], ProtoFieldValue]) -> None: + """Append present values while leaving absent values at their fill value.""" + for offset, message in enumerate(messages): + value = read(message) + if value is not None: + self(index + offset, value) + def finalize( self, dataset: xr.Dataset, count: int, dimension_names: tuple[str, ...], skip_if_empty: bool = False ) -> str | None: @@ -204,6 +216,21 @@ def __call__(self, index: int, value: ProtoFieldValue) -> None: value = self._type.from_proto(value) self._data[index, :] = value + def convert_all(self, index: int, messages: Sequence[Message], read: Callable[[Message], ProtoFieldValue]) -> None: + """Assign scalar columns in one NumPy operation instead of one write per row.""" + if self._type.value_dim != 1: + super().convert_all(index, messages, read) + return + values = [] + for message in messages: + value = read(message) + if value is None: + values.append(self._type.fill_value) + else: + self._empty = False + values.append(self._type.from_proto(value)) + self._data[index : index + len(messages), 0] = values + def finalize( self, dataset: xr.Dataset, count: int, dimension_names: tuple[str, ...], skip_if_empty: bool = False ) -> str | None: @@ -311,32 +338,49 @@ def _resize(self) -> None: self._data = data -def _create_field_converters(message: Message, buffer_size: int) -> dict[str, _FieldConverter]: +def _create_field_converters( + message: Message, buffer_size: int, fields: Collection[str] | None +) -> list[tuple[Callable[[Message], ProtoFieldValue], _FieldConverter]]: """ - Create a dictionary mapping from field names to field converters for the given protobuf message descriptor. + Compile field readers and scientific converters once for the selected schema. Args: descriptor: The protobuf message descriptor buffer_size: The size of the data arrays to create by the field converters Returns: - A dictionary mapping from field names to field converters + Field readers and their corresponding converters """ - converters = {} + converters = [] - for field in message.DESCRIPTOR.fields: + for field in message.desc().fields: + if fields is not None and field.name not in fields: + continue # if we have an unsupported field type we will get a ValueError, so we just skip those fields with contextlib.suppress(ValueError): - converter = _create_field_converter(cast(FieldDescriptor, field)) - converters[field.name] = converter + converter = _create_field_converter(field) + converters.append((_field_reader(field), converter)) if buffer_size > 0: - for converter in converters.values(): + for _, converter in converters: converter.resize(buffer_size) return converters -def _create_field_converter(field: FieldDescriptor) -> _FieldConverter: +def _field_reader(field: DescField) -> Callable[[Message], ProtoFieldValue]: + """Use direct attributes where presence is encoded in the value itself.""" + if isinstance(field.value, DescFieldValueSingular) and field.value.oneof is not None: + return lambda message: message[field] if field in message else None # noqa: SIM401 -- Message is not a dict + read = attrgetter(field.local_name) + if isinstance(field.value, DescFieldValueMessage): + return read + if isinstance(field.value, DescFieldValueList): + return lambda message: read(message) or None + # Explicit zero-valued scalars and implicit negative zero still need a presence check. + return lambda message: read(message) if field in message else None + + +def _create_field_converter(field: DescField) -> _FieldConverter: """ Create a field converter for the given protobuf field descriptor. @@ -348,7 +392,7 @@ def _create_field_converter(field: FieldDescriptor) -> _FieldConverter: A field converter for the given protobuf field descriptor """ field_type = infer_field_type(field) - if field.is_repeated: + if isinstance(field.value, DescFieldValueList): return _ArrayFieldConverter(field.name, field_type) return _SimpleFieldConverter(field.name, field_type) diff --git a/tilebox-datasets/tilebox/datasets/protobuf_conversion/to_protobuf.py b/tilebox-datasets/tilebox/datasets/protobuf_conversion/to_protobuf.py index 1326a91..bcb33ca 100644 --- a/tilebox-datasets/tilebox/datasets/protobuf_conversion/to_protobuf.py +++ b/tilebox-datasets/tilebox/datasets/protobuf_conversion/to_protobuf.py @@ -1,12 +1,13 @@ from collections.abc import Collection, Iterable, Iterator, Mapping, Sequence +from functools import lru_cache from typing import Any, TypeVar, cast from uuid import UUID import numpy as np import pandas as pd import xarray as xr -from google.protobuf.descriptor import Descriptor, FieldDescriptor -from google.protobuf.message import Message +from protobuf import DescField as FieldDescriptor +from protobuf import DescFieldValueList, DescFieldValueMessage, DescMessage, Message from tilebox.datasets.protobuf_conversion.field_types import ( ProtobufFieldType, @@ -42,13 +43,18 @@ def to_messages( """ required = set(required_fields or []) ignore = set(ignore_fields or []) - field_descriptors_by_name = cast(Descriptor, message_type.DESCRIPTOR).fields_by_name + field_descriptors_by_name = _fields_by_name(message_type) records = _iter_records(data, field_descriptors_by_name, ignore) return _records_to_messages(records, message_type, field_descriptors_by_name, required, ignore) def marshal_messages(messages: list[Message]) -> list[bytes]: - return [m.SerializeToString(deterministic=True) for m in messages] + return [m.to_binary() for m in messages] + + +@lru_cache(maxsize=128) +def _fields_by_name(message_type: type[Message]) -> dict[str, FieldDescriptor]: + return {field.name: field for field in message_type.desc().fields} def _iter_records( @@ -109,7 +115,7 @@ def _xarray_records( for field_name in validated_names: values = data[field_name].to_numpy() descriptor = descriptors.get(field_name) - if descriptor is not None and descriptor.is_repeated: + if descriptor is not None and isinstance(descriptor.value, DescFieldValueList): field_type = infer_field_type(descriptor) values = trim_trailing_fill_values(values, field_type.fill_value) columns[field_name] = values @@ -210,17 +216,24 @@ def _record_to_message( descriptor = descriptors[field_name] field_type = infer_field_type(descriptor) try: - if descriptor.is_repeated: + if isinstance(descriptor.value, DescFieldValueList): converted = _convert_repeated_value(value, field_type) + element = descriptor.value.element + if isinstance(element, DescMessage): + converted = [element.type.from_binary(cast(Message, item).to_binary()) for item in converted] else: converted = field_type.to_proto(value) + if isinstance(descriptor.value, DescFieldValueMessage) and converted is not None: + converted = descriptor.value.message.type.from_binary(cast(Message, converted).to_binary()) except (TypeError, ValueError) as error: raise type(error)(f"Field {field_name!r}: {error}") from error if converted is None: if field_name in required: raise ValueError(f"Field {field_name!r}: Invalid value for required field") continue - values[field_name] = converted + values[descriptor.local_name] = converted + # Dataset schemas do not support native oneofs, so fields can be passed directly + # to the constructor without grouping oneof members. return message_type(**values) diff --git a/tilebox-datasets/tilebox/datasets/query/expression.py b/tilebox-datasets/tilebox/datasets/query/expression.py index aa6d002..89d12f2 100644 --- a/tilebox-datasets/tilebox/datasets/query/expression.py +++ b/tilebox-datasets/tilebox/datasets/query/expression.py @@ -4,10 +4,11 @@ from math import isfinite from typing import TYPE_CHECKING, ClassVar, NoReturn, TypeAlias -from google.protobuf.duration_pb2 import Duration +from protobuf.wkt.google.protobuf.duration_pb import Duration +from protobuf.wkt.google.protobuf.timestamp_pb import Timestamp -from tilebox.datasets.datasets.v1 import data_access_pb2 from tilebox.datasets.query.time_interval import datetime_to_timestamp, timedelta_to_duration +from tilebox.datasets.v1 import data_access_pb if TYPE_CHECKING: import numpy as np @@ -30,13 +31,13 @@ class Expression: """ def __and__(self, other: "Expression") -> "Expression": - return _combine(data_access_pb2.LOGICAL_OPERATOR_AND, self, other) + return _combine(data_access_pb.LogicalOperator.AND, self, other) def __or__(self, other: "Expression") -> "Expression": - return _combine(data_access_pb2.LOGICAL_OPERATOR_OR, self, other) + return _combine(data_access_pb.LogicalOperator.OR, self, other) def __invert__(self) -> "Expression": - return _Logical(data_access_pb2.LOGICAL_OPERATOR_NOT, (self,)) + return _Logical(data_access_pb.LogicalOperator.NOT, (self,)) def __bool__(self) -> NoReturn: raise TypeError( @@ -44,22 +45,23 @@ def __bool__(self) -> NoReturn: "Use &, |, and ~; chained comparisons are unsupported." ) - def to_message(self) -> data_access_pb2.FilterExpression: + def to_message(self) -> data_access_pb.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": + def from_message(cls, message: data_access_pb.FilterExpression) -> "Expression": """Construct an expression from its protobuf representation.""" - variants = [name for name in ("logical", "comparison", "is_null") if message.HasField(name)] + variants = [name for name in ("logical", "comparison", "is_null") if message.has_field(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"): + assert comparison is not None + if comparison.value is None: 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}") @@ -71,13 +73,15 @@ def from_message(cls, message: data_access_pb2.FilterExpression) -> "Expression" query_value, ) if variant == "is_null": + assert message.is_null is not None return _NullCheck(message.is_null.field_name) logical = message.logical + assert logical is not None 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 logical.operator == data_access_pb.LogicalOperator.NOT: if len(operands) != 1: raise ValueError("NOT requires exactly one operand") elif len(operands) < 2: @@ -96,22 +100,22 @@ class Field: __hash__ = None def __eq__(self, value: object) -> Expression: # ty: ignore[invalid-method-override] - return self._comparison(data_access_pb2.FIELD_COMPARISON_OPERATOR_EQUAL, value) + return self._comparison(data_access_pb.FieldComparisonOperator.EQUAL, value) def __ne__(self, value: object) -> Expression: # ty: ignore[invalid-method-override] - return self._comparison(data_access_pb2.FIELD_COMPARISON_OPERATOR_NOT_EQUAL, value) + return self._comparison(data_access_pb.FieldComparisonOperator.NOT_EQUAL, value) def __lt__(self, value: _QueryScalar) -> Expression: - return self._comparison(data_access_pb2.FIELD_COMPARISON_OPERATOR_LESS_THAN, value) + return self._comparison(data_access_pb.FieldComparisonOperator.LESS_THAN, value) def __le__(self, value: _QueryScalar) -> Expression: - return self._comparison(data_access_pb2.FIELD_COMPARISON_OPERATOR_LESS_THAN_OR_EQUAL, value) + return self._comparison(data_access_pb.FieldComparisonOperator.LESS_THAN_OR_EQUAL, value) def __gt__(self, value: _QueryScalar) -> Expression: - return self._comparison(data_access_pb2.FIELD_COMPARISON_OPERATOR_GREATER_THAN, value) + return self._comparison(data_access_pb.FieldComparisonOperator.GREATER_THAN, value) def __ge__(self, value: _QueryScalar) -> Expression: - return self._comparison(data_access_pb2.FIELD_COMPARISON_OPERATOR_GREATER_THAN_OR_EQUAL, value) + return self._comparison(data_access_pb.FieldComparisonOperator.GREATER_THAN_OR_EQUAL, value) def is_null(self) -> Expression: """Match datapoints where this field is missing or explicitly null.""" @@ -121,7 +125,7 @@ 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: + def _comparison(self, operator: data_access_pb.FieldComparisonOperator, value: object) -> Expression: query_value = _QueryValue.parse(value) _validate_comparison(operator, query_value) return _Comparison(self.name, operator, query_value) @@ -152,8 +156,7 @@ def parse(cls, value: object) -> "_QueryValue": # noqa: C901, PLR0911, PLR0912 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))) + duration = Duration.from_nanos(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): @@ -187,8 +190,8 @@ def parse(cls, value: object) -> "_QueryValue": # noqa: C901, PLR0911, PLR0912 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)] + def from_message(cls, message: data_access_pb.FieldQueryValue) -> "_QueryValue": + variants = [name for name in _QUERY_VALUE_KINDS if message.has_field(name)] if len(variants) != 1: raise ValueError("A field query value must contain exactly one typed literal") kind = variants[0] @@ -199,23 +202,23 @@ def from_message(cls, message: data_access_pb2.FieldQueryValue) -> "_QueryValue" _validate_duration(*value) return cls(kind, value) - def to_message(self) -> data_access_pb2.FieldQueryValue: + def to_message(self) -> data_access_pb.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}, + return data_access_pb.FieldQueryValue( + timestamp_value=Timestamp(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}, + return data_access_pb.FieldQueryValue( + duration_value=Duration(seconds=seconds, nanos=nanos), ) - message = data_access_pb2.FieldQueryValue() + message = data_access_pb.FieldQueryValue() setattr(message, self.kind, self.value) return message @@ -223,12 +226,12 @@ def to_message(self) -> data_access_pb2.FieldQueryValue: @dataclass(frozen=True, slots=True) class _Comparison(Expression): field_name: str - operator: data_access_pb2.FieldComparisonOperator + operator: data_access_pb.FieldComparisonOperator value: _QueryValue - def to_message(self) -> data_access_pb2.FilterExpression: - return data_access_pb2.FilterExpression( - comparison=data_access_pb2.FieldComparison( + def to_message(self) -> data_access_pb.FilterExpression: + return data_access_pb.FilterExpression( + comparison=data_access_pb.FieldComparison( field_name=self.field_name, operator=self.operator, value=self.value.to_message(), @@ -240,27 +243,27 @@ def to_message(self) -> data_access_pb2.FilterExpression: 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), + def to_message(self) -> data_access_pb.FilterExpression: + return data_access_pb.FilterExpression( + is_null=data_access_pb.FieldNullCheck(field_name=self.field_name), ) @dataclass(frozen=True, slots=True) class _Logical(Expression): - operator: data_access_pb2.LogicalOperator + operator: data_access_pb.LogicalOperator operands: tuple[Expression, ...] - def to_message(self) -> data_access_pb2.FilterExpression: - return data_access_pb2.FilterExpression( - logical=data_access_pb2.LogicalExpression( + def to_message(self) -> data_access_pb.FilterExpression: + return data_access_pb.FilterExpression( + logical=data_access_pb.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: +def _combine(operator: data_access_pb.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, ...] = () @@ -277,12 +280,12 @@ def _nanoseconds_to_parts(nanoseconds: int) -> tuple[int, int]: def _validate_comparison( - operator: data_access_pb2.FieldComparisonOperator, + operator: data_access_pb.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, + data_access_pb.FieldComparisonOperator.EQUAL, + data_access_pb.FieldComparisonOperator.NOT_EQUAL, }: raise TypeError("Boolean query values only support == and !=") if value.kind == "double_value": @@ -302,17 +305,17 @@ def _validate_duration(seconds: int, nanos: int) -> None: _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, + data_access_pb.FieldComparisonOperator.EQUAL, + data_access_pb.FieldComparisonOperator.NOT_EQUAL, + data_access_pb.FieldComparisonOperator.LESS_THAN, + data_access_pb.FieldComparisonOperator.LESS_THAN_OR_EQUAL, + data_access_pb.FieldComparisonOperator.GREATER_THAN, + data_access_pb.FieldComparisonOperator.GREATER_THAN_OR_EQUAL, } _LOGICAL_OPERATORS = { - data_access_pb2.LOGICAL_OPERATOR_AND, - data_access_pb2.LOGICAL_OPERATOR_OR, - data_access_pb2.LOGICAL_OPERATOR_NOT, + data_access_pb.LogicalOperator.AND, + data_access_pb.LogicalOperator.OR, + data_access_pb.LogicalOperator.NOT, } _QUERY_VALUE_KINDS = ( "bool_value", diff --git a/tilebox-datasets/tilebox/datasets/query/id_interval.py b/tilebox-datasets/tilebox/datasets/query/id_interval.py index 4ca106b..79e3630 100644 --- a/tilebox-datasets/tilebox/datasets/query/id_interval.py +++ b/tilebox-datasets/tilebox/datasets/query/id_interval.py @@ -1,70 +1,5 @@ -from dataclasses import dataclass -from typing import TypeAlias -from uuid import UUID +"""Compatibility imports for shared ID intervals.""" -from tilebox.datasets.tilebox.v1 import query_pb2 -from tilebox.datasets.uuid import uuid_message_to_uuid, uuid_to_uuid_message +from tilebox.query.id_interval import IDInterval, IDIntervalLike -IDIntervalLike: TypeAlias = "tuple[str, str] | tuple[UUID, UUID] | IDInterval" - - -@dataclass(frozen=True) -class IDInterval: - start_id: UUID - end_id: UUID - start_exclusive: bool - end_inclusive: bool - - @classmethod - def from_message(cls, interval: query_pb2.IDInterval) -> "IDInterval": - return cls( - start_id=uuid_message_to_uuid(interval.start_id), - end_id=uuid_message_to_uuid(interval.end_id), - start_exclusive=interval.start_exclusive, - end_inclusive=interval.end_inclusive, - ) - - def to_message(self) -> query_pb2.IDInterval: - return query_pb2.IDInterval( - start_id=uuid_to_uuid_message(self.start_id), - end_id=uuid_to_uuid_message(self.end_id), - start_exclusive=self.start_exclusive, - end_inclusive=self.end_inclusive, - ) - - @classmethod - def parse(cls, arg: IDIntervalLike, start_exclusive: bool = False, end_inclusive: bool = True) -> "IDInterval": - """ - Convert a variety of input types to a IDInterval. - - Supported input types: - - IDInterval: Return the input as is - - tuple of two UUIDs: Return an IDInterval with start and end id set to the given values - - tuple of two strings: Return an IDInterval with start and end id set to the UUIDs parsed from the given strings - - Args: - arg: The input to convert - start_exclusive: Whether the start id is exclusive - end_inclusive: Whether the end id is inclusive - - Returns: - IDInterval: The parsed ID interval - """ - if isinstance(arg, IDInterval): - return arg - - if isinstance(arg, tuple) and len(arg) == 2: - start, end = arg - if isinstance(start, UUID) and isinstance(end, UUID): - return IDInterval( - start_id=start, end_id=end, start_exclusive=start_exclusive, end_inclusive=end_inclusive - ) - if isinstance(start, str) and isinstance(end, str): - return IDInterval( - start_id=UUID(start), - end_id=UUID(end), - start_exclusive=start_exclusive, - end_inclusive=end_inclusive, - ) - - raise ValueError(f"Failed to convert {arg} ({type(arg)}) to IDInterval") +__all__ = ["IDInterval", "IDIntervalLike"] diff --git a/tilebox-datasets/tilebox/datasets/query/pagination.py b/tilebox-datasets/tilebox/datasets/query/pagination.py index e5791b7..46d5ea2 100644 --- a/tilebox-datasets/tilebox/datasets/query/pagination.py +++ b/tilebox-datasets/tilebox/datasets/query/pagination.py @@ -1,21 +1,5 @@ -from dataclasses import dataclass -from uuid import UUID +"""Compatibility import for shared pagination.""" -from tilebox.datasets.tilebox.v1 import query_pb2 -from tilebox.datasets.uuid import uuid_message_to_optional_uuid, uuid_to_uuid_message +from tilebox.query.pagination import Pagination - -@dataclass -class Pagination: - limit: int | None = None - starting_after: UUID | None = None - - @classmethod - def from_message(cls, page: query_pb2.Pagination | None) -> "Pagination": - if page is None: - return cls() - # convert falsish values (0 or empty string) to None - return cls(limit=page.limit or None, starting_after=uuid_message_to_optional_uuid(page.starting_after)) - - def to_message(self) -> query_pb2.Pagination: - return query_pb2.Pagination(limit=self.limit, starting_after=uuid_to_uuid_message(self.starting_after)) +__all__ = ["Pagination"] diff --git a/tilebox-datasets/tilebox/datasets/query/time_interval.py b/tilebox-datasets/tilebox/datasets/query/time_interval.py index bb02bd0..bdc5d26 100644 --- a/tilebox-datasets/tilebox/datasets/query/time_interval.py +++ b/tilebox-datasets/tilebox/datasets/query/time_interval.py @@ -1,248 +1,31 @@ -from dataclasses import dataclass -from datetime import UTC, datetime, timedelta, timezone -from typing import TYPE_CHECKING, Any, Self, TypeAlias - -from google.protobuf.duration_pb2 import Duration -from google.protobuf.timestamp_pb2 import Timestamp - -from tilebox.datasets.tilebox.v1 import query_pb2 - -if TYPE_CHECKING: - from pandas.core.tools.datetimes import DatetimeScalar - from xarray import DataArray, Dataset - -_SMALLEST_POSSIBLE_TIMEDELTA = timedelta(microseconds=1) -_EPOCH = datetime(1970, 1, 1, tzinfo=UTC) - -# A type alias for the different types that can be used to specify a time interval -TimeIntervalLike: TypeAlias = ( - "DatetimeScalar | tuple[DatetimeScalar, DatetimeScalar] | list[DatetimeScalar] | DataArray | Dataset | TimeInterval" +"""Compatibility imports for shared time intervals and timestamp conversions.""" + +from _tilebox.time import ( + _convert_to_datetime, + datetime_to_timestamp, + datetime_to_us, + duration_to_timedelta, + timedelta_to_duration, + timestamp_to_datetime, + us_to_datetime, +) +from tilebox.query.time_interval import ( + _EMPTY_TIME_INTERVAL, + _SMALLEST_POSSIBLE_TIMEDELTA, + TimeInterval, + TimeIntervalLike, ) -# once we require python >= 3.12 we can replace this with a type statement, which doesn't require a string at all -# type TimeIntervalLike = DatetimeScalar | tuple[DatetimeScalar ... | TimeInterval - - -@dataclass(frozen=True) -class TimeInterval: - """ - A time interval from a given start to a given end time. - Both the start and end time can be exclusive or inclusive. - """ - - start: datetime - end: datetime - - # We use exclusive for start and inclusive for end, because that way when both are false - # we have a half-open interval [start, end) which is the default behaviour we want to achieve. - start_exclusive: bool = False - end_inclusive: bool = False - - def __post_init__(self) -> None: - """Validate the time interval""" - if not isinstance(self.start, datetime): - raise TypeError(f"Start time {self.start} must be a datetime object") - if not isinstance(self.end, datetime): - raise TypeError(f"End time {self.end} must be a datetime object") - - # in case datetime objects are timezone naive, we assume they are in UTC - if self.start.tzinfo is None: - object.__setattr__(self, "start", self.start.replace(tzinfo=UTC)) # since self is frozen - if self.end.tzinfo is None: - object.__setattr__(self, "end", self.end.replace(tzinfo=UTC)) # since self is frozen - - def to_half_open(self) -> "TimeInterval": - """Convert the time interval to a half-open interval [start, end)""" - return TimeInterval( - start=self.start + int(self.start_exclusive) * _SMALLEST_POSSIBLE_TIMEDELTA, - end=self.end + int(self.end_inclusive) * _SMALLEST_POSSIBLE_TIMEDELTA, - start_exclusive=False, - end_inclusive=False, - ) - - def astimezone(self, tzinfo: timezone) -> "TimeInterval": - """Convert start and end time to the given timezone""" - return TimeInterval( - start=self.start.astimezone(tzinfo), - end=self.end.astimezone(tzinfo), - start_exclusive=self.start_exclusive, - end_inclusive=self.end_inclusive, - ) - - def __eq__(self, other: object) -> bool: - """Check whether two intervals are equal""" - if not isinstance(other, TimeInterval): - return False - - a, b = self.to_half_open(), other.to_half_open() - return (a.start, a.end) == (b.start, b.end) - - def __hash__(self) -> int: - """Hash the time interval""" - - # if two intervals are equal, they should have the same hash, so we convert to half-open intervals first - half_open = self.to_half_open() - return hash((half_open.start, half_open.end)) - - def __repr__(self) -> str: - return self.format() - - def format(self, endpoints: bool = True, sep: str = ", ", timespec: str = "milliseconds") -> str: - """Human readable representation of the time interval - - Args: - endpoints: Whether to format as interval using scientific interval notation [start, end]. - "[", "]" for closed intervals and "(", ")" for open intervals. - sep: The separator to use between the start and end of the interval. - timespec: Specifies the number of additional terms of the time to include. Valid options are 'auto', - 'hours', 'minutes', 'seconds', 'milliseconds' and 'microseconds'. - """ - if self == _EMPTY_TIME_INTERVAL: - return "" - - start = self.start.isoformat(timespec=timespec).replace("+00:00", " UTC") - end = self.end.isoformat(timespec=timespec).replace("+00:00", " UTC") - - formatted = f"{start}{sep}{end}" - if endpoints: - start_ch = "[" if not self.start_exclusive else "(" - end_ch = "]" if self.end_inclusive else ")" - formatted = f"{start_ch}{formatted}{end_ch}" - return formatted - - def __str__(self) -> str: - """Human readable representation of the time interval""" - return self.format() - - @classmethod - def parse(cls, arg: TimeIntervalLike) -> "TimeInterval": - """ - Convert a variety of input types to a TimeInterval. - - Supported input types: - - TimeInterval: Return the input as is - - DatetimeScalar: Return a TimeInterval with start and end time set to the same value and the end time inclusive - - tuple of two DatetimeScalar: Return a TimeInterval with start and end time - - xr.DataArray: Return a TimeInterval with start and end time set to the first and last value in the array and - the end time inclusive - - xr.Dataset: Return 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 - - Args: - arg: The input to convert - - Returns: - TimeInterval: The parsed time interval - """ - - if isinstance(arg, TimeInterval): - return arg - - if isinstance(arg, list | tuple) and len(arg) == 2: - start, end = arg - return TimeInterval(start=_convert_to_datetime(start), end=_convert_to_datetime(end)) - - from pandas.core.tools.datetimes import DatetimeScalar # noqa: PLC0415 - - if isinstance(arg, DatetimeScalar | int): - dt = _convert_to_datetime(arg) - return TimeInterval(start=dt, end=dt, start_exclusive=False, end_inclusive=True) - - import numpy as np # noqa: PLC0415 - import xarray as xr # noqa: PLC0415 - - if isinstance(arg, xr.DataArray) and arg.ndim == 1 and arg.size > 0 and arg.dtype == np.dtype("datetime64[ns]"): - start = arg.data[0] - end = arg.data[-1] - return TimeInterval( - start=_convert_to_datetime(start), - end=_convert_to_datetime(end), - start_exclusive=False, - end_inclusive=True, - ) - - if isinstance(arg, xr.Dataset) and "time" in arg.coords: - return cls.parse(arg.time) - - raise ValueError(f"Failed to convert {arg} ({type(arg)}) to TimeInterval)") - - @classmethod - def from_message(cls, interval: query_pb2.TimeInterval) -> Self: - """Convert a TimeInterval protobuf message to a TimeInterval object.""" - - start = timestamp_to_datetime(interval.start_time) - end = timestamp_to_datetime(interval.end_time) - if start == _EPOCH and end == _EPOCH and not interval.start_exclusive and not interval.end_inclusive: - return cls(start, end, start_exclusive=True, end_inclusive=False) - - return cls( - start=start, - end=end, - start_exclusive=interval.start_exclusive, - end_inclusive=interval.end_inclusive, - ) - - def to_message(self) -> query_pb2.TimeInterval: - """Convert a TimeInterval object to a TimeInterval protobuf message.""" - return query_pb2.TimeInterval( - start_time=datetime_to_timestamp(self.start), - end_time=datetime_to_timestamp(self.end), - start_exclusive=self.start_exclusive, - end_inclusive=self.end_inclusive, - ) - - -# A sentinel value to use for time interval that are empty -_EMPTY_TIME_INTERVAL = TimeInterval(_EPOCH, _EPOCH, start_exclusive=True, end_inclusive=False) - - -def _convert_to_datetime(arg: Any) -> datetime: - """Convert the given datetime scalar to a datetime object in the UTC timezone""" - from pandas.core.tools.datetimes import to_datetime # noqa: PLC0415 - - dt: datetime = to_datetime(arg, utc=True).to_pydatetime() - if dt.tzinfo is None: - dt = dt.replace(tzinfo=UTC) - return dt.astimezone(UTC) - - -def timestamp_to_datetime(timestamp: Timestamp) -> datetime: - """Convert a protobuf timestamp to a datetime object.""" - # datetime.fromtimestamp() expects a timestamp in seconds, not microseconds - # if we pass it as a floating point number, we will run into rounding errors - offset = timedelta(seconds=timestamp.seconds, microseconds=timestamp.nanos // 1000) - return _EPOCH + offset - - -def datetime_to_timestamp(dt: datetime) -> Timestamp: - """Convert a datetime object to a protobuf timestamp.""" - if dt.tzinfo is None: - dt = dt.replace(tzinfo=UTC) - # manual epoch offset calculation to avoid rounding errors and support negative timestamps (before 1970) - offset_us = datetime_to_us(dt.astimezone(UTC)) - seconds, us = divmod(offset_us, 10**6) - return Timestamp(seconds=seconds, nanos=us * 10**3) - - -def datetime_to_us(dt: datetime) -> int: - """Convert a datetime object to a timestamp in microseconds since the epoch.""" - offset = dt - _EPOCH - # implementation taken from timedelta.total_seconds() but without dividing by 10**6 at the end to avoid floating - # point rounding errors - return (offset.days * 86400 + offset.seconds) * 10**6 + offset.microseconds - - -def us_to_datetime(us: int) -> datetime: - """Convert a timestamp in microseconds since the epoch to a datetime object.""" - return _EPOCH + timedelta(microseconds=us) - - -def timedelta_to_duration(td: timedelta) -> Duration: - """Convert a timedelta to a duration protobuf message.""" - duration = Duration() - duration.FromTimedelta(td) - return duration - -def duration_to_timedelta(duration: Duration) -> timedelta: - """Convert a duration protobuf message to a timedelta.""" - return timedelta(seconds=duration.seconds, microseconds=duration.nanos // 1000) +__all__ = [ + "_EMPTY_TIME_INTERVAL", + "_SMALLEST_POSSIBLE_TIMEDELTA", + "TimeInterval", + "TimeIntervalLike", + "_convert_to_datetime", + "datetime_to_timestamp", + "datetime_to_us", + "duration_to_timedelta", + "timedelta_to_duration", + "timestamp_to_datetime", + "us_to_datetime", +] diff --git a/tilebox-datasets/tilebox/datasets/schema.py b/tilebox-datasets/tilebox/datasets/schema.py index 64ea0fe..b9291a9 100644 --- a/tilebox-datasets/tilebox/datasets/schema.py +++ b/tilebox-datasets/tilebox/datasets/schema.py @@ -5,24 +5,17 @@ ``{"name": "assets", "type": Assets}``. """ -from typing import Any, TypeAlias, cast +from typing import TypeAlias from uuid import UUID -from google.protobuf.message import Message as WireMessage from protobuf import Message from shapely import Geometry -from tilebox.datasets.datasets.stac.v1.asset_pb import Assets -from tilebox.datasets.datasets.stac.v1.asset_pb2 import Assets as AssetsWire -from tilebox.datasets.datasets.stac.v1.authentication_pb import Authentication -from tilebox.datasets.datasets.stac.v1.authentication_pb2 import Authentication as AuthenticationWire -from tilebox.datasets.datasets.stac.v1.core_pb import Links, Provider -from tilebox.datasets.datasets.stac.v1.core_pb2 import Links as LinksWire -from tilebox.datasets.datasets.stac.v1.core_pb2 import Provider as ProviderWire -from tilebox.datasets.datasets.stac.v1.processing_pb import ProcessingSoftware -from tilebox.datasets.datasets.stac.v1.processing_pb2 import ProcessingSoftware as ProcessingSoftwareWire -from tilebox.datasets.datasets.stac.v1.storage_pb import Storage -from tilebox.datasets.datasets.stac.v1.storage_pb2 import Storage as StorageWire +from tilebox.datasets.stac.v1.asset_pb import Assets +from tilebox.datasets.stac.v1.authentication_pb import Authentication +from tilebox.datasets.stac.v1.core_pb import Links, Provider +from tilebox.datasets.stac.v1.processing_pb import ProcessingSoftware +from tilebox.datasets.stac.v1.storage_pb import Storage MessageFieldType: TypeAlias = ( type[Assets] @@ -40,19 +33,15 @@ ) """A generated message class supported as a scalar or repeated dataset field.""" -_MESSAGE_FIELD_TYPES: dict[type[Message[Any]], type[WireMessage]] = { - Assets: AssetsWire, - Authentication: AuthenticationWire, - Links: LinksWire, - Provider: ProviderWire, - ProcessingSoftware: ProcessingSoftwareWire, - Storage: StorageWire, -} +_MESSAGE_FIELD_TYPES = (Assets, Authentication, Links, Provider, ProcessingSoftware, Storage) -def _wire_message_type(value_type: object) -> type[WireMessage] | None: - """Return the wire class for a supported public message class.""" - return _MESSAGE_FIELD_TYPES.get(cast("type[Message[Any]]", value_type)) +def _message_field_type(value_type: object) -> type[Message] | None: + """Return a supported native message class.""" + for message_type in _MESSAGE_FIELD_TYPES: + if value_type is message_type: + return message_type + return None __all__ = [ diff --git a/tilebox-datasets/tilebox/datasets/service.py b/tilebox-datasets/tilebox/datasets/service.py deleted file mode 100644 index a8f2d69..0000000 --- a/tilebox-datasets/tilebox/datasets/service.py +++ /dev/null @@ -1,326 +0,0 @@ -from typing import Any -from uuid import UUID - -from google.protobuf import timestamp_pb2 -from google.protobuf.descriptor_pb2 import FieldDescriptorProto -from promise import Promise - -from tilebox.datasets.data.collection import CollectionInfo -from tilebox.datasets.data.data_access import QueryFilters -from tilebox.datasets.data.datapoint import AnyMessage, IngestResponse, QueryResultPage -from tilebox.datasets.data.datasets import ( - Dataset, - DatasetKind, - DatasetType, - Field, - FieldAnnotation, - FieldDict, - ListDatasetsResponse, -) -from tilebox.datasets.datasets.v1 import well_known_types_pb2 -from tilebox.datasets.datasets.v1.collections_pb2 import ( - CreateCollectionRequest, - DeleteCollectionRequest, - GetCollectionByNameRequest, - ListCollectionsRequest, -) -from tilebox.datasets.datasets.v1.data_access_pb2 import QueryByIDRequest, QueryRequest -from tilebox.datasets.datasets.v1.data_ingestion_pb2 import DeleteRequest, IngestRequest -from tilebox.datasets.datasets.v1.datasets_pb2 import ( - CreateDatasetRequest, - GetDatasetRequest, - ListDatasetsRequest, - UpdateDatasetRequest, -) -from tilebox.datasets.query.pagination import Pagination -from tilebox.datasets.tilebox.v1 import id_pb2 -from tilebox.datasets.uuid import must_uuid_to_uuid_message, uuid_to_uuid_message - - -class TileboxDatasetService: - def __init__( - self, - dataset_service_stub: Any, - collection_service_stub: Any, - data_access_service_stub: Any, - data_ingestion_service_stub: Any, - ) -> None: - """ - Typed access to the gRPC endpoints of a timeseries dataset. - - Args: - channel: The gRPC channel to use for the service. - """ - self._dataset_service = dataset_service_stub - self._collection_service = collection_service_stub - self._data_access_service = data_access_service_stub - self._data_ingestion_service = data_ingestion_service_stub - - def create_dataset( - self, kind: DatasetKind, code_name: str, name: str, custom_fields: list[FieldDict] - ) -> Promise[Dataset]: - """Create a new dataset. - - Args: - kind: The kind of the dataset. - code_name: The code name of the dataset. - name: The name of the dataset. - fields: The custom fields of the dataset - - Returns: - The created dataset. - """ - dataset_type = DatasetType( - kind, _REQUIRED_FIELDS_PER_DATASET_KIND[kind] + [Field.from_dict(f) for f in custom_fields] - ) - req = CreateDatasetRequest(name=name, type=dataset_type.to_message(), code_name=code_name) - return Promise.resolve(self._dataset_service.CreateDataset(req)).then(Dataset.from_message) - - def update_dataset( - self, kind: DatasetKind, dataset_id: UUID, name: str | None, custom_fields: list[FieldDict] - ) -> Promise[Dataset]: - """Update a dataset. - - Args: - kind: The kind of the dataset to update, cannot be changed. - dataset_id: The id of the dataset to update, cannot be changed. - name: The new name of the dataset. - custom_fields: The new list of custom fields of the dataset. - - Returns: - The updated dataset. - """ - dataset_type = DatasetType( - kind, _REQUIRED_FIELDS_PER_DATASET_KIND[kind] + [Field.from_dict(f) for f in custom_fields] - ) - req = UpdateDatasetRequest(id=uuid_to_uuid_message(dataset_id), name=name, type=dataset_type.to_message()) - return Promise.resolve(self._dataset_service.UpdateDataset(req)).then(Dataset.from_message) - - def create_or_update_dataset( - self, kind: DatasetKind, code_name: str, name: str, custom_fields: list[FieldDict] - ) -> Promise[Dataset]: - """Create a new dataset, or update it if it already exists. - - Args: - kind: The kind of the dataset. - code_name: The code name of the dataset. - name: The name of the dataset. - custom_fields: The custom fields of the dataset - - Returns: - The created or updated dataset. - """ - return ( - Promise.resolve(self._dataset_service.GetDataset(GetDatasetRequest(slug=code_name))) - .then( - did_fulfill=lambda dataset: self.update_dataset( - kind, Dataset.from_message(dataset).id, name, custom_fields - ), - did_reject=lambda _: self.create_dataset(kind, code_name, name, custom_fields), - ) - .then(Dataset.from_message) - ) - - def list_datasets(self) -> Promise[ListDatasetsResponse]: - """List all datasets and dataset groups.""" - return Promise.resolve(self._dataset_service.ListDatasets(ListDatasetsRequest())).then( - ListDatasetsResponse.from_message - ) - - def get_dataset_by_id(self, dataset_id: UUID) -> Promise[Dataset]: - """Get a dataset by its id.""" - return Promise.resolve( - self._dataset_service.GetDataset(GetDatasetRequest(id=uuid_to_uuid_message(dataset_id))) - ).then(Dataset.from_message) - - def get_dataset_by_slug(self, slug: str) -> Promise[Dataset]: - """Get a dataset by its id.""" - return Promise.resolve(self._dataset_service.GetDataset(GetDatasetRequest(slug=slug))).then( - Dataset.from_message - ) - - def create_collection(self, dataset_id: UUID, name: str) -> Promise[CollectionInfo]: - """Create a new collection in a dataset. - - Args: - dataset_id: The id of the dataset to create the collection in. - name: The name of the collection to create. - - Returns: - The created collection info. - """ - req = CreateCollectionRequest(dataset_id=uuid_to_uuid_message(dataset_id), name=name) - return Promise.resolve(self._collection_service.CreateCollection(req)).then(CollectionInfo.from_message) - - def delete_collection(self, dataset_id: UUID, collection_id: UUID) -> Promise[None]: - """Delete a collection in a dataset by id. - - Args: - dataset_id: The id of the dataset to delete the collection from. - collection_id: The id of the collection to delete. - """ - req = DeleteCollectionRequest( - dataset_id=uuid_to_uuid_message(dataset_id), collection_id=uuid_to_uuid_message(collection_id) - ) - return Promise.resolve(self._collection_service.DeleteCollection(req)) - - def get_collections( - self, dataset_id: UUID, with_availability: bool = True, with_count: bool = False - ) -> Promise[list[CollectionInfo]]: - """List all available collections in this dataset.""" - req = ListCollectionsRequest( - dataset_id=uuid_to_uuid_message(dataset_id), with_availability=with_availability, with_count=with_count - ) - return Promise.resolve(self._collection_service.ListCollections(req)).then( - lambda response: [CollectionInfo.from_message(collection) for collection in response.data], - ) - - def get_collection_by_name( - self, dataset_id: UUID, collection_name: str, with_availability: bool = True, with_count: bool = False - ) -> Promise[CollectionInfo]: - """Fetch additional metadata about the datapoints in this collection.""" - req = GetCollectionByNameRequest( - collection_name=collection_name, - with_availability=with_availability, - with_count=with_count, - dataset_id=uuid_to_uuid_message(dataset_id), - ) - return Promise.resolve(self._collection_service.GetCollectionByName(req)).then( - CollectionInfo.from_message, - ) - - def query_by_id( - self, - dataset_id: UUID, - collection_ids: list[UUID], - datapoint_id: UUID, - skip_data: bool, - ) -> Promise[AnyMessage]: - req = QueryByIDRequest( - dataset_id=must_uuid_to_uuid_message(dataset_id), - collection_ids=list(map(must_uuid_to_uuid_message, collection_ids)), - id=must_uuid_to_uuid_message(datapoint_id), - skip_data=skip_data, - ) - return Promise.resolve(self._data_access_service.QueryByID(req)).then(AnyMessage.from_message) - - def query( - self, - dataset_id: UUID, - collection_ids: list[UUID], - filters: QueryFilters, - skip_data: bool, - page: Pagination | None = None, - ) -> Promise[QueryResultPage]: - req = QueryRequest( - dataset_id=must_uuid_to_uuid_message(dataset_id), - collection_ids=list(map(must_uuid_to_uuid_message, collection_ids)), - filters=filters.to_message(), - page=page.to_message() if page is not None else None, - skip_data=skip_data, - ) - return Promise.resolve(self._data_access_service.Query(req)).then(QueryResultPage.from_message) - - def ingest(self, collection_id: UUID, values: list[bytes], allow_existing: bool) -> Promise[IngestResponse]: - """Ingest a batch of datapoints into a collection. - - Args: - collection_id: The UUID of the collection to insert the datapoints into. - datapoints: The datapoints to insert. - allow_existing: Whether to allow existing datapoints as part of the request. - - Returns: - The number of datapoints that were ingested as well as the generated ids for those datapoints. - """ - req = IngestRequest( - collection_id=uuid_to_uuid_message(collection_id), - values=values, - allow_existing=allow_existing, - ) - return Promise.resolve(self._data_ingestion_service.Ingest(req)).then( - IngestResponse.from_message, - ) - - def delete(self, collection_id: UUID, datapoints: list[UUID]) -> Promise[int]: - """Delete a batch of datapoints from a collection. - - Args: - collection_id: The UUID of the collection to delete the datapoints from. - datapoints: The datapoints to delete. - - Returns: - The number of datapoints that were deleted. - """ - req = DeleteRequest( - collection_id=uuid_to_uuid_message(collection_id), - datapoint_ids=[id_pb2.ID(uuid=datapoint.bytes) for datapoint in datapoints], - ) - return Promise.resolve(self._data_ingestion_service.Delete(req)).then( - lambda response: response.num_deleted, - ) - - -_time_field = Field( - descriptor=FieldDescriptorProto( - name="time", - label=FieldDescriptorProto.Label.LABEL_OPTIONAL, - type=FieldDescriptorProto.TYPE_MESSAGE, - type_name=f".{timestamp_pb2.Timestamp.DESCRIPTOR.full_name}", - ), - annotation=FieldAnnotation( - description="The timestamp associated with each data point.", - example_value="2022-10-17T14:35:28Z", - source_json_pointer="/properties/datetime", - json_schema_ref=( - "https://schemas.stacspec.org/v1.1.0/item-spec/json-schema/datetime.json#/properties/datetime" - ), - ), -) - -_id_field = Field( - descriptor=FieldDescriptorProto( - name="id", - label=FieldDescriptorProto.Label.LABEL_OPTIONAL, - type=FieldDescriptorProto.TYPE_MESSAGE, - type_name=f".{well_known_types_pb2.UUID.DESCRIPTOR.full_name}", - ), - 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", - ), -) - -_ingestion_time_field = Field( - descriptor=FieldDescriptorProto( - name="ingestion_time", - label=FieldDescriptorProto.Label.LABEL_OPTIONAL, - type=FieldDescriptorProto.TYPE_MESSAGE, - type_name=f".{timestamp_pb2.Timestamp.DESCRIPTOR.full_name}", - ), - 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/tilebox:ingestion_time", - ), -) - -_geometry_field = Field( - descriptor=FieldDescriptorProto( - name="geometry", - label=FieldDescriptorProto.Label.LABEL_OPTIONAL, - type=FieldDescriptorProto.TYPE_MESSAGE, - type_name=f".{well_known_types_pb2.Geometry.DESCRIPTOR.full_name}", - ), - annotation=FieldAnnotation( - description="The geometry associated with each data point.", - example_value="POLYGON ((112.345 -36.789, ...))", - source_json_pointer="/geometry", - json_schema_ref="https://geojson.org/schema/Geometry.json", - ), -) - -_REQUIRED_FIELDS_PER_DATASET_KIND: dict[DatasetKind, list[Field]] = { - DatasetKind.TEMPORAL: [_time_field, _id_field, _ingestion_time_field], - DatasetKind.SPATIOTEMPORAL: [_time_field, _id_field, _ingestion_time_field, _geometry_field], -} diff --git a/tilebox-datasets/tilebox/datasets/datasets/stac/v1/asset_metadata_pb.py b/tilebox-datasets/tilebox/datasets/stac/v1/asset_metadata_pb.py similarity index 98% rename from tilebox-datasets/tilebox/datasets/datasets/stac/v1/asset_metadata_pb.py rename to tilebox-datasets/tilebox/datasets/stac/v1/asset_metadata_pb.py index 5c8520f..4bbbe19 100644 --- a/tilebox-datasets/tilebox/datasets/datasets/stac/v1/asset_metadata_pb.py +++ b/tilebox-datasets/tilebox/datasets/stac/v1/asset_metadata_pb.py @@ -1,5 +1,5 @@ # Generated from datasets/stac/v1/asset_metadata.proto. DO NOT EDIT. -# Generated by protoc-gen-py v0.2.0 with parameter "init_files=false". +# Generated by protoc-gen-py v0.6.0 with parameter "init_files=false,map_imports=datasets/**:tilebox,map_imports=workflows/**:tilebox,map_imports=tilebox/**:_tilebox.proto,map_imports=opentelemetry/**:tilebox.workflows._otel". # ruff: noqa: PGH004 # ruff: noqa # fmt: off diff --git a/tilebox-datasets/tilebox/datasets/datasets/stac/v1/asset_pb.py b/tilebox-datasets/tilebox/datasets/stac/v1/asset_pb.py similarity index 97% rename from tilebox-datasets/tilebox/datasets/datasets/stac/v1/asset_pb.py rename to tilebox-datasets/tilebox/datasets/stac/v1/asset_pb.py index a205168..1eea368 100644 --- a/tilebox-datasets/tilebox/datasets/datasets/stac/v1/asset_pb.py +++ b/tilebox-datasets/tilebox/datasets/stac/v1/asset_pb.py @@ -1,5 +1,5 @@ # Generated from datasets/stac/v1/asset.proto. DO NOT EDIT. -# Generated by protoc-gen-py v0.2.0 with parameter "init_files=false". +# Generated by protoc-gen-py v0.6.0 with parameter "init_files=false,map_imports=datasets/**:tilebox,map_imports=workflows/**:tilebox,map_imports=tilebox/**:_tilebox.proto,map_imports=opentelemetry/**:tilebox.workflows._otel". # ruff: noqa: PGH004 # ruff: noqa # fmt: off @@ -10,17 +10,15 @@ from protobuf import Enum, Message from protobuf._codegen import file_desc - -from . import asset_metadata_pb, core_pb, product_pb, sar_pb, satellite_pb +from tilebox.datasets.stac.v1 import asset_metadata_pb, core_pb, product_pb, sar_pb, satellite_pb if TYPE_CHECKING: from protobuf import DescFile - - from .asset_metadata_pb import ClassificationBitField, ClassificationClass, EOProperties, File, Projection, RasterProperties, View - from .core_pb import MediaType - from .product_pb import ProductProperties - from .sar_pb import SARProperties - from .satellite_pb import SatelliteProperties + from tilebox.datasets.stac.v1.asset_metadata_pb import ClassificationBitField, ClassificationClass, EOProperties, File, Projection, RasterProperties, View + from tilebox.datasets.stac.v1.core_pb import MediaType + from tilebox.datasets.stac.v1.product_pb import ProductProperties + from tilebox.datasets.stac.v1.sar_pb import SARProperties + from tilebox.datasets.stac.v1.satellite_pb import SatelliteProperties _StatisticsFields: TypeAlias = Literal["minimum", "maximum"] diff --git a/tilebox-datasets/tilebox/datasets/datasets/stac/v1/authentication_pb.py b/tilebox-datasets/tilebox/datasets/stac/v1/authentication_pb.py similarity index 97% rename from tilebox-datasets/tilebox/datasets/datasets/stac/v1/authentication_pb.py rename to tilebox-datasets/tilebox/datasets/stac/v1/authentication_pb.py index 2e72fc9..c18edd3 100644 --- a/tilebox-datasets/tilebox/datasets/datasets/stac/v1/authentication_pb.py +++ b/tilebox-datasets/tilebox/datasets/stac/v1/authentication_pb.py @@ -1,5 +1,5 @@ # Generated from datasets/stac/v1/authentication.proto. DO NOT EDIT. -# Generated by protoc-gen-py v0.2.0 with parameter "init_files=false". +# Generated by protoc-gen-py v0.6.0 with parameter "init_files=false,map_imports=datasets/**:tilebox,map_imports=workflows/**:tilebox,map_imports=tilebox/**:_tilebox.proto,map_imports=opentelemetry/**:tilebox.workflows._otel". # ruff: noqa: PGH004 # ruff: noqa # fmt: off @@ -10,11 +10,11 @@ from protobuf import Enum, Message from protobuf._codegen import file_desc -from protobuf.wkt import struct_pb +from protobuf.wkt.google.protobuf import struct_pb if TYPE_CHECKING: from protobuf import DescFile - from protobuf.wkt import Struct + from protobuf.wkt.google.protobuf.struct_pb import Struct _AuthenticationFields: TypeAlias = Literal["schemes"] diff --git a/tilebox-datasets/tilebox/datasets/datasets/stac/v1/core_pb.py b/tilebox-datasets/tilebox/datasets/stac/v1/core_pb.py similarity index 98% rename from tilebox-datasets/tilebox/datasets/datasets/stac/v1/core_pb.py rename to tilebox-datasets/tilebox/datasets/stac/v1/core_pb.py index 777faae..dd34ca1 100644 --- a/tilebox-datasets/tilebox/datasets/datasets/stac/v1/core_pb.py +++ b/tilebox-datasets/tilebox/datasets/stac/v1/core_pb.py @@ -1,5 +1,5 @@ # Generated from datasets/stac/v1/core.proto. DO NOT EDIT. -# Generated by protoc-gen-py v0.2.0 with parameter "init_files=false". +# Generated by protoc-gen-py v0.6.0 with parameter "init_files=false,map_imports=datasets/**:tilebox,map_imports=workflows/**:tilebox,map_imports=tilebox/**:_tilebox.proto,map_imports=opentelemetry/**:tilebox.workflows._otel". # ruff: noqa: PGH004 # ruff: noqa # fmt: off diff --git a/tilebox-datasets/tilebox/datasets/datasets/stac/v1/processing_pb.py b/tilebox-datasets/tilebox/datasets/stac/v1/processing_pb.py similarity index 87% rename from tilebox-datasets/tilebox/datasets/datasets/stac/v1/processing_pb.py rename to tilebox-datasets/tilebox/datasets/stac/v1/processing_pb.py index 40d11bb..22dd786 100644 --- a/tilebox-datasets/tilebox/datasets/datasets/stac/v1/processing_pb.py +++ b/tilebox-datasets/tilebox/datasets/stac/v1/processing_pb.py @@ -1,5 +1,5 @@ # Generated from datasets/stac/v1/processing.proto. DO NOT EDIT. -# Generated by protoc-gen-py v0.2.0 with parameter "init_files=false". +# Generated by protoc-gen-py v0.6.0 with parameter "init_files=false,map_imports=datasets/**:tilebox,map_imports=workflows/**:tilebox,map_imports=tilebox/**:_tilebox.proto,map_imports=opentelemetry/**:tilebox.workflows._otel". # ruff: noqa: PGH004 # ruff: noqa # fmt: off diff --git a/tilebox-datasets/tilebox/datasets/datasets/stac/v1/product_pb.py b/tilebox-datasets/tilebox/datasets/stac/v1/product_pb.py similarity index 93% rename from tilebox-datasets/tilebox/datasets/datasets/stac/v1/product_pb.py rename to tilebox-datasets/tilebox/datasets/stac/v1/product_pb.py index b9d37a7..ecf98ed 100644 --- a/tilebox-datasets/tilebox/datasets/datasets/stac/v1/product_pb.py +++ b/tilebox-datasets/tilebox/datasets/stac/v1/product_pb.py @@ -1,5 +1,5 @@ # Generated from datasets/stac/v1/product.proto. DO NOT EDIT. -# Generated by protoc-gen-py v0.2.0 with parameter "init_files=false". +# Generated by protoc-gen-py v0.6.0 with parameter "init_files=false,map_imports=datasets/**:tilebox,map_imports=workflows/**:tilebox,map_imports=tilebox/**:_tilebox.proto,map_imports=opentelemetry/**:tilebox.workflows._otel". # ruff: noqa: PGH004 # ruff: noqa # fmt: off diff --git a/tilebox-datasets/tilebox/datasets/datasets/stac/v1/sar_pb.py b/tilebox-datasets/tilebox/datasets/stac/v1/sar_pb.py similarity index 97% rename from tilebox-datasets/tilebox/datasets/datasets/stac/v1/sar_pb.py rename to tilebox-datasets/tilebox/datasets/stac/v1/sar_pb.py index c71fe03..1713db1 100644 --- a/tilebox-datasets/tilebox/datasets/datasets/stac/v1/sar_pb.py +++ b/tilebox-datasets/tilebox/datasets/stac/v1/sar_pb.py @@ -1,5 +1,5 @@ # Generated from datasets/stac/v1/sar.proto. DO NOT EDIT. -# Generated by protoc-gen-py v0.2.0 with parameter "init_files=false". +# Generated by protoc-gen-py v0.6.0 with parameter "init_files=false,map_imports=datasets/**:tilebox,map_imports=workflows/**:tilebox,map_imports=tilebox/**:_tilebox.proto,map_imports=opentelemetry/**:tilebox.workflows._otel". # ruff: noqa: PGH004 # ruff: noqa # fmt: off diff --git a/tilebox-datasets/tilebox/datasets/datasets/stac/v1/satellite_pb.py b/tilebox-datasets/tilebox/datasets/stac/v1/satellite_pb.py similarity index 95% rename from tilebox-datasets/tilebox/datasets/datasets/stac/v1/satellite_pb.py rename to tilebox-datasets/tilebox/datasets/stac/v1/satellite_pb.py index e07df68..862aa59 100644 --- a/tilebox-datasets/tilebox/datasets/datasets/stac/v1/satellite_pb.py +++ b/tilebox-datasets/tilebox/datasets/stac/v1/satellite_pb.py @@ -1,5 +1,5 @@ # Generated from datasets/stac/v1/satellite.proto. DO NOT EDIT. -# Generated by protoc-gen-py v0.2.0 with parameter "init_files=false". +# Generated by protoc-gen-py v0.6.0 with parameter "init_files=false,map_imports=datasets/**:tilebox,map_imports=workflows/**:tilebox,map_imports=tilebox/**:_tilebox.proto,map_imports=opentelemetry/**:tilebox.workflows._otel". # ruff: noqa: PGH004 # ruff: noqa # fmt: off @@ -10,11 +10,11 @@ from protobuf import Enum, Message from protobuf._codegen import file_desc -from protobuf.wkt import timestamp_pb +from protobuf.wkt.google.protobuf import timestamp_pb if TYPE_CHECKING: from protobuf import DescFile - from protobuf.wkt import Timestamp + from protobuf.wkt.google.protobuf.timestamp_pb import Timestamp _SatelliteOrbitStateVectorFields: TypeAlias = Literal["datetime", "values"] diff --git a/tilebox-datasets/tilebox/datasets/datasets/stac/v1/storage_pb.py b/tilebox-datasets/tilebox/datasets/stac/v1/storage_pb.py similarity index 95% rename from tilebox-datasets/tilebox/datasets/datasets/stac/v1/storage_pb.py rename to tilebox-datasets/tilebox/datasets/stac/v1/storage_pb.py index e6052fc..4d8c16d 100644 --- a/tilebox-datasets/tilebox/datasets/datasets/stac/v1/storage_pb.py +++ b/tilebox-datasets/tilebox/datasets/stac/v1/storage_pb.py @@ -1,5 +1,5 @@ # Generated from datasets/stac/v1/storage.proto. DO NOT EDIT. -# Generated by protoc-gen-py v0.2.0 with parameter "init_files=false". +# Generated by protoc-gen-py v0.6.0 with parameter "init_files=false,map_imports=datasets/**:tilebox,map_imports=workflows/**:tilebox,map_imports=tilebox/**:_tilebox.proto,map_imports=opentelemetry/**:tilebox.workflows._otel". # ruff: noqa: PGH004 # ruff: noqa # fmt: off @@ -10,11 +10,11 @@ from protobuf import Enum, Message from protobuf._codegen import file_desc -from protobuf.wkt import struct_pb +from protobuf.wkt.google.protobuf import struct_pb if TYPE_CHECKING: from protobuf import DescFile - from protobuf.wkt import Value + from protobuf.wkt.google.protobuf.struct_pb import Value _StorageFields: TypeAlias = Literal["schemes"] diff --git a/tilebox-datasets/tilebox/datasets/sync/client.py b/tilebox-datasets/tilebox/datasets/sync/client.py index d7c82dd..284584c 100644 --- a/tilebox-datasets/tilebox/datasets/sync/client.py +++ b/tilebox-datasets/tilebox/datasets/sync/client.py @@ -1,167 +1,101 @@ -import os +"""Synchronous datasets API backed by native Connect clients.""" + +from collections.abc import Sequence +from typing import Any, Self from uuid import UUID -from _tilebox.grpc.channel import ( - ConnectStubAdapter, - Transport, - connect_address, - open_channel, -) -from _tilebox.grpc.error import NotFoundError, with_pythonic_errors -from _tilebox.grpc.public import _PUBLIC_RPC_METHOD_PREFIX -from tilebox.datasets.client import _TILEBOX_API_KEY_ENV_VAR, _TILEBOX_API_URL, _TILEBOX_DEV_API_URL -from tilebox.datasets.client import Client as BaseClient -from tilebox.datasets.data.datasets import DatasetKind, FieldDict -from tilebox.datasets.datasets.v1.collections_pb2_grpc import CollectionServiceStub -from tilebox.datasets.datasets.v1.data_access_pb2_grpc import DataAccessServiceStub -from tilebox.datasets.datasets.v1.data_ingestion_pb2_grpc import DataIngestionServiceStub -from tilebox.datasets.datasets.v1.datasets_pb2_grpc import DatasetServiceStub +from _tilebox.client import Connection, Transport +from _tilebox.errors import NotFoundError +from tilebox.datasets.client import _construct_root_group, _log_server_message, connection_options +from tilebox.datasets.data.datasets import Dataset, DatasetKind, DatasetType, FieldDict, ListDatasetsResponse from tilebox.datasets.group import Group -from tilebox.datasets.notices import print_notice -from tilebox.datasets.service import TileboxDatasetService from tilebox.datasets.sync.dataset import DatasetClient +from tilebox.datasets.uuid import as_uuid, uuid_to_uuid_message +from tilebox.datasets.v1.datasets_connect import DatasetServiceClientSync +from tilebox.datasets.v1.datasets_pb import ( + CreateDatasetRequest, + GetDatasetRequest, + ListDatasetsRequest, + UpdateDatasetRequest, +) class Client: + """Access datasets using blocking Connect RPCs.""" + def __init__( self, *, url: str | None = None, token: str | None = None, warn_if_unauthenticated: bool = True, - transport: Transport = "grpc", + transport: Transport = "http2", + interceptors: Sequence[Any] = (), ) -> None: - """ - Create a Tilebox datasets client. + """Use Connect over HTTP/2 by default, or force HTTP/1 with transport="http1". - Args: - url: Tilebox API URL. If not set, uses the `TILEBOX_API_URL` environment variable, - or defaulting to "https://api.tilebox.com". - token: The API Key to authenticate with. If not set the `TILEBOX_API_KEY` environment variable will be used. - If no token is provided or found, anonymous open data access will be used. - warn_if_unauthenticated: Whether to warn if no API key is provided and the client is used with the default - Tilebox API URL. Defaults to True. - transport: Network transport to use for API requests. Defaults to "grpc". Use "http1" to force - the Connect protocol over HTTP/1.1 for networks that do not support gRPC over HTTP/2 correctly. + The legacy transport="grpc" spelling is an alias for "http2". """ - if url is None: - url = os.environ.get("TILEBOX_API_URL") or _TILEBOX_API_URL - url = url.removesuffix("/") + url, token, prefix = connection_options(url, token, warn_if_unauthenticated) + self._connection = Connection(url, token, prefix, transport=transport, interceptors=interceptors) + self._datasets = self._connection.client(DatasetServiceClientSync) - if token is None: - token = os.environ.get(_TILEBOX_API_KEY_ENV_VAR, None) + def close(self) -> None: + """Release the connection's HTTP resources.""" + self._connection.close() - is_tilebox_deployment = url in (_TILEBOX_API_URL, _TILEBOX_DEV_API_URL) - if token is None and is_tilebox_deployment and warn_if_unauthenticated: - print_notice( - "[yellow]" - "No Tilebox API key detected. Using [bold]anonymous open data access[/bold] without authentication. " - "For higher throughput and rate limits, sign up for a free account at https://console.tilebox.com." - "[/yellow]" - ) - - rpc_method_prefix = _PUBLIC_RPC_METHOD_PREFIX if (is_tilebox_deployment and token is None) else None - match transport: - case "grpc": - channel = open_channel(url, token, rpc_method_prefix=rpc_method_prefix) - dataset_service_stub = with_pythonic_errors(DatasetServiceStub(channel)) - collection_service_stub = with_pythonic_errors(CollectionServiceStub(channel)) - data_access_service_stub = with_pythonic_errors(DataAccessServiceStub(channel)) - data_ingestion_service_stub = with_pythonic_errors(DataIngestionServiceStub(channel)) - case "http1": - from pyqwest import HTTPVersion, SyncClient, SyncHTTPTransport # noqa: PLC0415 + def __enter__(self) -> Self: + return self - from tilebox.datasets.datasets.v1.collections_connect import ( # noqa: PLC0415 - CollectionServiceClientSync, - ) - from tilebox.datasets.datasets.v1.data_access_connect import ( # noqa: PLC0415 - DataAccessServiceClientSync, - ) - from tilebox.datasets.datasets.v1.data_ingestion_connect import ( # noqa: PLC0415 - DataIngestionServiceClientSync, - ) - from tilebox.datasets.datasets.v1.datasets_connect import DatasetServiceClientSync # noqa: PLC0415 - - address = connect_address(url, rpc_method_prefix) - http_client = SyncClient( - SyncHTTPTransport(http_version=HTTPVersion.HTTP1, tls_include_system_certs=True) - ) - headers = None if token is None else {"authorization": f"Bearer {token}"} - dataset_service_stub = ConnectStubAdapter( - DatasetServiceClientSync(address, http_client=http_client), - headers, - rpc_method_prefix, - ) - collection_service_stub = ConnectStubAdapter( - CollectionServiceClientSync(address, http_client=http_client), - headers, - rpc_method_prefix, - ) - data_access_service_stub = ConnectStubAdapter( - DataAccessServiceClientSync(address, http_client=http_client), - headers, - rpc_method_prefix, - ) - data_ingestion_service_stub = ConnectStubAdapter( - DataIngestionServiceClientSync(address, http_client=http_client), - headers, - rpc_method_prefix, - ) - case _: - raise ValueError(f"Unsupported transport: {transport}") - service = TileboxDatasetService( - dataset_service_stub, collection_service_stub, data_access_service_stub, data_ingestion_service_stub - ) - self._client = BaseClient(service) + def __exit__(self, *_: object) -> None: + self.close() def create_or_update_dataset( - self, - kind: DatasetKind, - code_name: str, - fields: list[FieldDict] | None = None, - *, - name: str | None = None, + self, kind: DatasetKind, code_name: str, fields: list[FieldDict] | None = None, *, name: str | None = None ) -> DatasetClient: - """Create a new dataset. - - Args: - kind: The kind of the dataset. - code_name: The code name of the dataset. - fields: The custom fields of the dataset. Supported generated - message field types are available from :mod:`tilebox.datasets.schema`. - name: The name of the dataset. Defaults to the code name. - - Returns: - The created dataset. - """ - + """Create a dataset, or update it if the slug already exists.""" try: - dataset = self.dataset(code_name) + existing = Dataset.from_message(self._datasets.get_dataset(GetDatasetRequest(slug=code_name))) except NotFoundError: - return self._client.create_dataset(kind, code_name, fields or [], name or code_name, DatasetClient).get() - - return self._client.update_dataset( - kind, - dataset._dataset.id, # noqa: SLF001 - fields or [], - name or dataset._dataset.name, # noqa: SLF001 - DatasetClient, - ).get() + dataset = Dataset.from_message( + self._datasets.create_dataset( + CreateDatasetRequest( + code_name=code_name, + name=name or code_name, + type=DatasetType.from_fields(kind, fields or []).to_message(), + ) + ) + ) + else: + dataset = Dataset.from_message( + self._datasets.update_dataset( + UpdateDatasetRequest( + id=uuid_to_uuid_message(existing.id), + name=name or existing.name, + type=DatasetType.from_fields(kind, fields or []).to_message(), + ) + ) + ) + return DatasetClient(self._connection, dataset) def datasets(self) -> Group: - """Fetch all available datasets.""" - return self._client.datasets(DatasetClient).get() + """Fetch the hierarchy of accessible datasets.""" + response = ListDatasetsResponse.from_message(self._datasets.list_datasets(ListDatasetsRequest())) + _log_server_message(response) + return _construct_root_group( + [DatasetClient(self._connection, dataset) for dataset in response.datasets], response.groups + ) def dataset(self, slug: str) -> DatasetClient: - """Get a dataset by its slug, e.g. `open_data.copernicus.sentinel1_sar`. - - Args: - slug: The slug of the dataset. - - Returns: - The dataset if it exists. - """ - return self._client.dataset(slug, DatasetClient).get() + """Fetch a dataset by slug.""" + return DatasetClient( + self._connection, Dataset.from_message(self._datasets.get_dataset(GetDatasetRequest(slug=slug))) + ) def _dataset_by_id(self, dataset_id: str | UUID) -> DatasetClient: - return self._client._dataset_by_id(dataset_id, DatasetClient).get() # noqa: SLF001 + return DatasetClient( + self._connection, + Dataset.from_message( + self._datasets.get_dataset(GetDatasetRequest(id=uuid_to_uuid_message(as_uuid(dataset_id)))) + ), + ) diff --git a/tilebox-datasets/tilebox/datasets/sync/dataset.py b/tilebox-datasets/tilebox/datasets/sync/dataset.py index 92713be..fd07570 100644 --- a/tilebox-datasets/tilebox/datasets/sync/dataset.py +++ b/tilebox-datasets/tilebox/datasets/sync/dataset.py @@ -1,18 +1,22 @@ from collections.abc import Iterator +from dataclasses import replace from functools import partial from uuid import UUID from warnings import warn import xarray as xr +from protobuf import Message from tqdm.auto import tqdm -from _tilebox.grpc.error import ArgumentError, NotFoundError -from _tilebox.grpc.pagination import Pagination as PaginationProtocol -from _tilebox.grpc.pagination import paginated_request -from _tilebox.grpc.producer_consumer import concurrent_producer_consumer +from _tilebox.client import Connection +from _tilebox.concurrency import concurrent_producer_consumer +from _tilebox.errors import ArgumentError, NotFoundError +from _tilebox.pagination import Pagination as PaginationProtocol +from _tilebox.pagination import paginated_request +from _tilebox.proto.tilebox.v1.id_pb import ID from tilebox.datasets.data.collection import Collection, CollectionInfo from tilebox.datasets.data.data_access import QueryFilters, SpatialFilter, SpatialFilterLike -from tilebox.datasets.data.datapoint import QueryResultPage +from tilebox.datasets.data.datapoint import DATAPOINT_METADATA_FIELDS, AnyMessage, IngestResponse, QueryResultPage from tilebox.datasets.data.datasets import Dataset from tilebox.datasets.message_pool import get_message_type from tilebox.datasets.progress import ProgressCallback @@ -28,13 +32,23 @@ 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 -from tilebox.datasets.service import TileboxDatasetService from tilebox.datasets.sync.pagination import ( with_progressbar, with_time_progress_callback, with_time_progressbar, ) -from tilebox.datasets.uuid import as_uuid +from tilebox.datasets.uuid import as_uuid, must_uuid_to_uuid_message, uuid_to_uuid_message +from tilebox.datasets.v1.collections_connect import CollectionServiceClientSync +from tilebox.datasets.v1.collections_pb import ( + CreateCollectionRequest, + DeleteCollectionRequest, + GetCollectionByNameRequest, + ListCollectionsRequest, +) +from tilebox.datasets.v1.data_access_connect import DataAccessServiceClientSync +from tilebox.datasets.v1.data_access_pb import QueryByIDRequest, QueryRequest +from tilebox.datasets.v1.data_ingestion_connect import DataIngestionServiceClientSync +from tilebox.datasets.v1.data_ingestion_pb import DeleteRequest, IngestRequest # allow private member access: we allow it here because we want to make as much private as possible so that we can # minimize the publicly facing API (which allows us to change internals later, and also limits to auto-completion) @@ -46,12 +60,15 @@ class DatasetClient: def __init__( self, - service: TileboxDatasetService, + connection: Connection, dataset: Dataset, ) -> None: - self._service = service + self._collections = connection.client(CollectionServiceClientSync) + self._data_access = connection.client(DataAccessServiceClientSync) + self._data_ingestion = connection.client(DataIngestionServiceClientSync) self.name = dataset.name self._dataset = dataset + self._message_type = get_message_type(dataset.type) def collections(self, availability: bool | None = None, count: bool | None = None) -> dict[str, "CollectionClient"]: """ @@ -79,7 +96,12 @@ def collections(self, availability: bool | None = None, count: bool | None = Non stacklevel=2, ) - collections = self._service.get_collections(self._dataset.id, True, True).get() + response = self._collections.list_collections( + ListCollectionsRequest( + dataset_id=uuid_to_uuid_message(self._dataset.id), with_availability=True, with_count=True + ) + ) + collections = [CollectionInfo.from_message(collection) for collection in response.data] return {collection.collection.name: CollectionClient(self, collection) for collection in collections} @@ -107,7 +129,11 @@ def create_collection(self, name: str) -> "CollectionClient": Returns: The created collection. """ - info = self._service.create_collection(self._dataset.id, name).get() + info = CollectionInfo.from_message( + self._collections.create_collection( + CreateCollectionRequest(dataset_id=uuid_to_uuid_message(self._dataset.id), name=name) + ) + ) return CollectionClient(self, info) def collection(self, name: str) -> "CollectionClient": @@ -120,7 +146,16 @@ def collection(self, name: str) -> "CollectionClient": The collection with the given name. """ try: - info = self._service.get_collection_by_name(self._dataset.id, name, True, True).get() + info = CollectionInfo.from_message( + self._collections.get_collection_by_name( + GetCollectionByNameRequest( + dataset_id=uuid_to_uuid_message(self._dataset.id), + collection_name=name, + with_availability=True, + with_count=True, + ) + ) + ) except NotFoundError: raise NotFoundError(f"No such collection {name}") from None @@ -139,7 +174,11 @@ def delete_collection(self, collection: "str | UUID | CollectionClient") -> None else: # str collection_id = self.collection(collection)._collection.id - self._service.delete_collection(self._dataset.id, collection_id).get() + self._collections.delete_collection( + DeleteCollectionRequest( + dataset_id=uuid_to_uuid_message(self._dataset.id), collection_id=uuid_to_uuid_message(collection_id) + ) + ) def find( self, @@ -162,21 +201,26 @@ def find( """ collection_ids = self._collection_ids(collections) try: - datapoint = self._service.query_by_id( - self._dataset.id, - collection_ids, - as_uuid(datapoint_id), - skip_data, - ).get() + datapoint = AnyMessage.from_message( + self._data_access.query_by_id( + QueryByIDRequest( + dataset_id=must_uuid_to_uuid_message(self._dataset.id), + collection_ids=list(map(must_uuid_to_uuid_message, collection_ids)), + id=must_uuid_to_uuid_message(as_uuid(datapoint_id)), + skip_data=skip_data, + ) + ) + ) except ArgumentError: raise ValueError(f"Invalid datapoint id: {datapoint_id} is not a valid UUID") from None except NotFoundError: raise NotFoundError(f"No such datapoint {datapoint_id}") from None - message_type = get_message_type(datapoint.type_url) - data = message_type.FromString(datapoint.value) + data = self._message_type.from_binary(datapoint.value) - converter = MessageToXarrayConverter(initial_capacity=1) + converter = MessageToXarrayConverter( + initial_capacity=1, fields=DATAPOINT_METADATA_FIELDS if skip_data else None + ) converter.convert(data) return converter.finalize("time", skip_empty_fields=skip_data).isel(time=0) @@ -214,7 +258,7 @@ def query( # noqa: PLR0913 collection_ids = self._collection_ids(collections) pages = _iter_query_pages( - self._service, + self._data_access, self._dataset.id, collection_ids, temporal_extent, @@ -222,6 +266,7 @@ def query( # noqa: PLR0913 skip_data, filter_expression=filter, dataset_name=self.name, + message_type=self._message_type, show_progress=show_progress, ) return _convert_to_dataset(pages, skip_empty_fields=skip_data) @@ -242,7 +287,7 @@ def _collection_ids( if collections is None: return [] - all_collections: list[CollectionInfo] = self._service.get_collections(self._dataset.id, True, True).get() + all_collections = [collection.info() for collection in self.collections().values()] # find all valid collection names and ids collections_by_name = {c.collection.name: c.collection.id for c in all_collections} valid_collection_ids = {c.collection.id for c in all_collections} @@ -318,9 +363,16 @@ def info(self, availability: bool | None = None, count: bool | None = None) -> C if self._info is None: # only load collection info if it hasn't been loaded yet (or it has been invalidated) try: - self._info = self._dataset._service.get_collection_by_name( - self._dataset._dataset.id, self.name, True, True - ).get() + self._info = CollectionInfo.from_message( + self._dataset._collections.get_collection_by_name( + GetCollectionByNameRequest( + dataset_id=uuid_to_uuid_message(self._dataset._dataset.id), + collection_name=self.name, + with_availability=True, + with_count=True, + ) + ) + ) except NotFoundError: raise NotFoundError(f"No such collection {self.name}") from None @@ -338,18 +390,26 @@ def find(self, datapoint_id: str | UUID, skip_data: bool = False) -> xr.Dataset: The datapoint as an xarray dataset """ try: - datapoint = self._dataset._service.query_by_id( - self._dataset._dataset.id, [self._collection.id], as_uuid(datapoint_id), skip_data - ).get() + datapoint = AnyMessage.from_message( + self._dataset._data_access.query_by_id( + QueryByIDRequest( + dataset_id=must_uuid_to_uuid_message(self._dataset._dataset.id), + collection_ids=[must_uuid_to_uuid_message(self._collection.id)], + id=must_uuid_to_uuid_message(as_uuid(datapoint_id)), + skip_data=skip_data, + ) + ) + ) except ArgumentError: raise ValueError(f"Invalid datapoint id: {datapoint_id} is not a valid UUID") from None except NotFoundError: raise NotFoundError(f"No such datapoint {datapoint_id}") from None - message_type = get_message_type(datapoint.type_url) - data = message_type.FromString(datapoint.value) + data = self._dataset._message_type.from_binary(datapoint.value) - converter = MessageToXarrayConverter(initial_capacity=1) + converter = MessageToXarrayConverter( + initial_capacity=1, fields=DATAPOINT_METADATA_FIELDS if skip_data else None + ) converter.convert(data) return converter.finalize("time", skip_empty_fields=skip_data).isel(time=0) @@ -378,12 +438,13 @@ def _find_interval( def request(page: PaginationProtocol) -> QueryResultPage: return _query_page( - self._dataset._service, + self._dataset._data_access, self._dataset._dataset.id, [self._collection.id], filters, skip_data, page, + message_type=self._dataset._message_type, ) initial_page = Pagination() @@ -478,7 +539,7 @@ def query( raise ValueError("A temporal_extent for your query must be specified") pages = _iter_query_pages( - self._dataset._service, + self._dataset._data_access, self._dataset._dataset.id, [self._collection.id], temporal_extent, @@ -486,6 +547,7 @@ def query( skip_data, filter_expression=filter, dataset_name=self._dataset.name, + message_type=self._dataset._message_type, show_progress=show_progress, ) return _convert_to_dataset(pages, skip_empty_fields=skip_data) @@ -499,13 +561,14 @@ def _iter_pages( page_size: int | None = None, ) -> Iterator[QueryResultPage]: return _iter_query_pages( - self._dataset._service, + self._dataset._data_access, self._dataset._dataset.id, [self._collection.id], temporal_extent, spatial_extent, skip_data, dataset_name=self._dataset.name, + message_type=self._dataset._message_type, show_progress=show_progress, page_size=page_size, ) @@ -535,7 +598,7 @@ def ingest( Returns: List of datapoint ids that were ingested. """ - message_type = get_message_type(self._dataset._dataset.type.type_url) + message_type = self._dataset._message_type messages = marshal_messages( to_messages(data, message_type, required_fields=["time"], ignore_fields=["id", "ingestion_time"]) ) @@ -551,7 +614,15 @@ def ingest( ) as progress_bar: for chunk_start in range(0, len(messages), _INGEST_CHUNK_SIZE): chunk = messages[chunk_start : chunk_start + _INGEST_CHUNK_SIZE] - response = self._dataset._service.ingest(self._collection.id, chunk, allow_existing).get() + response = IngestResponse.from_message( + self._dataset._data_ingestion.ingest( + IngestRequest( + collection_id=uuid_to_uuid_message(self._collection.id), + values=chunk, + allow_existing=allow_existing, + ) + ) + ) ingested_ids.extend(response.datapoint_ids) progress_bar.update(len(chunk)) @@ -594,7 +665,12 @@ def delete(self, datapoints: DatapointIDs, *, show_progress: bool | ProgressCall ) as progress_bar: for chunk_start in range(0, len(datapoint_ids), _DELETE_CHUNK_SIZE): chunk = datapoint_ids[chunk_start : chunk_start + _DELETE_CHUNK_SIZE] - num_deleted += self._dataset._service.delete(self._collection.id, chunk).get() + num_deleted += self._dataset._data_ingestion.delete( + DeleteRequest( + collection_id=uuid_to_uuid_message(self._collection.id), + datapoint_ids=[ID(uuid=value.bytes) for value in chunk], + ) + ).num_deleted progress_bar.update(len(chunk)) if callable(show_progress): @@ -604,19 +680,34 @@ def delete(self, datapoints: DatapointIDs, *, show_progress: bool | ProgressCall def _query_page( # noqa: PLR0913, PLR0917 - service: TileboxDatasetService, + service: DataAccessServiceClientSync, dataset_id: UUID, collection_ids: list[UUID] | None, filters: QueryFilters, skip_data: bool, page: PaginationProtocol | None = None, + *, + message_type: type[Message], ) -> QueryResultPage: query_page = Pagination(page.limit, page.starting_after) if page else Pagination() - return service.query(dataset_id, collection_ids or [], filters, skip_data, query_page).get() + return replace( + QueryResultPage.from_message( + service.query( + QueryRequest( + dataset_id=must_uuid_to_uuid_message(dataset_id), + collection_ids=list(map(must_uuid_to_uuid_message, collection_ids or [])), + filters=filters.to_message(), + skip_data=skip_data, + page=query_page.to_message(), + ) + ) + ), + message_type=message_type, + ) def _iter_query_pages( # noqa: PLR0913, PLR0917 - service: TileboxDatasetService, + service: DataAccessServiceClientSync, dataset_id: UUID, collection_ids: list[UUID] | None, temporal_extent: TimeIntervalLike, @@ -625,6 +716,7 @@ def _iter_query_pages( # noqa: PLR0913, PLR0917 *, filter_expression: Expression | None = None, dataset_name: str, + message_type: type[Message], show_progress: bool | ProgressCallback = False, page_size: int | None = None, ) -> Iterator[QueryResultPage]: @@ -635,7 +727,7 @@ def _iter_query_pages( # noqa: PLR0913, PLR0917 filter_expression, ) - request = partial(_query_page, service, dataset_id, collection_ids, filters, skip_data) + request = partial(_query_page, service, dataset_id, collection_ids, filters, skip_data, message_type=message_type) initial_page = Pagination(limit=page_size) pages = paginated_request(request, initial_page) @@ -661,11 +753,14 @@ def _convert_to_dataset(pages: Iterator[QueryResultPage], skip_empty_fields: boo Returns: The datapoints from the individual pages converted and combined into a single xarray dataset """ - converter = MessageToXarrayConverter() + converter = MessageToXarrayConverter(fields=DATAPOINT_METADATA_FIELDS if skip_empty_fields else None) def convert_page(page: QueryResultPage) -> None: - message_type = get_message_type(page.data.type_url) - messages = [message_type.FromString(v) for v in page.data.value] + if not page.data.value: + return + if page.message_type is None: + raise ValueError("Query page is not bound to a dataset schema") + messages = [page.message_type.from_binary(v) for v in page.data.value] converter.convert_all(messages) # lets parse the incoming pages already while we wait for the next page from the server diff --git a/tilebox-datasets/tilebox/datasets/sync/pagination.py b/tilebox-datasets/tilebox/datasets/sync/pagination.py index 46d6346..7229e00 100644 --- a/tilebox-datasets/tilebox/datasets/sync/pagination.py +++ b/tilebox-datasets/tilebox/datasets/sync/pagination.py @@ -16,7 +16,7 @@ def with_progressbar( paginated_request: Iterator[ResultPage], progress_description: str, ) -> Iterator[ResultPage]: - """Make a paginated request to a gRPC service endpoint while displaying the progress in a tqdm progress bar. + """Consume paginated RPC results while displaying progress in a tqdm progress bar. We don't know the total amount of work beforehand, so the progress bar will just show the number of data points that have been downloaded so far. @@ -45,7 +45,7 @@ def with_progressbar( progress_bar.update(first_page.n_datapoints) # one page has already been downloaded progress_bar.start_t = actual_start_time # set the start time to the actual start time before the first page for page in paginated_request: # now loop over the remaining pages - progress_bar.update(first_page.n_datapoints) + progress_bar.update(page.n_datapoints) yield page @@ -54,7 +54,7 @@ def with_time_progressbar( interval: TimeInterval, progress_description: str, ) -> Iterator[ResultPage]: - """Make a paginated request to a gRPC service endpoint while displaying the progress in a tqdm progress bar. + """Consume paginated RPC results while displaying progress in a tqdm progress bar. The given interval is used to estimate a total amount of work for the progress bar. Then the event_time of the latest data point returned in each page is used to update the progress bar accordingly. @@ -108,7 +108,7 @@ def with_time_progress_callback( interval: TimeInterval, progress_callback: ProgressCallback, ) -> Iterator[ResultPage]: - """Make a paginated request to a gRPC service endpoint, and reporting progress percentage to a callback function. + """Consume paginated RPC results and report progress percentages to a callback. The given interval is used to estimate a total amount of work for the progress bar. Then the event_time of the latest data point returned in each page is used to calculate progress percentage. diff --git a/tilebox-datasets/tilebox/datasets/tilebox/v1/id_pb2.py b/tilebox-datasets/tilebox/datasets/tilebox/v1/id_pb2.py deleted file mode 100644 index e2e8d78..0000000 --- a/tilebox-datasets/tilebox/datasets/tilebox/v1/id_pb2.py +++ /dev/null @@ -1,37 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by the protocol buffer compiler. DO NOT EDIT! -# NO CHECKED-IN PROTOBUF GENCODE -# source: tilebox/v1/id.proto -# Protobuf Python Version: 6.30.0 -"""Generated protocol buffer code.""" -from google.protobuf import descriptor as _descriptor -from google.protobuf import descriptor_pool as _descriptor_pool -from google.protobuf import runtime_version as _runtime_version -from google.protobuf import symbol_database as _symbol_database -from google.protobuf.internal import builder as _builder -_runtime_version.ValidateProtobufRuntimeVersion( - _runtime_version.Domain.PUBLIC, - 6, - 30, - 0, - '', - 'tilebox/v1/id.proto' -) -# @@protoc_insertion_point(imports) - -_sym_db = _symbol_database.Default() - - - - -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x13tilebox/v1/id.proto\x12\ntilebox.v1\"\x18\n\x02ID\x12\x12\n\x04uuid\x18\x01 \x01(\x0cR\x04uuidBg\n\x0e\x63om.tilebox.v1B\x07IdProtoP\x01\xa2\x02\x03TXX\xaa\x02\nTilebox.V1\xca\x02\nTilebox\\V1\xe2\x02\x16Tilebox\\V1\\GPBMetadata\xea\x02\x0bTilebox::V1\x92\x03\x02\x08\x02\x62\x08\x65\x64itionsp\xe8\x07') - -_globals = globals() -_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) -_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'tilebox.v1.id_pb2', _globals) -if not _descriptor._USE_C_DESCRIPTORS: - _globals['DESCRIPTOR']._loaded_options = None - _globals['DESCRIPTOR']._serialized_options = b'\n\016com.tilebox.v1B\007IdProtoP\001\242\002\003TXX\252\002\nTilebox.V1\312\002\nTilebox\\V1\342\002\026Tilebox\\V1\\GPBMetadata\352\002\013Tilebox::V1\222\003\002\010\002' - _globals['_ID']._serialized_start=35 - _globals['_ID']._serialized_end=59 -# @@protoc_insertion_point(module_scope) diff --git a/tilebox-datasets/tilebox/datasets/tilebox/v1/id_pb2.pyi b/tilebox-datasets/tilebox/datasets/tilebox/v1/id_pb2.pyi deleted file mode 100644 index 2cc30bd..0000000 --- a/tilebox-datasets/tilebox/datasets/tilebox/v1/id_pb2.pyi +++ /dev/null @@ -1,11 +0,0 @@ -from google.protobuf import descriptor as _descriptor -from google.protobuf import message as _message -from typing import ClassVar as _ClassVar, Optional as _Optional - -DESCRIPTOR: _descriptor.FileDescriptor - -class ID(_message.Message): - __slots__ = ("uuid",) - UUID_FIELD_NUMBER: _ClassVar[int] - uuid: bytes - def __init__(self, uuid: _Optional[bytes] = ...) -> None: ... diff --git a/tilebox-datasets/tilebox/datasets/tilebox/v1/id_pb2_grpc.py b/tilebox-datasets/tilebox/datasets/tilebox/v1/id_pb2_grpc.py deleted file mode 100644 index 8a93939..0000000 --- a/tilebox-datasets/tilebox/datasets/tilebox/v1/id_pb2_grpc.py +++ /dev/null @@ -1,3 +0,0 @@ -# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! -"""Client and server classes corresponding to protobuf-defined services.""" -import grpc diff --git a/tilebox-datasets/tilebox/datasets/tilebox/v1/query_pb2.py b/tilebox-datasets/tilebox/datasets/tilebox/v1/query_pb2.py deleted file mode 100644 index 0319551..0000000 --- a/tilebox-datasets/tilebox/datasets/tilebox/v1/query_pb2.py +++ /dev/null @@ -1,49 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by the protocol buffer compiler. DO NOT EDIT! -# NO CHECKED-IN PROTOBUF GENCODE -# source: tilebox/v1/query.proto -# Protobuf Python Version: 6.30.0 -"""Generated protocol buffer code.""" -from google.protobuf import descriptor as _descriptor -from google.protobuf import descriptor_pool as _descriptor_pool -from google.protobuf import runtime_version as _runtime_version -from google.protobuf import symbol_database as _symbol_database -from google.protobuf.internal import builder as _builder -_runtime_version.ValidateProtobufRuntimeVersion( - _runtime_version.Domain.PUBLIC, - 6, - 30, - 0, - '', - 'tilebox/v1/query.proto' -) -# @@protoc_insertion_point(imports) - -_sym_db = _symbol_database.Default() - - -from google.protobuf import timestamp_pb2 as google_dot_protobuf_dot_timestamp__pb2 -from tilebox.datasets.tilebox.v1 import id_pb2 as tilebox_dot_v1_dot_id__pb2 - - -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x16tilebox/v1/query.proto\x12\ntilebox.v1\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\x13tilebox/v1/id.proto\"\xce\x01\n\x0cTimeInterval\x12\x39\n\nstart_time\x18\x01 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\tstartTime\x12\x35\n\x08\x65nd_time\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\x07\x65ndTime\x12\'\n\x0fstart_exclusive\x18\x03 \x01(\x08R\x0estartExclusive\x12#\n\rend_inclusive\x18\x04 \x01(\x08R\x0c\x65ndInclusive\"\xac\x01\n\nIDInterval\x12)\n\x08start_id\x18\x01 \x01(\x0b\x32\x0e.tilebox.v1.IDR\x07startId\x12%\n\x06\x65nd_id\x18\x02 \x01(\x0b\x32\x0e.tilebox.v1.IDR\x05\x65ndId\x12\'\n\x0fstart_exclusive\x18\x03 \x01(\x08R\x0estartExclusive\x12#\n\rend_inclusive\x18\x04 \x01(\x08R\x0c\x65ndInclusive\"g\n\nPagination\x12\x1b\n\x05limit\x18\x01 \x01(\x03\x42\x05\xaa\x01\x02\x08\x01R\x05limit\x12<\n\x0estarting_after\x18\x02 \x01(\x0b\x32\x0e.tilebox.v1.IDB\x05\xaa\x01\x02\x08\x01R\rstartingAfter*l\n\rSortDirection\x12\x1e\n\x1aSORT_DIRECTION_UNSPECIFIED\x10\x00\x12\x1c\n\x18SORT_DIRECTION_ASCENDING\x10\x01\x12\x1d\n\x19SORT_DIRECTION_DESCENDING\x10\x02\x42j\n\x0e\x63om.tilebox.v1B\nQueryProtoP\x01\xa2\x02\x03TXX\xaa\x02\nTilebox.V1\xca\x02\nTilebox\\V1\xe2\x02\x16Tilebox\\V1\\GPBMetadata\xea\x02\x0bTilebox::V1\x92\x03\x02\x08\x02\x62\x08\x65\x64itionsp\xe8\x07') - -_globals = globals() -_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) -_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'tilebox.v1.query_pb2', _globals) -if not _descriptor._USE_C_DESCRIPTORS: - _globals['DESCRIPTOR']._loaded_options = None - _globals['DESCRIPTOR']._serialized_options = b'\n\016com.tilebox.v1B\nQueryProtoP\001\242\002\003TXX\252\002\nTilebox.V1\312\002\nTilebox\\V1\342\002\026Tilebox\\V1\\GPBMetadata\352\002\013Tilebox::V1\222\003\002\010\002' - _globals['_PAGINATION'].fields_by_name['limit']._loaded_options = None - _globals['_PAGINATION'].fields_by_name['limit']._serialized_options = b'\252\001\002\010\001' - _globals['_PAGINATION'].fields_by_name['starting_after']._loaded_options = None - _globals['_PAGINATION'].fields_by_name['starting_after']._serialized_options = b'\252\001\002\010\001' - _globals['_SORTDIRECTION']._serialized_start=581 - _globals['_SORTDIRECTION']._serialized_end=689 - _globals['_TIMEINTERVAL']._serialized_start=93 - _globals['_TIMEINTERVAL']._serialized_end=299 - _globals['_IDINTERVAL']._serialized_start=302 - _globals['_IDINTERVAL']._serialized_end=474 - _globals['_PAGINATION']._serialized_start=476 - _globals['_PAGINATION']._serialized_end=579 -# @@protoc_insertion_point(module_scope) diff --git a/tilebox-datasets/tilebox/datasets/tilebox/v1/query_pb2.pyi b/tilebox-datasets/tilebox/datasets/tilebox/v1/query_pb2.pyi deleted file mode 100644 index 342117b..0000000 --- a/tilebox-datasets/tilebox/datasets/tilebox/v1/query_pb2.pyi +++ /dev/null @@ -1,50 +0,0 @@ -from google.protobuf import timestamp_pb2 as _timestamp_pb2 -from tilebox.datasets.tilebox.v1 import id_pb2 as _id_pb2 -from google.protobuf.internal import enum_type_wrapper as _enum_type_wrapper -from google.protobuf import descriptor as _descriptor -from google.protobuf import message as _message -from collections.abc import Mapping as _Mapping -from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union - -DESCRIPTOR: _descriptor.FileDescriptor - -class SortDirection(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): - __slots__ = () - SORT_DIRECTION_UNSPECIFIED: _ClassVar[SortDirection] - SORT_DIRECTION_ASCENDING: _ClassVar[SortDirection] - SORT_DIRECTION_DESCENDING: _ClassVar[SortDirection] -SORT_DIRECTION_UNSPECIFIED: SortDirection -SORT_DIRECTION_ASCENDING: SortDirection -SORT_DIRECTION_DESCENDING: SortDirection - -class TimeInterval(_message.Message): - __slots__ = ("start_time", "end_time", "start_exclusive", "end_inclusive") - START_TIME_FIELD_NUMBER: _ClassVar[int] - END_TIME_FIELD_NUMBER: _ClassVar[int] - START_EXCLUSIVE_FIELD_NUMBER: _ClassVar[int] - END_INCLUSIVE_FIELD_NUMBER: _ClassVar[int] - start_time: _timestamp_pb2.Timestamp - end_time: _timestamp_pb2.Timestamp - start_exclusive: bool - end_inclusive: bool - def __init__(self, start_time: _Optional[_Union[_timestamp_pb2.Timestamp, _Mapping]] = ..., end_time: _Optional[_Union[_timestamp_pb2.Timestamp, _Mapping]] = ..., start_exclusive: bool = ..., end_inclusive: bool = ...) -> None: ... - -class IDInterval(_message.Message): - __slots__ = ("start_id", "end_id", "start_exclusive", "end_inclusive") - START_ID_FIELD_NUMBER: _ClassVar[int] - END_ID_FIELD_NUMBER: _ClassVar[int] - START_EXCLUSIVE_FIELD_NUMBER: _ClassVar[int] - END_INCLUSIVE_FIELD_NUMBER: _ClassVar[int] - start_id: _id_pb2.ID - end_id: _id_pb2.ID - start_exclusive: bool - end_inclusive: bool - def __init__(self, start_id: _Optional[_Union[_id_pb2.ID, _Mapping]] = ..., end_id: _Optional[_Union[_id_pb2.ID, _Mapping]] = ..., start_exclusive: bool = ..., end_inclusive: bool = ...) -> None: ... - -class Pagination(_message.Message): - __slots__ = ("limit", "starting_after") - LIMIT_FIELD_NUMBER: _ClassVar[int] - STARTING_AFTER_FIELD_NUMBER: _ClassVar[int] - limit: int - starting_after: _id_pb2.ID - def __init__(self, limit: _Optional[int] = ..., starting_after: _Optional[_Union[_id_pb2.ID, _Mapping]] = ...) -> None: ... diff --git a/tilebox-datasets/tilebox/datasets/tilebox/v1/query_pb2_grpc.py b/tilebox-datasets/tilebox/datasets/tilebox/v1/query_pb2_grpc.py deleted file mode 100644 index 8a93939..0000000 --- a/tilebox-datasets/tilebox/datasets/tilebox/v1/query_pb2_grpc.py +++ /dev/null @@ -1,3 +0,0 @@ -# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! -"""Client and server classes corresponding to protobuf-defined services.""" -import grpc diff --git a/tilebox-datasets/tilebox/datasets/uuid.py b/tilebox-datasets/tilebox/datasets/uuid.py index f684883..99b58b4 100644 --- a/tilebox-datasets/tilebox/datasets/uuid.py +++ b/tilebox-datasets/tilebox/datasets/uuid.py @@ -1,39 +1,17 @@ -# allow the uuid module name which shadows the builtin: -from uuid import UUID - -from tilebox.datasets.datasets.v1 import well_known_types_pb2 -from tilebox.datasets.tilebox.v1 import id_pb2 - -_NIL_UUID = UUID(int=0) - - -def uuid_message_to_uuid(uuid_message: id_pb2.ID | well_known_types_pb2.UUID) -> UUID: - if uuid_message.uuid == b"": - return _NIL_UUID - return UUID(bytes=uuid_message.uuid) - - -def uuid_message_to_optional_uuid(uuid_message: id_pb2.ID | well_known_types_pb2.UUID | None) -> UUID | None: - if uuid_message is None: - return None - if uuid_message.uuid == b"": - return None - return UUID(bytes=uuid_message.uuid) - - -def uuid_to_uuid_message(uuid: UUID | None) -> id_pb2.ID | None: - if uuid is None or uuid == _NIL_UUID: - return None - return id_pb2.ID(uuid=uuid.bytes) - - -def must_uuid_to_uuid_message(uuid: UUID) -> id_pb2.ID: - if uuid == _NIL_UUID: - raise ValueError("UUID must not be Nil") - return id_pb2.ID(uuid=uuid.bytes) - - -def as_uuid(uuid: str | UUID) -> UUID: - if isinstance(uuid, str): - return UUID(uuid) - return uuid +"""Compatibility imports for shared UUID conversions.""" + +from _tilebox.uuid import ( + as_uuid, + must_uuid_to_uuid_message, + uuid_message_to_optional_uuid, + uuid_message_to_uuid, + uuid_to_uuid_message, +) + +__all__ = [ + "as_uuid", + "must_uuid_to_uuid_message", + "uuid_message_to_optional_uuid", + "uuid_message_to_uuid", + "uuid_to_uuid_message", +] diff --git a/tilebox-datasets/tilebox/datasets/datasets/v1/collections_connect.py b/tilebox-datasets/tilebox/datasets/v1/collections_connect.py similarity index 55% rename from tilebox-datasets/tilebox/datasets/datasets/v1/collections_connect.py rename to tilebox-datasets/tilebox/datasets/v1/collections_connect.py index 815658a..26694af 100644 --- a/tilebox-datasets/tilebox/datasets/datasets/v1/collections_connect.py +++ b/tilebox-datasets/tilebox/datasets/v1/collections_connect.py @@ -1,39 +1,63 @@ -# -*- coding: utf-8 -*- -# Generated by https://github.com/connectrpc/connect-python. DO NOT EDIT! -# source: datasets/v1/collections.proto +# Generated from datasets/v1/collections.proto. DO NOT EDIT. +# Generated by protoc-gen-connectrpc-py v0.12.1 with parameter "map_imports=datasets/**:tilebox,map_imports=workflows/**:tilebox,map_imports=tilebox/**:_tilebox.proto,map_imports=opentelemetry/**:tilebox.workflows._otel". +# ruff: noqa: PGH004 +# ruff: noqa +# fmt: off -from collections.abc import AsyncGenerator, AsyncIterator, Iterable, Iterator, Mapping -from typing import Protocol +from __future__ import annotations + +from typing import Protocol, TYPE_CHECKING from connectrpc.client import ConnectClient, ConnectClientSync from connectrpc.code import Code -from connectrpc.codec import Codec -from connectrpc.compression import Compression from connectrpc.errors import ConnectError -from connectrpc.interceptor import Interceptor, InterceptorSync from connectrpc.method import IdempotencyLevel, MethodInfo -from connectrpc.request import Headers, RequestContext -from connectrpc.server import ConnectASGIApplication, ConnectWSGIApplication, Endpoint, EndpointSync -import tilebox.datasets.datasets.v1.collections_pb2 as datasets_dot_v1_dot_collections__pb2 -import tilebox.datasets.datasets.v1.core_pb2 as datasets_dot_v1_dot_core__pb2 +from connectrpc.server import ConnectASGIApplication, ConnectWSGIApplication, DEFAULT_READ_MAX_BYTES, Endpoint, EndpointSync +from protobuf import DescService +from tilebox.datasets.v1 import collections_pb +from tilebox.datasets.v1.collections_pb import CreateCollectionRequest, DeleteCollectionRequest, DeleteCollectionResponse, GetCollectionByNameRequest, ListCollectionsRequest, ListCollectionsResponse +from tilebox.datasets.v1.core_pb import CollectionInfo + +if TYPE_CHECKING: + from collections.abc import AsyncGenerator, Iterable, Mapping + + from connectrpc.codec import Codec + from connectrpc.compression import Compression + from connectrpc.interceptor import Interceptor, InterceptorSync + from connectrpc.request import Headers, RequestContext class CollectionService(Protocol): - async def create_collection(self, request: datasets_dot_v1_dot_collections__pb2.CreateCollectionRequest, ctx: RequestContext) -> datasets_dot_v1_dot_core__pb2.CollectionInfo: - raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") + """ + CollectionService is the service definition for the Tilebox datasets service, which provides access to datasets + """ + async def create_collection(self, request: CreateCollectionRequest, ctx: RequestContext[CreateCollectionRequest, CollectionInfo], /) -> CollectionInfo: + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') - async def get_collection_by_name(self, request: datasets_dot_v1_dot_collections__pb2.GetCollectionByNameRequest, ctx: RequestContext) -> datasets_dot_v1_dot_core__pb2.CollectionInfo: - raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") + async def get_collection_by_name(self, request: GetCollectionByNameRequest, ctx: RequestContext[GetCollectionByNameRequest, CollectionInfo], /) -> CollectionInfo: + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') - async def delete_collection(self, request: datasets_dot_v1_dot_collections__pb2.DeleteCollectionRequest, ctx: RequestContext) -> datasets_dot_v1_dot_collections__pb2.DeleteCollectionResponse: - raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") + async def delete_collection(self, request: DeleteCollectionRequest, ctx: RequestContext[DeleteCollectionRequest, DeleteCollectionResponse], /) -> DeleteCollectionResponse: + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') - async def list_collections(self, request: datasets_dot_v1_dot_collections__pb2.ListCollectionsRequest, ctx: RequestContext) -> datasets_dot_v1_dot_collections__pb2.ListCollectionsResponse: - raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") + async def list_collections(self, request: ListCollectionsRequest, ctx: RequestContext[ListCollectionsRequest, ListCollectionsResponse], /) -> ListCollectionsResponse: + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') + @classmethod + def desc(cls) -> DescService: + """Returns the descriptor for this service.""" + return next(s for s in collections_pb.desc().services if s.type_name == 'datasets.v1.CollectionService') class CollectionServiceASGIApplication(ConnectASGIApplication[CollectionService]): - def __init__(self, service: CollectionService | AsyncGenerator[CollectionService], *, interceptors: Iterable[Interceptor]=(), read_max_bytes: int | None = None, compressions: Iterable[Compression] | None = None, codecs: Iterable[Codec] | None = None) -> None: + def __init__( + self, + service: CollectionService | AsyncGenerator[CollectionService], + *, + interceptors: Iterable[Interceptor] = (), + read_max_bytes: int | None = DEFAULT_READ_MAX_BYTES, + compressions: Iterable[Compression] | None = None, + codecs: Iterable[Codec] | None = None, + ) -> None: super().__init__( service=service, endpoints=lambda svc: { @@ -41,8 +65,8 @@ def __init__(self, service: CollectionService | AsyncGenerator[CollectionService method=MethodInfo( name="CreateCollection", service_name="datasets.v1.CollectionService", - input=datasets_dot_v1_dot_collections__pb2.CreateCollectionRequest, - output=datasets_dot_v1_dot_core__pb2.CollectionInfo, + input=CreateCollectionRequest, + output=CollectionInfo, idempotency_level=IdempotencyLevel.UNKNOWN, ), function=svc.create_collection, @@ -51,8 +75,8 @@ def __init__(self, service: CollectionService | AsyncGenerator[CollectionService method=MethodInfo( name="GetCollectionByName", service_name="datasets.v1.CollectionService", - input=datasets_dot_v1_dot_collections__pb2.GetCollectionByNameRequest, - output=datasets_dot_v1_dot_core__pb2.CollectionInfo, + input=GetCollectionByNameRequest, + output=CollectionInfo, idempotency_level=IdempotencyLevel.UNKNOWN, ), function=svc.get_collection_by_name, @@ -61,8 +85,8 @@ def __init__(self, service: CollectionService | AsyncGenerator[CollectionService method=MethodInfo( name="DeleteCollection", service_name="datasets.v1.CollectionService", - input=datasets_dot_v1_dot_collections__pb2.DeleteCollectionRequest, - output=datasets_dot_v1_dot_collections__pb2.DeleteCollectionResponse, + input=DeleteCollectionRequest, + output=DeleteCollectionResponse, idempotency_level=IdempotencyLevel.UNKNOWN, ), function=svc.delete_collection, @@ -71,8 +95,8 @@ def __init__(self, service: CollectionService | AsyncGenerator[CollectionService method=MethodInfo( name="ListCollections", service_name="datasets.v1.CollectionService", - input=datasets_dot_v1_dot_collections__pb2.ListCollectionsRequest, - output=datasets_dot_v1_dot_collections__pb2.ListCollectionsResponse, + input=ListCollectionsRequest, + output=ListCollectionsResponse, idempotency_level=IdempotencyLevel.UNKNOWN, ), function=svc.list_collections, @@ -91,20 +115,23 @@ def path(self) -> str: class CollectionServiceClient(ConnectClient): + """ + CollectionService is the service definition for the Tilebox datasets service, which provides access to datasets + """ async def create_collection( self, - request: datasets_dot_v1_dot_collections__pb2.CreateCollectionRequest, + request: CreateCollectionRequest, *, headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, - ) -> datasets_dot_v1_dot_core__pb2.CollectionInfo: + ) -> CollectionInfo: return await self.execute_unary( request=request, method=MethodInfo( name="CreateCollection", service_name="datasets.v1.CollectionService", - input=datasets_dot_v1_dot_collections__pb2.CreateCollectionRequest, - output=datasets_dot_v1_dot_core__pb2.CollectionInfo, + input=CreateCollectionRequest, + output=CollectionInfo, idempotency_level=IdempotencyLevel.UNKNOWN, ), headers=headers, @@ -113,18 +140,18 @@ async def create_collection( async def get_collection_by_name( self, - request: datasets_dot_v1_dot_collections__pb2.GetCollectionByNameRequest, + request: GetCollectionByNameRequest, *, headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, - ) -> datasets_dot_v1_dot_core__pb2.CollectionInfo: + ) -> CollectionInfo: return await self.execute_unary( request=request, method=MethodInfo( name="GetCollectionByName", service_name="datasets.v1.CollectionService", - input=datasets_dot_v1_dot_collections__pb2.GetCollectionByNameRequest, - output=datasets_dot_v1_dot_core__pb2.CollectionInfo, + input=GetCollectionByNameRequest, + output=CollectionInfo, idempotency_level=IdempotencyLevel.UNKNOWN, ), headers=headers, @@ -133,18 +160,18 @@ async def get_collection_by_name( async def delete_collection( self, - request: datasets_dot_v1_dot_collections__pb2.DeleteCollectionRequest, + request: DeleteCollectionRequest, *, headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, - ) -> datasets_dot_v1_dot_collections__pb2.DeleteCollectionResponse: + ) -> DeleteCollectionResponse: return await self.execute_unary( request=request, method=MethodInfo( name="DeleteCollection", service_name="datasets.v1.CollectionService", - input=datasets_dot_v1_dot_collections__pb2.DeleteCollectionRequest, - output=datasets_dot_v1_dot_collections__pb2.DeleteCollectionResponse, + input=DeleteCollectionRequest, + output=DeleteCollectionResponse, idempotency_level=IdempotencyLevel.UNKNOWN, ), headers=headers, @@ -153,49 +180,62 @@ async def delete_collection( async def list_collections( self, - request: datasets_dot_v1_dot_collections__pb2.ListCollectionsRequest, + request: ListCollectionsRequest, *, headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, - ) -> datasets_dot_v1_dot_collections__pb2.ListCollectionsResponse: + ) -> ListCollectionsResponse: return await self.execute_unary( request=request, method=MethodInfo( name="ListCollections", service_name="datasets.v1.CollectionService", - input=datasets_dot_v1_dot_collections__pb2.ListCollectionsRequest, - output=datasets_dot_v1_dot_collections__pb2.ListCollectionsResponse, + input=ListCollectionsRequest, + output=ListCollectionsResponse, idempotency_level=IdempotencyLevel.UNKNOWN, ), headers=headers, timeout_ms=timeout_ms, ) +class CollectionServiceSync(Protocol): + """ + CollectionService is the service definition for the Tilebox datasets service, which provides access to datasets + """ + def create_collection(self, request: CreateCollectionRequest, ctx: RequestContext[CreateCollectionRequest, CollectionInfo], /) -> CollectionInfo: + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') + def get_collection_by_name(self, request: GetCollectionByNameRequest, ctx: RequestContext[GetCollectionByNameRequest, CollectionInfo], /) -> CollectionInfo: + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') + def delete_collection(self, request: DeleteCollectionRequest, ctx: RequestContext[DeleteCollectionRequest, DeleteCollectionResponse], /) -> DeleteCollectionResponse: + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') + def list_collections(self, request: ListCollectionsRequest, ctx: RequestContext[ListCollectionsRequest, ListCollectionsResponse], /) -> ListCollectionsResponse: + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') -class CollectionServiceSync(Protocol): - def create_collection(self, request: datasets_dot_v1_dot_collections__pb2.CreateCollectionRequest, ctx: RequestContext) -> datasets_dot_v1_dot_core__pb2.CollectionInfo: - raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") - def get_collection_by_name(self, request: datasets_dot_v1_dot_collections__pb2.GetCollectionByNameRequest, ctx: RequestContext) -> datasets_dot_v1_dot_core__pb2.CollectionInfo: - raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") - def delete_collection(self, request: datasets_dot_v1_dot_collections__pb2.DeleteCollectionRequest, ctx: RequestContext) -> datasets_dot_v1_dot_collections__pb2.DeleteCollectionResponse: - raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") - def list_collections(self, request: datasets_dot_v1_dot_collections__pb2.ListCollectionsRequest, ctx: RequestContext) -> datasets_dot_v1_dot_collections__pb2.ListCollectionsResponse: - raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") - + @classmethod + def desc(cls) -> DescService: + """Returns the descriptor for this service.""" + return next(s for s in collections_pb.desc().services if s.type_name == 'datasets.v1.CollectionService') class CollectionServiceWSGIApplication(ConnectWSGIApplication): - def __init__(self, service: CollectionServiceSync, interceptors: Iterable[InterceptorSync]=(), read_max_bytes: int | None = None, compressions: Iterable[Compression] | None = None, codecs: Iterable[Codec] | None = None) -> None: + def __init__( + self, + service: CollectionServiceSync, + interceptors: Iterable[InterceptorSync] = (), + read_max_bytes: int | None = DEFAULT_READ_MAX_BYTES, + compressions: Iterable[Compression] | None = None, + codecs: Iterable[Codec] | None = None, + ) -> None: super().__init__( endpoints={ "/datasets.v1.CollectionService/CreateCollection": EndpointSync.unary( method=MethodInfo( name="CreateCollection", service_name="datasets.v1.CollectionService", - input=datasets_dot_v1_dot_collections__pb2.CreateCollectionRequest, - output=datasets_dot_v1_dot_core__pb2.CollectionInfo, + input=CreateCollectionRequest, + output=CollectionInfo, idempotency_level=IdempotencyLevel.UNKNOWN, ), function=service.create_collection, @@ -204,8 +244,8 @@ def __init__(self, service: CollectionServiceSync, interceptors: Iterable[Interc method=MethodInfo( name="GetCollectionByName", service_name="datasets.v1.CollectionService", - input=datasets_dot_v1_dot_collections__pb2.GetCollectionByNameRequest, - output=datasets_dot_v1_dot_core__pb2.CollectionInfo, + input=GetCollectionByNameRequest, + output=CollectionInfo, idempotency_level=IdempotencyLevel.UNKNOWN, ), function=service.get_collection_by_name, @@ -214,8 +254,8 @@ def __init__(self, service: CollectionServiceSync, interceptors: Iterable[Interc method=MethodInfo( name="DeleteCollection", service_name="datasets.v1.CollectionService", - input=datasets_dot_v1_dot_collections__pb2.DeleteCollectionRequest, - output=datasets_dot_v1_dot_collections__pb2.DeleteCollectionResponse, + input=DeleteCollectionRequest, + output=DeleteCollectionResponse, idempotency_level=IdempotencyLevel.UNKNOWN, ), function=service.delete_collection, @@ -224,8 +264,8 @@ def __init__(self, service: CollectionServiceSync, interceptors: Iterable[Interc method=MethodInfo( name="ListCollections", service_name="datasets.v1.CollectionService", - input=datasets_dot_v1_dot_collections__pb2.ListCollectionsRequest, - output=datasets_dot_v1_dot_collections__pb2.ListCollectionsResponse, + input=ListCollectionsRequest, + output=ListCollectionsResponse, idempotency_level=IdempotencyLevel.UNKNOWN, ), function=service.list_collections, @@ -244,80 +284,80 @@ def path(self) -> str: class CollectionServiceClientSync(ConnectClientSync): + """ + CollectionService is the service definition for the Tilebox datasets service, which provides access to datasets + """ def create_collection( self, - request: datasets_dot_v1_dot_collections__pb2.CreateCollectionRequest, + request: CreateCollectionRequest, *, headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, - ) -> datasets_dot_v1_dot_core__pb2.CollectionInfo: + ) -> CollectionInfo: return self.execute_unary( request=request, method=MethodInfo( name="CreateCollection", service_name="datasets.v1.CollectionService", - input=datasets_dot_v1_dot_collections__pb2.CreateCollectionRequest, - output=datasets_dot_v1_dot_core__pb2.CollectionInfo, + input=CreateCollectionRequest, + output=CollectionInfo, idempotency_level=IdempotencyLevel.UNKNOWN, ), headers=headers, timeout_ms=timeout_ms, ) - def get_collection_by_name( self, - request: datasets_dot_v1_dot_collections__pb2.GetCollectionByNameRequest, + request: GetCollectionByNameRequest, *, headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, - ) -> datasets_dot_v1_dot_core__pb2.CollectionInfo: + ) -> CollectionInfo: return self.execute_unary( request=request, method=MethodInfo( name="GetCollectionByName", service_name="datasets.v1.CollectionService", - input=datasets_dot_v1_dot_collections__pb2.GetCollectionByNameRequest, - output=datasets_dot_v1_dot_core__pb2.CollectionInfo, + input=GetCollectionByNameRequest, + output=CollectionInfo, idempotency_level=IdempotencyLevel.UNKNOWN, ), headers=headers, timeout_ms=timeout_ms, ) - def delete_collection( self, - request: datasets_dot_v1_dot_collections__pb2.DeleteCollectionRequest, + request: DeleteCollectionRequest, *, headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, - ) -> datasets_dot_v1_dot_collections__pb2.DeleteCollectionResponse: + ) -> DeleteCollectionResponse: return self.execute_unary( request=request, method=MethodInfo( name="DeleteCollection", service_name="datasets.v1.CollectionService", - input=datasets_dot_v1_dot_collections__pb2.DeleteCollectionRequest, - output=datasets_dot_v1_dot_collections__pb2.DeleteCollectionResponse, + input=DeleteCollectionRequest, + output=DeleteCollectionResponse, idempotency_level=IdempotencyLevel.UNKNOWN, ), headers=headers, timeout_ms=timeout_ms, ) - def list_collections( self, - request: datasets_dot_v1_dot_collections__pb2.ListCollectionsRequest, + request: ListCollectionsRequest, *, headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, - ) -> datasets_dot_v1_dot_collections__pb2.ListCollectionsResponse: + ) -> ListCollectionsResponse: return self.execute_unary( request=request, method=MethodInfo( name="ListCollections", service_name="datasets.v1.CollectionService", - input=datasets_dot_v1_dot_collections__pb2.ListCollectionsRequest, - output=datasets_dot_v1_dot_collections__pb2.ListCollectionsResponse, + input=ListCollectionsRequest, + output=ListCollectionsResponse, idempotency_level=IdempotencyLevel.UNKNOWN, ), headers=headers, diff --git a/tilebox-datasets/tilebox/datasets/v1/collections_pb.py b/tilebox-datasets/tilebox/datasets/v1/collections_pb.py new file mode 100644 index 0000000..14cf269 --- /dev/null +++ b/tilebox-datasets/tilebox/datasets/v1/collections_pb.py @@ -0,0 +1,298 @@ +# Generated from datasets/v1/collections.proto. DO NOT EDIT. +# Generated by protoc-gen-py v0.6.0 with parameter "init_files=false,map_imports=datasets/**:tilebox,map_imports=workflows/**:tilebox,map_imports=tilebox/**:_tilebox.proto,map_imports=opentelemetry/**:tilebox.workflows._otel". +# ruff: noqa: PGH004 +# ruff: noqa +# fmt: off + +from __future__ import annotations + +from typing import Literal, NoReturn, TYPE_CHECKING, TypeAlias + +from _tilebox.proto.tilebox.v1 import id_pb +from protobuf import Message +from protobuf._codegen import file_desc +from tilebox.datasets.v1 import core_pb + +if TYPE_CHECKING: + from _tilebox.proto.tilebox.v1.id_pb import ID + from protobuf import DescFile + from tilebox.datasets.v1.core_pb import CollectionInfo + + +_CreateCollectionRequestFields: TypeAlias = Literal["dataset_id", "name"] + +class CreateCollectionRequest(Message[_CreateCollectionRequestFields]): + """ + CreateCollectionRequest is used to create a new collection. + + ```proto + message datasets.v1.CreateCollectionRequest + ``` + + Attributes: + dataset_id: + The dataset id where the collection should be created. + + ```proto + optional tilebox.v1.ID dataset_id = 1; + ``` + name: + The name of the collection to create. + + ```proto + string name = 2; + ``` + """ + + __slots__ = ("dataset_id", "name") + + if TYPE_CHECKING: + + def __init__( + self, + *, + dataset_id: ID | None = None, + name: str = "", + ) -> None: + pass + + dataset_id: ID | None + name: str + +_GetCollectionByNameRequestFields: TypeAlias = Literal["collection_name", "with_availability", "with_count", "dataset_id"] + +class GetCollectionByNameRequest(Message[_GetCollectionByNameRequestFields]): + """ + GetCollectionByNameRequest contains the request parameters for retrieving a single collection by its name. + + ```proto + message datasets.v1.GetCollectionByNameRequest + ``` + + Attributes: + collection_name: + The name of the collection to retrieve. + + ```proto + string collection_name = 1; + ``` + with_availability: + If true, the response will include the availability interval. + + ```proto + bool with_availability = 2; + ``` + with_count: + If true, the response will include the number of entries. + + ```proto + bool with_count = 3; + ``` + dataset_id: + The dataset id. + + ```proto + optional tilebox.v1.ID dataset_id = 4; + ``` + """ + + __slots__ = ("collection_name", "with_availability", "with_count", "dataset_id") + + if TYPE_CHECKING: + + def __init__( + self, + *, + collection_name: str = "", + with_availability: bool = False, + with_count: bool = False, + dataset_id: ID | None = None, + ) -> None: + pass + + collection_name: str + with_availability: bool + with_count: bool + dataset_id: ID | None + +_DeleteCollectionRequestFields: TypeAlias = Literal["collection_id", "dataset_id"] + +class DeleteCollectionRequest(Message[_DeleteCollectionRequestFields]): + """ + DeleteCollectionRequest contains the request parameters for deleting a single collection. + + ```proto + message datasets.v1.DeleteCollectionRequest + ``` + + Attributes: + collection_id: + The id of the collection to delete. + + ```proto + optional tilebox.v1.ID collection_id = 1; + ``` + dataset_id: + The dataset id. + + ```proto + optional tilebox.v1.ID dataset_id = 2; + ``` + """ + + __slots__ = ("collection_id", "dataset_id") + + if TYPE_CHECKING: + + def __init__( + self, + *, + collection_id: ID | None = None, + dataset_id: ID | None = None, + ) -> None: + pass + + collection_id: ID | None + dataset_id: ID | None + +_DeleteCollectionResponseFields: TypeAlias = NoReturn + +class DeleteCollectionResponse(Message[_DeleteCollectionResponseFields]): + """ + DeleteCollectionResponse is the response to DeleteCollectionRequest. + + ```proto + message datasets.v1.DeleteCollectionResponse + ``` + """ + + __slots__ = () + + if TYPE_CHECKING: + + def __init__( + self, + ) -> None: + pass + +_ListCollectionsRequestFields: TypeAlias = Literal["dataset_id", "with_availability", "with_count"] + +class ListCollectionsRequest(Message[_ListCollectionsRequestFields]): + """ + ListCollectionsRequest contains the request parameters + for retrieving a list of all available collections for a given dataset. + + ```proto + message datasets.v1.ListCollectionsRequest + ``` + + Attributes: + dataset_id: + The dataset id. + + ```proto + optional tilebox.v1.ID dataset_id = 1; + ``` + with_availability: + If true, the response will include the availability interval. + + ```proto + bool with_availability = 2; + ``` + with_count: + If true, the response will include the number of entries. + + ```proto + bool with_count = 3; + ``` + """ + + __slots__ = ("dataset_id", "with_availability", "with_count") + + if TYPE_CHECKING: + + def __init__( + self, + *, + dataset_id: ID | None = None, + with_availability: bool = False, + with_count: bool = False, + ) -> None: + pass + + dataset_id: ID | None + with_availability: bool + with_count: bool + +_ListCollectionsResponseFields: TypeAlias = Literal["data", "owned_collections", "maximum_owned_collections"] + +class ListCollectionsResponse(Message[_ListCollectionsResponseFields]): + """ + ListCollectionsResponse is the response to ListCollectionsRequest. + It contains a list of collections for the requested dataset, as well as + some organization level metadata about the number of collections the organization owns + and the maximum number of collections it can own. + + ```proto + message datasets.v1.ListCollectionsResponse + ``` + + Attributes: + data: + List of collections in the dataset. + + ```proto + repeated datasets.v1.CollectionInfo data = 1; + ``` + owned_collections: + Number of collections in all datasets that the users organization is the owner of + + ```proto + int64 owned_collections = 2; + ``` + maximum_owned_collections: + Maximum number of collections that can be created and therefore owned by the organization. Nil means unlimited. + + ```proto + optional int64 maximum_owned_collections = 3; + ``` + """ + + __slots__ = ("data", "owned_collections", "maximum_owned_collections") + + if TYPE_CHECKING: + + def __init__( + self, + *, + data: list[CollectionInfo] | None = None, + owned_collections: int = 0, + maximum_owned_collections: int | None = None, + ) -> None: + pass + + data: list[CollectionInfo] + owned_collections: int + maximum_owned_collections: int + + +_DESC = file_desc( + b'\n\x1ddatasets/v1/collections.proto\x12\x0bdatasets.v1\x1a\x16datasets/v1/core.proto\x1a\x13tilebox/v1/id.proto"\\\n\x17CreateCollectionRequest\x12-\n\ndataset_id\x18\x01 \x01(\x0b2\x0e.tilebox.v1.IDR\tdatasetId\x12\x12\n\x04name\x18\x02 \x01(\tR\x04name"\xc0\x01\n\x1aGetCollectionByNameRequest\x12\'\n\x0fcollection_name\x18\x01 \x01(\tR\x0ecollectionName\x12+\n\x11with_availability\x18\x02 \x01(\x08R\x10withAvailability\x12\x1d\n\nwith_count\x18\x03 \x01(\x08R\twithCount\x12-\n\ndataset_id\x18\x04 \x01(\x0b2\x0e.tilebox.v1.IDR\tdatasetId"}\n\x17DeleteCollectionRequest\x123\n\rcollection_id\x18\x01 \x01(\x0b2\x0e.tilebox.v1.IDR\x0ccollectionId\x12-\n\ndataset_id\x18\x02 \x01(\x0b2\x0e.tilebox.v1.IDR\tdatasetId"\x1a\n\x18DeleteCollectionResponse"\x93\x01\n\x16ListCollectionsRequest\x12-\n\ndataset_id\x18\x01 \x01(\x0b2\x0e.tilebox.v1.IDR\tdatasetId\x12+\n\x11with_availability\x18\x02 \x01(\x08R\x10withAvailability\x12\x1d\n\nwith_count\x18\x03 \x01(\x08R\twithCount"\xba\x01\n\x17ListCollectionsResponse\x12/\n\x04data\x18\x01 \x03(\x0b2\x1b.datasets.v1.CollectionInfoR\x04data\x12+\n\x11owned_collections\x18\x02 \x01(\x03R\x10ownedCollections\x12A\n\x19maximum_owned_collections\x18\x03 \x01(\x03R\x17maximumOwnedCollectionsB\x05\xaa\x01\x02\x08\x012\x86\x03\n\x11CollectionService\x12U\n\x10CreateCollection\x12$.datasets.v1.CreateCollectionRequest\x1a\x1b.datasets.v1.CollectionInfo\x12[\n\x13GetCollectionByName\x12\'.datasets.v1.GetCollectionByNameRequest\x1a\x1b.datasets.v1.CollectionInfo\x12_\n\x10DeleteCollection\x12$.datasets.v1.DeleteCollectionRequest\x1a%.datasets.v1.DeleteCollectionResponse\x12\\\n\x0fListCollections\x12#.datasets.v1.ListCollectionsRequest\x1a$.datasets.v1.ListCollectionsResponseB\x05\x92\x03\x02\x08\x02b\x08editionsp\xe8\x07', + [ + core_pb.desc(), + id_pb.desc(), + ], + { + "CreateCollectionRequest": CreateCollectionRequest, + "GetCollectionByNameRequest": GetCollectionByNameRequest, + "DeleteCollectionRequest": DeleteCollectionRequest, + "DeleteCollectionResponse": DeleteCollectionResponse, + "ListCollectionsRequest": ListCollectionsRequest, + "ListCollectionsResponse": ListCollectionsResponse, + }, +) + + +def desc() -> DescFile: + """Returns the descriptor for the file `datasets/v1/collections.proto`.""" + return _DESC diff --git a/tilebox-datasets/tilebox/datasets/v1/core_pb.py b/tilebox-datasets/tilebox/datasets/v1/core_pb.py new file mode 100644 index 0000000..75fdbc1 --- /dev/null +++ b/tilebox-datasets/tilebox/datasets/v1/core_pb.py @@ -0,0 +1,462 @@ +# Generated from datasets/v1/core.proto. DO NOT EDIT. +# Generated by protoc-gen-py v0.6.0 with parameter "init_files=false,map_imports=datasets/**:tilebox,map_imports=workflows/**:tilebox,map_imports=tilebox/**:_tilebox.proto,map_imports=opentelemetry/**:tilebox.workflows._otel". +# ruff: noqa: PGH004 +# ruff: noqa +# fmt: off + +from __future__ import annotations + +from typing import Literal, TYPE_CHECKING, TypeAlias + +from _tilebox.proto.tilebox.v1 import id_pb, query_pb +from protobuf import Enum, Message +from protobuf._codegen import file_desc +from tilebox.datasets.v1 import dataset_type_pb + +if TYPE_CHECKING: + from _tilebox.proto.tilebox.v1.id_pb import ID + from _tilebox.proto.tilebox.v1.query_pb import TimeInterval + from protobuf import DescFile + from tilebox.datasets.v1.dataset_type_pb import AnnotatedType + + +_AnyFields: TypeAlias = Literal["type_url", "value"] + +class Any(Message[_AnyFields]): + """ + Any is a message that can hold any other message as bytes. + We don't use google.protobuf.Any because we want the JSON representation of the value field to be bytes. + + ```proto + message datasets.v1.Any + ``` + + Attributes: + type_url: + ```proto + string type_url = 1; + ``` + value: + Must be a valid serialized protocol buffer of the above specified type. + + ```proto + bytes value = 2; + ``` + """ + + __slots__ = ("type_url", "value") + + if TYPE_CHECKING: + + def __init__( + self, + *, + type_url: str = "", + value: bytes = b"", + ) -> None: + pass + + type_url: str + value: bytes + +_RepeatedAnyFields: TypeAlias = Literal["type_url", "value"] + +class RepeatedAny(Message[_RepeatedAnyFields]): + """ + RepeatedAny is a message holding a list of messages, that all share the same variable message type. + It is preferable over a repeated Any field because it avoids repeating the type_url for every single element. + + ```proto + message datasets.v1.RepeatedAny + ``` + + Attributes: + type_url: + ```proto + string type_url = 1; + ``` + value: + Must be a valid serialized protocol buffer of the above specified type. + + ```proto + repeated bytes value = 2; + ``` + """ + + __slots__ = ("type_url", "value") + + if TYPE_CHECKING: + + def __init__( + self, + *, + type_url: str = "", + value: list[bytes] | None = None, + ) -> None: + pass + + type_url: str + value: list[bytes] + +_CollectionFields: TypeAlias = Literal["name", "id"] + +class Collection(Message[_CollectionFields]): + """ + Collection contains basic information about a dataset collection + + ```proto + message datasets.v1.Collection + ``` + + Attributes: + name: + ```proto + string name = 2; + ``` + id: + ```proto + optional tilebox.v1.ID id = 3; + ``` + """ + + __slots__ = ("name", "id") + + if TYPE_CHECKING: + + def __init__( + self, + *, + name: str = "", + id: ID | None = None, + ) -> None: + pass + + name: str + id: ID | None + +_CollectionInfoFields: TypeAlias = Literal["collection", "availability", "count"] + +class CollectionInfo(Message[_CollectionInfoFields]): + """ + CollectionInfo contains information about the data available in a dataset collection + + ```proto + message datasets.v1.CollectionInfo + ``` + + Attributes: + collection: + ```proto + optional datasets.v1.Collection collection = 1; + ``` + availability: + The time interval for which data is available. + + ```proto + optional tilebox.v1.TimeInterval availability = 2; + ``` + count: + Number of entries in the dataset. + + ```proto + optional uint64 count = 3; + ``` + """ + + __slots__ = ("collection", "availability", "count") + + if TYPE_CHECKING: + + def __init__( + self, + *, + collection: Collection | None = None, + availability: TimeInterval | None = None, + count: int | None = None, + ) -> None: + pass + + collection: Collection | None + availability: TimeInterval | None + count: int + +_DatasetFields: TypeAlias = Literal["id", "group_id", "type", "code_name", "name", "summary", "icon", "description", "permissions", "visibility", "slug", "type_editable", "collections"] + +class Dataset(Message[_DatasetFields]): + """ + Dataset contains information about a single dataset + + ```proto + message datasets.v1.Dataset + ``` + + Attributes: + id: + dataset id + + ```proto + optional tilebox.v1.ID id = 1; + ``` + group_id: + the dataset group to which this dataset belongs + + ```proto + optional tilebox.v1.ID group_id = 2; + ``` + type: + the type of the dataset + + ```proto + optional datasets.v1.AnnotatedType type = 3; + ``` + code_name: + normalized snake case name of the dataset, which can be used as a valid code identifier + + ```proto + string code_name = 4; + ``` + name: + display name of the dataset + + ```proto + string name = 5; + ``` + summary: + summary of the dataset + + ```proto + string summary = 6; + ``` + icon: + optional icon of the dataset + + ```proto + string icon = 7; + ``` + description: + description of the dataset, in markdown format + + ```proto + string description = 8; + ``` + permissions: + permissions of the requesting user on the dataset + + ```proto + repeated datasets.v1.DatasetPermission permissions = 10 [packed = true]; + ``` + visibility: + visibility of the requesting user on the dataset + + ```proto + datasets.v1.Visibility visibility = 11; + ``` + slug: + complete slug of the dataset + + ```proto + string slug = 12; + ``` + type_editable: + true if the dataset type is editable + + ```proto + bool type_editable = 13; + ``` + collections: + list of collections in the dataset + + ```proto + repeated datasets.v1.CollectionInfo collections = 14; + ``` + """ + + __slots__ = ("id", "group_id", "type", "code_name", "name", "summary", "icon", "description", "permissions", "visibility", "slug", "type_editable", "collections") + + if TYPE_CHECKING: + + def __init__( + self, + *, + id: ID | None = None, + group_id: ID | None = None, + type: AnnotatedType | None = None, + code_name: str = "", + name: str = "", + summary: str = "", + icon: str = "", + description: str = "", + permissions: list[DatasetPermission] | None = None, + visibility: Visibility | None = None, + slug: str = "", + type_editable: bool = False, + collections: list[CollectionInfo] | None = None, + ) -> None: + pass + + id: ID | None + group_id: ID | None + type: AnnotatedType | None + code_name: str + name: str + summary: str + icon: str + description: str + permissions: list[DatasetPermission] + visibility: Visibility + slug: str + type_editable: bool + collections: list[CollectionInfo] + +_DatasetGroupFields: TypeAlias = Literal["id", "parent_id", "code_name", "name", "icon"] + +class DatasetGroup(Message[_DatasetGroupFields]): + """ + DatasetGroup represent a dataset group + + ```proto + message datasets.v1.DatasetGroup + ``` + + Attributes: + id: + group id + + ```proto + optional tilebox.v1.ID id = 1; + ``` + parent_id: + optional parent group id + + ```proto + optional tilebox.v1.ID parent_id = 2; + ``` + code_name: + normalized snake case name of the dataset group, which can be used as a valid code identifier + + ```proto + string code_name = 3; + ``` + name: + display name of the group + + ```proto + string name = 4; + ``` + icon: + optional icon of the group + + ```proto + string icon = 5; + ``` + """ + + __slots__ = ("id", "parent_id", "code_name", "name", "icon") + + if TYPE_CHECKING: + + def __init__( + self, + *, + id: ID | None = None, + parent_id: ID | None = None, + code_name: str = "", + name: str = "", + icon: str = "", + ) -> None: + pass + + id: ID | None + parent_id: ID | None + code_name: str + name: str + icon: str + +class DatasetPermission(Enum): + """ + DatasetPermission is a permission a user (as part of an org) can have on a dataset + + ```proto + enum datasets.v1.DatasetPermission + ``` + + Attributes: + UNSPECIFIED: + ```proto + DATASET_PERMISSION_UNSPECIFIED = 0 + ``` + ACCESS_DATA: + ```proto + DATASET_PERMISSION_ACCESS_DATA = 1 + ``` + WRITE_DATA: + ```proto + DATASET_PERMISSION_WRITE_DATA = 2 + ``` + EDIT: + ```proto + DATASET_PERMISSION_EDIT = 3 + ``` + """ + + UNSPECIFIED = 0 + ACCESS_DATA = 1 + WRITE_DATA = 2 + EDIT = 3 + +class Visibility(Enum): + """ + Visibility is a visibility a user (as part of an org) can have on a dataset + + ```proto + enum datasets.v1.Visibility + ``` + + Attributes: + UNSPECIFIED: + ```proto + VISIBILITY_UNSPECIFIED = 0 + ``` + PRIVATE: + ```proto + VISIBILITY_PRIVATE = 1 + ``` + SHARED_WITH_ME: + ```proto + VISIBILITY_SHARED_WITH_ME = 2 + ``` + PUBLIC: + ```proto + VISIBILITY_PUBLIC = 3 + ``` + """ + + UNSPECIFIED = 0 + PRIVATE = 1 + SHARED_WITH_ME = 2 + PUBLIC = 3 + + +_DESC = file_desc( + b'\n\x16datasets/v1/core.proto\x12\x0bdatasets.v1\x1a\x1edatasets/v1/dataset_type.proto\x1a\x13tilebox/v1/id.proto\x1a\x16tilebox/v1/query.proto"6\n\x03Any\x12\x19\n\x08type_url\x18\x01 \x01(\tR\x07typeUrl\x12\x14\n\x05value\x18\x02 \x01(\x0cR\x05value">\n\x0bRepeatedAny\x12\x19\n\x08type_url\x18\x01 \x01(\tR\x07typeUrl\x12\x14\n\x05value\x18\x02 \x03(\x0cR\x05value"F\n\nCollection\x12\x12\n\x04name\x18\x02 \x01(\tR\x04name\x12\x1e\n\x02id\x18\x03 \x01(\x0b2\x0e.tilebox.v1.IDR\x02idJ\x04\x08\x01\x10\x02"\xab\x01\n\x0eCollectionInfo\x127\n\ncollection\x18\x01 \x01(\x0b2\x17.datasets.v1.CollectionR\ncollection\x12C\n\x0cavailability\x18\x02 \x01(\x0b2\x18.tilebox.v1.TimeIntervalR\x0cavailabilityB\x05\xaa\x01\x02\x08\x01\x12\x1b\n\x05count\x18\x03 \x01(\x04R\x05countB\x05\xaa\x01\x02\x08\x01"\xf8\x03\n\x07Dataset\x12\x1e\n\x02id\x18\x01 \x01(\x0b2\x0e.tilebox.v1.IDR\x02id\x12)\n\x08group_id\x18\x02 \x01(\x0b2\x0e.tilebox.v1.IDR\x07groupId\x12.\n\x04type\x18\x03 \x01(\x0b2\x1a.datasets.v1.AnnotatedTypeR\x04type\x12\x1b\n\tcode_name\x18\x04 \x01(\tR\x08codeName\x12\x12\n\x04name\x18\x05 \x01(\tR\x04name\x12\x18\n\x07summary\x18\x06 \x01(\tR\x07summary\x12\x12\n\x04icon\x18\x07 \x01(\tR\x04icon\x12 \n\x0bdescription\x18\x08 \x01(\tR\x0bdescription\x12@\n\x0bpermissions\x18\n \x03(\x0e2\x1e.datasets.v1.DatasetPermissionR\x0bpermissions\x127\n\nvisibility\x18\x0b \x01(\x0e2\x17.datasets.v1.VisibilityR\nvisibility\x12\x12\n\x04slug\x18\x0c \x01(\tR\x04slug\x12#\n\rtype_editable\x18\r \x01(\x08R\x0ctypeEditable\x12=\n\x0bcollections\x18\x0e \x03(\x0b2\x1b.datasets.v1.CollectionInfoR\x0bcollections"\xa0\x01\n\x0cDatasetGroup\x12\x1e\n\x02id\x18\x01 \x01(\x0b2\x0e.tilebox.v1.IDR\x02id\x12+\n\tparent_id\x18\x02 \x01(\x0b2\x0e.tilebox.v1.IDR\x08parentId\x12\x1b\n\tcode_name\x18\x03 \x01(\tR\x08codeName\x12\x12\n\x04name\x18\x04 \x01(\tR\x04name\x12\x12\n\x04icon\x18\x05 \x01(\tR\x04icon*\x9b\x01\n\x11DatasetPermission\x12"\n\x1eDATASET_PERMISSION_UNSPECIFIED\x10\x00\x12"\n\x1eDATASET_PERMISSION_ACCESS_DATA\x10\x01\x12!\n\x1dDATASET_PERMISSION_WRITE_DATA\x10\x02\x12\x1b\n\x17DATASET_PERMISSION_EDIT\x10\x03*v\n\nVisibility\x12\x1a\n\x16VISIBILITY_UNSPECIFIED\x10\x00\x12\x16\n\x12VISIBILITY_PRIVATE\x10\x01\x12\x1d\n\x19VISIBILITY_SHARED_WITH_ME\x10\x02\x12\x15\n\x11VISIBILITY_PUBLIC\x10\x03B\x05\x92\x03\x02\x08\x02b\x08editionsp\xe8\x07', + [ + dataset_type_pb.desc(), + id_pb.desc(), + query_pb.desc(), + ], + { + "Any": Any, + "RepeatedAny": RepeatedAny, + "Collection": Collection, + "CollectionInfo": CollectionInfo, + "Dataset": Dataset, + "DatasetGroup": DatasetGroup, + "DatasetPermission": DatasetPermission, + "Visibility": Visibility, + }, +) + + +def desc() -> DescFile: + """Returns the descriptor for the file `datasets/v1/core.proto`.""" + return _DESC diff --git a/tilebox-datasets/tilebox/datasets/v1/data_access_connect.py b/tilebox-datasets/tilebox/datasets/v1/data_access_connect.py new file mode 100644 index 0000000..1237ef1 --- /dev/null +++ b/tilebox-datasets/tilebox/datasets/v1/data_access_connect.py @@ -0,0 +1,259 @@ +# Generated from datasets/v1/data_access.proto. DO NOT EDIT. +# Generated by protoc-gen-connectrpc-py v0.12.1 with parameter "map_imports=datasets/**:tilebox,map_imports=workflows/**:tilebox,map_imports=tilebox/**:_tilebox.proto,map_imports=opentelemetry/**:tilebox.workflows._otel". +# ruff: noqa: PGH004 +# ruff: noqa +# fmt: off + +from __future__ import annotations + +from typing import Protocol, TYPE_CHECKING + +from connectrpc.client import ConnectClient, ConnectClientSync +from connectrpc.code import Code +from connectrpc.errors import ConnectError +from connectrpc.method import IdempotencyLevel, MethodInfo +from connectrpc.server import ConnectASGIApplication, ConnectWSGIApplication, DEFAULT_READ_MAX_BYTES, Endpoint, EndpointSync +from protobuf import DescService +from tilebox.datasets.v1 import data_access_pb +from tilebox.datasets.v1.core_pb import Any +from tilebox.datasets.v1.data_access_pb import QueryByIDRequest, QueryRequest, QueryResultPage + +if TYPE_CHECKING: + from collections.abc import AsyncGenerator, Iterable, Mapping + + from connectrpc.codec import Codec + from connectrpc.compression import Compression + from connectrpc.interceptor import Interceptor, InterceptorSync + from connectrpc.request import Headers, RequestContext + + +class DataAccessService(Protocol): + """ + DataAccessService provides data access and querying capabilities for Tilebox datasets. + """ + async def query_by_id(self, request: QueryByIDRequest, ctx: RequestContext[QueryByIDRequest, Any], /) -> Any: + """ + QueryByID returns a single data point by its ID. + """ + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') + + async def query(self, request: QueryRequest, ctx: RequestContext[QueryRequest, QueryResultPage], /) -> QueryResultPage: + """ + Query returns a list of data points matching the given query filters. + """ + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') + + @classmethod + def desc(cls) -> DescService: + """Returns the descriptor for this service.""" + return next(s for s in data_access_pb.desc().services if s.type_name == 'datasets.v1.DataAccessService') + +class DataAccessServiceASGIApplication(ConnectASGIApplication[DataAccessService]): + def __init__( + self, + service: DataAccessService | AsyncGenerator[DataAccessService], + *, + interceptors: Iterable[Interceptor] = (), + read_max_bytes: int | None = DEFAULT_READ_MAX_BYTES, + compressions: Iterable[Compression] | None = None, + codecs: Iterable[Codec] | None = None, + ) -> None: + super().__init__( + service=service, + endpoints=lambda svc: { + "/datasets.v1.DataAccessService/QueryByID": Endpoint.unary( + method=MethodInfo( + name="QueryByID", + service_name="datasets.v1.DataAccessService", + input=QueryByIDRequest, + output=Any, + idempotency_level=IdempotencyLevel.UNKNOWN, + ), + function=svc.query_by_id, + ), + "/datasets.v1.DataAccessService/Query": Endpoint.unary( + method=MethodInfo( + name="Query", + service_name="datasets.v1.DataAccessService", + input=QueryRequest, + output=QueryResultPage, + idempotency_level=IdempotencyLevel.UNKNOWN, + ), + function=svc.query, + ), + }, + interceptors=interceptors, + read_max_bytes=read_max_bytes, + compressions=compressions, + codecs=codecs, + ) + + @property + def path(self) -> str: + """Returns the URL path to mount the application to when serving multiple applications.""" + return "/datasets.v1.DataAccessService" + + +class DataAccessServiceClient(ConnectClient): + """ + DataAccessService provides data access and querying capabilities for Tilebox datasets. + """ + async def query_by_id( + self, + request: QueryByIDRequest, + *, + headers: Headers | Mapping[str, str] | None = None, + timeout_ms: int | None = None, + ) -> Any: + """ + QueryByID returns a single data point by its ID. + """ + return await self.execute_unary( + request=request, + method=MethodInfo( + name="QueryByID", + service_name="datasets.v1.DataAccessService", + input=QueryByIDRequest, + output=Any, + idempotency_level=IdempotencyLevel.UNKNOWN, + ), + headers=headers, + timeout_ms=timeout_ms, + ) + + async def query( + self, + request: QueryRequest, + *, + headers: Headers | Mapping[str, str] | None = None, + timeout_ms: int | None = None, + ) -> QueryResultPage: + """ + Query returns a list of data points matching the given query filters. + """ + return await self.execute_unary( + request=request, + method=MethodInfo( + name="Query", + service_name="datasets.v1.DataAccessService", + input=QueryRequest, + output=QueryResultPage, + idempotency_level=IdempotencyLevel.UNKNOWN, + ), + headers=headers, + timeout_ms=timeout_ms, + ) + +class DataAccessServiceSync(Protocol): + """ + DataAccessService provides data access and querying capabilities for Tilebox datasets. + """ + def query_by_id(self, request: QueryByIDRequest, ctx: RequestContext[QueryByIDRequest, Any], /) -> Any: + """ + QueryByID returns a single data point by its ID. + """ + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') + + def query(self, request: QueryRequest, ctx: RequestContext[QueryRequest, QueryResultPage], /) -> QueryResultPage: + """ + Query returns a list of data points matching the given query filters. + """ + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') + + @classmethod + def desc(cls) -> DescService: + """Returns the descriptor for this service.""" + return next(s for s in data_access_pb.desc().services if s.type_name == 'datasets.v1.DataAccessService') + +class DataAccessServiceWSGIApplication(ConnectWSGIApplication): + def __init__( + self, + service: DataAccessServiceSync, + interceptors: Iterable[InterceptorSync] = (), + read_max_bytes: int | None = DEFAULT_READ_MAX_BYTES, + compressions: Iterable[Compression] | None = None, + codecs: Iterable[Codec] | None = None, + ) -> None: + super().__init__( + endpoints={ + "/datasets.v1.DataAccessService/QueryByID": EndpointSync.unary( + method=MethodInfo( + name="QueryByID", + service_name="datasets.v1.DataAccessService", + input=QueryByIDRequest, + output=Any, + idempotency_level=IdempotencyLevel.UNKNOWN, + ), + function=service.query_by_id, + ), + "/datasets.v1.DataAccessService/Query": EndpointSync.unary( + method=MethodInfo( + name="Query", + service_name="datasets.v1.DataAccessService", + input=QueryRequest, + output=QueryResultPage, + idempotency_level=IdempotencyLevel.UNKNOWN, + ), + function=service.query, + ), + }, + interceptors=interceptors, + read_max_bytes=read_max_bytes, + compressions=compressions, + codecs=codecs, + ) + + @property + def path(self) -> str: + """Returns the URL path to mount the application to when serving multiple applications.""" + return "/datasets.v1.DataAccessService" + + +class DataAccessServiceClientSync(ConnectClientSync): + """ + DataAccessService provides data access and querying capabilities for Tilebox datasets. + """ + def query_by_id( + self, + request: QueryByIDRequest, + *, + headers: Headers | Mapping[str, str] | None = None, + timeout_ms: int | None = None, + ) -> Any: + """ + QueryByID returns a single data point by its ID. + """ + return self.execute_unary( + request=request, + method=MethodInfo( + name="QueryByID", + service_name="datasets.v1.DataAccessService", + input=QueryByIDRequest, + output=Any, + idempotency_level=IdempotencyLevel.UNKNOWN, + ), + headers=headers, + timeout_ms=timeout_ms, + ) + def query( + self, + request: QueryRequest, + *, + headers: Headers | Mapping[str, str] | None = None, + timeout_ms: int | None = None, + ) -> QueryResultPage: + """ + Query returns a list of data points matching the given query filters. + """ + return self.execute_unary( + request=request, + method=MethodInfo( + name="Query", + service_name="datasets.v1.DataAccessService", + input=QueryRequest, + output=QueryResultPage, + idempotency_level=IdempotencyLevel.UNKNOWN, + ), + headers=headers, + timeout_ms=timeout_ms, + ) diff --git a/tilebox-datasets/tilebox/datasets/v1/data_access_pb.py b/tilebox-datasets/tilebox/datasets/v1/data_access_pb.py new file mode 100644 index 0000000..dc6c6f2 --- /dev/null +++ b/tilebox-datasets/tilebox/datasets/v1/data_access_pb.py @@ -0,0 +1,731 @@ +# Generated from datasets/v1/data_access.proto. DO NOT EDIT. +# Generated by protoc-gen-py v0.6.0 with parameter "init_files=false,map_imports=datasets/**:tilebox,map_imports=workflows/**:tilebox,map_imports=tilebox/**:_tilebox.proto,map_imports=opentelemetry/**:tilebox.workflows._otel". +# ruff: noqa: PGH004 +# ruff: noqa +# fmt: off + +from __future__ import annotations + +from typing import Literal, TYPE_CHECKING, TypeAlias + +from _tilebox.proto.tilebox.v1 import id_pb, query_pb +from protobuf import Enum, Message +from protobuf._codegen import file_desc +from protobuf.wkt.google.protobuf import duration_pb, timestamp_pb +from tilebox.datasets.v1 import core_pb, well_known_types_pb + +if TYPE_CHECKING: + from _tilebox.proto.tilebox.v1.id_pb import ID + from _tilebox.proto.tilebox.v1.query_pb import IDInterval, Pagination, TimeInterval + from protobuf import DescFile + from protobuf.wkt.google.protobuf.duration_pb import Duration + from protobuf.wkt.google.protobuf.timestamp_pb import Timestamp + from tilebox.datasets.v1.core_pb import RepeatedAny + from tilebox.datasets.v1.well_known_types_pb import Geometry + + +_QueryByIDRequestFields: TypeAlias = Literal["dataset_id", "collection_ids", "id", "skip_data"] + +class QueryByIDRequest(Message[_QueryByIDRequestFields]): + """ + QueryByIDRequest contains the request parameters for retrieving a single data point by its id. + + ```proto + message datasets.v1.QueryByIDRequest + ``` + + Attributes: + dataset_id: + The dataset id to query. If not set, the dataset is derived from the collection ids, in which case there must be at least one collection id set. + + ```proto + optional tilebox.v1.ID dataset_id = 4; + ``` + collection_ids: + collection ids to query. + + ```proto + repeated tilebox.v1.ID collection_ids = 1; + ``` + id: + The id of the requested data point. + + ```proto + optional tilebox.v1.ID id = 2; + ``` + skip_data: + If true, only the datapoint metadata is returned. + + ```proto + bool skip_data = 3; + ``` + """ + + __slots__ = ("dataset_id", "collection_ids", "id", "skip_data") + + if TYPE_CHECKING: + + def __init__( + self, + *, + dataset_id: ID | None = None, + collection_ids: list[ID] | None = None, + id: ID | None = None, + skip_data: bool = False, + ) -> None: + pass + + dataset_id: ID | None + collection_ids: list[ID] + id: ID | None + skip_data: bool + +_QueryFiltersFields: TypeAlias = Literal["time_interval", "datapoint_interval", "spatial_extent", "expressions"] + +class QueryFilters(Message[_QueryFiltersFields]): + """ + QueryFilters contains the filters to apply to a query. + + ```proto + message datasets.v1.QueryFilters + ``` + + Attributes: + time_interval: + ```proto + optional tilebox.v1.TimeInterval time_interval = 1; + ``` + datapoint_interval: + ```proto + optional tilebox.v1.IDInterval datapoint_interval = 2; + ``` + spatial_extent: + ```proto + optional datasets.v1.SpatialFilter spatial_extent = 3; + ``` + expressions: + Additional expressions over fields marked queryable in the dataset schema. + All top-level expressions are combined with each other and with the interval and spatial filters using logical AND. + Use a nested LogicalExpression for explicit OR, NOT, or grouped boolean logic. + Protovalidate limits each list of expressions to 64 items. Before compiling a query, the server must also enforce: + - at most 256 FilterExpression nodes in total across all top-level and nested expressions; + - a maximum expression depth of 8, where a top-level FilterExpression has depth 1 and each nested operand adds 1; + - every referenced field exists and is marked queryable; and + - every comparison operator and literal kind is valid for the referenced field's descriptor. + + ```proto + repeated datasets.v1.FilterExpression expressions = 4; + ``` + """ + + __slots__ = ("time_interval", "datapoint_interval", "spatial_extent", "expressions") + + if TYPE_CHECKING: + + def __init__( + self, + *, + time_interval: TimeInterval | None = None, + datapoint_interval: IDInterval | None = None, + spatial_extent: SpatialFilter | None = None, + expressions: list[FilterExpression] | None = None, + ) -> None: + pass + + time_interval: TimeInterval | None + datapoint_interval: IDInterval | None + spatial_extent: SpatialFilter | None + expressions: list[FilterExpression] + +_FilterExpressionFields: TypeAlias = Literal["logical", "comparison", "is_null"] + +class FilterExpression(Message[_FilterExpressionFields]): + """ + FilterExpression is a typed filter expression compatible with the Basic CQL2 property-to-literal subset. + Expressions use three-valued Boolean logic compatible with SQL and CQL2: true, false, and null (unknown). A datapoint + matches only when the complete filter expression evaluates to true; false and null results are excluded. + + ```proto + message datasets.v1.FilterExpression + ``` + + Attributes: + logical: + Exactly one expression node must be set. + + ```proto + optional datasets.v1.LogicalExpression logical = 1; + ``` + comparison: + ```proto + optional datasets.v1.FieldComparison comparison = 2; + ``` + is_null: + ```proto + optional datasets.v1.FieldNullCheck is_null = 3; + ``` + """ + + __slots__ = ("logical", "comparison", "is_null") + + if TYPE_CHECKING: + + def __init__( + self, + *, + logical: LogicalExpression | None = None, + comparison: FieldComparison | None = None, + is_null: FieldNullCheck | None = None, + ) -> None: + pass + + logical: LogicalExpression | None + comparison: FieldComparison | None + is_null: FieldNullCheck | None + +_LogicalExpressionFields: TypeAlias = Literal["operator", "operands"] + +class LogicalExpression(Message[_LogicalExpressionFields]): + """ + LogicalExpression combines nested filter expressions using SQL- and CQL2-compatible three-valued Boolean logic. + AND, OR, and NOT propagate null according to their standard truth tables; in particular, NOT(null) evaluates to null. + + ```proto + message datasets.v1.LogicalExpression + ``` + + Attributes: + operator: + The logical operation to apply to operands. + + ```proto + datasets.v1.LogicalOperator operator = 1; + ``` + operands: + Operands for the logical operation. AND and OR require at least two; NOT requires exactly one. + + ```proto + repeated datasets.v1.FilterExpression operands = 2; + ``` + """ + + __slots__ = ("operator", "operands") + + if TYPE_CHECKING: + + def __init__( + self, + *, + operator: LogicalOperator | None = None, + operands: list[FilterExpression] | None = None, + ) -> None: + pass + + operator: LogicalOperator + operands: list[FilterExpression] + +_FieldComparisonFields: TypeAlias = Literal["field_name", "operator", "value"] + +class FieldComparison(Message[_FieldComparisonFields]): + """ + FieldComparison compares a queryable dataset field to a typed literal value. + Every comparison evaluates to null (unknown) when the field is absent or explicitly null, including NOT_EQUAL. + Consequently, both `field != value` and `NOT(field == value)` require the field to be present. To include absent or + null fields, explicitly combine the comparison with FieldNullCheck using OR. + + ```proto + message datasets.v1.FieldComparison + ``` + + Attributes: + field_name: + Name of a top-level field in the dataset schema. + + ```proto + string field_name = 1; + ``` + operator: + Comparison operation to apply. + + ```proto + datasets.v1.FieldComparisonOperator operator = 2; + ``` + value: + Literal value. Its kind must match the field's protobuf descriptor. + + ```proto + optional datasets.v1.FieldQueryValue value = 3; + ``` + """ + + __slots__ = ("field_name", "operator", "value") + + if TYPE_CHECKING: + + def __init__( + self, + *, + field_name: str = "", + operator: FieldComparisonOperator | None = None, + value: FieldQueryValue | None = None, + ) -> None: + pass + + field_name: str + operator: FieldComparisonOperator + value: FieldQueryValue | None + +_FieldNullCheckFields: TypeAlias = Literal["field_name"] + +class FieldNullCheck(Message[_FieldNullCheckFields]): + """ + FieldNullCheck matches datapoints for which a queryable field is absent or explicitly null. These states are + equivalent for filtering. IS NOT NULL is represented by wrapping this expression in LOGICAL_OPERATOR_NOT and matches + only datapoints for which the field is present and non-null. + + ```proto + message datasets.v1.FieldNullCheck + ``` + + Attributes: + field_name: + Name of a top-level field in the dataset schema. + + ```proto + string field_name = 1; + ``` + """ + + __slots__ = ("field_name",) + + if TYPE_CHECKING: + + def __init__( + self, + *, + field_name: str = "", + ) -> None: + pass + + field_name: str + +_FieldQueryValueFields: TypeAlias = Literal["bool_value", "int64_value", "uint64_value", "double_value", "string_value", "timestamp_value", "duration_value", "enum_name", "bytes_value"] + +class FieldQueryValue(Message[_FieldQueryValueFields]): + """ + FieldQueryValue is a typed scalar or well-known-type literal used in a field comparison. + + ```proto + message datasets.v1.FieldQueryValue + ``` + + Attributes: + bool_value: + Exactly one typed literal must be set. Explicit presence preserves zero and empty values. + + ```proto + optional bool bool_value = 1; + ``` + int64_value: + ```proto + optional int64 int64_value = 2; + ``` + uint64_value: + ```proto + optional uint64 uint64_value = 3; + ``` + double_value: + ```proto + optional double double_value = 4; + ``` + string_value: + ```proto + optional string string_value = 5; + ``` + timestamp_value: + ```proto + optional google.protobuf.Timestamp timestamp_value = 6; + ``` + duration_value: + ```proto + optional google.protobuf.Duration duration_value = 7; + ``` + enum_name: + Symbolic value from the field's enum descriptor. + + ```proto + optional string enum_name = 8; + ``` + bytes_value: + ```proto + optional bytes bytes_value = 9; + ``` + """ + + __slots__ = ("bool_value", "int64_value", "uint64_value", "double_value", "string_value", "timestamp_value", "duration_value", "enum_name", "bytes_value") + + if TYPE_CHECKING: + + def __init__( + self, + *, + bool_value: bool | None = None, + int64_value: int | None = None, + uint64_value: int | None = None, + double_value: float | None = None, + string_value: str | None = None, + timestamp_value: Timestamp | None = None, + duration_value: Duration | None = None, + enum_name: str | None = None, + bytes_value: bytes | None = None, + ) -> None: + pass + + bool_value: bool + int64_value: int + uint64_value: int + double_value: float + string_value: str + timestamp_value: Timestamp | None + duration_value: Duration | None + enum_name: str + bytes_value: bytes + +_SpatialFilterFields: TypeAlias = Literal["geometry", "mode", "coordinate_system"] + +class SpatialFilter(Message[_SpatialFilterFields]): + """ + SpatialFilter defines a spatial filter operation as part of a query. + + ```proto + message datasets.v1.SpatialFilter + ``` + + Attributes: + geometry: + The geometry to filter by. + + ```proto + optional datasets.v1.Geometry geometry = 1; + ``` + mode: + How to compare datapoint geometries with the filter geometry. + + ```proto + datasets.v1.SpatialFilterMode mode = 2; + ``` + coordinate_system: + Coordinate system in which to perform geometry calculations. + + ```proto + datasets.v1.SpatialCoordinateSystem coordinate_system = 3; + ``` + """ + + __slots__ = ("geometry", "mode", "coordinate_system") + + if TYPE_CHECKING: + + def __init__( + self, + *, + geometry: Geometry | None = None, + mode: SpatialFilterMode | None = None, + coordinate_system: SpatialCoordinateSystem | None = None, + ) -> None: + pass + + geometry: Geometry | None + mode: SpatialFilterMode + coordinate_system: SpatialCoordinateSystem + +_QueryRequestFields: TypeAlias = Literal["dataset_id", "collection_ids", "filters", "page", "skip_data"] + +class QueryRequest(Message[_QueryRequestFields]): + """ + QueryRequest contains the request parameters for retrieving data from a Tilebox dataset. + + ```proto + message datasets.v1.QueryRequest + ``` + + Attributes: + dataset_id: + The dataset id to query. If not set, the dataset is derived from the collection ids, in which case there must be at least one collection id set. + + ```proto + optional tilebox.v1.ID dataset_id = 5; + ``` + collection_ids: + collection ids to query. If empty, all collections of the dataset are queried. + + ```proto + repeated tilebox.v1.ID collection_ids = 1; + ``` + filters: + Filters to apply to the query. + + ```proto + optional datasets.v1.QueryFilters filters = 2; + ``` + page: + The pagination parameters for this request. + + ```proto + optional tilebox.v1.Pagination page = 3; + ``` + skip_data: + If true, only datapoint metadata, such as id, time and ingestion_time are returned. + + ```proto + bool skip_data = 4; + ``` + """ + + __slots__ = ("dataset_id", "collection_ids", "filters", "page", "skip_data") + + if TYPE_CHECKING: + + def __init__( + self, + *, + dataset_id: ID | None = None, + collection_ids: list[ID] | None = None, + filters: QueryFilters | None = None, + page: Pagination | None = None, + skip_data: bool = False, + ) -> None: + pass + + dataset_id: ID | None + collection_ids: list[ID] + filters: QueryFilters | None + page: Pagination | None + skip_data: bool + +_QueryResultPageFields: TypeAlias = Literal["data", "next_page"] + +class QueryResultPage(Message[_QueryResultPageFields]): + """ + QueryResultPage is a single page of data points of a Tilebox dataset + + ```proto + message datasets.v1.QueryResultPage + ``` + + Attributes: + data: + The datapoints. + + ```proto + optional datasets.v1.RepeatedAny data = 1; + ``` + next_page: + The pagination parameters for the next page. + + ```proto + optional tilebox.v1.Pagination next_page = 2; + ``` + """ + + __slots__ = ("data", "next_page") + + if TYPE_CHECKING: + + def __init__( + self, + *, + data: RepeatedAny | None = None, + next_page: Pagination | None = None, + ) -> None: + pass + + data: RepeatedAny | None + next_page: Pagination | None + +class LogicalOperator(Enum): + """ + LogicalOperator specifies how nested filter expressions are combined. + + ```proto + enum datasets.v1.LogicalOperator + ``` + + Attributes: + UNSPECIFIED: + ```proto + LOGICAL_OPERATOR_UNSPECIFIED = 0 + ``` + AND: + ```proto + LOGICAL_OPERATOR_AND = 1 + ``` + OR: + ```proto + LOGICAL_OPERATOR_OR = 2 + ``` + NOT: + ```proto + LOGICAL_OPERATOR_NOT = 3 + ``` + """ + + UNSPECIFIED = 0 + AND = 1 + OR = 2 + NOT = 3 + +class FieldComparisonOperator(Enum): + """ + FieldComparisonOperator specifies a Basic CQL2 comparison operation. + + ```proto + enum datasets.v1.FieldComparisonOperator + ``` + + Attributes: + UNSPECIFIED: + ```proto + FIELD_COMPARISON_OPERATOR_UNSPECIFIED = 0 + ``` + EQUAL: + ```proto + FIELD_COMPARISON_OPERATOR_EQUAL = 1 + ``` + NOT_EQUAL: + ```proto + FIELD_COMPARISON_OPERATOR_NOT_EQUAL = 2 + ``` + LESS_THAN: + ```proto + FIELD_COMPARISON_OPERATOR_LESS_THAN = 3 + ``` + LESS_THAN_OR_EQUAL: + ```proto + FIELD_COMPARISON_OPERATOR_LESS_THAN_OR_EQUAL = 4 + ``` + GREATER_THAN: + ```proto + FIELD_COMPARISON_OPERATOR_GREATER_THAN = 5 + ``` + GREATER_THAN_OR_EQUAL: + ```proto + FIELD_COMPARISON_OPERATOR_GREATER_THAN_OR_EQUAL = 6 + ``` + """ + + UNSPECIFIED = 0 + EQUAL = 1 + NOT_EQUAL = 2 + LESS_THAN = 3 + LESS_THAN_OR_EQUAL = 4 + GREATER_THAN = 5 + GREATER_THAN_OR_EQUAL = 6 + +class SpatialFilterMode(Enum): + """ + SpatialFilterMode specifies how geometries are compared to a given spatial filter. + + ```proto + enum datasets.v1.SpatialFilterMode + ``` + + Attributes: + UNSPECIFIED: + Unspecified spatial filter mode, will default to intersects. + + ```proto + SPATIAL_FILTER_MODE_UNSPECIFIED = 0 + ``` + INTERSECTS: + Any geometry that intersects the filter geometry is included. + + ```proto + SPATIAL_FILTER_MODE_INTERSECTS = 1 + ``` + FILTER_CONTAINS_GEOMETRY: + Only datapoint geometries fully contained within the filter geometry are included. + + ```proto + SPATIAL_FILTER_MODE_FILTER_CONTAINS_GEOMETRY = 2 + ``` + GEOMETRY_CONTAINS_FILTER: + Only datapoint geometries that fully contain the filter geometry are included. + + ```proto + SPATIAL_FILTER_MODE_GEOMETRY_CONTAINS_FILTER = 3 + ``` + """ + + UNSPECIFIED = 0 + INTERSECTS = 1 + FILTER_CONTAINS_GEOMETRY = 2 + GEOMETRY_CONTAINS_FILTER = 3 + +class SpatialCoordinateSystem(Enum): + """ + SpatialCoordinateSystem specifies the coordinate system in which to perform geometry calculations such as + intersections. + + ```proto + enum datasets.v1.SpatialCoordinateSystem + ``` + + Attributes: + UNSPECIFIED: + Unspecified coordinate system, will default to cartesian. + + ```proto + SPATIAL_COORDINATE_SYSTEM_UNSPECIFIED = 0 + ``` + CARTESIAN: + Cartesian (lat/lon) coordinate system + + ```proto + SPATIAL_COORDINATE_SYSTEM_CARTESIAN = 1 + ``` + SPHERICAL: + Spherical (x/y/z) coordinate system + + ```proto + SPATIAL_COORDINATE_SYSTEM_SPHERICAL = 2 + ``` + """ + + UNSPECIFIED = 0 + CARTESIAN = 1 + SPHERICAL = 2 + + +_DESC = file_desc( + b'\n\x1ddatasets/v1/data_access.proto\x12\x0bdatasets.v1\x1a\x16datasets/v1/core.proto\x1a"datasets/v1/well_known_types.proto\x1a\x1egoogle/protobuf/duration.proto\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\x13tilebox/v1/id.proto\x1a\x16tilebox/v1/query.proto"\xb5\x01\n\x10QueryByIDRequest\x12-\n\ndataset_id\x18\x04 \x01(\x0b2\x0e.tilebox.v1.IDR\tdatasetId\x125\n\x0ecollection_ids\x18\x01 \x03(\x0b2\x0e.tilebox.v1.IDR\rcollectionIds\x12\x1e\n\x02id\x18\x02 \x01(\x0b2\x0e.tilebox.v1.IDR\x02id\x12\x1b\n\tskip_data\x18\x03 \x01(\x08R\x08skipData"\x98\x02\n\x0cQueryFilters\x12=\n\rtime_interval\x18\x01 \x01(\x0b2\x18.tilebox.v1.TimeIntervalR\x0ctimeInterval\x12E\n\x12datapoint_interval\x18\x02 \x01(\x0b2\x16.tilebox.v1.IDIntervalR\x11datapointInterval\x12A\n\x0espatial_extent\x18\x03 \x01(\x0b2\x1a.datasets.v1.SpatialFilterR\rspatialExtent\x12?\n\x0bexpressions\x18\x04 \x03(\x0b2\x1d.datasets.v1.FilterExpressionR\x0bexpressions"\xc0\x01\n\x10FilterExpression\x128\n\x07logical\x18\x01 \x01(\x0b2\x1e.datasets.v1.LogicalExpressionR\x07logical\x12<\n\ncomparison\x18\x02 \x01(\x0b2\x1c.datasets.v1.FieldComparisonR\ncomparison\x124\n\x07is_null\x18\x03 \x01(\x0b2\x1b.datasets.v1.FieldNullCheckR\x06isNull"\x88\x01\n\x11LogicalExpression\x128\n\x08operator\x18\x01 \x01(\x0e2\x1c.datasets.v1.LogicalOperatorR\x08operator\x129\n\x08operands\x18\x02 \x03(\x0b2\x1d.datasets.v1.FilterExpressionR\x08operands"\xa6\x01\n\x0fFieldComparison\x12\x1d\n\nfield_name\x18\x01 \x01(\tR\tfieldName\x12@\n\x08operator\x18\x02 \x01(\x0e2$.datasets.v1.FieldComparisonOperatorR\x08operator\x122\n\x05value\x18\x03 \x01(\x0b2\x1c.datasets.v1.FieldQueryValueR\x05value"/\n\x0eFieldNullCheck\x12\x1d\n\nfield_name\x18\x01 \x01(\tR\tfieldName"\xb0\x03\n\x0fFieldQueryValue\x12$\n\nbool_value\x18\x01 \x01(\x08R\tboolValueB\x05\xaa\x01\x02\x08\x01\x12&\n\x0bint64_value\x18\x02 \x01(\x03R\nint64ValueB\x05\xaa\x01\x02\x08\x01\x12(\n\x0cuint64_value\x18\x03 \x01(\x04R\x0buint64ValueB\x05\xaa\x01\x02\x08\x01\x12(\n\x0cdouble_value\x18\x04 \x01(\x01R\x0bdoubleValueB\x05\xaa\x01\x02\x08\x01\x12(\n\x0cstring_value\x18\x05 \x01(\tR\x0bstringValueB\x05\xaa\x01\x02\x08\x01\x12C\n\x0ftimestamp_value\x18\x06 \x01(\x0b2\x1a.google.protobuf.TimestampR\x0etimestampValue\x12@\n\x0eduration_value\x18\x07 \x01(\x0b2\x19.google.protobuf.DurationR\rdurationValue\x12"\n\tenum_name\x18\x08 \x01(\tR\x08enumNameB\x05\xaa\x01\x02\x08\x01\x12&\n\x0bbytes_value\x18\t \x01(\x0cR\nbytesValueB\x05\xaa\x01\x02\x08\x01"\xc9\x01\n\rSpatialFilter\x121\n\x08geometry\x18\x01 \x01(\x0b2\x15.datasets.v1.GeometryR\x08geometry\x122\n\x04mode\x18\x02 \x01(\x0e2\x1e.datasets.v1.SpatialFilterModeR\x04mode\x12Q\n\x11coordinate_system\x18\x03 \x01(\x0e2$.datasets.v1.SpatialCoordinateSystemR\x10coordinateSystem"\xf9\x01\n\x0cQueryRequest\x12-\n\ndataset_id\x18\x05 \x01(\x0b2\x0e.tilebox.v1.IDR\tdatasetId\x125\n\x0ecollection_ids\x18\x01 \x03(\x0b2\x0e.tilebox.v1.IDR\rcollectionIds\x123\n\x07filters\x18\x02 \x01(\x0b2\x19.datasets.v1.QueryFiltersR\x07filters\x121\n\x04page\x18\x03 \x01(\x0b2\x16.tilebox.v1.PaginationR\x04pageB\x05\xaa\x01\x02\x08\x01\x12\x1b\n\tskip_data\x18\x04 \x01(\x08R\x08skipData"{\n\x0fQueryResultPage\x12,\n\x04data\x18\x01 \x01(\x0b2\x18.datasets.v1.RepeatedAnyR\x04data\x12:\n\tnext_page\x18\x02 \x01(\x0b2\x16.tilebox.v1.PaginationR\x08nextPageB\x05\xaa\x01\x02\x08\x01*\x80\x01\n\x0fLogicalOperator\x12 \n\x1cLOGICAL_OPERATOR_UNSPECIFIED\x10\x00\x12\x18\n\x14LOGICAL_OPERATOR_AND\x10\x01\x12\x17\n\x13LOGICAL_OPERATOR_OR\x10\x02\x12\x18\n\x14LOGICAL_OPERATOR_NOT\x10\x03*\xce\x02\n\x17FieldComparisonOperator\x12)\n%FIELD_COMPARISON_OPERATOR_UNSPECIFIED\x10\x00\x12#\n\x1fFIELD_COMPARISON_OPERATOR_EQUAL\x10\x01\x12\'\n#FIELD_COMPARISON_OPERATOR_NOT_EQUAL\x10\x02\x12\'\n#FIELD_COMPARISON_OPERATOR_LESS_THAN\x10\x03\x120\n,FIELD_COMPARISON_OPERATOR_LESS_THAN_OR_EQUAL\x10\x04\x12*\n&FIELD_COMPARISON_OPERATOR_GREATER_THAN\x10\x05\x123\n/FIELD_COMPARISON_OPERATOR_GREATER_THAN_OR_EQUAL\x10\x06*\xc0\x01\n\x11SpatialFilterMode\x12#\n\x1fSPATIAL_FILTER_MODE_UNSPECIFIED\x10\x00\x12"\n\x1eSPATIAL_FILTER_MODE_INTERSECTS\x10\x01\x120\n,SPATIAL_FILTER_MODE_FILTER_CONTAINS_GEOMETRY\x10\x02\x120\n,SPATIAL_FILTER_MODE_GEOMETRY_CONTAINS_FILTER\x10\x03*\x96\x01\n\x17SpatialCoordinateSystem\x12)\n%SPATIAL_COORDINATE_SYSTEM_UNSPECIFIED\x10\x00\x12\'\n#SPATIAL_COORDINATE_SYSTEM_CARTESIAN\x10\x01\x12\'\n#SPATIAL_COORDINATE_SYSTEM_SPHERICAL\x10\x022\x93\x01\n\x11DataAccessService\x12<\n\tQueryByID\x12\x1d.datasets.v1.QueryByIDRequest\x1a\x10.datasets.v1.Any\x12@\n\x05Query\x12\x19.datasets.v1.QueryRequest\x1a\x1c.datasets.v1.QueryResultPageB\x05\x92\x03\x02\x08\x02b\x08editionsp\xe8\x07', + [ + core_pb.desc(), + well_known_types_pb.desc(), + duration_pb.desc(), + timestamp_pb.desc(), + id_pb.desc(), + query_pb.desc(), + ], + { + "QueryByIDRequest": QueryByIDRequest, + "QueryFilters": QueryFilters, + "FilterExpression": FilterExpression, + "LogicalExpression": LogicalExpression, + "FieldComparison": FieldComparison, + "FieldNullCheck": FieldNullCheck, + "FieldQueryValue": FieldQueryValue, + "SpatialFilter": SpatialFilter, + "QueryRequest": QueryRequest, + "QueryResultPage": QueryResultPage, + "LogicalOperator": LogicalOperator, + "FieldComparisonOperator": FieldComparisonOperator, + "SpatialFilterMode": SpatialFilterMode, + "SpatialCoordinateSystem": SpatialCoordinateSystem, + }, +) + + +def desc() -> DescFile: + """Returns the descriptor for the file `datasets/v1/data_access.proto`.""" + return _DESC diff --git a/tilebox-datasets/tilebox/datasets/datasets/v1/data_ingestion_connect.py b/tilebox-datasets/tilebox/datasets/v1/data_ingestion_connect.py similarity index 54% rename from tilebox-datasets/tilebox/datasets/datasets/v1/data_ingestion_connect.py rename to tilebox-datasets/tilebox/datasets/v1/data_ingestion_connect.py index 7937dc1..ce2c436 100644 --- a/tilebox-datasets/tilebox/datasets/datasets/v1/data_ingestion_connect.py +++ b/tilebox-datasets/tilebox/datasets/v1/data_ingestion_connect.py @@ -1,32 +1,56 @@ -# -*- coding: utf-8 -*- -# Generated by https://github.com/connectrpc/connect-python. DO NOT EDIT! -# source: datasets/v1/data_ingestion.proto +# Generated from datasets/v1/data_ingestion.proto. DO NOT EDIT. +# Generated by protoc-gen-connectrpc-py v0.12.1 with parameter "map_imports=datasets/**:tilebox,map_imports=workflows/**:tilebox,map_imports=tilebox/**:_tilebox.proto,map_imports=opentelemetry/**:tilebox.workflows._otel". +# ruff: noqa: PGH004 +# ruff: noqa +# fmt: off -from collections.abc import AsyncGenerator, AsyncIterator, Iterable, Iterator, Mapping -from typing import Protocol +from __future__ import annotations + +from typing import Protocol, TYPE_CHECKING from connectrpc.client import ConnectClient, ConnectClientSync from connectrpc.code import Code -from connectrpc.codec import Codec -from connectrpc.compression import Compression from connectrpc.errors import ConnectError -from connectrpc.interceptor import Interceptor, InterceptorSync from connectrpc.method import IdempotencyLevel, MethodInfo -from connectrpc.request import Headers, RequestContext -from connectrpc.server import ConnectASGIApplication, ConnectWSGIApplication, Endpoint, EndpointSync -import tilebox.datasets.datasets.v1.data_ingestion_pb2 as datasets_dot_v1_dot_data__ingestion__pb2 +from connectrpc.server import ConnectASGIApplication, ConnectWSGIApplication, DEFAULT_READ_MAX_BYTES, Endpoint, EndpointSync +from protobuf import DescService +from tilebox.datasets.v1 import data_ingestion_pb +from tilebox.datasets.v1.data_ingestion_pb import DeleteRequest, DeleteResponse, IngestRequest, IngestResponse + +if TYPE_CHECKING: + from collections.abc import AsyncGenerator, Iterable, Mapping + + from connectrpc.codec import Codec + from connectrpc.compression import Compression + from connectrpc.interceptor import Interceptor, InterceptorSync + from connectrpc.request import Headers, RequestContext class DataIngestionService(Protocol): - async def ingest(self, request: datasets_dot_v1_dot_data__ingestion__pb2.IngestRequest, ctx: RequestContext) -> datasets_dot_v1_dot_data__ingestion__pb2.IngestResponse: - raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") + """ + DataIngestionService provides data ingestion and deletion capabilities for Tilebox datasets. + """ + async def ingest(self, request: IngestRequest, ctx: RequestContext[IngestRequest, IngestResponse], /) -> IngestResponse: + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') - async def delete(self, request: datasets_dot_v1_dot_data__ingestion__pb2.DeleteRequest, ctx: RequestContext) -> datasets_dot_v1_dot_data__ingestion__pb2.DeleteResponse: - raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") + async def delete(self, request: DeleteRequest, ctx: RequestContext[DeleteRequest, DeleteResponse], /) -> DeleteResponse: + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') + @classmethod + def desc(cls) -> DescService: + """Returns the descriptor for this service.""" + return next(s for s in data_ingestion_pb.desc().services if s.type_name == 'datasets.v1.DataIngestionService') class DataIngestionServiceASGIApplication(ConnectASGIApplication[DataIngestionService]): - def __init__(self, service: DataIngestionService | AsyncGenerator[DataIngestionService], *, interceptors: Iterable[Interceptor]=(), read_max_bytes: int | None = None, compressions: Iterable[Compression] | None = None, codecs: Iterable[Codec] | None = None) -> None: + def __init__( + self, + service: DataIngestionService | AsyncGenerator[DataIngestionService], + *, + interceptors: Iterable[Interceptor] = (), + read_max_bytes: int | None = DEFAULT_READ_MAX_BYTES, + compressions: Iterable[Compression] | None = None, + codecs: Iterable[Codec] | None = None, + ) -> None: super().__init__( service=service, endpoints=lambda svc: { @@ -34,8 +58,8 @@ def __init__(self, service: DataIngestionService | AsyncGenerator[DataIngestionS method=MethodInfo( name="Ingest", service_name="datasets.v1.DataIngestionService", - input=datasets_dot_v1_dot_data__ingestion__pb2.IngestRequest, - output=datasets_dot_v1_dot_data__ingestion__pb2.IngestResponse, + input=IngestRequest, + output=IngestResponse, idempotency_level=IdempotencyLevel.UNKNOWN, ), function=svc.ingest, @@ -44,8 +68,8 @@ def __init__(self, service: DataIngestionService | AsyncGenerator[DataIngestionS method=MethodInfo( name="Delete", service_name="datasets.v1.DataIngestionService", - input=datasets_dot_v1_dot_data__ingestion__pb2.DeleteRequest, - output=datasets_dot_v1_dot_data__ingestion__pb2.DeleteResponse, + input=DeleteRequest, + output=DeleteResponse, idempotency_level=IdempotencyLevel.UNKNOWN, ), function=svc.delete, @@ -64,20 +88,23 @@ def path(self) -> str: class DataIngestionServiceClient(ConnectClient): + """ + DataIngestionService provides data ingestion and deletion capabilities for Tilebox datasets. + """ async def ingest( self, - request: datasets_dot_v1_dot_data__ingestion__pb2.IngestRequest, + request: IngestRequest, *, headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, - ) -> datasets_dot_v1_dot_data__ingestion__pb2.IngestResponse: + ) -> IngestResponse: return await self.execute_unary( request=request, method=MethodInfo( name="Ingest", service_name="datasets.v1.DataIngestionService", - input=datasets_dot_v1_dot_data__ingestion__pb2.IngestRequest, - output=datasets_dot_v1_dot_data__ingestion__pb2.IngestResponse, + input=IngestRequest, + output=IngestResponse, idempotency_level=IdempotencyLevel.UNKNOWN, ), headers=headers, @@ -86,45 +113,56 @@ async def ingest( async def delete( self, - request: datasets_dot_v1_dot_data__ingestion__pb2.DeleteRequest, + request: DeleteRequest, *, headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, - ) -> datasets_dot_v1_dot_data__ingestion__pb2.DeleteResponse: + ) -> DeleteResponse: return await self.execute_unary( request=request, method=MethodInfo( name="Delete", service_name="datasets.v1.DataIngestionService", - input=datasets_dot_v1_dot_data__ingestion__pb2.DeleteRequest, - output=datasets_dot_v1_dot_data__ingestion__pb2.DeleteResponse, + input=DeleteRequest, + output=DeleteResponse, idempotency_level=IdempotencyLevel.UNKNOWN, ), headers=headers, timeout_ms=timeout_ms, ) - - - - class DataIngestionServiceSync(Protocol): - def ingest(self, request: datasets_dot_v1_dot_data__ingestion__pb2.IngestRequest, ctx: RequestContext) -> datasets_dot_v1_dot_data__ingestion__pb2.IngestResponse: - raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") - def delete(self, request: datasets_dot_v1_dot_data__ingestion__pb2.DeleteRequest, ctx: RequestContext) -> datasets_dot_v1_dot_data__ingestion__pb2.DeleteResponse: - raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") + """ + DataIngestionService provides data ingestion and deletion capabilities for Tilebox datasets. + """ + def ingest(self, request: IngestRequest, ctx: RequestContext[IngestRequest, IngestResponse], /) -> IngestResponse: + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') + def delete(self, request: DeleteRequest, ctx: RequestContext[DeleteRequest, DeleteResponse], /) -> DeleteResponse: + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') + + @classmethod + def desc(cls) -> DescService: + """Returns the descriptor for this service.""" + return next(s for s in data_ingestion_pb.desc().services if s.type_name == 'datasets.v1.DataIngestionService') class DataIngestionServiceWSGIApplication(ConnectWSGIApplication): - def __init__(self, service: DataIngestionServiceSync, interceptors: Iterable[InterceptorSync]=(), read_max_bytes: int | None = None, compressions: Iterable[Compression] | None = None, codecs: Iterable[Codec] | None = None) -> None: + def __init__( + self, + service: DataIngestionServiceSync, + interceptors: Iterable[InterceptorSync] = (), + read_max_bytes: int | None = DEFAULT_READ_MAX_BYTES, + compressions: Iterable[Compression] | None = None, + codecs: Iterable[Codec] | None = None, + ) -> None: super().__init__( endpoints={ "/datasets.v1.DataIngestionService/Ingest": EndpointSync.unary( method=MethodInfo( name="Ingest", service_name="datasets.v1.DataIngestionService", - input=datasets_dot_v1_dot_data__ingestion__pb2.IngestRequest, - output=datasets_dot_v1_dot_data__ingestion__pb2.IngestResponse, + input=IngestRequest, + output=IngestResponse, idempotency_level=IdempotencyLevel.UNKNOWN, ), function=service.ingest, @@ -133,8 +171,8 @@ def __init__(self, service: DataIngestionServiceSync, interceptors: Iterable[Int method=MethodInfo( name="Delete", service_name="datasets.v1.DataIngestionService", - input=datasets_dot_v1_dot_data__ingestion__pb2.DeleteRequest, - output=datasets_dot_v1_dot_data__ingestion__pb2.DeleteResponse, + input=DeleteRequest, + output=DeleteResponse, idempotency_level=IdempotencyLevel.UNKNOWN, ), function=service.delete, @@ -153,40 +191,42 @@ def path(self) -> str: class DataIngestionServiceClientSync(ConnectClientSync): + """ + DataIngestionService provides data ingestion and deletion capabilities for Tilebox datasets. + """ def ingest( self, - request: datasets_dot_v1_dot_data__ingestion__pb2.IngestRequest, + request: IngestRequest, *, headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, - ) -> datasets_dot_v1_dot_data__ingestion__pb2.IngestResponse: + ) -> IngestResponse: return self.execute_unary( request=request, method=MethodInfo( name="Ingest", service_name="datasets.v1.DataIngestionService", - input=datasets_dot_v1_dot_data__ingestion__pb2.IngestRequest, - output=datasets_dot_v1_dot_data__ingestion__pb2.IngestResponse, + input=IngestRequest, + output=IngestResponse, idempotency_level=IdempotencyLevel.UNKNOWN, ), headers=headers, timeout_ms=timeout_ms, ) - def delete( self, - request: datasets_dot_v1_dot_data__ingestion__pb2.DeleteRequest, + request: DeleteRequest, *, headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, - ) -> datasets_dot_v1_dot_data__ingestion__pb2.DeleteResponse: + ) -> DeleteResponse: return self.execute_unary( request=request, method=MethodInfo( name="Delete", service_name="datasets.v1.DataIngestionService", - input=datasets_dot_v1_dot_data__ingestion__pb2.DeleteRequest, - output=datasets_dot_v1_dot_data__ingestion__pb2.DeleteResponse, + input=DeleteRequest, + output=DeleteResponse, idempotency_level=IdempotencyLevel.UNKNOWN, ), headers=headers, diff --git a/tilebox-datasets/tilebox/datasets/v1/data_ingestion_pb.py b/tilebox-datasets/tilebox/datasets/v1/data_ingestion_pb.py new file mode 100644 index 0000000..10fa933 --- /dev/null +++ b/tilebox-datasets/tilebox/datasets/v1/data_ingestion_pb.py @@ -0,0 +1,206 @@ +# Generated from datasets/v1/data_ingestion.proto. DO NOT EDIT. +# Generated by protoc-gen-py v0.6.0 with parameter "init_files=false,map_imports=datasets/**:tilebox,map_imports=workflows/**:tilebox,map_imports=tilebox/**:_tilebox.proto,map_imports=opentelemetry/**:tilebox.workflows._otel". +# ruff: noqa: PGH004 +# ruff: noqa +# fmt: off + +from __future__ import annotations + +from typing import Literal, TYPE_CHECKING, TypeAlias + +from _tilebox.proto.tilebox.v1 import id_pb +from protobuf import Message +from protobuf._codegen import file_desc + +if TYPE_CHECKING: + from _tilebox.proto.tilebox.v1.id_pb import ID + from protobuf import DescFile + + +_IngestRequestFields: TypeAlias = Literal["collection_id", "values", "allow_existing"] + +class IngestRequest(Message[_IngestRequestFields]): + """ + IngestRequest is used to ingest one or multiple datapoints into a collection. + + ```proto + message datasets.v1.IngestRequest + ``` + + Attributes: + collection_id: + The collection to insert the datapoints into. + + ```proto + optional tilebox.v1.ID collection_id = 1; + ``` + values: + The datapoints to insert. The values here are encoded protobuf messages. The type of the message is determined + by the type of the dataset that the specified collection belongs to. + + ```proto + repeated bytes values = 2; + ``` + allow_existing: + Whether to allow existing datapoints as part of the request. If true, datapoints that already exist will be + ignored, and the number of such existing datapoints will be returned in the response. If false, any datapoints + that already exist will result in an error. Setting this to true is useful for achieving idempotency (e.g. + allowing re-ingestion of datapoints that have already been ingested in the past). + + ```proto + bool allow_existing = 3; + ``` + """ + + __slots__ = ("collection_id", "values", "allow_existing") + + if TYPE_CHECKING: + + def __init__( + self, + *, + collection_id: ID | None = None, + values: list[bytes] | None = None, + allow_existing: bool = False, + ) -> None: + pass + + collection_id: ID | None + values: list[bytes] + allow_existing: bool + +_IngestResponseFields: TypeAlias = Literal["num_created", "num_existing", "datapoint_ids"] + +class IngestResponse(Message[_IngestResponseFields]): + """ + IngestResponse is the response to a IngestRequest, indicating the number of datapoints that were + ingested as well as the generated ids for those datapoints. + + ```proto + message datasets.v1.IngestResponse + ``` + + Attributes: + num_created: + The number of datapoints that were created. + + ```proto + int64 num_created = 1; + ``` + num_existing: + The number of datapoints that were ignored because they already existed. + + ```proto + int64 num_existing = 2; + ``` + datapoint_ids: + The ids of the datapoints in the same order as the datapoints in the request. + + ```proto + repeated tilebox.v1.ID datapoint_ids = 3; + ``` + """ + + __slots__ = ("num_created", "num_existing", "datapoint_ids") + + if TYPE_CHECKING: + + def __init__( + self, + *, + num_created: int = 0, + num_existing: int = 0, + datapoint_ids: list[ID] | None = None, + ) -> None: + pass + + num_created: int + num_existing: int + datapoint_ids: list[ID] + +_DeleteRequestFields: TypeAlias = Literal["collection_id", "datapoint_ids"] + +class DeleteRequest(Message[_DeleteRequestFields]): + """ + DeleteRequest is used to delete multiple datapoints from a collection. + + ```proto + message datasets.v1.DeleteRequest + ``` + + Attributes: + collection_id: + ```proto + optional tilebox.v1.ID collection_id = 1; + ``` + datapoint_ids: + ```proto + repeated tilebox.v1.ID datapoint_ids = 2; + ``` + """ + + __slots__ = ("collection_id", "datapoint_ids") + + if TYPE_CHECKING: + + def __init__( + self, + *, + collection_id: ID | None = None, + datapoint_ids: list[ID] | None = None, + ) -> None: + pass + + collection_id: ID | None + datapoint_ids: list[ID] + +_DeleteResponseFields: TypeAlias = Literal["num_deleted"] + +class DeleteResponse(Message[_DeleteResponseFields]): + """ + DeleteResponse is used to indicate that multiple datapoints were deleted. + + ```proto + message datasets.v1.DeleteResponse + ``` + + Attributes: + num_deleted: + The number of datapoints that were deleted. + + ```proto + int64 num_deleted = 1; + ``` + """ + + __slots__ = ("num_deleted",) + + if TYPE_CHECKING: + + def __init__( + self, + *, + num_deleted: int = 0, + ) -> None: + pass + + num_deleted: int + + +_DESC = file_desc( + b'\n datasets/v1/data_ingestion.proto\x12\x0bdatasets.v1\x1a\x13tilebox/v1/id.proto"\x83\x01\n\rIngestRequest\x123\n\rcollection_id\x18\x01 \x01(\x0b2\x0e.tilebox.v1.IDR\x0ccollectionId\x12\x16\n\x06values\x18\x02 \x03(\x0cR\x06values\x12%\n\x0eallow_existing\x18\x03 \x01(\x08R\rallowExisting"\x89\x01\n\x0eIngestResponse\x12\x1f\n\x0bnum_created\x18\x01 \x01(\x03R\nnumCreated\x12!\n\x0cnum_existing\x18\x02 \x01(\x03R\x0bnumExisting\x123\n\rdatapoint_ids\x18\x03 \x03(\x0b2\x0e.tilebox.v1.IDR\x0cdatapointIds"y\n\rDeleteRequest\x123\n\rcollection_id\x18\x01 \x01(\x0b2\x0e.tilebox.v1.IDR\x0ccollectionId\x123\n\rdatapoint_ids\x18\x02 \x03(\x0b2\x0e.tilebox.v1.IDR\x0cdatapointIds"1\n\x0eDeleteResponse\x12\x1f\n\x0bnum_deleted\x18\x01 \x01(\x03R\nnumDeleted2\x9c\x01\n\x14DataIngestionService\x12A\n\x06Ingest\x12\x1a.datasets.v1.IngestRequest\x1a\x1b.datasets.v1.IngestResponse\x12A\n\x06Delete\x12\x1a.datasets.v1.DeleteRequest\x1a\x1b.datasets.v1.DeleteResponseB\x05\x92\x03\x02\x08\x02b\x08editionsp\xe8\x07', + [ + id_pb.desc(), + ], + { + "IngestRequest": IngestRequest, + "IngestResponse": IngestResponse, + "DeleteRequest": DeleteRequest, + "DeleteResponse": DeleteResponse, + }, +) + + +def desc() -> DescFile: + """Returns the descriptor for the file `datasets/v1/data_ingestion.proto`.""" + return _DESC diff --git a/tilebox-datasets/tilebox/datasets/v1/dataset_type_pb.py b/tilebox-datasets/tilebox/datasets/v1/dataset_type_pb.py new file mode 100644 index 0000000..8e38a12 --- /dev/null +++ b/tilebox-datasets/tilebox/datasets/v1/dataset_type_pb.py @@ -0,0 +1,424 @@ +# Generated from datasets/v1/dataset_type.proto. DO NOT EDIT. +# Generated by protoc-gen-py v0.6.0 with parameter "init_files=false,map_imports=datasets/**:tilebox,map_imports=workflows/**:tilebox,map_imports=tilebox/**:_tilebox.proto,map_imports=opentelemetry/**:tilebox.workflows._otel". +# ruff: noqa: PGH004 +# ruff: noqa +# fmt: off + +from __future__ import annotations + +from typing import Literal, TYPE_CHECKING, TypeAlias + +from _tilebox.proto.tilebox.v1 import id_pb +from protobuf import Enum, Message +from protobuf._codegen import file_desc +from protobuf.wkt.google.protobuf import descriptor_pb, timestamp_pb +from tilebox.datasets.v1 import well_known_types_pb + +if TYPE_CHECKING: + from _tilebox.proto.tilebox.v1.id_pb import ID + from protobuf import DescFile + from protobuf.wkt.google.protobuf.descriptor_pb import FieldDescriptorProto, FileDescriptorSet + from protobuf.wkt.google.protobuf.timestamp_pb import Timestamp + from tilebox.datasets.v1.well_known_types_pb import Geometry + + +_FieldFields: TypeAlias = Literal["descriptor", "annotation"] + +class Field(Message[_FieldFields]): + """ + Field is the request representation of a dataset field. It intentionally combines only a protobuf field descriptor + with its Tilebox annotation, matching the information represented separately by AnnotatedType. + + ```proto + message datasets.v1.Field + ``` + + Attributes: + descriptor: + The descriptor contains the name of the field, the type, optional labels (e.g. repeated) and other information. + If the type is TYPE_MESSAGE, then the type_name must be a fully qualified name to a well known type, e.g. + `datasets.v1.Vec3` or `google.protobuf.Timestamp`. + + ```proto + optional google.protobuf.FieldDescriptorProto descriptor = 1; + ``` + annotation: + Additional documentation, source mapping, and query metadata for the field. + + ```proto + optional datasets.v1.FieldAnnotation annotation = 2; + ``` + """ + + __slots__ = ("descriptor", "annotation") + + if TYPE_CHECKING: + + def __init__( + self, + *, + descriptor: FieldDescriptorProto | None = None, + annotation: FieldAnnotation | None = None, + ) -> None: + pass + + descriptor: FieldDescriptorProto | None + annotation: FieldAnnotation | None + +_FieldAnnotationFields: TypeAlias = Literal["description", "example_value", "source_json_pointer", "queryable", "json_schema_ref", "roles"] + +class FieldAnnotation(Message[_FieldAnnotationFields]): + """ + FieldAnnotation contains additional information about a field in a dataset. + + ```proto + message datasets.v1.FieldAnnotation + ``` + + Attributes: + description: + ```proto + string description = 1; + ``` + example_value: + ```proto + string example_value = 2; + ``` + source_json_pointer: + RFC 6901 JSON Pointer locating the field value in the source document. + This allows flattened dataset fields to be reconstructed without relying on their protobuf field names. + + ```proto + optional string source_json_pointer = 3; + ``` + queryable: + Whether the field is projected into query storage and may be used in server-side filter expressions. + Queryable fields are not necessarily backed by a secondary database index. + + ```proto + bool queryable = 4; + ``` + json_schema_ref: + Optional JSON Schema reference for this field when it corresponds to a well known schema. + For example, this may reference the canonical STAC extension schema definition for a property. + Emitted as `$ref` when advertising this field as a queryable in STAC. + + ```proto + optional string json_schema_ref = 5; + ``` + roles: + Semantic display roles fulfilled by this field. + + ```proto + repeated datasets.v1.FieldRole roles = 6 [packed = true]; + ``` + """ + + __slots__ = ("description", "example_value", "source_json_pointer", "queryable", "json_schema_ref", "roles") + + if TYPE_CHECKING: + + def __init__( + self, + *, + description: str = "", + example_value: str = "", + source_json_pointer: str | None = None, + queryable: bool = False, + json_schema_ref: str | None = None, + roles: list[FieldRole] | None = None, + ) -> None: + pass + + description: str + example_value: str + source_json_pointer: str + queryable: bool + json_schema_ref: str + roles: list[FieldRole] + +_DatasetTypeFields: TypeAlias = Literal["kind", "fields"] + +class DatasetType(Message[_DatasetTypeFields]): + """ + DatasetType is the request representation of a dataset type, used when creating or updating a dataset. + + ```proto + message datasets.v1.DatasetType + ``` + + Attributes: + kind: + kind denotes the kind of dataset this type describes. We do not rely on the default fields to be set in our + array of fields - since that way users could circumvent those by manually sending requests not from our Console. + + ```proto + datasets.v1.DatasetKind kind = 1; + ``` + fields: + A list of fields that this dataset consists of. + + ```proto + repeated datasets.v1.Field fields = 2; + ``` + """ + + __slots__ = ("kind", "fields") + + if TYPE_CHECKING: + + def __init__( + self, + *, + kind: DatasetKind | None = None, + fields: list[Field] | None = None, + ) -> None: + pass + + kind: DatasetKind + fields: list[Field] + +_AnnotatedTypeFields: TypeAlias = Literal["descriptor_set", "type_url", "field_annotations", "kind"] + +class AnnotatedType(Message[_AnnotatedTypeFields]): + """ + AnnotatedType is the resolved representation returned on Dataset messages, including GetDataset responses. + It combines the generated protobuf descriptors with the corresponding Tilebox field annotations. + + ```proto + message datasets.v1.AnnotatedType + ``` + + Attributes: + descriptor_set: + ```proto + optional google.protobuf.FileDescriptorSet descriptor_set = 1; + ``` + type_url: + the url of the type, one of the types defined in the descriptor + + ```proto + string type_url = 2; + ``` + field_annotations: + Annotations corresponding by index to the FieldDescriptorProto entries of the message selected by type_url. + Empty annotations must be retained so this positional mapping remains stable. + + ```proto + repeated datasets.v1.FieldAnnotation field_annotations = 4; + ``` + kind: + ```proto + datasets.v1.DatasetKind kind = 5; + ``` + """ + + __slots__ = ("descriptor_set", "type_url", "field_annotations", "kind") + + if TYPE_CHECKING: + + def __init__( + self, + *, + descriptor_set: FileDescriptorSet | None = None, + type_url: str = "", + field_annotations: list[FieldAnnotation] | None = None, + kind: DatasetKind | None = None, + ) -> None: + pass + + descriptor_set: FileDescriptorSet | None + type_url: str + field_annotations: list[FieldAnnotation] + kind: DatasetKind + +_TemporalDatapointFields: TypeAlias = Literal["time", "id", "ingestion_time"] + +class TemporalDatapoint(Message[_TemporalDatapointFields]): + """ + TemporalDatapoint is a single datapoint in a temporal dataset. The message is a superset of all + message types that are Temporal Datasets. + + ```proto + message datasets.v1.TemporalDatapoint + ``` + + Attributes: + time: + The timestamp associated with each data point. + + ```proto + optional google.protobuf.Timestamp time = 1; + ``` + id: + A universally unique identifier (UUID) that uniquely identifies each data point. + + ```proto + optional tilebox.v1.ID id = 2; + ``` + ingestion_time: + The time the data point was ingested into the Tilebox API. + + ```proto + optional google.protobuf.Timestamp ingestion_time = 3; + ``` + """ + + __slots__ = ("time", "id", "ingestion_time") + + if TYPE_CHECKING: + + def __init__( + self, + *, + time: Timestamp | None = None, + id: ID | None = None, + ingestion_time: Timestamp | None = None, + ) -> None: + pass + + time: Timestamp | None + id: ID | None + ingestion_time: Timestamp | None + +_SpatioTemporalDatapointFields: TypeAlias = Literal["time", "id", "ingestion_time", "geometry"] + +class SpatioTemporalDatapoint(Message[_SpatioTemporalDatapointFields]): + """ + SpatioTemporalDatapoint is a single datapoint in a spatiotemporal dataset. The message is a superset of all + message types that are spatiotemporal Datasets. + + ```proto + message datasets.v1.SpatioTemporalDatapoint + ``` + + Attributes: + time: + The timestamp associated with each data point. + + ```proto + optional google.protobuf.Timestamp time = 1; + ``` + id: + A universally unique identifier (UUID) that uniquely identifies each data point. + + ```proto + optional tilebox.v1.ID id = 2; + ``` + ingestion_time: + The time the data point was ingested into the Tilebox API. + + ```proto + optional google.protobuf.Timestamp ingestion_time = 3; + ``` + geometry: + The geometry associated with each data point. + + ```proto + optional datasets.v1.Geometry geometry = 4; + ``` + """ + + __slots__ = ("time", "id", "ingestion_time", "geometry") + + if TYPE_CHECKING: + + def __init__( + self, + *, + time: Timestamp | None = None, + id: ID | None = None, + ingestion_time: Timestamp | None = None, + geometry: Geometry | None = None, + ) -> None: + pass + + time: Timestamp | None + id: ID | None + ingestion_time: Timestamp | None + geometry: Geometry | None + +class DatasetKind(Enum): + """ + DatasetKind is an enum describing the kind of dataset. A dataset kind specifies a set of default fields, that + cannot be modified and will always be present for every DatasetMessageType of that kind. + + ```proto + enum datasets.v1.DatasetKind + ``` + + Attributes: + UNSPECIFIED: + ```proto + DATASET_KIND_UNSPECIFIED = 0 + ``` + TEMPORAL: + A temporal dataset is a dataset that contains a "time" field, which is a timestamp. + + ```proto + DATASET_KIND_TEMPORAL = 1 + ``` + SPATIOTEMPORAL: + A spatio temporal dataset is a dataset that contains a "start_time" and "end_time" field which are timestamps + as well as a "geometry" field which is Polygon or MultiPolygon. + + ```proto + DATASET_KIND_SPATIOTEMPORAL = 2 + ``` + """ + + UNSPECIFIED = 0 + TEMPORAL = 1 + SPATIOTEMPORAL = 2 + +class FieldRole(Enum): + """ + FieldRole describes a semantic display role fulfilled by a dataset field. + Thumbnail discovery is intentionally not represented here: thumbnails are assets whose STAC asset roles include + thumbnail. + + ```proto + enum datasets.v1.FieldRole + ``` + + Attributes: + UNSPECIFIED: + ```proto + FIELD_ROLE_UNSPECIFIED = 0 + ``` + PRIMARY_TITLE: + The primary human-readable title or name used when displaying a datapoint. + + ```proto + FIELD_ROLE_PRIMARY_TITLE = 1 + ``` + """ + + UNSPECIFIED = 0 + PRIMARY_TITLE = 1 + + +_DESC = file_desc( + b'\n\x1edatasets/v1/dataset_type.proto\x12\x0bdatasets.v1\x1a"datasets/v1/well_known_types.proto\x1a google/protobuf/descriptor.proto\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\x13tilebox/v1/id.proto"\x8c\x01\n\x05Field\x12E\n\ndescriptor\x18\x01 \x01(\x0b2%.google.protobuf.FieldDescriptorProtoR\ndescriptor\x12<\n\nannotation\x18\x02 \x01(\x0b2\x1c.datasets.v1.FieldAnnotationR\nannotation"\x8a\x02\n\x0fFieldAnnotation\x12 \n\x0bdescription\x18\x01 \x01(\tR\x0bdescription\x12#\n\rexample_value\x18\x02 \x01(\tR\x0cexampleValue\x125\n\x13source_json_pointer\x18\x03 \x01(\tR\x11sourceJsonPointerB\x05\xaa\x01\x02\x08\x01\x12\x1c\n\tqueryable\x18\x04 \x01(\x08R\tqueryable\x12-\n\x0fjson_schema_ref\x18\x05 \x01(\tR\rjsonSchemaRefB\x05\xaa\x01\x02\x08\x01\x12,\n\x05roles\x18\x06 \x03(\x0e2\x16.datasets.v1.FieldRoleR\x05roles"g\n\x0bDatasetType\x12,\n\x04kind\x18\x01 \x01(\x0e2\x18.datasets.v1.DatasetKindR\x04kind\x12*\n\x06fields\x18\x02 \x03(\x0b2\x12.datasets.v1.FieldR\x06fields"\xf4\x01\n\rAnnotatedType\x12I\n\x0edescriptor_set\x18\x01 \x01(\x0b2".google.protobuf.FileDescriptorSetR\rdescriptorSet\x12\x19\n\x08type_url\x18\x02 \x01(\tR\x07typeUrl\x12I\n\x11field_annotations\x18\x04 \x03(\x0b2\x1c.datasets.v1.FieldAnnotationR\x10fieldAnnotations\x12,\n\x04kind\x18\x05 \x01(\x0e2\x18.datasets.v1.DatasetKindR\x04kindJ\x04\x08\x03\x10\x04"\xa6\x01\n\x11TemporalDatapoint\x12.\n\x04time\x18\x01 \x01(\x0b2\x1a.google.protobuf.TimestampR\x04time\x12\x1e\n\x02id\x18\x02 \x01(\x0b2\x0e.tilebox.v1.IDR\x02id\x12A\n\x0eingestion_time\x18\x03 \x01(\x0b2\x1a.google.protobuf.TimestampR\ringestionTime"\xdf\x01\n\x17SpatioTemporalDatapoint\x12.\n\x04time\x18\x01 \x01(\x0b2\x1a.google.protobuf.TimestampR\x04time\x12\x1e\n\x02id\x18\x02 \x01(\x0b2\x0e.tilebox.v1.IDR\x02id\x12A\n\x0eingestion_time\x18\x03 \x01(\x0b2\x1a.google.protobuf.TimestampR\ringestionTime\x121\n\x08geometry\x18\x04 \x01(\x0b2\x15.datasets.v1.GeometryR\x08geometry*g\n\x0bDatasetKind\x12\x1c\n\x18DATASET_KIND_UNSPECIFIED\x10\x00\x12\x19\n\x15DATASET_KIND_TEMPORAL\x10\x01\x12\x1f\n\x1bDATASET_KIND_SPATIOTEMPORAL\x10\x02*E\n\tFieldRole\x12\x1a\n\x16FIELD_ROLE_UNSPECIFIED\x10\x00\x12\x1c\n\x18FIELD_ROLE_PRIMARY_TITLE\x10\x01B\x05\x92\x03\x02\x08\x02b\x08editionsp\xe8\x07', + [ + well_known_types_pb.desc(), + descriptor_pb.desc(), + timestamp_pb.desc(), + id_pb.desc(), + ], + { + "Field": Field, + "FieldAnnotation": FieldAnnotation, + "DatasetType": DatasetType, + "AnnotatedType": AnnotatedType, + "TemporalDatapoint": TemporalDatapoint, + "SpatioTemporalDatapoint": SpatioTemporalDatapoint, + "DatasetKind": DatasetKind, + "FieldRole": FieldRole, + }, +) + + +def desc() -> DescFile: + """Returns the descriptor for the file `datasets/v1/dataset_type.proto`.""" + return _DESC diff --git a/tilebox-datasets/tilebox/datasets/datasets/v1/datasets_connect.py b/tilebox-datasets/tilebox/datasets/v1/datasets_connect.py similarity index 56% rename from tilebox-datasets/tilebox/datasets/datasets/v1/datasets_connect.py rename to tilebox-datasets/tilebox/datasets/v1/datasets_connect.py index 4824622..4af3ef4 100644 --- a/tilebox-datasets/tilebox/datasets/datasets/v1/datasets_connect.py +++ b/tilebox-datasets/tilebox/datasets/v1/datasets_connect.py @@ -1,42 +1,66 @@ -# -*- coding: utf-8 -*- -# Generated by https://github.com/connectrpc/connect-python. DO NOT EDIT! -# source: datasets/v1/datasets.proto +# Generated from datasets/v1/datasets.proto. DO NOT EDIT. +# Generated by protoc-gen-connectrpc-py v0.12.1 with parameter "map_imports=datasets/**:tilebox,map_imports=workflows/**:tilebox,map_imports=tilebox/**:_tilebox.proto,map_imports=opentelemetry/**:tilebox.workflows._otel". +# ruff: noqa: PGH004 +# ruff: noqa +# fmt: off -from collections.abc import AsyncGenerator, AsyncIterator, Iterable, Iterator, Mapping -from typing import Protocol +from __future__ import annotations + +from typing import Protocol, TYPE_CHECKING from connectrpc.client import ConnectClient, ConnectClientSync from connectrpc.code import Code -from connectrpc.codec import Codec -from connectrpc.compression import Compression from connectrpc.errors import ConnectError -from connectrpc.interceptor import Interceptor, InterceptorSync from connectrpc.method import IdempotencyLevel, MethodInfo -from connectrpc.request import Headers, RequestContext -from connectrpc.server import ConnectASGIApplication, ConnectWSGIApplication, Endpoint, EndpointSync -import tilebox.datasets.datasets.v1.core_pb2 as datasets_dot_v1_dot_core__pb2 -import tilebox.datasets.datasets.v1.datasets_pb2 as datasets_dot_v1_dot_datasets__pb2 +from connectrpc.server import ConnectASGIApplication, ConnectWSGIApplication, DEFAULT_READ_MAX_BYTES, Endpoint, EndpointSync +from protobuf import DescService +from tilebox.datasets.v1 import datasets_pb +from tilebox.datasets.v1.core_pb import Dataset +from tilebox.datasets.v1.datasets_pb import CreateDatasetRequest, DeleteDatasetRequest, DeleteDatasetResponse, GetDatasetRequest, ListDatasetsRequest, ListDatasetsResponse, UpdateDatasetRequest + +if TYPE_CHECKING: + from collections.abc import AsyncGenerator, Iterable, Mapping + + from connectrpc.codec import Codec + from connectrpc.compression import Compression + from connectrpc.interceptor import Interceptor, InterceptorSync + from connectrpc.request import Headers, RequestContext class DatasetService(Protocol): - async def create_dataset(self, request: datasets_dot_v1_dot_datasets__pb2.CreateDatasetRequest, ctx: RequestContext) -> datasets_dot_v1_dot_core__pb2.Dataset: - raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") + """ + DatasetsService is the CRUD service for Tilebox datasets. + """ + async def create_dataset(self, request: CreateDatasetRequest, ctx: RequestContext[CreateDatasetRequest, Dataset], /) -> Dataset: + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') - async def get_dataset(self, request: datasets_dot_v1_dot_datasets__pb2.GetDatasetRequest, ctx: RequestContext) -> datasets_dot_v1_dot_core__pb2.Dataset: - raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") + async def get_dataset(self, request: GetDatasetRequest, ctx: RequestContext[GetDatasetRequest, Dataset], /) -> Dataset: + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') - async def update_dataset(self, request: datasets_dot_v1_dot_datasets__pb2.UpdateDatasetRequest, ctx: RequestContext) -> datasets_dot_v1_dot_core__pb2.Dataset: - raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") + async def update_dataset(self, request: UpdateDatasetRequest, ctx: RequestContext[UpdateDatasetRequest, Dataset], /) -> Dataset: + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') - async def delete_dataset(self, request: datasets_dot_v1_dot_datasets__pb2.DeleteDatasetRequest, ctx: RequestContext) -> datasets_dot_v1_dot_datasets__pb2.DeleteDatasetResponse: - raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") + async def delete_dataset(self, request: DeleteDatasetRequest, ctx: RequestContext[DeleteDatasetRequest, DeleteDatasetResponse], /) -> DeleteDatasetResponse: + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') - async def list_datasets(self, request: datasets_dot_v1_dot_datasets__pb2.ListDatasetsRequest, ctx: RequestContext) -> datasets_dot_v1_dot_datasets__pb2.ListDatasetsResponse: - raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") + async def list_datasets(self, request: ListDatasetsRequest, ctx: RequestContext[ListDatasetsRequest, ListDatasetsResponse], /) -> ListDatasetsResponse: + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') + @classmethod + def desc(cls) -> DescService: + """Returns the descriptor for this service.""" + return next(s for s in datasets_pb.desc().services if s.type_name == 'datasets.v1.DatasetService') class DatasetServiceASGIApplication(ConnectASGIApplication[DatasetService]): - def __init__(self, service: DatasetService | AsyncGenerator[DatasetService], *, interceptors: Iterable[Interceptor]=(), read_max_bytes: int | None = None, compressions: Iterable[Compression] | None = None, codecs: Iterable[Codec] | None = None) -> None: + def __init__( + self, + service: DatasetService | AsyncGenerator[DatasetService], + *, + interceptors: Iterable[Interceptor] = (), + read_max_bytes: int | None = DEFAULT_READ_MAX_BYTES, + compressions: Iterable[Compression] | None = None, + codecs: Iterable[Codec] | None = None, + ) -> None: super().__init__( service=service, endpoints=lambda svc: { @@ -44,8 +68,8 @@ def __init__(self, service: DatasetService | AsyncGenerator[DatasetService], *, method=MethodInfo( name="CreateDataset", service_name="datasets.v1.DatasetService", - input=datasets_dot_v1_dot_datasets__pb2.CreateDatasetRequest, - output=datasets_dot_v1_dot_core__pb2.Dataset, + input=CreateDatasetRequest, + output=Dataset, idempotency_level=IdempotencyLevel.UNKNOWN, ), function=svc.create_dataset, @@ -54,8 +78,8 @@ def __init__(self, service: DatasetService | AsyncGenerator[DatasetService], *, method=MethodInfo( name="GetDataset", service_name="datasets.v1.DatasetService", - input=datasets_dot_v1_dot_datasets__pb2.GetDatasetRequest, - output=datasets_dot_v1_dot_core__pb2.Dataset, + input=GetDatasetRequest, + output=Dataset, idempotency_level=IdempotencyLevel.UNKNOWN, ), function=svc.get_dataset, @@ -64,8 +88,8 @@ def __init__(self, service: DatasetService | AsyncGenerator[DatasetService], *, method=MethodInfo( name="UpdateDataset", service_name="datasets.v1.DatasetService", - input=datasets_dot_v1_dot_datasets__pb2.UpdateDatasetRequest, - output=datasets_dot_v1_dot_core__pb2.Dataset, + input=UpdateDatasetRequest, + output=Dataset, idempotency_level=IdempotencyLevel.UNKNOWN, ), function=svc.update_dataset, @@ -74,8 +98,8 @@ def __init__(self, service: DatasetService | AsyncGenerator[DatasetService], *, method=MethodInfo( name="DeleteDataset", service_name="datasets.v1.DatasetService", - input=datasets_dot_v1_dot_datasets__pb2.DeleteDatasetRequest, - output=datasets_dot_v1_dot_datasets__pb2.DeleteDatasetResponse, + input=DeleteDatasetRequest, + output=DeleteDatasetResponse, idempotency_level=IdempotencyLevel.UNKNOWN, ), function=svc.delete_dataset, @@ -84,8 +108,8 @@ def __init__(self, service: DatasetService | AsyncGenerator[DatasetService], *, method=MethodInfo( name="ListDatasets", service_name="datasets.v1.DatasetService", - input=datasets_dot_v1_dot_datasets__pb2.ListDatasetsRequest, - output=datasets_dot_v1_dot_datasets__pb2.ListDatasetsResponse, + input=ListDatasetsRequest, + output=ListDatasetsResponse, idempotency_level=IdempotencyLevel.UNKNOWN, ), function=svc.list_datasets, @@ -104,20 +128,23 @@ def path(self) -> str: class DatasetServiceClient(ConnectClient): + """ + DatasetsService is the CRUD service for Tilebox datasets. + """ async def create_dataset( self, - request: datasets_dot_v1_dot_datasets__pb2.CreateDatasetRequest, + request: CreateDatasetRequest, *, headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, - ) -> datasets_dot_v1_dot_core__pb2.Dataset: + ) -> Dataset: return await self.execute_unary( request=request, method=MethodInfo( name="CreateDataset", service_name="datasets.v1.DatasetService", - input=datasets_dot_v1_dot_datasets__pb2.CreateDatasetRequest, - output=datasets_dot_v1_dot_core__pb2.Dataset, + input=CreateDatasetRequest, + output=Dataset, idempotency_level=IdempotencyLevel.UNKNOWN, ), headers=headers, @@ -126,18 +153,18 @@ async def create_dataset( async def get_dataset( self, - request: datasets_dot_v1_dot_datasets__pb2.GetDatasetRequest, + request: GetDatasetRequest, *, headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, - ) -> datasets_dot_v1_dot_core__pb2.Dataset: + ) -> Dataset: return await self.execute_unary( request=request, method=MethodInfo( name="GetDataset", service_name="datasets.v1.DatasetService", - input=datasets_dot_v1_dot_datasets__pb2.GetDatasetRequest, - output=datasets_dot_v1_dot_core__pb2.Dataset, + input=GetDatasetRequest, + output=Dataset, idempotency_level=IdempotencyLevel.UNKNOWN, ), headers=headers, @@ -146,18 +173,18 @@ async def get_dataset( async def update_dataset( self, - request: datasets_dot_v1_dot_datasets__pb2.UpdateDatasetRequest, + request: UpdateDatasetRequest, *, headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, - ) -> datasets_dot_v1_dot_core__pb2.Dataset: + ) -> Dataset: return await self.execute_unary( request=request, method=MethodInfo( name="UpdateDataset", service_name="datasets.v1.DatasetService", - input=datasets_dot_v1_dot_datasets__pb2.UpdateDatasetRequest, - output=datasets_dot_v1_dot_core__pb2.Dataset, + input=UpdateDatasetRequest, + output=Dataset, idempotency_level=IdempotencyLevel.UNKNOWN, ), headers=headers, @@ -166,18 +193,18 @@ async def update_dataset( async def delete_dataset( self, - request: datasets_dot_v1_dot_datasets__pb2.DeleteDatasetRequest, + request: DeleteDatasetRequest, *, headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, - ) -> datasets_dot_v1_dot_datasets__pb2.DeleteDatasetResponse: + ) -> DeleteDatasetResponse: return await self.execute_unary( request=request, method=MethodInfo( name="DeleteDataset", service_name="datasets.v1.DatasetService", - input=datasets_dot_v1_dot_datasets__pb2.DeleteDatasetRequest, - output=datasets_dot_v1_dot_datasets__pb2.DeleteDatasetResponse, + input=DeleteDatasetRequest, + output=DeleteDatasetResponse, idempotency_level=IdempotencyLevel.UNKNOWN, ), headers=headers, @@ -186,51 +213,65 @@ async def delete_dataset( async def list_datasets( self, - request: datasets_dot_v1_dot_datasets__pb2.ListDatasetsRequest, + request: ListDatasetsRequest, *, headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, - ) -> datasets_dot_v1_dot_datasets__pb2.ListDatasetsResponse: + ) -> ListDatasetsResponse: return await self.execute_unary( request=request, method=MethodInfo( name="ListDatasets", service_name="datasets.v1.DatasetService", - input=datasets_dot_v1_dot_datasets__pb2.ListDatasetsRequest, - output=datasets_dot_v1_dot_datasets__pb2.ListDatasetsResponse, + input=ListDatasetsRequest, + output=ListDatasetsResponse, idempotency_level=IdempotencyLevel.UNKNOWN, ), headers=headers, timeout_ms=timeout_ms, ) +class DatasetServiceSync(Protocol): + """ + DatasetsService is the CRUD service for Tilebox datasets. + """ + def create_dataset(self, request: CreateDatasetRequest, ctx: RequestContext[CreateDatasetRequest, Dataset], /) -> Dataset: + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') + def get_dataset(self, request: GetDatasetRequest, ctx: RequestContext[GetDatasetRequest, Dataset], /) -> Dataset: + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') + def update_dataset(self, request: UpdateDatasetRequest, ctx: RequestContext[UpdateDatasetRequest, Dataset], /) -> Dataset: + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') + def delete_dataset(self, request: DeleteDatasetRequest, ctx: RequestContext[DeleteDatasetRequest, DeleteDatasetResponse], /) -> DeleteDatasetResponse: + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') -class DatasetServiceSync(Protocol): - def create_dataset(self, request: datasets_dot_v1_dot_datasets__pb2.CreateDatasetRequest, ctx: RequestContext) -> datasets_dot_v1_dot_core__pb2.Dataset: - raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") - def get_dataset(self, request: datasets_dot_v1_dot_datasets__pb2.GetDatasetRequest, ctx: RequestContext) -> datasets_dot_v1_dot_core__pb2.Dataset: - raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") - def update_dataset(self, request: datasets_dot_v1_dot_datasets__pb2.UpdateDatasetRequest, ctx: RequestContext) -> datasets_dot_v1_dot_core__pb2.Dataset: - raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") - def delete_dataset(self, request: datasets_dot_v1_dot_datasets__pb2.DeleteDatasetRequest, ctx: RequestContext) -> datasets_dot_v1_dot_datasets__pb2.DeleteDatasetResponse: - raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") - def list_datasets(self, request: datasets_dot_v1_dot_datasets__pb2.ListDatasetsRequest, ctx: RequestContext) -> datasets_dot_v1_dot_datasets__pb2.ListDatasetsResponse: - raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") + def list_datasets(self, request: ListDatasetsRequest, ctx: RequestContext[ListDatasetsRequest, ListDatasetsResponse], /) -> ListDatasetsResponse: + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') + @classmethod + def desc(cls) -> DescService: + """Returns the descriptor for this service.""" + return next(s for s in datasets_pb.desc().services if s.type_name == 'datasets.v1.DatasetService') class DatasetServiceWSGIApplication(ConnectWSGIApplication): - def __init__(self, service: DatasetServiceSync, interceptors: Iterable[InterceptorSync]=(), read_max_bytes: int | None = None, compressions: Iterable[Compression] | None = None, codecs: Iterable[Codec] | None = None) -> None: + def __init__( + self, + service: DatasetServiceSync, + interceptors: Iterable[InterceptorSync] = (), + read_max_bytes: int | None = DEFAULT_READ_MAX_BYTES, + compressions: Iterable[Compression] | None = None, + codecs: Iterable[Codec] | None = None, + ) -> None: super().__init__( endpoints={ "/datasets.v1.DatasetService/CreateDataset": EndpointSync.unary( method=MethodInfo( name="CreateDataset", service_name="datasets.v1.DatasetService", - input=datasets_dot_v1_dot_datasets__pb2.CreateDatasetRequest, - output=datasets_dot_v1_dot_core__pb2.Dataset, + input=CreateDatasetRequest, + output=Dataset, idempotency_level=IdempotencyLevel.UNKNOWN, ), function=service.create_dataset, @@ -239,8 +280,8 @@ def __init__(self, service: DatasetServiceSync, interceptors: Iterable[Intercept method=MethodInfo( name="GetDataset", service_name="datasets.v1.DatasetService", - input=datasets_dot_v1_dot_datasets__pb2.GetDatasetRequest, - output=datasets_dot_v1_dot_core__pb2.Dataset, + input=GetDatasetRequest, + output=Dataset, idempotency_level=IdempotencyLevel.UNKNOWN, ), function=service.get_dataset, @@ -249,8 +290,8 @@ def __init__(self, service: DatasetServiceSync, interceptors: Iterable[Intercept method=MethodInfo( name="UpdateDataset", service_name="datasets.v1.DatasetService", - input=datasets_dot_v1_dot_datasets__pb2.UpdateDatasetRequest, - output=datasets_dot_v1_dot_core__pb2.Dataset, + input=UpdateDatasetRequest, + output=Dataset, idempotency_level=IdempotencyLevel.UNKNOWN, ), function=service.update_dataset, @@ -259,8 +300,8 @@ def __init__(self, service: DatasetServiceSync, interceptors: Iterable[Intercept method=MethodInfo( name="DeleteDataset", service_name="datasets.v1.DatasetService", - input=datasets_dot_v1_dot_datasets__pb2.DeleteDatasetRequest, - output=datasets_dot_v1_dot_datasets__pb2.DeleteDatasetResponse, + input=DeleteDatasetRequest, + output=DeleteDatasetResponse, idempotency_level=IdempotencyLevel.UNKNOWN, ), function=service.delete_dataset, @@ -269,8 +310,8 @@ def __init__(self, service: DatasetServiceSync, interceptors: Iterable[Intercept method=MethodInfo( name="ListDatasets", service_name="datasets.v1.DatasetService", - input=datasets_dot_v1_dot_datasets__pb2.ListDatasetsRequest, - output=datasets_dot_v1_dot_datasets__pb2.ListDatasetsResponse, + input=ListDatasetsRequest, + output=ListDatasetsResponse, idempotency_level=IdempotencyLevel.UNKNOWN, ), function=service.list_datasets, @@ -289,100 +330,99 @@ def path(self) -> str: class DatasetServiceClientSync(ConnectClientSync): + """ + DatasetsService is the CRUD service for Tilebox datasets. + """ def create_dataset( self, - request: datasets_dot_v1_dot_datasets__pb2.CreateDatasetRequest, + request: CreateDatasetRequest, *, headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, - ) -> datasets_dot_v1_dot_core__pb2.Dataset: + ) -> Dataset: return self.execute_unary( request=request, method=MethodInfo( name="CreateDataset", service_name="datasets.v1.DatasetService", - input=datasets_dot_v1_dot_datasets__pb2.CreateDatasetRequest, - output=datasets_dot_v1_dot_core__pb2.Dataset, + input=CreateDatasetRequest, + output=Dataset, idempotency_level=IdempotencyLevel.UNKNOWN, ), headers=headers, timeout_ms=timeout_ms, ) - def get_dataset( self, - request: datasets_dot_v1_dot_datasets__pb2.GetDatasetRequest, + request: GetDatasetRequest, *, headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, - ) -> datasets_dot_v1_dot_core__pb2.Dataset: + ) -> Dataset: return self.execute_unary( request=request, method=MethodInfo( name="GetDataset", service_name="datasets.v1.DatasetService", - input=datasets_dot_v1_dot_datasets__pb2.GetDatasetRequest, - output=datasets_dot_v1_dot_core__pb2.Dataset, + input=GetDatasetRequest, + output=Dataset, idempotency_level=IdempotencyLevel.UNKNOWN, ), headers=headers, timeout_ms=timeout_ms, ) - def update_dataset( self, - request: datasets_dot_v1_dot_datasets__pb2.UpdateDatasetRequest, + request: UpdateDatasetRequest, *, headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, - ) -> datasets_dot_v1_dot_core__pb2.Dataset: + ) -> Dataset: return self.execute_unary( request=request, method=MethodInfo( name="UpdateDataset", service_name="datasets.v1.DatasetService", - input=datasets_dot_v1_dot_datasets__pb2.UpdateDatasetRequest, - output=datasets_dot_v1_dot_core__pb2.Dataset, + input=UpdateDatasetRequest, + output=Dataset, idempotency_level=IdempotencyLevel.UNKNOWN, ), headers=headers, timeout_ms=timeout_ms, ) - def delete_dataset( self, - request: datasets_dot_v1_dot_datasets__pb2.DeleteDatasetRequest, + request: DeleteDatasetRequest, *, headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, - ) -> datasets_dot_v1_dot_datasets__pb2.DeleteDatasetResponse: + ) -> DeleteDatasetResponse: return self.execute_unary( request=request, method=MethodInfo( name="DeleteDataset", service_name="datasets.v1.DatasetService", - input=datasets_dot_v1_dot_datasets__pb2.DeleteDatasetRequest, - output=datasets_dot_v1_dot_datasets__pb2.DeleteDatasetResponse, + input=DeleteDatasetRequest, + output=DeleteDatasetResponse, idempotency_level=IdempotencyLevel.UNKNOWN, ), headers=headers, timeout_ms=timeout_ms, ) - def list_datasets( self, - request: datasets_dot_v1_dot_datasets__pb2.ListDatasetsRequest, + request: ListDatasetsRequest, *, headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, - ) -> datasets_dot_v1_dot_datasets__pb2.ListDatasetsResponse: + ) -> ListDatasetsResponse: return self.execute_unary( request=request, method=MethodInfo( name="ListDatasets", service_name="datasets.v1.DatasetService", - input=datasets_dot_v1_dot_datasets__pb2.ListDatasetsRequest, - output=datasets_dot_v1_dot_datasets__pb2.ListDatasetsResponse, + input=ListDatasetsRequest, + output=ListDatasetsResponse, idempotency_level=IdempotencyLevel.UNKNOWN, ), headers=headers, diff --git a/tilebox-datasets/tilebox/datasets/v1/datasets_pb.py b/tilebox-datasets/tilebox/datasets/v1/datasets_pb.py new file mode 100644 index 0000000..91145ed --- /dev/null +++ b/tilebox-datasets/tilebox/datasets/v1/datasets_pb.py @@ -0,0 +1,371 @@ +# Generated from datasets/v1/datasets.proto. DO NOT EDIT. +# Generated by protoc-gen-py v0.6.0 with parameter "init_files=false,map_imports=datasets/**:tilebox,map_imports=workflows/**:tilebox,map_imports=tilebox/**:_tilebox.proto,map_imports=opentelemetry/**:tilebox.workflows._otel". +# ruff: noqa: PGH004 +# ruff: noqa +# fmt: off + +from __future__ import annotations + +from typing import Literal, NoReturn, TYPE_CHECKING, TypeAlias + +from _tilebox.proto.tilebox.v1 import id_pb +from protobuf import Message +from protobuf._codegen import file_desc +from tilebox.datasets.v1 import core_pb, dataset_type_pb + +if TYPE_CHECKING: + from _tilebox.proto.tilebox.v1.id_pb import ID + from protobuf import DescFile + from tilebox.datasets.v1.core_pb import Dataset, DatasetGroup + from tilebox.datasets.v1.dataset_type_pb import DatasetType + + +_CreateDatasetRequestFields: TypeAlias = Literal["name", "type", "summary", "code_name", "description"] + +class CreateDatasetRequest(Message[_CreateDatasetRequestFields]): + """ + CreateDatasetRequest is used to create a new dataset. + + ```proto + message datasets.v1.CreateDatasetRequest + ``` + + Attributes: + name: + name of the dataset to create. + + ```proto + string name = 1; + ``` + type: + Message type of the dataset to create, including per-field source mappings, queryable metadata, and roles. + + ```proto + optional datasets.v1.DatasetType type = 2; + ``` + summary: + short text summary of the dataset to create. + + ```proto + string summary = 3; + ``` + code_name: + normalized snake case name of the dataset to create. + + ```proto + string code_name = 4; + ``` + description: + optional description of the dataset, in markdown format + + ```proto + string description = 5; + ``` + """ + + __slots__ = ("name", "type", "summary", "code_name", "description") + + if TYPE_CHECKING: + + def __init__( + self, + *, + name: str = "", + type: DatasetType | None = None, + summary: str = "", + code_name: str = "", + description: str = "", + ) -> None: + pass + + name: str + type: DatasetType | None + summary: str + code_name: str + description: str + +_GetDatasetRequestFields: TypeAlias = Literal["slug", "id"] + +class GetDatasetRequest(Message[_GetDatasetRequestFields]): + """ + GetDatasetRequest is the request message for the GetDataset RPC method for fetching a single dataset + + ```proto + message datasets.v1.GetDatasetRequest + ``` + + Attributes: + slug: + slug of the dataset to be returned, e.g. "open_data.copernicus.sentinel1_sar" + it must contain at least one group and one dataset code name separated by a "." + + ```proto + string slug = 1; + ``` + id: + or alternatively a dataset id + + ```proto + optional tilebox.v1.ID id = 2; + ``` + """ + + __slots__ = ("slug", "id") + + if TYPE_CHECKING: + + def __init__( + self, + *, + slug: str = "", + id: ID | None = None, + ) -> None: + pass + + slug: str + id: ID | None + +_UpdateDatasetRequestFields: TypeAlias = Literal["id", "name", "type", "summary", "code_name", "description"] + +class UpdateDatasetRequest(Message[_UpdateDatasetRequestFields]): + """ + UpdateDatasetRequest updates a dataset, by updating all fields that are set to their new values. + If a field is not set, it will not be updated. + + ```proto + message datasets.v1.UpdateDatasetRequest + ``` + + Attributes: + id: + id of the dataset to update. + + ```proto + optional tilebox.v1.ID id = 1; + ``` + name: + updated name of the dataset. + + ```proto + optional string name = 2; + ``` + type: + Updated type of the dataset, including per-field source mappings, queryable metadata, and roles. + + ```proto + optional datasets.v1.DatasetType type = 3; + ``` + summary: + updated summary of the dataset. + + ```proto + optional string summary = 4; + ``` + code_name: + updated code name of the dataset. + + ```proto + optional string code_name = 5; + ``` + description: + updated description of the dataset, in markdown format + + ```proto + optional string description = 6; + ``` + """ + + __slots__ = ("id", "name", "type", "summary", "code_name", "description") + + if TYPE_CHECKING: + + def __init__( + self, + *, + id: ID | None = None, + name: str | None = None, + type: DatasetType | None = None, + summary: str | None = None, + code_name: str | None = None, + description: str | None = None, + ) -> None: + pass + + id: ID | None + name: str + type: DatasetType | None + summary: str + code_name: str + description: str + +_DeleteDatasetRequestFields: TypeAlias = Literal["id"] + +class DeleteDatasetRequest(Message[_DeleteDatasetRequestFields]): + """ + DeleteDatasetRequest is used to delete a dataset + + ```proto + message datasets.v1.DeleteDatasetRequest + ``` + + Attributes: + id: + id of the dataset to delete. + + ```proto + optional tilebox.v1.ID id = 1; + ``` + """ + + __slots__ = ("id",) + + if TYPE_CHECKING: + + def __init__( + self, + *, + id: ID | None = None, + ) -> None: + pass + + id: ID | None + +_DeleteDatasetResponseFields: TypeAlias = Literal["trashed"] + +class DeleteDatasetResponse(Message[_DeleteDatasetResponseFields]): + """ + DeleteDatasetResponse is the response to DeleteDatasetRequest + + ```proto + message datasets.v1.DeleteDatasetResponse + ``` + + Attributes: + trashed: + whether the dataset has been trashed or not + + ```proto + bool trashed = 1; + ``` + """ + + __slots__ = ("trashed",) + + if TYPE_CHECKING: + + def __init__( + self, + *, + trashed: bool = False, + ) -> None: + pass + + trashed: bool + +_ListDatasetsRequestFields: TypeAlias = NoReturn + +class ListDatasetsRequest(Message[_ListDatasetsRequestFields]): + """ + ListDatasetsRequest is used to request a list of datasets + + ```proto + message datasets.v1.ListDatasetsRequest + ``` + """ + + __slots__ = () + + if TYPE_CHECKING: + + def __init__( + self, + ) -> None: + pass + +_ListDatasetsResponseFields: TypeAlias = Literal["datasets", "groups", "server_message", "owned_datasets", "maximum_owned_datasets"] + +class ListDatasetsResponse(Message[_ListDatasetsResponseFields]): + """ + A list of datasets and dataset groups + + ```proto + message datasets.v1.ListDatasetsResponse + ``` + + Attributes: + datasets: + list of datasets a user has access to + + ```proto + repeated datasets.v1.Dataset datasets = 1; + ``` + groups: + list of dataset groups a user has access to + + ```proto + repeated datasets.v1.DatasetGroup groups = 2; + ``` + server_message: + an optional message to be displayed to the user when they access a list of datasets + + ```proto + string server_message = 3; + ``` + owned_datasets: + Number of datasets that the users organization is the owner of + + ```proto + int64 owned_datasets = 4; + ``` + maximum_owned_datasets: + Maximum number of datasets that can be created and therefore owned by the organization. Nil means unlimited. + + ```proto + optional int64 maximum_owned_datasets = 5; + ``` + """ + + __slots__ = ("datasets", "groups", "server_message", "owned_datasets", "maximum_owned_datasets") + + if TYPE_CHECKING: + + def __init__( + self, + *, + datasets: list[Dataset] | None = None, + groups: list[DatasetGroup] | None = None, + server_message: str = "", + owned_datasets: int = 0, + maximum_owned_datasets: int | None = None, + ) -> None: + pass + + datasets: list[Dataset] + groups: list[DatasetGroup] + server_message: str + owned_datasets: int + maximum_owned_datasets: int + + +_DESC = file_desc( + b'\n\x1adatasets/v1/datasets.proto\x12\x0bdatasets.v1\x1a\x16datasets/v1/core.proto\x1a\x1edatasets/v1/dataset_type.proto\x1a\x13tilebox/v1/id.proto"\xb1\x01\n\x14CreateDatasetRequest\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\x12,\n\x04type\x18\x02 \x01(\x0b2\x18.datasets.v1.DatasetTypeR\x04type\x12\x18\n\x07summary\x18\x03 \x01(\tR\x07summary\x12\x1b\n\tcode_name\x18\x04 \x01(\tR\x08codeName\x12 \n\x0bdescription\x18\x05 \x01(\tR\x0bdescription"G\n\x11GetDatasetRequest\x12\x12\n\x04slug\x18\x01 \x01(\tR\x04slug\x12\x1e\n\x02id\x18\x02 \x01(\x0b2\x0e.tilebox.v1.IDR\x02id"\xf4\x01\n\x14UpdateDatasetRequest\x12\x1e\n\x02id\x18\x01 \x01(\x0b2\x0e.tilebox.v1.IDR\x02id\x12\x19\n\x04name\x18\x02 \x01(\tR\x04nameB\x05\xaa\x01\x02\x08\x01\x123\n\x04type\x18\x03 \x01(\x0b2\x18.datasets.v1.DatasetTypeR\x04typeB\x05\xaa\x01\x02\x08\x01\x12\x1f\n\x07summary\x18\x04 \x01(\tR\x07summaryB\x05\xaa\x01\x02\x08\x01\x12"\n\tcode_name\x18\x05 \x01(\tR\x08codeNameB\x05\xaa\x01\x02\x08\x01\x12\'\n\x0bdescription\x18\x06 \x01(\tR\x0bdescriptionB\x05\xaa\x01\x02\x08\x01"6\n\x14DeleteDatasetRequest\x12\x1e\n\x02id\x18\x01 \x01(\x0b2\x0e.tilebox.v1.IDR\x02id"1\n\x15DeleteDatasetResponse\x12\x18\n\x07trashed\x18\x01 \x01(\x08R\x07trashed"\x15\n\x13ListDatasetsRequest"\x86\x02\n\x14ListDatasetsResponse\x120\n\x08datasets\x18\x01 \x03(\x0b2\x14.datasets.v1.DatasetR\x08datasets\x121\n\x06groups\x18\x02 \x03(\x0b2\x19.datasets.v1.DatasetGroupR\x06groups\x12%\n\x0eserver_message\x18\x03 \x01(\tR\rserverMessage\x12%\n\x0eowned_datasets\x18\x04 \x01(\x03R\rownedDatasets\x12;\n\x16maximum_owned_datasets\x18\x05 \x01(\x03R\x14maximumOwnedDatasetsB\x05\xaa\x01\x02\x08\x012\x95\x03\n\x0eDatasetService\x12H\n\rCreateDataset\x12!.datasets.v1.CreateDatasetRequest\x1a\x14.datasets.v1.Dataset\x12B\n\nGetDataset\x12\x1e.datasets.v1.GetDatasetRequest\x1a\x14.datasets.v1.Dataset\x12H\n\rUpdateDataset\x12!.datasets.v1.UpdateDatasetRequest\x1a\x14.datasets.v1.Dataset\x12V\n\rDeleteDataset\x12!.datasets.v1.DeleteDatasetRequest\x1a".datasets.v1.DeleteDatasetResponse\x12S\n\x0cListDatasets\x12 .datasets.v1.ListDatasetsRequest\x1a!.datasets.v1.ListDatasetsResponseB\x05\x92\x03\x02\x08\x02b\x08editionsp\xe8\x07', + [ + core_pb.desc(), + dataset_type_pb.desc(), + id_pb.desc(), + ], + { + "CreateDatasetRequest": CreateDatasetRequest, + "GetDatasetRequest": GetDatasetRequest, + "UpdateDatasetRequest": UpdateDatasetRequest, + "DeleteDatasetRequest": DeleteDatasetRequest, + "DeleteDatasetResponse": DeleteDatasetResponse, + "ListDatasetsRequest": ListDatasetsRequest, + "ListDatasetsResponse": ListDatasetsResponse, + }, +) + + +def desc() -> DescFile: + """Returns the descriptor for the file `datasets/v1/datasets.proto`.""" + return _DESC diff --git a/tilebox-datasets/tilebox/datasets/v1/timeseries_pb.py b/tilebox-datasets/tilebox/datasets/v1/timeseries_pb.py new file mode 100644 index 0000000..3da42b0 --- /dev/null +++ b/tilebox-datasets/tilebox/datasets/v1/timeseries_pb.py @@ -0,0 +1,146 @@ +# Generated from datasets/v1/timeseries.proto. DO NOT EDIT. +# Generated by protoc-gen-py v0.6.0 with parameter "init_files=false,map_imports=datasets/**:tilebox,map_imports=workflows/**:tilebox,map_imports=tilebox/**:_tilebox.proto,map_imports=opentelemetry/**:tilebox.workflows._otel". +# ruff: noqa: PGH004 +# ruff: noqa +# fmt: off + +from __future__ import annotations + +from typing import Literal, TYPE_CHECKING, TypeAlias + +from _tilebox.proto.tilebox.v1 import id_pb, query_pb +from protobuf import Message +from protobuf._codegen import file_desc +from protobuf.wkt.google.protobuf import duration_pb + +if TYPE_CHECKING: + from _tilebox.proto.tilebox.v1.id_pb import ID + from _tilebox.proto.tilebox.v1.query_pb import IDInterval, TimeInterval + from protobuf import DescFile + from protobuf.wkt.google.protobuf.duration_pb import Duration + + +_TimeseriesDatasetChunkFields: TypeAlias = Literal["dataset_id", "collection_id", "time_interval", "datapoint_interval", "branch_factor", "chunk_size", "datapoints_per_365_days"] + +class TimeseriesDatasetChunk(Message[_TimeseriesDatasetChunkFields]): + """ + TimeseriesDatasetChunk is a message that represents a chunk of a timeseries dataset. + used by workflow tasks that are executed once for each datapoint in a timeseries dataset + + ```proto + message datasets.v1.TimeseriesDatasetChunk + ``` + + Attributes: + dataset_id: + ```proto + optional tilebox.v1.ID dataset_id = 1; + ``` + collection_id: + ```proto + optional tilebox.v1.ID collection_id = 2; + ``` + time_interval: + ```proto + optional tilebox.v1.TimeInterval time_interval = 3; + ``` + datapoint_interval: + ```proto + optional tilebox.v1.IDInterval datapoint_interval = 4; + ``` + branch_factor: + ```proto + int32 branch_factor = 5; + ``` + chunk_size: + ```proto + int32 chunk_size = 6; + ``` + datapoints_per_365_days: + ```proto + int64 datapoints_per_365_days = 7; + ``` + """ + + __slots__ = ("dataset_id", "collection_id", "time_interval", "datapoint_interval", "branch_factor", "chunk_size", "datapoints_per_365_days") + + if TYPE_CHECKING: + + def __init__( + self, + *, + dataset_id: ID | None = None, + collection_id: ID | None = None, + time_interval: TimeInterval | None = None, + datapoint_interval: IDInterval | None = None, + branch_factor: int = 0, + chunk_size: int = 0, + datapoints_per_365_days: int = 0, + ) -> None: + pass + + dataset_id: ID | None + collection_id: ID | None + time_interval: TimeInterval | None + datapoint_interval: IDInterval | None + branch_factor: int + chunk_size: int + datapoints_per_365_days: int + +_TimeChunkFields: TypeAlias = Literal["time_interval", "chunk_size"] + +class TimeChunk(Message[_TimeChunkFields]): + """ + TimeChunk is a message that represents a time interval and a chunk size. + used by workflow tasks that are executed once for each chunk of size chunk_size in a time interval + e.g. for a time interval of 100 days, and a chunk size of 1 day, such a workflow will spawn a tree of + eventually 100 leaf tasks + + ```proto + message datasets.v1.TimeChunk + ``` + + Attributes: + time_interval: + ```proto + optional tilebox.v1.TimeInterval time_interval = 1; + ``` + chunk_size: + ```proto + optional google.protobuf.Duration chunk_size = 2; + ``` + """ + + __slots__ = ("time_interval", "chunk_size") + + if TYPE_CHECKING: + + def __init__( + self, + *, + time_interval: TimeInterval | None = None, + chunk_size: Duration | None = None, + ) -> None: + pass + + time_interval: TimeInterval | None + chunk_size: Duration | None + + +_DESC = file_desc( + b'\n\x1cdatasets/v1/timeseries.proto\x12\x0bdatasets.v1\x1a\x1egoogle/protobuf/duration.proto\x1a\x13tilebox/v1/id.proto\x1a\x16tilebox/v1/query.proto"\xfd\x02\n\x16TimeseriesDatasetChunk\x12-\n\ndataset_id\x18\x01 \x01(\x0b2\x0e.tilebox.v1.IDR\tdatasetId\x123\n\rcollection_id\x18\x02 \x01(\x0b2\x0e.tilebox.v1.IDR\x0ccollectionId\x12=\n\rtime_interval\x18\x03 \x01(\x0b2\x18.tilebox.v1.TimeIntervalR\x0ctimeInterval\x12E\n\x12datapoint_interval\x18\x04 \x01(\x0b2\x16.tilebox.v1.IDIntervalR\x11datapointInterval\x12#\n\rbranch_factor\x18\x05 \x01(\x05R\x0cbranchFactor\x12\x1d\n\nchunk_size\x18\x06 \x01(\x05R\tchunkSize\x125\n\x17datapoints_per_365_days\x18\x07 \x01(\x03R\x14datapointsPer365Days"\x84\x01\n\tTimeChunk\x12=\n\rtime_interval\x18\x01 \x01(\x0b2\x18.tilebox.v1.TimeIntervalR\x0ctimeInterval\x128\n\nchunk_size\x18\x02 \x01(\x0b2\x19.google.protobuf.DurationR\tchunkSizeB\x05\x92\x03\x02\x08\x02b\x08editionsp\xe8\x07', + [ + duration_pb.desc(), + id_pb.desc(), + query_pb.desc(), + ], + { + "TimeseriesDatasetChunk": TimeseriesDatasetChunk, + "TimeChunk": TimeChunk, + }, +) + + +def desc() -> DescFile: + """Returns the descriptor for the file `datasets/v1/timeseries.proto`.""" + return _DESC diff --git a/tilebox-datasets/tilebox/datasets/v1/well_known_types_pb.py b/tilebox-datasets/tilebox/datasets/v1/well_known_types_pb.py new file mode 100644 index 0000000..a2596f8 --- /dev/null +++ b/tilebox-datasets/tilebox/datasets/v1/well_known_types_pb.py @@ -0,0 +1,637 @@ +# Generated from datasets/v1/well_known_types.proto. DO NOT EDIT. +# Generated by protoc-gen-py v0.6.0 with parameter "init_files=false,map_imports=datasets/**:tilebox,map_imports=workflows/**:tilebox,map_imports=tilebox/**:_tilebox.proto,map_imports=opentelemetry/**:tilebox.workflows._otel". +# ruff: noqa: PGH004 +# ruff: noqa +# fmt: off + +from __future__ import annotations + +from typing import Literal, TYPE_CHECKING, TypeAlias + +from protobuf import Enum, Message +from protobuf._codegen import file_desc + +if TYPE_CHECKING: + from protobuf import DescFile + + +_UUIDFields: TypeAlias = Literal["uuid"] + +class UUID(Message[_UUIDFields]): + """ + Bytes field (in message) + + ```proto + message datasets.v1.UUID + ``` + + Attributes: + uuid: + ```proto + bytes uuid = 1; + ``` + """ + + __slots__ = ("uuid",) + + if TYPE_CHECKING: + + def __init__( + self, + *, + uuid: bytes = b"", + ) -> None: + pass + + uuid: bytes + +_Vec3Fields: TypeAlias = Literal["x", "y", "z"] + +class Vec3(Message[_Vec3Fields]): + """ + A 3D vector + + ```proto + message datasets.v1.Vec3 + ``` + + Attributes: + x: + ```proto + double x = 1; + ``` + y: + ```proto + double y = 2; + ``` + z: + ```proto + double z = 3; + ``` + """ + + __slots__ = ("x", "y", "z") + + if TYPE_CHECKING: + + def __init__( + self, + *, + x: float = 0, + y: float = 0, + z: float = 0, + ) -> None: + pass + + x: float + y: float + z: float + +_QuaternionFields: TypeAlias = Literal["q1", "q2", "q3", "q4"] + +class Quaternion(Message[_QuaternionFields]): + """ + A quaternion + + ```proto + message datasets.v1.Quaternion + ``` + + Attributes: + q1: + ```proto + double q1 = 1; + ``` + q2: + ```proto + double q2 = 2; + ``` + q3: + ```proto + double q3 = 3; + ``` + q4: + ```proto + double q4 = 4; + ``` + """ + + __slots__ = ("q1", "q2", "q3", "q4") + + if TYPE_CHECKING: + + def __init__( + self, + *, + q1: float = 0, + q2: float = 0, + q3: float = 0, + q4: float = 0, + ) -> None: + pass + + q1: float + q2: float + q3: float + q4: float + +_LatLonFields: TypeAlias = Literal["latitude", "longitude"] + +class LatLon(Message[_LatLonFields]): + """ + LatLon is a pair of latitude and longitude values representing a point on the Earth's surface + + ```proto + message datasets.v1.LatLon + ``` + + Attributes: + latitude: + ```proto + double latitude = 1; + ``` + longitude: + ```proto + double longitude = 2; + ``` + """ + + __slots__ = ("latitude", "longitude") + + if TYPE_CHECKING: + + def __init__( + self, + *, + latitude: float = 0, + longitude: float = 0, + ) -> None: + pass + + latitude: float + longitude: float + +_LatLonAltFields: TypeAlias = Literal["latitude", "longitude", "altitude"] + +class LatLonAlt(Message[_LatLonAltFields]): + """ + LatLonAlt is a pair of latitude, longitude and altitude values representing a point on the Earth's surface + While we could also use a Vec3 for this, we use a separate message to make the intent of the data clearer. + + ```proto + message datasets.v1.LatLonAlt + ``` + + Attributes: + latitude: + ```proto + double latitude = 1; + ``` + longitude: + ```proto + double longitude = 2; + ``` + altitude: + ```proto + double altitude = 3; + ``` + """ + + __slots__ = ("latitude", "longitude", "altitude") + + if TYPE_CHECKING: + + def __init__( + self, + *, + latitude: float = 0, + longitude: float = 0, + altitude: float = 0, + ) -> None: + pass + + latitude: float + longitude: float + altitude: float + +_GeometryFields: TypeAlias = Literal["wkb"] + +class Geometry(Message[_GeometryFields]): + """ + Geometry is of a particular type, e.g. POINT, POLYGON, MULTIPOLYGON, encoded in a binary format + + ```proto + message datasets.v1.Geometry + ``` + + Attributes: + wkb: + well-known binary representation of a geometry + + ```proto + bytes wkb = 1; + ``` + """ + + __slots__ = ("wkb",) + + if TYPE_CHECKING: + + def __init__( + self, + *, + wkb: bytes = b"", + ) -> None: + pass + + wkb: bytes + +class FlightDirection(Enum): + """ + Satellite flight direction + + ```proto + enum datasets.v1.FlightDirection + ``` + + Attributes: + UNSPECIFIED: + ```proto + FLIGHT_DIRECTION_UNSPECIFIED = 0 + ``` + ASCENDING: + ```proto + FLIGHT_DIRECTION_ASCENDING = 1 + ``` + DESCENDING: + ```proto + FLIGHT_DIRECTION_DESCENDING = 2 + ``` + """ + + UNSPECIFIED = 0 + ASCENDING = 1 + DESCENDING = 2 + +class ObservationDirection(Enum): + """ + Observation direction + + ```proto + enum datasets.v1.ObservationDirection + ``` + + Attributes: + UNSPECIFIED: + ```proto + OBSERVATION_DIRECTION_UNSPECIFIED = 0 + ``` + LEFT: + ```proto + OBSERVATION_DIRECTION_LEFT = 1 + ``` + RIGHT: + ```proto + OBSERVATION_DIRECTION_RIGHT = 2 + ``` + """ + + UNSPECIFIED = 0 + LEFT = 1 + RIGHT = 2 + +class OpendataProvider(Enum): + """ + The open data provider. + + ```proto + enum datasets.v1.OpendataProvider + ``` + + Attributes: + UNSPECIFIED: + ```proto + OPENDATA_PROVIDER_UNSPECIFIED = 0 + ``` + ASF: + Alaska Satellite Facility + + ```proto + OPENDATA_PROVIDER_ASF = 1 + ``` + COPERNICUS_DATASPACE: + Copernicus Dataspace + + ```proto + OPENDATA_PROVIDER_COPERNICUS_DATASPACE = 2 + ``` + UMBRA: + Umbra Space + + ```proto + OPENDATA_PROVIDER_UMBRA = 3 + ``` + """ + + UNSPECIFIED = 0 + ASF = 1 + COPERNICUS_DATASPACE = 2 + UMBRA = 3 + +class ProcessingLevel(Enum): + """ + https://www.earthdata.nasa.gov/engage/open-data-services-and-software/data-information-policy/data-levels + + ```proto + enum datasets.v1.ProcessingLevel + ``` + + Attributes: + UNSPECIFIED: + ```proto + PROCESSING_LEVEL_UNSPECIFIED = 0 + ``` + L0: + Raw data + + ```proto + PROCESSING_LEVEL_L0 = 12 + ``` + L1: + ```proto + PROCESSING_LEVEL_L1 = 10 + ``` + L1A: + ```proto + PROCESSING_LEVEL_L1A = 1 + ``` + L1B: + ```proto + PROCESSING_LEVEL_L1B = 2 + ``` + L1C: + ```proto + PROCESSING_LEVEL_L1C = 3 + ``` + L2: + ```proto + PROCESSING_LEVEL_L2 = 4 + ``` + L2A: + ```proto + PROCESSING_LEVEL_L2A = 5 + ``` + L2B: + ```proto + PROCESSING_LEVEL_L2B = 6 + ``` + L3: + ```proto + PROCESSING_LEVEL_L3 = 7 + ``` + L3A: + ```proto + PROCESSING_LEVEL_L3A = 8 + ``` + L4: + ```proto + PROCESSING_LEVEL_L4 = 9 + ``` + NOT_APPLICABLE: + ```proto + PROCESSING_LEVEL_NOT_APPLICABLE = 11 + ``` + """ + + UNSPECIFIED = 0 + L0 = 12 + L1 = 10 + L1A = 1 + L1B = 2 + L1C = 3 + L2 = 4 + L2A = 5 + L2B = 6 + L3 = 7 + L3A = 8 + L4 = 9 + NOT_APPLICABLE = 11 + +class Polarization(Enum): + """ + Polarization of the radar signal. + + ```proto + enum datasets.v1.Polarization + ``` + + Attributes: + UNSPECIFIED: + ```proto + POLARIZATION_UNSPECIFIED = 0 + ``` + HH: + Horizontal transmit, Horizontal receive + + ```proto + POLARIZATION_HH = 1 + ``` + HV: + Horizontal transmit, Vertical receive + + ```proto + POLARIZATION_HV = 2 + ``` + VH: + Vertical transmit, Horizontal receive + + ```proto + POLARIZATION_VH = 3 + ``` + VV: + Vertical transmit, Vertical receive + + ```proto + POLARIZATION_VV = 4 + ``` + DUAL_HH: + HH+HH + + ```proto + POLARIZATION_DUAL_HH = 5 + ``` + DUAL_HV: + HV+HV + + ```proto + POLARIZATION_DUAL_HV = 6 + ``` + DUAL_VH: + VH+VH + + ```proto + POLARIZATION_DUAL_VH = 7 + ``` + DUAL_VV: + VV+VV + + ```proto + POLARIZATION_DUAL_VV = 8 + ``` + HH_HV: + HH+HV + + ```proto + POLARIZATION_HH_HV = 9 + ``` + VV_VH: + VV+VH + + ```proto + POLARIZATION_VV_VH = 10 + ``` + """ + + UNSPECIFIED = 0 + HH = 1 + HV = 2 + VH = 3 + VV = 4 + DUAL_HH = 5 + DUAL_HV = 6 + DUAL_VH = 7 + DUAL_VV = 8 + HH_HV = 9 + VV_VH = 10 + +class AcquisitionMode(Enum): + """ + Sentinel-1 SAR (beam mode): + https://sentinels.copernicus.eu/web/sentinel/technical-guides/sentinel-1-sar/sar-instrument/acquisition-modes + + ```proto + enum datasets.v1.AcquisitionMode + ``` + + Attributes: + UNSPECIFIED: + In case it is not set for a dataset + + ```proto + ACQUISITION_MODE_UNSPECIFIED = 0 + ``` + SM: + used by Sentinel-1 SAR: + Strip Map + + ```proto + ACQUISITION_MODE_SM = 1 + ``` + EW: + Extra Wide Swath + + ```proto + ACQUISITION_MODE_EW = 2 + ``` + IW: + Interferometric Wide Swath + + ```proto + ACQUISITION_MODE_IW = 3 + ``` + WV: + Wave + + ```proto + ACQUISITION_MODE_WV = 4 + ``` + SPOTLIGHT: + used by Umbra SAR: + Spotlight + + ```proto + ACQUISITION_MODE_SPOTLIGHT = 10 + ``` + NOBS: + used by Sentinel 2 MSI: + Nominal Observation + + ```proto + ACQUISITION_MODE_NOBS = 20 + ``` + EOBS: + Extended Observation + + ```proto + ACQUISITION_MODE_EOBS = 21 + ``` + DASC: + Dark Signal Calibration + + ```proto + ACQUISITION_MODE_DASC = 22 + ``` + ABSR: + Absolute Radiometry Calibration + + ```proto + ACQUISITION_MODE_ABSR = 23 + ``` + VIC: + Vicarious Calibration + + ```proto + ACQUISITION_MODE_VIC = 24 + ``` + RAW: + Raw Measurement + + ```proto + ACQUISITION_MODE_RAW = 25 + ``` + TST: + Test Mode + + ```proto + ACQUISITION_MODE_TST = 26 + ``` + """ + + UNSPECIFIED = 0 + SM = 1 + EW = 2 + IW = 3 + WV = 4 + SPOTLIGHT = 10 + NOBS = 20 + EOBS = 21 + DASC = 22 + ABSR = 23 + VIC = 24 + RAW = 25 + TST = 26 + + +_DESC = file_desc( + b'\n"datasets/v1/well_known_types.proto\x12\x0bdatasets.v1"\x1a\n\x04UUID\x12\x12\n\x04uuid\x18\x01 \x01(\x0cR\x04uuid"0\n\x04Vec3\x12\x0c\n\x01x\x18\x01 \x01(\x01R\x01x\x12\x0c\n\x01y\x18\x02 \x01(\x01R\x01y\x12\x0c\n\x01z\x18\x03 \x01(\x01R\x01z"L\n\nQuaternion\x12\x0e\n\x02q1\x18\x01 \x01(\x01R\x02q1\x12\x0e\n\x02q2\x18\x02 \x01(\x01R\x02q2\x12\x0e\n\x02q3\x18\x03 \x01(\x01R\x02q3\x12\x0e\n\x02q4\x18\x04 \x01(\x01R\x02q4"B\n\x06LatLon\x12\x1a\n\x08latitude\x18\x01 \x01(\x01R\x08latitude\x12\x1c\n\tlongitude\x18\x02 \x01(\x01R\tlongitude"a\n\tLatLonAlt\x12\x1a\n\x08latitude\x18\x01 \x01(\x01R\x08latitude\x12\x1c\n\tlongitude\x18\x02 \x01(\x01R\tlongitude\x12\x1a\n\x08altitude\x18\x03 \x01(\x01R\x08altitude"\x1c\n\x08Geometry\x12\x10\n\x03wkb\x18\x01 \x01(\x0cR\x03wkb*t\n\x0fFlightDirection\x12 \n\x1cFLIGHT_DIRECTION_UNSPECIFIED\x10\x00\x12\x1e\n\x1aFLIGHT_DIRECTION_ASCENDING\x10\x01\x12\x1f\n\x1bFLIGHT_DIRECTION_DESCENDING\x10\x02*~\n\x14ObservationDirection\x12%\n!OBSERVATION_DIRECTION_UNSPECIFIED\x10\x00\x12\x1e\n\x1aOBSERVATION_DIRECTION_LEFT\x10\x01\x12\x1f\n\x1bOBSERVATION_DIRECTION_RIGHT\x10\x02*\x99\x01\n\x10OpendataProvider\x12!\n\x1dOPENDATA_PROVIDER_UNSPECIFIED\x10\x00\x12\x19\n\x15OPENDATA_PROVIDER_ASF\x10\x01\x12*\n&OPENDATA_PROVIDER_COPERNICUS_DATASPACE\x10\x02\x12\x1b\n\x17OPENDATA_PROVIDER_UMBRA\x10\x03*\xf1\x02\n\x0fProcessingLevel\x12 \n\x1cPROCESSING_LEVEL_UNSPECIFIED\x10\x00\x12\x17\n\x13PROCESSING_LEVEL_L0\x10\x0c\x12\x17\n\x13PROCESSING_LEVEL_L1\x10\n\x12\x18\n\x14PROCESSING_LEVEL_L1A\x10\x01\x12\x18\n\x14PROCESSING_LEVEL_L1B\x10\x02\x12\x18\n\x14PROCESSING_LEVEL_L1C\x10\x03\x12\x17\n\x13PROCESSING_LEVEL_L2\x10\x04\x12\x18\n\x14PROCESSING_LEVEL_L2A\x10\x05\x12\x18\n\x14PROCESSING_LEVEL_L2B\x10\x06\x12\x17\n\x13PROCESSING_LEVEL_L3\x10\x07\x12\x18\n\x14PROCESSING_LEVEL_L3A\x10\x08\x12\x17\n\x13PROCESSING_LEVEL_L4\x10\t\x12#\n\x1fPROCESSING_LEVEL_NOT_APPLICABLE\x10\x0b*\x98\x02\n\x0cPolarization\x12\x1c\n\x18POLARIZATION_UNSPECIFIED\x10\x00\x12\x13\n\x0fPOLARIZATION_HH\x10\x01\x12\x13\n\x0fPOLARIZATION_HV\x10\x02\x12\x13\n\x0fPOLARIZATION_VH\x10\x03\x12\x13\n\x0fPOLARIZATION_VV\x10\x04\x12\x18\n\x14POLARIZATION_DUAL_HH\x10\x05\x12\x18\n\x14POLARIZATION_DUAL_HV\x10\x06\x12\x18\n\x14POLARIZATION_DUAL_VH\x10\x07\x12\x18\n\x14POLARIZATION_DUAL_VV\x10\x08\x12\x16\n\x12POLARIZATION_HH_HV\x10\t\x12\x16\n\x12POLARIZATION_VV_VH\x10\n*\xf1\x02\n\x0fAcquisitionMode\x12 \n\x1cACQUISITION_MODE_UNSPECIFIED\x10\x00\x12\x17\n\x13ACQUISITION_MODE_SM\x10\x01\x12\x17\n\x13ACQUISITION_MODE_EW\x10\x02\x12\x17\n\x13ACQUISITION_MODE_IW\x10\x03\x12\x17\n\x13ACQUISITION_MODE_WV\x10\x04\x12\x1e\n\x1aACQUISITION_MODE_SPOTLIGHT\x10\n\x12\x19\n\x15ACQUISITION_MODE_NOBS\x10\x14\x12\x19\n\x15ACQUISITION_MODE_EOBS\x10\x15\x12\x19\n\x15ACQUISITION_MODE_DASC\x10\x16\x12\x19\n\x15ACQUISITION_MODE_ABSR\x10\x17\x12\x18\n\x14ACQUISITION_MODE_VIC\x10\x18\x12\x18\n\x14ACQUISITION_MODE_RAW\x10\x19\x12\x18\n\x14ACQUISITION_MODE_TST\x10\x1aB\x05\x92\x03\x02\x08\x02b\x08editionsp\xe8\x07', + [], + { + "UUID": UUID, + "Vec3": Vec3, + "Quaternion": Quaternion, + "LatLon": LatLon, + "LatLonAlt": LatLonAlt, + "Geometry": Geometry, + "FlightDirection": FlightDirection, + "ObservationDirection": ObservationDirection, + "OpendataProvider": OpendataProvider, + "ProcessingLevel": ProcessingLevel, + "Polarization": Polarization, + "AcquisitionMode": AcquisitionMode, + }, +) + + +def desc() -> DescFile: + """Returns the descriptor for the file `datasets/v1/well_known_types.proto`.""" + return _DESC diff --git a/tilebox-grpc/README.md b/tilebox-grpc/README.md index c34c268..94283d8 100644 --- a/tilebox-grpc/README.md +++ b/tilebox-grpc/README.md @@ -1,54 +1,6 @@ -

- Tilebox Logo -
-

+# Tilebox gRPC -
- - PyPi Latest Release badge - - - Required Python Version badge - - - MIT License - - - Build Status - - - Join us on Discord - -
- -

- Documentation - | - Console - | - Example Gallery -

- -# Tilebox GRPC - -GRPC and Protobuf related functionality used by Tilebox python packages. - -## Quickstart - -Install using `pip`: - -```bash -pip install tilebox-grpc -``` - -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") -``` - -## License - -Distributed under the MIT License (`The MIT License`). +Compatibility package for the 0.67 release. This package contains no Python modules; +it depends on `tilebox-core>=0.67.0`, which now owns the shared SDK infrastructure. +Use `tilebox-core` directly in new dependency declarations. This shim is retained for +one release and is planned for removal afterward. diff --git a/tilebox-grpc/_tilebox/grpc/__init__.py b/tilebox-grpc/_tilebox/grpc/__init__.py deleted file mode 100644 index 9f86bb1..0000000 --- a/tilebox-grpc/_tilebox/grpc/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -import os - -# Suppress native gRPC fork-log spam while preserving explicit user settings. -# Remove once our minimum grpcio version resolves https://github.com/grpc/grpc/issues/42293. -# This must run before gRPC initializes; it also affects other Abseil-based native logging. -os.environ.setdefault("GRPC_VERBOSITY", "ERROR") diff --git a/tilebox-grpc/_tilebox/grpc/aio/__init__.py b/tilebox-grpc/_tilebox/grpc/aio/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/tilebox-grpc/_tilebox/grpc/aio/channel.py b/tilebox-grpc/_tilebox/grpc/aio/channel.py deleted file mode 100644 index f270ba5..0000000 --- a/tilebox-grpc/_tilebox/grpc/aio/channel.py +++ /dev/null @@ -1,120 +0,0 @@ -from collections.abc import Callable, Sequence -from typing import TypeVar - -from _tilebox.grpc.channel import ( - CHANNEL_OPTIONS, - ChannelInfo, - ChannelProtocol, - add_metadata, - parse_channel_info, - update_method, -) -from _tilebox.grpc.client_metadata import client_metadata -from grpc import Compression, ssl_channel_credentials -from grpc.aio import ( - Channel, - ClientCallDetails, - ClientInterceptor, - UnaryUnaryCall, - UnaryUnaryClientInterceptor, - insecure_channel, - secure_channel, -) - - -def open_channel(url: str, auth_token: str | None = None, rpc_method_prefix: str | None = None) -> Channel: - """Open an async gRPC channel to the given URL. - - Args: - url: The URL to open a channel to. Depending on the URL, the channel will be a secure (SSL) or insecure channel. - auth_token: Authentication token for the channel. If set, an interceptor channel will be created which adds - the given token as metadata to each request. - rpc_method_prefix: Optional prefix to prepend to each outgoing RPC method path, e.g. `/public`. - - Returns: - A gRPC channel. - """ - channel_info = parse_channel_info(url) - interceptors: list[ClientInterceptor] = [_ClientMetadataInterceptor()] - if auth_token is not None: - interceptors = [_AuthMetadataInterceptor(auth_token), *interceptors] # add auth interceptor as the first one - if rpc_method_prefix is not None: - interceptors = [*interceptors, _RpcMethodPrefixInterceptor(rpc_method_prefix)] - - return _open_channel(channel_info, interceptors) - - -def _open_channel(channel_info: ChannelInfo, interceptors: Sequence[ClientInterceptor]) -> Channel: - match channel_info.protocol: - case ChannelProtocol.HTTPS: - return secure_channel( - f"{channel_info.address}:{channel_info.port}", - ssl_channel_credentials(), - CHANNEL_OPTIONS, - compression=Compression.Gzip, - interceptors=interceptors, - ) - case ChannelProtocol.HTTP: - return insecure_channel( - f"{channel_info.address}:{channel_info.port}", - CHANNEL_OPTIONS, - compression=Compression.NoCompression, - interceptors=interceptors, - ) - case ChannelProtocol.UNIX: - return insecure_channel( - channel_info.address, CHANNEL_OPTIONS, compression=Compression.NoCompression, interceptors=interceptors - ) - case _: - raise ValueError(f"Unsupported channel protocol: {channel_info.protocol}") - - -RequestType = TypeVar("RequestType") - - -class _AuthMetadataInterceptor(UnaryUnaryClientInterceptor): - def __init__(self, auth_token: str) -> None: - """A gRPC channel interceptor which adds the authorization token as metadata to every request. - - Args: - auth_token: The authorization token. - """ - super().__init__() - self._auth = ("authorization", f"Bearer {auth_token}") - - async def intercept_unary_unary( - self, - continuation: Callable[[ClientCallDetails, RequestType], UnaryUnaryCall], - client_call_details: ClientCallDetails, - request: RequestType, - ) -> UnaryUnaryCall: - return await continuation(add_metadata(client_call_details, [self._auth]), request) - - -class _ClientMetadataInterceptor(UnaryUnaryClientInterceptor): - def __init__(self) -> None: - super().__init__() - self._metadata = [(key.lower(), value) for key, value in client_metadata().items()] - - async def intercept_unary_unary( - self, - continuation: Callable[[ClientCallDetails, RequestType], UnaryUnaryCall], - client_call_details: ClientCallDetails, - request: RequestType, - ) -> UnaryUnaryCall: - return await continuation(add_metadata(client_call_details, self._metadata), request) - - -class _RpcMethodPrefixInterceptor(UnaryUnaryClientInterceptor): - def __init__(self, prefix: str) -> None: - """A gRPC channel interceptor which prefixes every outgoing RPC method path.""" - super().__init__() - self._prefix = prefix - - async def intercept_unary_unary( - self, - continuation: Callable[[ClientCallDetails, RequestType], UnaryUnaryCall], - client_call_details: ClientCallDetails, - request: RequestType, - ) -> UnaryUnaryCall: - return await continuation(update_method(client_call_details, self._prefix), request) diff --git a/tilebox-grpc/_tilebox/grpc/aio/error.py b/tilebox-grpc/_tilebox/grpc/aio/error.py deleted file mode 100644 index e28fca8..0000000 --- a/tilebox-grpc/_tilebox/grpc/aio/error.py +++ /dev/null @@ -1,40 +0,0 @@ -from collections.abc import Callable -from typing import Any, TypeVar, cast - -from _tilebox.grpc.error import AnyRpcError, translate_rpc_error -from grpc import RpcError -from grpc.aio import AioRpcError - -Stub = TypeVar("Stub") - - -def with_pythonic_errors(stub: Stub) -> Stub: - """ - Wrap an async gRPC stub to translate rpc errors into pythonic exceptions. - - This is done this way instead of an error handling interceptor, because tracebacks are much easier to read when - they are raised directly at the location of the RPC method call. - - Args: - stub: The async grpc stub to wrap. - - Returns: - The stub with the rpc methods wrapped. - """ - for name, rpc in stub.__dict__.items(): - if callable(rpc): - setattr(stub, name, _wrap_rpc(rpc)) - return stub - - -def _wrap_rpc(rpc: Callable[[Any], Any]) -> Callable[[Any], Any]: - async def call(*args: Any, **kwargs: Any) -> Any: - try: - return await rpc(*args, **kwargs) - except (RpcError, AioRpcError) as err: - error = translate_rpc_error(cast(AnyRpcError, err)) - - # raise the appropriate exception for the error code we received - raise error from None - - return call diff --git a/tilebox-grpc/_tilebox/grpc/channel.py b/tilebox-grpc/_tilebox/grpc/channel.py deleted file mode 100644 index 71a7cca..0000000 --- a/tilebox-grpc/_tilebox/grpc/channel.py +++ /dev/null @@ -1,355 +0,0 @@ -import json -import re -from collections.abc import Awaitable, Callable, Mapping -from dataclasses import dataclass -from enum import Enum -from typing import Literal, TypeVar - -from _tilebox.grpc.client_metadata import client_metadata -from _tilebox.grpc.error import async_wrap_connect_rpc, wrap_connect_rpc -from grpc import ( - Channel, - Compression, - UnaryUnaryClientInterceptor, - insecure_channel, - intercept_channel, - secure_channel, - ssl_channel_credentials, -) -from grpc.aio import ( - ClientCallDetails, # import from aio, since grpc.ClientCallDetails is an empty base class -) - -Transport = Literal["grpc", "http1"] - -# We don't specify the service field, so the config applies to all services -# See https://github.com/grpc/grpc-proto/blob/master/grpc/service_config/service_config.proto#L50-L52 -_SERVICE_CONFIG = { - "methodConfig": [ - { - "name": [{"service": ""}], - "retryPolicy": { - "maxAttempts": 5, - "initialBackoff": "0.02s", - "maxBackoff": "5s", - "backoffMultiplier": 3, - "retryableStatusCodes": [ - "RESOURCE_EXHAUSTED", # 429 Too Many Requests - "UNAVAILABLE", # 503 Service Unavailable - ], - }, - } - ] -} - -CHANNEL_OPTIONS = [ - ("grpc.max_receive_message_length", 512 * 1024 * 1024), # Max 512 MB - ("grpc.service_config", json.dumps(_SERVICE_CONFIG)), -] - - -class ChannelProtocol(Enum): - HTTPS = 1 - HTTP = 2 - UNIX = 3 - - -@dataclass -class ChannelInfo: - address: str - """GRPC target address. For http(s) connections this is the host[:port] format without a scheme. For unix sockets - this is the unix socket path including the `unix://` prefix for absolute paths or `unix:` for relative paths.""" - port: int - """Port number for http(s) connections. For unix sockets this is always 0.""" - protocol: ChannelProtocol - """The protocol to use for the channel.""" - - -def open_channel(url: str, auth_token: str | None = None, rpc_method_prefix: str | None = None) -> Channel: - """Open a sync gRPC channel to the given URL. - - Args: - url: The URL to open a channel to. Depending on the URL, the channel will be a secure (SSL) or insecure channel. - auth_token: Authentication token for the channel. If set, an interceptor channel will be created which adds - the given token as metadata to each request. - rpc_method_prefix: Optional prefix to prepend to each outgoing RPC method path, e.g. `/public`. - - Returns: - A sync gRPC channel. - """ - channel_info = parse_channel_info(url) - interceptors: list[UnaryUnaryClientInterceptor] = [_ClientMetadataInterceptor()] - if auth_token is not None: - interceptors = [_AuthMetadataInterceptor(auth_token), *interceptors] # add auth interceptor as the first one - if rpc_method_prefix is not None: - interceptors = [*interceptors, _RpcMethodPrefixInterceptor(rpc_method_prefix)] - - return intercept_channel(_open_channel(channel_info), *interceptors) - - -def _open_channel(channel_info: ChannelInfo) -> Channel: - match channel_info.protocol: - case ChannelProtocol.HTTPS: - return secure_channel( - f"{channel_info.address}:{channel_info.port}", - ssl_channel_credentials(), - CHANNEL_OPTIONS, - compression=Compression.Gzip, - ) - case ChannelProtocol.HTTP: - return insecure_channel( - f"{channel_info.address}:{channel_info.port}", CHANNEL_OPTIONS, compression=Compression.NoCompression - ) - case ChannelProtocol.UNIX: - return insecure_channel(channel_info.address, CHANNEL_OPTIONS, compression=Compression.NoCompression) - case _: - raise ValueError(f"Unsupported channel protocol: {channel_info.protocol}") - - -_URL_SCHEME = re.compile(r"^(https?://)?([^: ]+)(:\d+)?/?$") - - -def parse_channel_info(url: str) -> ChannelInfo: - """Parse a given url into a ChannelInfo object that can be used to create a gRPC channel. - - For opening a gRPC channel we need a URL without a protocol (http/https) and a port number as part of the url. - - Unix domain socket should be formatted as unix:path or unix://absolute_path. For example: - - unix:path/to/socket - - unix:///absolute/path/to/socket - - Args: - url: The url to parse. - - Returns: - A ChannelInfo object that can be used to create a gRPC channel. - """ - # See https://github.com/grpc/grpc/blob/master/doc/naming.md - if url.startswith("unix:"): ## unix:///absolute/path or unix://path - return ChannelInfo(url, 0, ChannelProtocol.UNIX) - - # `urllib.parse.urlparse` behaves a bit weird with URLs that don't have a scheme but a port number, so regex it is - if (match := _URL_SCHEME.match(url)) is None: - raise ValueError(f"Invalid URL: {url}") - scheme, netloc, port = match.groups() - netloc = netloc.rstrip("/") - protocol = ChannelProtocol.HTTPS - - if scheme == "http://": # explicitly set http -> require a port - if port is None: - raise ValueError("Explicit port required for insecure HTTP channel") - protocol = ChannelProtocol.HTTP - - # no scheme, but a port that looks like a dev port -> insecure - if scheme is None and port is not None and port != ":443": - protocol = ChannelProtocol.HTTP - - port_number = 443 if port is None else int(port.removeprefix(":")) - - return ChannelInfo(netloc, port_number, protocol) - - -def connect_address(url: str, rpc_method_prefix: str | None = None) -> str: - """Return a Connect base URL matching the same URL handling as gRPC channels.""" - channel_info = parse_channel_info(url) - match channel_info.protocol: - case ChannelProtocol.HTTPS: - scheme = "https" - port = "" if channel_info.port == 443 else f":{channel_info.port}" - case ChannelProtocol.HTTP: - scheme = "http" - port = f":{channel_info.port}" - case ChannelProtocol.UNIX: - raise ValueError("Connect HTTP/1 transport does not support unix socket URLs") - case _: - raise ValueError(f"Unsupported channel protocol: {channel_info.protocol}") - - address = f"{scheme}://{channel_info.address}{port}" - normalized_prefix = "" if rpc_method_prefix is None else rpc_method_prefix.strip("/") - if normalized_prefix == "": - return address - return f"{address}/{normalized_prefix}" - - -RequestType = TypeVar("RequestType") -ResponseType = TypeVar("ResponseType") - - -class ConnectStubAdapter: - def __init__( - self, - client: object, - headers: Mapping[str, str] | None = None, - rpc_method_prefix: str | None = None, - ) -> None: - """Adapt a generated Connect client to the method names exposed by grpcio stubs.""" - method_path_prefix = _rpc_method_prefix_path(rpc_method_prefix) - service_name = _connect_service_name(client) - self._client = client - self._headers = {**(headers or {}), **client_metadata()} - - for connect_name in _connect_client_methods(client): - grpc_name = _snake_to_pascal_case(connect_name) - rpc = getattr(client, connect_name) - method_name = f"{method_path_prefix}/{service_name}/{grpc_name}" - setattr(self, grpc_name, wrap_connect_rpc(self._call(rpc), method_name)) - - def _call(self, rpc: Callable[..., ResponseType]) -> Callable[[RequestType], ResponseType]: - def call(request: RequestType) -> ResponseType: - return rpc(request, headers=self._headers) - - return call - - -class AsyncConnectStubAdapter: - def __init__( - self, - client: object, - headers: Mapping[str, str] | None = None, - rpc_method_prefix: str | None = None, - ) -> None: - """Adapt a generated async Connect client to the method names exposed by grpcio stubs.""" - method_path_prefix = _rpc_method_prefix_path(rpc_method_prefix) - service_name = _connect_service_name(client) - self._client = client - self._headers = {**(headers or {}), **client_metadata()} - - for connect_name in _connect_client_methods(client): - grpc_name = _snake_to_pascal_case(connect_name) - rpc = getattr(client, connect_name) - method_name = f"{method_path_prefix}/{service_name}/{grpc_name}" - setattr(self, grpc_name, async_wrap_connect_rpc(self._call(rpc), method_name)) - - def _call(self, rpc: Callable[..., Awaitable[ResponseType]]) -> Callable[[RequestType], Awaitable[ResponseType]]: - async def call(request: RequestType) -> ResponseType: - return await rpc(request, headers=self._headers) - - return call - - -def _connect_client_methods(client: object) -> dict[str, Callable[..., object]]: - return { - name: getattr(client, name) - for name, value in type(client).__dict__.items() - if not name.startswith("_") and callable(value) - } - - -def _snake_to_pascal_case(name: str) -> str: - return "".join(part.upper() if len(part) == 1 else part.title() for part in name.split("_")) - - -def _rpc_method_prefix_path(rpc_method_prefix: str | None) -> str: - if rpc_method_prefix is None: - return "" - normalized_prefix = rpc_method_prefix.strip("/") - if normalized_prefix == "": - return "" - return f"/{normalized_prefix}" - - -def _connect_service_name(client: object) -> str: - client_type = type(client) - service_name = client_type.__name__.removesuffix("ClientSync").removesuffix("Client") - module_parts = client_type.__module__.split(".") - package_parts = module_parts[:-1] - - if package_parts[:2] in (["tilebox", "datasets"], ["tilebox", "workflows"]): - package_parts = package_parts[2:] - - return ".".join([*package_parts, service_name]) - - -class _AuthMetadataInterceptor(UnaryUnaryClientInterceptor): - def __init__(self, auth_token: str) -> None: - """A sync gRPC channel interceptor which adds the authorization token as metadata to every request. - - Args: - auth_token: The authorization token. - """ - super().__init__() - self._auth = ("authorization", f"Bearer {auth_token}") - - def intercept_unary_unary( - self, - continuation: Callable[[ClientCallDetails, RequestType], ResponseType], - client_call_details: ClientCallDetails, - request: RequestType, - ) -> ResponseType: - return continuation(add_metadata(client_call_details, [self._auth]), request) - - -class _ClientMetadataInterceptor(UnaryUnaryClientInterceptor): - def __init__(self) -> None: - super().__init__() - self._metadata = [(key.lower(), value) for key, value in client_metadata().items()] - - def intercept_unary_unary( - self, - continuation: Callable[[ClientCallDetails, RequestType], ResponseType], - client_call_details: ClientCallDetails, - request: RequestType, - ) -> ResponseType: - return continuation(add_metadata(client_call_details, self._metadata), request) - - -class _RpcMethodPrefixInterceptor(UnaryUnaryClientInterceptor): - def __init__(self, prefix: str) -> None: - """A sync gRPC channel interceptor which prefixes every outgoing RPC method path.""" - super().__init__() - self._prefix = prefix - - def intercept_unary_unary( - self, - continuation: Callable[[ClientCallDetails, RequestType], ResponseType], - client_call_details: ClientCallDetails, - request: RequestType, - ) -> ResponseType: - return continuation(update_method(client_call_details, self._prefix), request) - - -def add_metadata( - client_call_details: ClientCallDetails, additional_metadata: list[tuple[str, str]] -) -> ClientCallDetails: - metadata = [] if client_call_details.metadata is None else list(client_call_details.metadata) - metadata.extend(additional_metadata) - return _replace_call_details(client_call_details, metadata=metadata) - - -def update_method(client_call_details: ClientCallDetails, prefix: str) -> ClientCallDetails: - return _replace_call_details(client_call_details, method=prefix_rpc_method(client_call_details.method, prefix)) - - -def prefix_rpc_method(method: str | bytes, prefix: str) -> str | bytes: - normalized_prefix = prefix.strip("/") - if normalized_prefix == "": - return method - - if isinstance(method, bytes): - prefix_bytes = f"/{normalized_prefix}".encode() - normalized_method = method if method.startswith(b"/") else b"/" + method - if normalized_method == prefix_bytes or normalized_method.startswith(prefix_bytes + b"/"): - return normalized_method - return prefix_bytes + normalized_method - - prefix = f"/{normalized_prefix}" - normalized_method = method if method.startswith("/") else f"/{method}" - if normalized_method == prefix or normalized_method.startswith(f"{prefix}/"): - return normalized_method - - return f"{prefix}{normalized_method}" - - -def _replace_call_details( - client_call_details: ClientCallDetails, - *, - method: str | bytes | None = None, - metadata: list[tuple[str, str | bytes]] | None = None, -) -> ClientCallDetails: - return ClientCallDetails( - client_call_details.method if method is None else method, - client_call_details.timeout, - client_call_details.metadata if metadata is None else metadata, - client_call_details.credentials, - client_call_details.wait_for_ready, - ) diff --git a/tilebox-grpc/_tilebox/grpc/error.py b/tilebox-grpc/_tilebox/grpc/error.py deleted file mode 100644 index ee6859e..0000000 --- a/tilebox-grpc/_tilebox/grpc/error.py +++ /dev/null @@ -1,303 +0,0 @@ -""" -This module contains error classes for translating various gRPC server response codes into more pythonic exceptions -""" - -from collections.abc import Awaitable, Callable, Coroutine -from typing import Any, Protocol, TypeVar, cast - -from _tilebox.grpc.public import _PUBLIC_RPC_METHOD_PREFIX -from grpc import RpcError, StatusCode -from grpc.aio import AioRpcError - - -class NetworkError(IOError): - """NetworkError indicates that a network error occurred while communicating with the server""" - - -class TooManyRequestsError(NetworkError): - """TooManyRequestsError indicates that too many requests originated from the same IP""" - - -class NetworkTimeoutError(NetworkError, TimeoutError): - """TimeoutError indicates that a request timed out""" - - -class AuthenticationError(IOError): - """AuthenticationError indicates that a server request failed due to a missing or invalid authentication token""" - - -class ArgumentError(ValueError): - """ArgumentError indicates that a server request failed due to missing or invalid arguments""" - - -class NotFoundError(KeyError): - """NotFoundError indicates that a given resource was not found on the server side""" - - -class SubscriptionLimitExceededError(IOError): - """SubscriptionLimitExceededError indicates that the subscription limit for a resource has been reached""" - - -class InternalServerError(KeyError): - """InternalServerError indicates that an unexpected error happened on the server side""" - - -class InvalidRequestError(IOError): - """""" - - -Stub = TypeVar("Stub") - - -def with_pythonic_errors(stub: Stub, async_funcs: bool = False) -> Stub: - """ - Wrap a sync gRPC stub to translate rpc errors into pythonic exceptions. - - This is done this way instead of an error handling interceptor, because tracebacks are much easier to read when - they are raised directly at the location of the RPC method call. - - Args: - stub: The grpc stub to wrap. - async_funcs: Whether to wrap the callables as coroutines or not. Defaults to False. - - Returns: - The stub with the rpc methods wrapped. - """ - wrap_func = _wrap_rpc if not async_funcs else _async_wrap_rpc - for name, rpc in stub.__dict__.items(): - if callable(rpc): - setattr(stub, name, wrap_func(rpc)) - return stub - - -class RPCState(Protocol): - @property - def method(self) -> str: ... - - -class AnyRpcError(Protocol): - """Protocol for gRPC errors that works for both sync and async gRPC.""" - - def code(self) -> StatusCode: ... - def details(self) -> str: ... - - @property - def args(self) -> tuple[RPCState, ...]: ... - - -def translate_rpc_error(err: AnyRpcError) -> BaseException: # noqa: PLR0911, PLR0912, C901 - # translate specific error codes to more pythonic errors - - # https://grpc.io/docs/guides/error/ - match err.code(): - case StatusCode.NOT_FOUND: - return NotFoundError(err.details()) - case StatusCode.INVALID_ARGUMENT: - return ArgumentError(err.details()) - case StatusCode.UNAUTHENTICATED: - return AuthenticationError(f"Unauthenticated: {err.details()}") - case StatusCode.PERMISSION_DENIED: - return AuthenticationError(f"Unauthorized: {err.details()}") - case StatusCode.RESOURCE_EXHAUSTED: - return SubscriptionLimitExceededError(err.details()) - case StatusCode.DEADLINE_EXCEEDED: - # Deadline expired before server returned status - return NetworkTimeoutError(f"Request timed out: {err.details()}") - case StatusCode.UNAVAILABLE: - is_too_many_requests = "status: 429" in err.details() - is_public, method_name = _is_public_method(err) - if is_too_many_requests: - if is_public: - return TooManyRequestsError( - "Too many requests, try again later. To increase your request quota create a free account at " - "https://console.tilebox.com and authenticate with an API Key." - ) - return TooManyRequestsError("Too many requests, try again later.") - - # Server shutting down, or some data transmitted and then the connection broke - if method_name != "": - return NetworkError(f"network error when requesting {method_name}: {err.details()}") - return NetworkError(err.details()) - case StatusCode.ABORTED: - return NetworkError(f"Request aborted: {err.details()}") - case StatusCode.UNKNOWN: - # Server threw an exception (or did something other than returning a status code to terminate the RPC) - return InternalServerError(f"Oops, something went wrong: {err.details()}") - case StatusCode.INTERNAL: - return InternalServerError(f"Oops, something went wrong: {err.details()}") - case StatusCode.CANCELLED: - # Client application cancelled the request - return KeyboardInterrupt(f"Request canceled by user: {err.details()}") - case StatusCode.UNIMPLEMENTED: - # Method not found on server, either due to a mismatch in Client / server version - # or because no token was provided and the given method is not part of the /public/ prefix - # so let's check which one it is - is_public, method_name = _is_public_method(err) - if is_public: - action = method_name.rsplit("/", maxsplit=1)[-1] - return AuthenticationError( - f"{action} is only available for authenticated users. Please provide a valid API Key " - f"token in your Client. Create a free account at https://console.tilebox.com" - ) - - if method_name != "": - return InvalidRequestError( - f"Requested URL {method_name} not found. Update your Tilebox Python Clients to the latest version." - ) - - # if we don't know anything else about the error, just forward the details directly - return InvalidRequestError(err.details()) - - # for all other errors we raise a generic internal server error - return InternalServerError(f"Oops, something went wrong: {err.details()}") - - -def translate_connect_error(err: Any, method_name: str = "") -> BaseException: # noqa: PLR0911, PLR0912, C901 - # translate specific connect error codes to more pythonic errors - from connectrpc.code import Code # noqa: PLC0415 - - match err.code: - case Code.NOT_FOUND: - return NotFoundError(err.message) - case Code.INVALID_ARGUMENT: - return ArgumentError(err.message) - case Code.UNAUTHENTICATED: - return AuthenticationError(f"Unauthenticated: {err.message}") - case Code.PERMISSION_DENIED: - return AuthenticationError(f"Unauthorized: {err.message}") - case Code.RESOURCE_EXHAUSTED: - is_public = _is_public_method_name(method_name) - if _is_too_many_requests_message(err.message): - if is_public: - return TooManyRequestsError( - "Too many requests, try again later. To increase your request quota create a free account at " - "https://console.tilebox.com and authenticate with an API Key." - ) - return TooManyRequestsError("Too many requests, try again later.") - return SubscriptionLimitExceededError(err.message) - case Code.DEADLINE_EXCEEDED: - return NetworkTimeoutError(f"Request timed out: {err.message}") - case Code.UNAVAILABLE: - if _is_too_many_requests_message(err.message): - if _is_public_method_name(method_name): - return TooManyRequestsError( - "Too many requests, try again later. To increase your request quota create a free account at " - "https://console.tilebox.com and authenticate with an API Key." - ) - return TooManyRequestsError("Too many requests, try again later.") - - if method_name != "": - return NetworkError(f"network error when requesting {method_name}: {err.message}") - return NetworkError(err.message) - case Code.ABORTED: - return NetworkError(f"Request aborted: {err.message}") - case Code.UNKNOWN: - return InternalServerError(f"Oops, something went wrong: {err.message}") - case Code.INTERNAL: - return InternalServerError(f"Oops, something went wrong: {err.message}") - case Code.CANCELED: - return KeyboardInterrupt(f"Request canceled by user: {err.message}") - case Code.UNIMPLEMENTED: - is_public = _is_public_method_name(method_name) - if is_public: - action = method_name.rsplit("/", maxsplit=1)[-1] - return AuthenticationError( - f"{action} is only available for authenticated users. Please provide a valid API Key " - f"token in your Client. Create a free account at https://console.tilebox.com" - ) - - if method_name != "": - return InvalidRequestError( - f"Requested URL {method_name} not found. Update your Tilebox Python Clients to the latest version." - ) - - return InvalidRequestError(err.message) - - return InternalServerError(f"Oops, something went wrong: {err.message}") - - -def _is_public_method(err: AnyRpcError) -> tuple[bool, str]: - """ - _is_public_method checks whether the error was the result of calling an RPC with the /public prefix - - Args: - err: A received RPC error - - Returns: - tuple[bool, str]: Whether the error originated from an RPC to a public endpoint, and if available - the method path that was called. - """ - if len(err.args) >= 1: - method = err.args[0].method - if method.startswith(_PUBLIC_RPC_METHOD_PREFIX): - return True, method - return False, method - - return False, "" - - -def _is_public_method_name(method_name: str) -> bool: - return method_name.startswith(_PUBLIC_RPC_METHOD_PREFIX) - - -def _is_too_many_requests_message(message: str) -> bool: - normalized_message = message.lower() - return "status: 429" in normalized_message or "too many requests" in normalized_message or "429" in message - - -def wrap_connect_rpc(rpc: Callable[[Any], Any], method_name: str = "") -> Callable[[Any], Any]: - def call(*args: Any, **kwargs: Any) -> Any: - from connectrpc.errors import ConnectError # noqa: PLC0415 - - try: - return rpc(*args, **kwargs) - except ConnectError as err: - error = translate_connect_error(err, method_name) - - # raise the appropriate exception for the error code we received - raise error from None - - return call - - -def async_wrap_connect_rpc( - rpc: Callable[[Any], Awaitable[Any]], method_name: str = "" -) -> Callable[[Any], Coroutine[Any, Any, Any]]: - async def call(*args: Any, **kwargs: Any) -> Any: - from connectrpc.errors import ConnectError # noqa: PLC0415 - - try: - return await rpc(*args, **kwargs) - except ConnectError as err: - error = translate_connect_error(err, method_name) - - # raise the appropriate exception for the error code we received - raise error from None - - return call - - -def _wrap_rpc(rpc: Callable[[Any], Any]) -> Callable[[Any], Any]: - def call(*args: Any, **kwargs: Any) -> Any: - try: - return rpc(*args, **kwargs) - except (RpcError, AioRpcError) as err: - error = translate_rpc_error(cast(AnyRpcError, err)) - - # raise the appropriate exception for the error code we received - raise error from None - - return call - - -def _async_wrap_rpc(rpc: Callable[[Any], Coroutine[Any, Any, Any]]) -> Callable[[Any], Coroutine[Any, Any, Any]]: - async def call(*args: Any, **kwargs: Any) -> Any: - try: - return await rpc(*args, **kwargs) - except (RpcError, AioRpcError) as err: - error = translate_rpc_error(cast(AnyRpcError, err)) - - # raise the appropriate exception for the error code we received - raise error from None - - return call diff --git a/tilebox-grpc/_tilebox/grpc/producer_consumer.py b/tilebox-grpc/_tilebox/grpc/producer_consumer.py deleted file mode 100644 index 7e76d18..0000000 --- a/tilebox-grpc/_tilebox/grpc/producer_consumer.py +++ /dev/null @@ -1,56 +0,0 @@ -from collections.abc import Callable, Iterator -from queue import Queue -from threading import Thread -from typing import Any, TypeVar - -T = TypeVar("T") - - -def concurrent_producer_consumer( - producer: Iterator[T], - consumer: Callable[[T], None] | Callable[[T], Any], - buffer_size: int = 10, -) -> None: - """ - Consume messages from a producer (e.g. a service endpoint) as they come in. This ensures that the consumer - is already computing each produced message while the next message is being produced. - - If the consumer is actually slower than the producer producing new message, the messages are buffered up to the - given buffer size. If the buffer is full, the producer will block until the consumer has consumed a message. - - Args: - producer: The generator to consume from - consumer: The consumer function to call for each message - buffer_size: The maximum number of messages to buffer before blocking the producer - """ - - queue = Queue(maxsize=buffer_size) - - # offload consuming to a separate thread, so that we can continue producing data on the main thread - consumer_thread = Thread(target=_consumer, args=(consumer, queue)) - consumer_thread.start() - # do this on the main thread - the producer may have callbacks (e.g. progress bar updates) and those should - # happen on the main thread - otherwise issues may arise with libraries like streamlit - _producer(producer, queue) - consumer_thread.join() - - -def _producer(producer: Iterator[T], queue: Queue[T | None]) -> None: - """ - Forward all messages from a producer (async iterator) and send them to a buffered send stream. - """ - for message in producer: - queue.put(message, block=True) - - queue.put(None, block=True) # indicate that we are done - - -def _consumer(consume: Callable[[T], None] | Callable[[T], Any], queue: Queue[T | None]) -> None: - """ - Receive messages from a buffered receive stream and forward them to a consumer until no more messages are available. - """ - while True: - message = queue.get(block=True) - if message is None: - break - consume(message) diff --git a/tilebox-grpc/_tilebox/grpc/public.py b/tilebox-grpc/_tilebox/grpc/public.py deleted file mode 100644 index ea01616..0000000 --- a/tilebox-grpc/_tilebox/grpc/public.py +++ /dev/null @@ -1 +0,0 @@ -_PUBLIC_RPC_METHOD_PREFIX = "/public" diff --git a/tilebox-grpc/_tilebox/grpc/replay.py b/tilebox-grpc/_tilebox/grpc/replay.py deleted file mode 100644 index 149306e..0000000 --- a/tilebox-grpc/_tilebox/grpc/replay.py +++ /dev/null @@ -1,173 +0,0 @@ -import base64 -from collections.abc import Callable -from functools import partial -from pathlib import Path -from typing import Any, TypeVar, cast - -from google.protobuf.message import Message - -from _tilebox.grpc.channel import ( - _AuthMetadataInterceptor, - _ClientMetadataInterceptor, - _open_channel, - parse_channel_info, -) -from _tilebox.grpc.error import AnyRpcError -from grpc import ( - Channel, - Future, - RpcError, - StatusCode, - UnaryUnaryClientInterceptor, - intercept_channel, -) -from grpc.aio import ( - AioRpcError, - ClientCallDetails, # import from aio, since grpc.ClientCallDetails is an empty base class - Metadata, -) - -RequestType = TypeVar("RequestType") -ResponseType = TypeVar("ResponseType") - - -def open_recording_channel(url: str, auth_token: str | None, recording: str | Path) -> Channel: - """Open a gRPC channel to the given URL and record all requests and responses to a file.""" - channel_info = parse_channel_info(url) - interceptors: list[UnaryUnaryClientInterceptor] = [_ClientMetadataInterceptor(), _RecordRPCsInterceptor(recording)] - if auth_token is not None: - interceptors = [_AuthMetadataInterceptor(auth_token), *interceptors] # add auth interceptor as the first one - - return intercept_channel(_open_channel(channel_info), *interceptors) - - -def open_replay_channel(recording: str | Path, assert_request_matches: bool = True) -> Channel: - return _ReplayChannel(recording, assert_request_matches) # ty: ignore[invalid-return-type] # not a subclass, but same interface so works - - -class _ConcreteValue(Future): - def __init__(self, result: Message) -> None: - self._result = result - - def cancel(self) -> bool: - return False - - def cancelled(self) -> bool: - return False - - def running(self) -> bool: - return False - - def done(self) -> bool: - return True - - def result(self, timeout: float | None = None) -> Message: - _ = timeout - return self._result - - def exception(self, timeout: float | None = None) -> BaseException | None: - _ = timeout - return None - - def traceback(self, timeout: float | None = None) -> None: - _ = timeout - - def add_done_callback(self, fn: Callable[[Message], None]) -> None: - fn(self._result) - - -class _RecordRPCsInterceptor(UnaryUnaryClientInterceptor): - def __init__(self, recording: str | Path) -> None: - self.recording = Path(recording) - self.recording.parent.mkdir(parents=True, exist_ok=True) - if self.recording.exists(): - self.recording.unlink() - - def intercept_unary_unary( - self, - continuation: Callable[[ClientCallDetails, RequestType], Future], - client_call_details: ClientCallDetails, - request: RequestType, - ) -> Future: - request_data = base64.b64encode(request.SerializeToString()) # ty: ignore[unresolved-attribute] - with self.recording.open("ab") as file: - method = client_call_details.method - if isinstance(method, str): - method = method.encode() - file.write(method) - file.write(b"\n") - file.write(request_data) - file.write(b"\n") - - try: - outcome = continuation(client_call_details, request) - result: Message = outcome.result() - response_data = base64.b64encode(result.SerializeToString()) - - with self.recording.open("ab") as file: - file.write(str(StatusCode.OK.value[0]).encode()) - file.write(b"\n") - file.write(response_data) - file.write(b"\n") - - except (RpcError, AioRpcError) as err: - err = cast(AnyRpcError, err) - with self.recording.open("ab") as file: - file.write(str(err.code().value[0]).encode()) - file.write(b"\n") - error_message = err.details() - file.write(error_message.encode() if error_message is not None else b"") - file.write(b"\n") - - raise # re-raise the error - - return outcome - - -class _ReplayChannel: - def __init__(self, recording: str | Path, assert_request_matches: bool = True) -> None: - self.recording = Path(recording).read_bytes().split(b"\n") - self.assert_request_matches = assert_request_matches - - def unary_unary( - self, - method: str, - request_serializer: Callable[[Message], bytes], - response_deserializer: Callable[[bytes], Message], - **kwargs: dict[str, Any], # noqa: ARG002 - ) -> Callable[[Message], Message]: - return partial(self.unary_unary_call, method, request_serializer, response_deserializer) - - def unary_unary_call( - self, - method: str, - request_serializer: Callable[[Message], bytes], - response_deserializer: Callable[[bytes], Message], - request: Message, - ) -> Message: - if len(self.recording) < 4: - raise ValueError(f"Replayed call to {method} was never recorded!") - recorded_method = self.recording.pop(0).decode() - recorded_request = self.recording.pop(0) - recorded_status = int(self.recording.pop(0).decode()) - recorded_response = self.recording.pop(0) - - if recorded_method != method: - raise ValueError(f"Expected method {method}, but got {recorded_method}") - - if self.assert_request_matches: - actual_request = request_serializer(request) - if actual_request != base64.b64decode(recorded_request): - raise ValueError( - f"Expected request for RPC call {recorded_method} differs from actual received request" - ) - - if recorded_status != StatusCode.OK.value[0]: # the recorded call was an error, so raise it again - code = _STATUS_CODES[recorded_status] - error = AioRpcError(code, Metadata(), Metadata(), recorded_response.decode()) - raise error - - return response_deserializer(base64.b64decode(recorded_response)) - - -_STATUS_CODES = {code.value[0]: code for code in StatusCode} diff --git a/tilebox-grpc/pyproject.toml b/tilebox-grpc/pyproject.toml index c26142b..cb3cf5c 100644 --- a/tilebox-grpc/pyproject.toml +++ b/tilebox-grpc/pyproject.toml @@ -1,56 +1,22 @@ [project] name = "tilebox-grpc" dynamic = ["version"] -description = "GRPC / Protocol Buffers functions for Tilebox" +description = "Compatibility package for tilebox-core" authors = [{ name = "Tilebox, Inc.", email = "support@tilebox.com" }] readme = "README.md" license = { text = "MIT License" } -classifiers = [ - "Intended Audience :: Science/Research", - "Intended Audience :: Developers", - "License :: OSI Approved :: MIT License", - "Operating System :: OS Independent", - "Programming Language :: Python", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.11", - "Programming Language :: Python :: 3.12", - "Programming Language :: Python :: 3.13", - "Programming Language :: Python :: 3.14", - "Topic :: Scientific/Engineering", - "Topic :: Software Development", -] requires-python = ">=3.11" -dependencies = [ - # Includes fixes for https://github.com/grpc/grpc/security/advisories/GHSA-hf3w-6hpw-qp67. - "grpcio>=1.84.0", - # connectrpc 0.11 switches from Google's protobuf runtime to protobuf-py - "connectrpc>=0.10.1,<0.11.0", - "pyqwest>=0.7.0", - "protobuf>=6.31.0", # FieldDescriptor.is_repeated - # for the libraries below we specify a minimum, tested to be working version - "lz4>=4", - "anyio>=4", -] - -[dependency-groups] -dev = ["pytest-asyncio>=0.24.0", "pytest-cov>=5.0.0", "pytest>=8.3.2"] +dependencies = ["tilebox-core>=0.67.0"] [project.urls] Homepage = "https://tilebox.com" Documentation = "https://docs.tilebox.com/" - [tool.hatch.build.targets.sdist] -packages = ["_tilebox"] +include = ["pyproject.toml", "README.md"] [tool.hatch.build.targets.wheel] -packages = ["_tilebox"] - -[tool.pytest.ini_options] -minversion = "6.0" -testpaths = ["tests"] -addopts = "-Wall -Werror --import-mode importlib" -asyncio_default_fixture_loop_scope = "function" +bypass-selection = true [build-system] requires = ["hatchling", "uv-dynamic-versioning"] diff --git a/tilebox-grpc/tests/__init__.py b/tilebox-grpc/tests/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/tilebox-grpc/tests/aio/test_channel.py b/tilebox-grpc/tests/aio/test_channel.py deleted file mode 100644 index 89b9dce..0000000 --- a/tilebox-grpc/tests/aio/test_channel.py +++ /dev/null @@ -1,61 +0,0 @@ -from unittest.mock import AsyncMock - -import pytest - -from _tilebox.grpc.aio.channel import ( - ClientCallDetails, - _AuthMetadataInterceptor, - _ClientMetadataInterceptor, - _RpcMethodPrefixInterceptor, -) -from _tilebox.grpc.client_metadata import CLIENT_HEADER - - -@pytest.mark.asyncio -@pytest.mark.parametrize("req_metadata", [None, [("some-other", "header")]]) -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") - - mock_method = AsyncMock() - - await interceptor.intercept_unary_unary( - mock_method, ClientCallDetails("/some-rpc-method", 10, req_metadata, None, True), AsyncMock() - ) - - mock_method.assert_called_once() - updated_call_details = mock_method.call_args[0][0] - assert ("authorization", "Bearer very-secret") in updated_call_details.metadata - - -@pytest.mark.asyncio -async def test_client_metadata_interceptor() -> None: - interceptor = _ClientMetadataInterceptor() - mock_method = AsyncMock() - - await interceptor.intercept_unary_unary( - mock_method, - ClientCallDetails("/some-rpc-method", 10, [("authorization", "Bearer token")], None, True), - AsyncMock(), - ) - - metadata = mock_method.call_args[0][0].metadata - assert ("authorization", "Bearer token") in metadata - assert len([key for key, _ in metadata if key == CLIENT_HEADER.lower()]) == 1 - assert any(key == CLIENT_HEADER.lower() and 'name="python"' in value for key, value in metadata) - - -@pytest.mark.asyncio -async def test_rpc_method_prefix_interceptor() -> None: - interceptor = _RpcMethodPrefixInterceptor("/public") - mock_method = AsyncMock() - - await interceptor.intercept_unary_unary( - mock_method, - ClientCallDetails("/datasets.v1.DatasetService/ListDatasets", 10, None, None, True), - AsyncMock(), - ) - - mock_method.assert_called_once() - updated_call_details = mock_method.call_args[0][0] - assert updated_call_details.method == "/public/datasets.v1.DatasetService/ListDatasets" diff --git a/tilebox-grpc/tests/aio/test_error.py b/tilebox-grpc/tests/aio/test_error.py deleted file mode 100644 index f1a7423..0000000 --- a/tilebox-grpc/tests/aio/test_error.py +++ /dev/null @@ -1,38 +0,0 @@ -from typing import TypeVar - -import pytest -from grpc import StatusCode -from grpc.aio import AioRpcError -from grpc.aio._metadata import Metadata - -from _tilebox.grpc.aio.error import with_pythonic_errors -from _tilebox.grpc.error import ArgumentError, AuthenticationError, InternalServerError, NotFoundError - -E = TypeVar("E", bound=BaseException) - - -@pytest.mark.asyncio -@pytest.mark.parametrize( - ("grpc_status", "exception_type"), - [ - (StatusCode.UNAUTHENTICATED, AuthenticationError), - (StatusCode.PERMISSION_DENIED, AuthenticationError), - (StatusCode.NOT_FOUND, NotFoundError), - (StatusCode.INVALID_ARGUMENT, ArgumentError), - (StatusCode.INTERNAL, InternalServerError), - ], -) -async def test_with_pythonic_errors(grpc_status: StatusCode, exception_type: type[E]) -> None: - # we use the aio error here since it's easier to mock, and it doesn't matter for the test - grpc_error = AioRpcError(grpc_status, Metadata(), Metadata()) - - class Stub: - def __init__(self) -> None: - async def _mock_rpc() -> None: - raise grpc_error - - self.some_rpc = _mock_rpc - - stub = with_pythonic_errors(Stub()) - with pytest.raises(exception_type, match=r".*"): - await stub.some_rpc() diff --git a/tilebox-grpc/tests/aio/test_producer_consumer.py b/tilebox-grpc/tests/aio/test_producer_consumer.py deleted file mode 100644 index ebafc55..0000000 --- a/tilebox-grpc/tests/aio/test_producer_consumer.py +++ /dev/null @@ -1,73 +0,0 @@ -import time -from collections.abc import AsyncIterator, Callable - -import anyio -import pytest - -from _tilebox.grpc.aio.producer_consumer import async_producer_consumer - - -async def _mock_producer(n: int, sleep: float) -> AsyncIterator[int]: - for i in range(n): - await anyio.sleep(sleep) - yield i - - -def _create_cpu_bound_consumer(processing_time: float, output_stack: list[int]) -> Callable[[int], None]: - def _mock_consumer(item: int) -> None: - time.sleep(processing_time) - output_stack.append(item) - - return _mock_consumer - - -@pytest.mark.asyncio -async def test_producer_consumer_slow_producer_fast_consumer() -> None: - """ - Assert that the producer consumer pattern behaves as expected with a slow producer and a fast consumer. - This means we make sure that each message is consumed as soon as it is produced, while we already wait for - the next produced message. - - So the total time should be (producer_sleep * n + consumer_sleep) and not ((producer_sleep + consumer_sleep) * n) - """ - n, producer_sleep, consumer_sleep = 10, 0.1, 0.05 - took = await _run_consumer_producer_example(n, producer_sleep, consumer_sleep) - assert (producer_sleep * n + consumer_sleep) < took < ((producer_sleep + consumer_sleep) * n) - - -@pytest.mark.asyncio -async def test_producer_consumer_equally_slow_producer_consumer() -> None: - """ - Assert that the producer consumer pattern behaves as expected with a producer and a consumer that are equally slow. - This means we make sure that each message is consumed as soon as it is produced, while we already wait for - the next produced message. - - So the total time should be (producer_sleep * n + consumer_sleep) and not ((producer_sleep + consumer_sleep) * n) - """ - n, producer_sleep, consumer_sleep = 10, 0.1, 0.05 - took = await _run_consumer_producer_example(n, producer_sleep, consumer_sleep) - assert (producer_sleep * n + consumer_sleep) < took < ((producer_sleep + consumer_sleep) * n) - - -@pytest.mark.asyncio -async def test_producer_consumer_fast_producer_slow_consumer() -> None: - """ - Assert that the producer consumer pattern behaves as expected with a consumer that is slower than the producer. - This means the messages are buffered in a message queue. - - So the total time should be (producer_sleep + consumer_sleep * n) and not ((producer_sleep + consumer_sleep) * n) - """ - n, producer_sleep, consumer_sleep = 10, 0.1, 0.05 - took = await _run_consumer_producer_example(n, producer_sleep, consumer_sleep) - assert (producer_sleep + consumer_sleep * n) < took < ((producer_sleep + consumer_sleep) * n) - - -async def _run_consumer_producer_example(n: int, producer_sleep: float, consumer_sleep: float) -> float: - # helper function to simulate a producer consumer example - outputs: list[int] = [] - producer = _mock_producer(n, producer_sleep) - consumer = _create_cpu_bound_consumer(consumer_sleep, outputs) - before = time.time() - await async_producer_consumer(producer, consumer) - assert outputs == list(range(n)) - return time.time() - before diff --git a/tilebox-grpc/tests/conftest.py b/tilebox-grpc/tests/conftest.py deleted file mode 100644 index e69de29..0000000 diff --git a/tilebox-grpc/tests/test_channel.py b/tilebox-grpc/tests/test_channel.py deleted file mode 100644 index f9f01fc..0000000 --- a/tilebox-grpc/tests/test_channel.py +++ /dev/null @@ -1,210 +0,0 @@ -from unittest.mock import MagicMock, patch - -import pytest - -from _tilebox.grpc.channel import ( - CHANNEL_OPTIONS, - AsyncConnectStubAdapter, - ChannelProtocol, - ClientCallDetails, - ConnectStubAdapter, - _ClientMetadataInterceptor, - _RpcMethodPrefixInterceptor, - connect_address, - open_channel, - parse_channel_info, -) -from _tilebox.grpc.client_metadata import CLIENT_HEADER - - -@patch("_tilebox.grpc.channel.secure_channel") -def test_open_secure_channel(open_func: MagicMock) -> None: - open_channel("api.tilebox.com") - open_func.assert_called_once() - assert open_func.call_args[0][0] == "api.tilebox.com:443" - assert open_func.call_args[0][-1] == CHANNEL_OPTIONS - - -@patch("_tilebox.grpc.channel.insecure_channel") -def test_open_insecure_channel(open_func: MagicMock) -> None: - open_channel("0.0.0.0:8083") - open_func.assert_called_once() - assert open_func.call_args[0][0] == "0.0.0.0:8083" - assert open_func.call_args[0][-1] == CHANNEL_OPTIONS - - -@patch("_tilebox.grpc.channel.intercept_channel") -@patch("_tilebox.grpc.channel.secure_channel") -def test_open_authenticated_channel(open_func: MagicMock, intercept_func: MagicMock) -> None: - open_channel("api.tilebox.com", auth_token="very-secret") # noqa: S106 - open_func.assert_called_once() - intercept_func.assert_called_once() - - assert intercept_func.call_args[0][1]._auth == ("authorization", "Bearer very-secret") - - -@patch("_tilebox.grpc.channel.client_metadata", return_value={CLIENT_HEADER: 'name="python"'}) -def test_client_metadata_interceptor(client_metadata: MagicMock) -> None: - interceptor = _ClientMetadataInterceptor() - continuation = MagicMock() - - interceptor.intercept_unary_unary( - continuation, - ClientCallDetails("/some-rpc-method", 10, [("authorization", "Bearer token")], None, True), - MagicMock(), - ) - - client_metadata.assert_called_once_with() - metadata = continuation.call_args[0][0].metadata - assert ("authorization", "Bearer token") in metadata - assert (CLIENT_HEADER.lower(), 'name="python"') in metadata - - -class _ConnectClient: - def get_value(self, request: str, *, headers: dict[str, str]) -> tuple[str, dict[str, str]]: - return request, headers - - -class _AsyncConnectClient: - async def get_value(self, request: str, *, headers: dict[str, str]) -> tuple[str, dict[str, str]]: - return request, headers - - -@patch("_tilebox.grpc.channel.client_metadata", return_value={CLIENT_HEADER: 'name="python"'}) -def test_connect_stub_adapter_adds_client_metadata(client_metadata: MagicMock) -> None: - adapter = ConnectStubAdapter(_ConnectClient(), {"authorization": "Bearer token"}) - - request, headers = adapter.GetValue("request") # ty: ignore[unresolved-attribute] # added dynamically - - assert request == "request" - assert headers == { - "authorization": "Bearer token", - CLIENT_HEADER: 'name="python"', - } - client_metadata.assert_called_once_with() - - -@pytest.mark.asyncio -@patch("_tilebox.grpc.channel.client_metadata", return_value={CLIENT_HEADER: 'name="python"'}) -async def test_async_connect_stub_adapter_adds_client_metadata(client_metadata: MagicMock) -> None: - adapter = AsyncConnectStubAdapter(_AsyncConnectClient(), {"authorization": "Bearer token"}) - - request, headers = await adapter.GetValue("request") # ty: ignore[unresolved-attribute] # added dynamically - - assert request == "request" - assert headers == { - "authorization": "Bearer token", - CLIENT_HEADER: 'name="python"', - } - client_metadata.assert_called_once_with() - - -@patch("_tilebox.grpc.channel.intercept_channel") -@patch("_tilebox.grpc.channel.secure_channel") -def test_open_channel_with_rpc_method_prefix(open_func: MagicMock, intercept_func: MagicMock) -> None: - open_channel("api.tilebox.com", rpc_method_prefix="/public") - open_func.assert_called_once() - intercept_func.assert_called_once() - - assert intercept_func.call_args[0][2]._prefix == "/public" - - -def test_rpc_method_prefix_interceptor() -> None: - interceptor = _RpcMethodPrefixInterceptor("/public") - continuation = MagicMock() - - interceptor.intercept_unary_unary( - continuation, - ClientCallDetails("/datasets.v1.DatasetService/ListDatasets", 10, None, None, True), - MagicMock(), - ) - - continuation.assert_called_once() - updated_call_details = continuation.call_args[0][0] - assert updated_call_details.method == "/public/datasets.v1.DatasetService/ListDatasets" - - -@pytest.mark.parametrize( - "url", - [ - "https://api.tilebox.com", - "api.tilebox.com", - "https://api.tilebox.com:443", - "api.tilebox.com:443", - "https://api.tilebox.com/", - "api.tilebox.com/", - "https://api.tilebox.com:443/", - "api.tilebox.com:443/", - ], -) -def test_parse_channel_info_secure(url: str) -> None: - channel_info = parse_channel_info(url) - assert channel_info.address == "api.tilebox.com" - assert channel_info.port == 443 - assert channel_info.protocol == ChannelProtocol.HTTPS - - -@pytest.mark.parametrize( - ("url", "expected_url_without_protocol"), - [ - ("0.0.0.0:8083", "0.0.0.0"), # noqa: S104 - ("http://0.0.0.0:8083", "0.0.0.0"), # noqa: S104 - ("http://localhost:8083", "localhost"), - ("localhost:8083", "localhost"), - ("http://some.insecure.url:8083", "some.insecure.url"), - ], -) -def test_parse_channel_info_insecure(url: str, expected_url_without_protocol: str) -> None: - channel_info = parse_channel_info(url) - assert channel_info.address == expected_url_without_protocol - assert channel_info.port == 8083 - assert channel_info.protocol == ChannelProtocol.HTTP - - -@pytest.mark.parametrize( - "url", - [ - "unix:path/to/s.sock", - "unix:///path/to/s.sock", - ], -) -def test_parse_channel_info_unix(url: str) -> None: - channel_info = parse_channel_info(url) - assert channel_info.address == url - assert channel_info.port == 0 - assert channel_info.protocol == ChannelProtocol.UNIX - - -def test_parse_channel_invalid() -> None: - with pytest.raises(ValueError, match=r"Invalid"): - parse_channel_info("i'm not a url") - - -def test_parse_channel_port_required_for_http() -> None: - with pytest.raises(ValueError, match=r"Explicit port required"): - parse_channel_info("http://0.0.0.0") - - -@pytest.mark.parametrize( - ("url", "expected_address"), - [ - ("https://api.tilebox.com", "https://api.tilebox.com"), - ("api.tilebox.com", "https://api.tilebox.com"), - ("https://api.tilebox.com:443", "https://api.tilebox.com"), - ("https://api.tilebox.com/", "https://api.tilebox.com"), - ("https://api.tilebox.com:8443", "https://api.tilebox.com:8443"), - ("localhost:8083", "http://localhost:8083"), - ("http://localhost:8083/", "http://localhost:8083"), - ], -) -def test_connect_address(url: str, expected_address: str) -> None: - assert connect_address(url) == expected_address - - -def test_connect_address_with_rpc_method_prefix() -> None: - assert connect_address("api.tilebox.com/", "/public/") == "https://api.tilebox.com/public" - - -def test_connect_address_unix_unsupported() -> None: - with pytest.raises(ValueError, match=r"does not support unix socket"): - connect_address("unix:path/to/s.sock") diff --git a/tilebox-grpc/tests/test_error.py b/tilebox-grpc/tests/test_error.py deleted file mode 100644 index 0c112d2..0000000 --- a/tilebox-grpc/tests/test_error.py +++ /dev/null @@ -1,57 +0,0 @@ -from typing import TypeVar - -import pytest -from grpc import StatusCode -from grpc.aio import AioRpcError -from grpc.aio._metadata import Metadata - -from _tilebox.grpc.error import ( - ArgumentError, - AuthenticationError, - InternalServerError, - NotFoundError, - TooManyRequestsError, - translate_connect_error, - with_pythonic_errors, -) - -E = TypeVar("E", bound=BaseException) - - -@pytest.mark.parametrize( - ("grpc_status", "exception_type"), - [ - (StatusCode.UNAUTHENTICATED, AuthenticationError), - (StatusCode.PERMISSION_DENIED, AuthenticationError), - (StatusCode.NOT_FOUND, NotFoundError), - (StatusCode.INVALID_ARGUMENT, ArgumentError), - (StatusCode.INTERNAL, InternalServerError), - ], -) -def test_with_pythonic_errors(grpc_status: StatusCode, exception_type: type[E]) -> None: - # we use the aio error here since it's easier to mock, and it doesn't matter for the test - grpc_error = AioRpcError(grpc_status, Metadata(), Metadata()) - - class Stub: - def __init__(self) -> None: - def _mock_rpc() -> None: - raise grpc_error - - self.some_rpc = _mock_rpc - - stub = with_pythonic_errors(Stub()) - with pytest.raises(exception_type, match=r".*"): - stub.some_rpc() - - -@pytest.mark.parametrize("message", ["status: 429", "HTTP 429", "Too Many Requests"]) -def test_translate_connect_unavailable_429(message: str) -> None: - from connectrpc.code import Code # noqa: PLC0415 - - class Error: - code = Code.UNAVAILABLE - - def __init__(self, message: str) -> None: - self.message = message - - assert isinstance(translate_connect_error(Error(message)), TooManyRequestsError) diff --git a/tilebox-grpc/tests/test_fork_logging.py b/tilebox-grpc/tests/test_fork_logging.py deleted file mode 100644 index 0835eb6..0000000 --- a/tilebox-grpc/tests/test_fork_logging.py +++ /dev/null @@ -1,81 +0,0 @@ -"""Check native gRPC diagnostics when a CLI starts during active RPCs. - -Run on macOS to check RPCs and report the known fork-log issue: - -uv run --isolated --no-project --with-editable ./tilebox-grpc --with grpcio==1.84.0 --with pytest pytest -c /dev/null -p no:cacheprovider -rx tilebox-grpc/tests/test_fork_logging.py - -RPC failures or diagnostics with Tilebox's default verbosity fail the test. -With explicit INFO verbosity, known fork diagnostics produce XFAIL after the RPC checks pass. -Linux may use vfork and not reproduce the diagnostics. This check uses no cloud credentials. -""" - -import os -import subprocess -import sys -from textwrap import dedent - -import pytest - - -# RPCs must survive CLI subprocess launches; known fork-log noise is reported separately. -@pytest.mark.skipif(sys.platform == "win32", reason="Windows does not use POSIX fork handlers") -@pytest.mark.parametrize("grpc_verbosity", [None, "INFO"]) -def test_cli_spawn_during_rpcs(grpc_verbosity: str | None) -> None: - env = {key: value for key, value in os.environ.items() if key != "GRPC_VERBOSITY"} - if grpc_verbosity is not None: - env["GRPC_VERBOSITY"] = grpc_verbosity - result = subprocess.run( # noqa: S603 - [ - sys.executable, - "-c", - dedent(""" - import subprocess - import sys - from concurrent.futures import ThreadPoolExecutor - from threading import Event - - import _tilebox.grpc - import grpc - - with ThreadPoolExecutor(max_workers=4) as executor: - server = grpc.server(executor) - server.add_generic_rpc_handlers((grpc.method_handlers_generic_handler( - "probe", {"echo": grpc.unary_unary_rpc_method_handler(lambda request, context: request)} - ),)) - port = server.add_insecure_port("127.0.0.1:0") - server.start() - stop = Event() - try: - with grpc.insecure_channel(f"127.0.0.1:{port}") as channel: - echo = channel.unary_unary("/probe/echo") - assert echo(b"before", timeout=5) == b"before" - - def poll(): - while not stop.is_set(): - assert echo(b"during", timeout=5) == b"during" - - poller = executor.submit(poll) - try: - for _ in range(25): - # Azure CLI authentication supplies cwd, which prevents posix_spawn. - subprocess.run([sys.executable, "-c", "pass"], cwd=".", check=True, timeout=5) - finally: - stop.set() - poller.result(timeout=10) - assert echo(b"after", timeout=5) == b"after" - finally: - stop.set() - server.stop(0).wait(10) - """), - ], - capture_output=True, - text=True, - timeout=60, - env=env, - check=False, - ) - assert result.returncode == 0, result.stderr - if grpc_verbosity is None: - assert "FD from fork parent still in poll list" not in result.stderr, result.stderr - if "FD from fork parent still in poll list" in result.stderr: - pytest.xfail("Known gRPC fork diagnostics: https://github.com/grpc/grpc/issues/42293") diff --git a/tilebox-storage/pyproject.toml b/tilebox-storage/pyproject.toml index 1ef1066..2be889d 100644 --- a/tilebox-storage/pyproject.toml +++ b/tilebox-storage/pyproject.toml @@ -21,7 +21,8 @@ classifiers = [ ] requires-python = ">=3.11" dependencies = [ - "tilebox-datasets>=0.56.0", + "tilebox-core>=0.67.0", + "tilebox-datasets>=0.67.0", "niquests>=3.18", "tenacity>=8", "aiofile>=3.8", diff --git a/tilebox-storage/tests/test_asset_client.py b/tilebox-storage/tests/test_asset_client.py index 6d8cd4b..60fc35f 100644 --- a/tilebox-storage/tests/test_asset_client.py +++ b/tilebox-storage/tests/test_asset_client.py @@ -20,13 +20,13 @@ from tilebox.datasets.assets import AssetCollection from tilebox.datasets.assets.assets import Asset, AssetLocation -from tilebox.datasets.datasets.stac.v1.asset_pb import Assets -from tilebox.datasets.datasets.stac.v1.authentication_pb import ( +from tilebox.datasets.stac.v1.asset_pb import Assets +from tilebox.datasets.stac.v1.authentication_pb import ( Authentication, AuthenticationScheme, KnownAuthenticationType, ) -from tilebox.datasets.datasets.stac.v1.storage_pb import KnownStorageType, Storage, StorageScheme +from tilebox.datasets.stac.v1.storage_pb import KnownStorageType, Storage, StorageScheme from tilebox.storage.aio import AssetAccessPolicy, Client if TYPE_CHECKING: diff --git a/tilebox-storage/tests/test_planetary_computer.py b/tilebox-storage/tests/test_planetary_computer.py index a8efd5f..84294c6 100644 --- a/tilebox-storage/tests/test_planetary_computer.py +++ b/tilebox-storage/tests/test_planetary_computer.py @@ -18,14 +18,14 @@ from protobuf.wkt import Struct, Value from tilebox.datasets.assets.assets import Asset, AssetLocation -from tilebox.datasets.datasets.stac.v1.authentication_pb import ( +from tilebox.datasets.stac.v1.authentication_pb import ( AuthenticationFlow, AuthenticationParameter, AuthenticationScheme, KnownAuthenticationType, SignedURLFlow, ) -from tilebox.datasets.datasets.stac.v1.storage_pb import KnownStorageType, StorageScheme +from tilebox.datasets.stac.v1.storage_pb import KnownStorageType, StorageScheme from tilebox.storage.aio import Client diff --git a/tilebox-storage/tilebox/storage/_authentication.py b/tilebox-storage/tilebox/storage/_authentication.py index aa170f9..9736391 100644 --- a/tilebox-storage/tilebox/storage/_authentication.py +++ b/tilebox-storage/tilebox/storage/_authentication.py @@ -12,7 +12,7 @@ from obstore.store import S3Credential, S3CredentialProvider - from tilebox.datasets.datasets.stac.v1.authentication_pb import AuthenticationScheme + from tilebox.datasets.stac.v1.authentication_pb import AuthenticationScheme def register_authentication(registry: Registry, s3_credentials: Mapping[str, "S3CredentialProvider"]) -> None: diff --git a/tilebox-storage/tilebox/storage/_backends.py b/tilebox-storage/tilebox/storage/_backends.py index 841566e..76ca7ef 100644 --- a/tilebox-storage/tilebox/storage/_backends.py +++ b/tilebox-storage/tilebox/storage/_backends.py @@ -11,7 +11,7 @@ if TYPE_CHECKING: from obstore.store import ObjectStore - from tilebox.datasets.datasets.stac.v1.storage_pb import StorageScheme + from tilebox.datasets.stac.v1.storage_pb import StorageScheme def register_backends(registry: Registry) -> None: diff --git a/tilebox-storage/tilebox/storage/_planetary_computer.py b/tilebox-storage/tilebox/storage/_planetary_computer.py index b0f7b7f..b4f0ca9 100644 --- a/tilebox-storage/tilebox/storage/_planetary_computer.py +++ b/tilebox-storage/tilebox/storage/_planetary_computer.py @@ -13,7 +13,7 @@ from niquests import AsyncResponse, AsyncSession, Response from obstore.store import AzureSASToken - from tilebox.datasets.datasets.stac.v1.authentication_pb import SignedURLFlow + from tilebox.datasets.stac.v1.authentication_pb import SignedURLFlow def bind_credentials(spec: StoreSpec, path: str, signed: "SignedURLFlow") -> tuple[StoreSpec, str]: diff --git a/tilebox-storage/tilebox/storage/_registry.py b/tilebox-storage/tilebox/storage/_registry.py index 1b62a5d..041c488 100644 --- a/tilebox-storage/tilebox/storage/_registry.py +++ b/tilebox-storage/tilebox/storage/_registry.py @@ -13,8 +13,8 @@ from obstore.store import ObjectStore, S3CredentialProvider from tilebox.datasets.assets.assets import AssetLocation - from tilebox.datasets.datasets.stac.v1.authentication_pb import AuthenticationScheme, SignedURLFlow - from tilebox.datasets.datasets.stac.v1.storage_pb import StorageScheme + from tilebox.datasets.stac.v1.authentication_pb import AuthenticationScheme, SignedURLFlow + from tilebox.datasets.stac.v1.storage_pb import StorageScheme @dataclass(frozen=True, slots=True) diff --git a/tilebox-storage/tilebox/storage/aio.py b/tilebox-storage/tilebox/storage/aio.py index 0c87593..1d707e3 100644 --- a/tilebox-storage/tilebox/storage/aio.py +++ b/tilebox-storage/tilebox/storage/aio.py @@ -16,7 +16,7 @@ import anyio from aiofile import async_open -from _tilebox.grpc.aio.producer_consumer import async_producer_consumer +from _tilebox.aio.concurrency import async_producer_consumer from tilebox.storage.client import ( AssetAccessPolicy, Client, diff --git a/tilebox-storage/tilebox/storage/client.py b/tilebox-storage/tilebox/storage/client.py index 0cf2bda..b4f0dbe 100644 --- a/tilebox-storage/tilebox/storage/client.py +++ b/tilebox-storage/tilebox/storage/client.py @@ -17,8 +17,8 @@ from obstore.store import ObjectStore, S3CredentialProvider from tilebox.datasets.assets.assets import Asset, AssetLocation - from tilebox.datasets.datasets.stac.v1.authentication_pb import AuthenticationScheme - from tilebox.datasets.datasets.stac.v1.storage_pb import StorageScheme + from tilebox.datasets.stac.v1.authentication_pb import AuthenticationScheme + from tilebox.datasets.stac.v1.storage_pb import StorageScheme @dataclass(frozen=True, slots=True) diff --git a/tilebox-workflows/pyproject.toml b/tilebox-workflows/pyproject.toml index 93e167f..7db025a 100644 --- a/tilebox-workflows/pyproject.toml +++ b/tilebox-workflows/pyproject.toml @@ -21,10 +21,8 @@ classifiers = [ ] requires-python = ">=3.11" dependencies = [ - "tilebox-datasets>=0.56.0", - "tilebox-grpc>=0.28.0", - # Require the security fixes even with an older tilebox-grpc release. - "grpcio>=1.84.0", + "tilebox-core>=0.67.0", + "uvicorn>=0.35", "google-auth[requests]>=2.29", "azure-identity>=1.23", "opentelemetry-api>=1.44.0", @@ -49,9 +47,9 @@ geospatial = [ [dependency-groups] dev = [ + "tilebox-datasets>=0.67.0", # cross-SDK serialization and compatibility tests "boto3-stubs[s3]>=1.40.2", "google-cloud-storage>=3.2.0", - "grpcio-status>=1.78.0", "hypothesis>=6.112.1", "ipywidgets>=8.1.7", "odc-geo>=0.5", @@ -72,7 +70,7 @@ packages = ["tilebox"] packages = ["tilebox"] [tool.coverage.run] -omit = ["tilebox/workflows/workflows/v1/*"] +omit = ["tilebox/workflows/v1/*", "tilebox/workflows/_otel/opentelemetry/*"] [tool.pytest.ini_options] minversion = "6.0" diff --git a/tilebox-workflows/tests/automations/test_client.py b/tilebox-workflows/tests/automations/test_client.py index f77cada..772ae14 100644 --- a/tilebox-workflows/tests/automations/test_client.py +++ b/tilebox-workflows/tests/automations/test_client.py @@ -1,16 +1,16 @@ from unittest.mock import MagicMock from uuid import UUID, uuid4 -from google.protobuf.empty_pb2 import Empty from hypothesis.stateful import Bundle, RuleBasedStateMachine, consumes, rule from hypothesis.strategies import lists +from protobuf.wkt.google.protobuf.empty_pb import Empty from tests.tasks_data import alphanumerical_text, cron_triggers, storage_event_triggers, task_identifiers -from _tilebox.grpc.error import NotFoundError -from tilebox.datasets.tilebox.v1.id_pb2 import ID +from _tilebox.errors import NotFoundError +from _tilebox.proto.tilebox.v1.id_pb import ID from tilebox.datasets.uuid import uuid_message_to_uuid from tilebox.workflows.automations import CronTask, StorageEventTask -from tilebox.workflows.automations.client import AutomationClient, AutomationService +from tilebox.workflows.automations.client import AutomationClient from tilebox.workflows.data import ( AutomationPrototype, CronTrigger, @@ -18,20 +18,20 @@ TaskIdentifier, uuid_to_uuid_message, ) -from tilebox.workflows.workflows.v1.automation_pb2 import AutomationPrototype as AutomationPrototypeMessage -from tilebox.workflows.workflows.v1.automation_pb2 import Automations, DeleteAutomationRequest -from tilebox.workflows.workflows.v1.automation_pb2 import CronTrigger as CronTriggerMessage -from tilebox.workflows.workflows.v1.automation_pb2 import StorageEventTrigger as StorageEventTriggerMessage -from tilebox.workflows.workflows.v1.automation_pb2_grpc import AutomationServiceStub +from tilebox.workflows.v1.automation_connect import AutomationServiceClientSync +from tilebox.workflows.v1.automation_pb import AutomationPrototype as AutomationPrototypeMessage +from tilebox.workflows.v1.automation_pb import Automations, DeleteAutomationRequest +from tilebox.workflows.v1.automation_pb import CronTrigger as CronTriggerMessage +from tilebox.workflows.v1.automation_pb import StorageEventTrigger as StorageEventTriggerMessage -class MockAutomationService(AutomationServiceStub): - """A mock implementation of the gRPC automation service, that stores automations in memory as a dict.""" +class MockAutomationService: + """Store automations in memory for client behavior tests.""" def __init__(self) -> None: self.automations: dict[UUID, AutomationPrototypeMessage] = {} - def CreateAutomation(self, req: AutomationPrototypeMessage) -> AutomationPrototypeMessage: # noqa: N802 + def create_automation(self, req: AutomationPrototypeMessage) -> AutomationPrototypeMessage: automation_id = uuid4() created = AutomationPrototypeMessage( id=uuid_to_uuid_message(automation_id), # assign an auto generated id to the automation @@ -52,27 +52,27 @@ def CreateAutomation(self, req: AutomationPrototypeMessage) -> AutomationPrototy self.automations[automation_id] = created return created - def UpdateAutomation(self, req: AutomationPrototypeMessage) -> AutomationPrototypeMessage: # noqa: N802 + def update_automation(self, req: AutomationPrototypeMessage) -> AutomationPrototypeMessage: automation_id = uuid_message_to_uuid(req.id) if automation_id not in self.automations: raise NotFoundError(f"Automation {automation_id} not found") self.automations[automation_id] = req return req - def GetAutomation(self, req: ID) -> AutomationPrototypeMessage: # noqa: N802 + def get_automation(self, req: ID) -> AutomationPrototypeMessage: automation_id = uuid_message_to_uuid(req) if automation_id in self.automations: return self.automations[automation_id] raise NotFoundError(f"Automation {automation_id} not found") - def DeleteAutomation(self, req: DeleteAutomationRequest) -> None: # noqa: N802 + def delete_automation(self, req: DeleteAutomationRequest) -> None: automation_id = uuid_message_to_uuid(req.automation_id) if automation_id in self.automations: del self.automations[automation_id] else: raise NotFoundError(f"Automation {automation_id} not found") - def ListAutomations(self, req: Empty) -> Automations: # noqa: N802 + def list_automations(self, req: Empty) -> Automations: _ = req return Automations(automations=list(self.automations.values())) @@ -92,8 +92,7 @@ class AutomationCRUDOperations(RuleBasedStateMachine): def __init__(self) -> None: super().__init__() - service = AutomationService(MagicMock()) - service.service = MockAutomationService() # mock the gRPC service + service = MagicMock(spec=AutomationServiceClientSync, wraps=MockAutomationService()) self.client = AutomationClient(service) self.count_automations = 0 diff --git a/tilebox-workflows/tests/automations/test_cron.py b/tilebox-workflows/tests/automations/test_cron.py index a9a57c5..17575ff 100644 --- a/tilebox-workflows/tests/automations/test_cron.py +++ b/tilebox-workflows/tests/automations/test_cron.py @@ -2,7 +2,7 @@ from datetime import UTC, datetime import pytest -from tests.proto.test_pb2 import SampleArgs +from tests.proto.test_pb import SampleArgs from tilebox.workflows.automations import CronTask diff --git a/tilebox-workflows/tests/automations/test_storage_event.py b/tilebox-workflows/tests/automations/test_storage_event.py index fb83f89..14dc5ac 100644 --- a/tilebox-workflows/tests/automations/test_storage_event.py +++ b/tilebox-workflows/tests/automations/test_storage_event.py @@ -2,7 +2,7 @@ import pytest from hypothesis import given -from tests.proto.test_pb2 import SampleArgs +from tests.proto.test_pb import SampleArgs from tests.tasks_data import storage_locations from tilebox.workflows.automations import StorageEventTask diff --git a/tilebox-workflows/tests/automations/test_storage_read.py b/tilebox-workflows/tests/automations/test_storage_read.py index 8c2c63d..e2b9ce8 100644 --- a/tilebox-workflows/tests/automations/test_storage_read.py +++ b/tilebox-workflows/tests/automations/test_storage_read.py @@ -33,7 +33,7 @@ _default_s3_storage_client, ) from tilebox.workflows.observability.tracing import NoopWorkflowTracer -from tilebox.workflows.workflows.v1 import storage_location_pb2 as storage_pb +from tilebox.workflows.v1 import storage_location_pb as storage_pb _ID = UUID("01a0fd71-8262-2fde-7283-43dab469c23f") _ACCOUNT = "/subscriptions/test/resourceGroups/rg/providers/Microsoft.Storage/storageAccounts/sourceaccount" @@ -111,7 +111,7 @@ def _azure_message() -> storage_pb.StorageLocation: return storage_pb.StorageLocation( id=uuid_to_uuid_message(_ID), reference=storage_pb.StorageLocationReference( - type=storage_pb.STORAGE_TYPE_AZURE_BLOB, + type=storage_pb.StorageType.AZURE_BLOB, azure_blob=storage_pb.AzureBlobReference(storage_account_resource_id=_ACCOUNT, container="sourcecontainer"), ), ) @@ -222,7 +222,7 @@ def test_gcs_trigger_read() -> None: message = storage_pb.StorageLocation( id=uuid_to_uuid_message(_ID), reference=storage_pb.StorageLocationReference( - type=storage_pb.STORAGE_TYPE_GCS, + type=storage_pb.StorageType.GCS, gcs_bucket=storage_pb.GCSBucketReference(project_id="test-project", bucket=bucket), ), ) @@ -313,7 +313,7 @@ def test_s3_reference_region_and_store_cache(monkeypatch: pytest.MonkeyPatch) -> message = storage_pb.StorageLocation( id=uuid_to_uuid_message(_ID), reference=storage_pb.StorageLocationReference( - type=storage_pb.STORAGE_TYPE_AWS_S3, + type=storage_pb.StorageType.AWS_S3, aws_s3_bucket=storage_pb.AWSS3BucketReference(bucket="source-bucket", region="eu-west-2"), ), ) @@ -454,7 +454,7 @@ def test_local_read(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: storage_pb.StorageLocation( id=uuid_to_uuid_message(_ID), reference=storage_pb.StorageLocationReference( - type=storage_pb.STORAGE_TYPE_LOCAL, + type=storage_pb.StorageType.LOCAL, local=storage_pb.LocalReference(path=str(tmp_path)), ), ) @@ -494,7 +494,7 @@ def test_local_read_accepts_empty_directory(tmp_path: Path) -> None: [ ( storage_pb.StorageLocationReference( - type=storage_pb.STORAGE_TYPE_GCS, + type=storage_pb.StorageType.GCS, gcs_bucket=storage_pb.GCSBucketReference(project_id="project", bucket="bucket"), ), StorageType.GCS, @@ -502,7 +502,7 @@ def test_local_read_accepts_empty_directory(tmp_path: Path) -> None: ), ( storage_pb.StorageLocationReference( - type=storage_pb.STORAGE_TYPE_AWS_S3, + type=storage_pb.StorageType.AWS_S3, aws_s3_bucket=storage_pb.AWSS3BucketReference(bucket="bucket"), ), StorageType.AWS_S3, @@ -510,7 +510,7 @@ def test_local_read_accepts_empty_directory(tmp_path: Path) -> None: ), ( storage_pb.StorageLocationReference( - type=storage_pb.STORAGE_TYPE_LOCAL, local=storage_pb.LocalReference(path="/data") + type=storage_pb.StorageType.LOCAL, local=storage_pb.LocalReference(path="/data") ), StorageType.LOCAL, "/data", @@ -528,32 +528,16 @@ def test_reference_coordinates( assert storage.location == expected_location -def test_grpc_storage_location_service_route() -> None: - # gRPC listing uses StorageLocationService and retains Azure account metadata. - channel = MagicMock() - channel.unary_unary.return_value.return_value = storage_pb.StorageLocations(locations=[_azure_message()]) - with ( - patch("tilebox.workflows.client.open_channel", return_value=channel), - patch("tilebox.workflows.client.WorkflowTracer", return_value=NoopWorkflowTracer()), - ): - locations = Client(token="test-key").storage_locations().all() # noqa: S106 - assert isinstance(locations[0], AzureBlobStorageLocation) - assert locations[0].storage_account_resource_id == _ACCOUNT - assert "/workflows.v1.StorageLocationService/ListStorageLocations" in [ - call.args[0] for call in channel.unary_unary.call_args_list - ] - - -def test_http1_storage_location_service_route() -> None: - # HTTP/1.1 listing uses the Connect storage-location client and retains Azure account metadata. +def test_storage_location_service_route() -> None: + # Listing uses the typed storage-location client and retains Azure account metadata. with ( patch( - "tilebox.workflows.workflows.v1.storage_location_connect.StorageLocationServiceClientSync.list_storage_locations", + "tilebox.workflows.v1.storage_location_connect.StorageLocationServiceClientSync.list_storage_locations", return_value=storage_pb.StorageLocations(locations=[_azure_message()]), ) as list_locations, patch("tilebox.workflows.client.WorkflowTracer", return_value=NoopWorkflowTracer()), ): - locations = Client(token="test-key", transport="http1").storage_locations().all() # noqa: S106 + locations = Client(token="test-key").storage_locations().all() # noqa: S106 assert isinstance(locations[0], AzureBlobStorageLocation) assert locations[0].storage_account_resource_id == _ACCOUNT list_locations.assert_called_once() diff --git a/tilebox-workflows/tests/clusters/test_client.py b/tilebox-workflows/tests/clusters/test_client.py index 10c2be9..f66ba40 100644 --- a/tilebox-workflows/tests/clusters/test_client.py +++ b/tilebox-workflows/tests/clusters/test_client.py @@ -4,16 +4,16 @@ from hypothesis.stateful import Bundle, RuleBasedStateMachine, consumes, rule from tests.tasks_data import clusters -from _tilebox.grpc.error import NotFoundError +from _tilebox.errors import NotFoundError from tilebox.workflows.clusters.client import ClusterClient -from tilebox.workflows.clusters.service import ClusterService from tilebox.workflows.data import ( Cluster, ) -from tilebox.workflows.workflows.v1.workflows_pb2 import ( +from tilebox.workflows.v1.workflows_connect import WorkflowsServiceClientSync +from tilebox.workflows.v1.workflows_pb import ( Cluster as ClusterMessage, ) -from tilebox.workflows.workflows.v1.workflows_pb2 import ( +from tilebox.workflows.v1.workflows_pb import ( CreateClusterRequest, DeleteClusterRequest, GetClusterRequest, @@ -21,51 +21,49 @@ ListClustersResponse, UpdateClusterRequest, ) -from tilebox.workflows.workflows.v1.workflows_pb2_grpc import WorkflowsServiceStub -class MockClusterService(WorkflowsServiceStub): - """A mock implementation of the gRPC cluster service, that stores clusters in memory as a dict.""" +class MockClusterService: + """Store clusters in memory for client behavior tests.""" def __init__(self) -> None: self.clusters: dict[str, ClusterMessage] = {} - def CreateCluster(self, req: CreateClusterRequest) -> ClusterMessage: # noqa: N802 + def create_cluster(self, req: CreateClusterRequest) -> ClusterMessage: cluster = ClusterMessage(slug=req.slug or str(uuid4()), display_name=req.name, description=req.description) self.clusters[cluster.slug] = cluster return cluster - def UpdateCluster(self, req: UpdateClusterRequest) -> ClusterMessage: # noqa: N802 + def update_cluster(self, req: UpdateClusterRequest) -> ClusterMessage: if req.cluster_slug not in self.clusters: raise NotFoundError(f"Cluster {req.cluster_slug} not found") - cluster = ClusterMessage.FromString(self.clusters[req.cluster_slug].SerializeToString()) - if req.HasField("name"): + cluster = ClusterMessage.from_binary(self.clusters[req.cluster_slug].to_binary()) + if req.has_field("name"): cluster.display_name = req.name - if req.HasField("description"): + if req.has_field("description"): cluster.description = req.description self.clusters[cluster.slug] = cluster return cluster - def GetCluster(self, req: GetClusterRequest) -> ClusterMessage: # noqa: N802 + def get_cluster(self, req: GetClusterRequest) -> ClusterMessage: if req.cluster_slug in self.clusters: return self.clusters[req.cluster_slug] raise NotFoundError(f"Cluster {req.cluster_slug} not found") - def DeleteCluster(self, req: DeleteClusterRequest) -> None: # noqa: N802 + def delete_cluster(self, req: DeleteClusterRequest) -> None: if req.cluster_slug in self.clusters: del self.clusters[req.cluster_slug] else: raise NotFoundError(f"Cluster {req.cluster_slug} not found") - def ListClusters(self, req: ListClustersRequest) -> ListClustersResponse: # noqa: N802 + def list_clusters(self, req: ListClustersRequest) -> ListClustersResponse: _ = req return ListClustersResponse(clusters=list(self.clusters.values())) def test_create_cluster_can_use_custom_slug() -> None: - service = ClusterService(MagicMock()) - service.service = MockClusterService() + service = MagicMock(spec=WorkflowsServiceClientSync, wraps=MockClusterService()) cluster_client = ClusterClient(service) cluster = cluster_client.create("Test Cluster", description="Test description", slug="test-cluster") @@ -90,8 +88,7 @@ class ClusterCRUDOperations(RuleBasedStateMachine): def __init__(self) -> None: super().__init__() - service = ClusterService(MagicMock()) - service.service = MockClusterService() # mock the gRPC service + service = MagicMock(spec=WorkflowsServiceClientSync, wraps=MockClusterService()) self.cluster_client = ClusterClient(service) self.count_clusters = 0 diff --git a/tilebox-workflows/tests/jobs/test_client.py b/tilebox-workflows/tests/jobs/test_client.py index 99e45ec..eb12773 100644 --- a/tilebox-workflows/tests/jobs/test_client.py +++ b/tilebox-workflows/tests/jobs/test_client.py @@ -6,33 +6,29 @@ from hypothesis.stateful import Bundle, RuleBasedStateMachine, consumes, rule from tests.tasks_data import jobs -from _tilebox.grpc.error import NotFoundError +from _tilebox.errors import NotFoundError from tilebox.datasets.query.id_interval import IDInterval from tilebox.datasets.query.pagination import Pagination from tilebox.datasets.query.time_interval import datetime_to_timestamp +from tilebox.datasets.uuid import uuid_message_to_optional_uuid from tilebox.workflows.data import ( Job, JobState, LogRecord, - LogRecords, LogSeverity, LogSeverityLiteral, - QueryJobLogsResponse, - QueryJobSpansResponse, Span, - Spans, uuid_message_to_uuid, uuid_to_uuid_message, ) from tilebox.workflows.jobs.client import JobClient -from tilebox.workflows.jobs.service import JobService -from tilebox.workflows.jobs.telemetry_service import TelemetryService from tilebox.workflows.observability.tracing import NoopWorkflowTracer from tilebox.workflows.task import ExecutionContext, Task -from tilebox.workflows.workflows.v1.core_pb2 import Job as JobMessage -from tilebox.workflows.workflows.v1.core_pb2 import JobState as JobStateEnum -from tilebox.workflows.workflows.v1.diagram_pb2 import Diagram -from tilebox.workflows.workflows.v1.job_pb2 import ( +from tilebox.workflows.v1.core_pb import Job as JobMessage +from tilebox.workflows.v1.core_pb import JobState as JobStateEnum +from tilebox.workflows.v1.diagram_pb import Diagram +from tilebox.workflows.v1.job_connect import JobServiceClientSync +from tilebox.workflows.v1.job_pb import ( CancelJobRequest, CancelJobResponse, GetJobRequest, @@ -43,8 +39,7 @@ SubmitJobRequest, VisualizeJobRequest, ) -from tilebox.workflows.workflows.v1.job_pb2_grpc import JobServiceStub -from tilebox.workflows.workflows.v1.telemetry_pb2 import PaginatedLogsData, PaginatedSpansData +from tilebox.workflows.v1.telemetry_pb import PaginatedLogsData, PaginatedSpansData class DummyTask(Task): @@ -78,8 +73,11 @@ def test_query_logs_paginates() -> None: ] telemetry_service = MagicMock() telemetry_service.query_job_logs.side_effect = [ - QueryJobLogsResponse(LogRecords([log_records[0]]), Pagination(starting_after=next_page_start)), - QueryJobLogsResponse(LogRecords([log_records[1]]), Pagination()), + PaginatedLogsData( + resource_logs=[log_records[0].to_message()], + next_page=Pagination(starting_after=next_page_start).to_message(), + ), + PaginatedLogsData(resource_logs=[log_records[1].to_message()]), ] job_client = JobClient(MagicMock(), telemetry_service, NoopWorkflowTracer()) job_id = uuid4() @@ -88,7 +86,10 @@ def test_query_logs_paginates() -> None: assert logs == log_records assert list(logs.to_pandas()["body"]) == ["first", "second"] - assert [call.args[1].starting_after for call in telemetry_service.query_job_logs.call_args_list] == [ + assert [ + uuid_message_to_optional_uuid(call.args[0].page.starting_after) + for call in telemetry_service.query_job_logs.call_args_list + ] == [ None, next_page_start, ] @@ -107,15 +108,13 @@ def test_query_logs_filters_by_task_and_severity( level: LogSeverity | LogSeverityLiteral | list[LogSeverity] | list[LogSeverityLiteral], expected: list[int] ) -> None: service = MagicMock() - service.QueryJobLogs.return_value = PaginatedLogsData() - telemetry_service = TelemetryService(MagicMock()) - telemetry_service.service = service - job_client = JobClient(MagicMock(), telemetry_service, NoopWorkflowTracer()) + service.query_job_logs.return_value = PaginatedLogsData() + job_client = JobClient(MagicMock(), service, NoopWorkflowTracer()) task_id = uuid4() job_client.query_logs(uuid4(), task_id=str(task_id), level=level) - request = service.QueryJobLogs.call_args.args[0] + request = service.query_job_logs.call_args.args[0] assert uuid_message_to_uuid(request.task_id) == task_id assert list(request.filters.severity_levels) == expected @@ -152,8 +151,10 @@ def test_query_spans_paginates() -> None: ] telemetry_service = MagicMock() telemetry_service.query_job_spans.side_effect = [ - QueryJobSpansResponse(Spans([spans[0]]), Pagination(starting_after=next_page_start)), - QueryJobSpansResponse(Spans([spans[1]]), Pagination()), + PaginatedSpansData( + resource_spans=[spans[0].to_message()], next_page=Pagination(starting_after=next_page_start).to_message() + ), + PaginatedSpansData(resource_spans=[spans[1].to_message()]), ] job_client = JobClient(MagicMock(), telemetry_service, NoopWorkflowTracer()) job_id = uuid4() @@ -162,7 +163,10 @@ def test_query_spans_paginates() -> None: assert queried_spans == spans assert list(queried_spans.to_pandas()["name"]) == ["first", "second"] - assert [call.args[1].starting_after for call in telemetry_service.query_job_spans.call_args_list] == [ + assert [ + uuid_message_to_optional_uuid(call.args[0].page.starting_after) + for call in telemetry_service.query_job_spans.call_args_list + ] == [ None, next_page_start, ] @@ -170,96 +174,94 @@ def test_query_spans_paginates() -> None: def test_query_spans_filters_by_task() -> None: service = MagicMock() - service.QueryJobSpans.return_value = PaginatedSpansData() - telemetry_service = TelemetryService(MagicMock()) - telemetry_service.service = service - job_client = JobClient(MagicMock(), telemetry_service, NoopWorkflowTracer()) + service.query_job_spans.return_value = PaginatedSpansData() + job_client = JobClient(MagicMock(), service, NoopWorkflowTracer()) task_id = uuid4() job_client.query_spans(uuid4(), task_id=str(task_id)) - request = service.QueryJobSpans.call_args.args[0] + request = service.query_job_spans.call_args.args[0] assert uuid_message_to_uuid(request.task_id) == task_id -class MockJobService(JobServiceStub): - """A mock implementation of the gRPC job service, that stores jobs in memory as a dict.""" +class MockJobService: + """Store jobs in memory for client behavior tests.""" def __init__(self) -> None: self.jobs: dict[UUID, JobMessage] = {} self.query_requests: list[QueryJobsRequest] = [] - def SubmitJob(self, req: SubmitJobRequest) -> JobMessage: # noqa: N802 + def submit_job(self, req: SubmitJobRequest) -> JobMessage: job_id = uuid4() job = JobMessage( id=uuid_to_uuid_message(job_id), name=req.job_name, trace_parent=req.trace_parent, - state=JobStateEnum.JOB_STATE_SUBMITTED, + state=JobStateEnum.SUBMITTED, submitted_at=datetime_to_timestamp(datetime.now(tz=UTC)), ) self.jobs[job_id] = job return job - def GetJob(self, req: GetJobRequest) -> JobMessage: # noqa: N802 + def get_job(self, req: GetJobRequest) -> JobMessage: job_id = uuid_message_to_uuid(req.job_id) if job_id not in self.jobs: raise NotFoundError(f"No such job: {job_id}") return self.jobs[job_id] - def RetryJob(self, req: RetryJobRequest) -> RetryJobResponse: # noqa: N802 + def retry_job(self, req: RetryJobRequest) -> RetryJobResponse: job_id = uuid_message_to_uuid(req.job_id) if job_id not in self.jobs: raise NotFoundError(f"No such job: {job_id}") - copy = JobMessage.FromString(self.jobs[job_id].SerializeToString()) - copy.state = JobStateEnum.JOB_STATE_SUBMITTED + copy = JobMessage.from_binary(self.jobs[job_id].to_binary()) + copy.state = JobStateEnum.SUBMITTED self.jobs[job_id] = copy return RetryJobResponse(num_tasks_rescheduled=1) - def CancelJob(self, req: CancelJobRequest) -> CancelJobResponse: # noqa: N802 + def cancel_job(self, req: CancelJobRequest) -> CancelJobResponse: job_id = uuid_message_to_uuid(req.job_id) if job_id not in self.jobs: raise NotFoundError(f"No such job: {job_id}") - copy = JobMessage.FromString(self.jobs[job_id].SerializeToString()) - copy.state = JobStateEnum.JOB_STATE_CANCELED + copy = JobMessage.from_binary(self.jobs[job_id].to_binary()) + copy.state = JobStateEnum.CANCELED self.jobs[job_id] = copy return CancelJobResponse() - def VisualizeJob(self, req: VisualizeJobRequest) -> Diagram: # noqa: N802 + def visualize_job(self, req: VisualizeJobRequest) -> Diagram: job_id = uuid_message_to_uuid(req.job_id) if job_id not in self.jobs: raise NotFoundError(f"No such job: {job_id}") job = self.jobs[job_id] - if job.state == JobStateEnum.JOB_STATE_CANCELED: + if job.state == JobStateEnum.CANCELED: return Diagram(svg=b"Job canceled") return Diagram(svg=b"Job queued") - def QueryJobs(self, req: QueryJobsRequest) -> QueryJobsResponse: # noqa: N802 + def query_jobs(self, req: QueryJobsRequest) -> QueryJobsResponse: self.query_requests.append(req) return QueryJobsResponse(jobs=list(self.jobs.values())) def test_query_filters_by_clusters() -> None: - service = JobService(MagicMock()) mock_service = MockJobService() - service.service = mock_service + service = MagicMock(spec=JobServiceClientSync, wraps=mock_service) job_client = JobClient(service, MagicMock(), NoopWorkflowTracer()) job_client.query((uuid4(), uuid4()), clusters=["cluster-a", "cluster-b"]) - assert list(mock_service.query_requests[-1].filters.cluster_slugs) == ["cluster-a", "cluster-b"] + filters = mock_service.query_requests[-1].filters + assert filters is not None + assert list(filters.cluster_slugs) == ["cluster-a", "cluster-b"] @pytest.mark.parametrize("use_interval", [False, True]) def test_query_uuid_interval_preserves_bounds(use_interval: bool) -> None: - service = JobService(MagicMock()) mock_service = MockJobService() - service.service = mock_service + service = MagicMock(spec=JobServiceClientSync, wraps=mock_service) job_client = JobClient(service, MagicMock(), NoopWorkflowTracer()) start, end = UUID(int=17), UUID(int=93) extent = IDInterval(start, end, start_exclusive=True, end_inclusive=False) if use_interval else (start, end) @@ -267,7 +269,9 @@ def test_query_uuid_interval_preserves_bounds(use_interval: bool) -> None: job_client.query(extent) filters = mock_service.query_requests[-1].filters - assert not filters.HasField("time_interval") + assert filters is not None + assert filters.id_interval is not None + assert not filters.has_field("time_interval") assert uuid_message_to_uuid(filters.id_interval.start_id) == start assert uuid_message_to_uuid(filters.id_interval.end_id) == end assert filters.id_interval.start_exclusive is use_interval @@ -275,32 +279,32 @@ def test_query_uuid_interval_preserves_bounds(use_interval: bool) -> None: def test_query_empty_cluster_list_applies_no_cluster_filter() -> None: - service = JobService(MagicMock()) mock_service = MockJobService() - service.service = mock_service + service = MagicMock(spec=JobServiceClientSync, wraps=mock_service) job_client = JobClient(service, MagicMock(), NoopWorkflowTracer()) job_client.query((uuid4(), uuid4()), clusters=[]) - assert list(mock_service.query_requests[-1].filters.cluster_slugs) == [] + filters = mock_service.query_requests[-1].filters + assert filters is not None + assert list(filters.cluster_slugs) == [] def test_query_without_temporal_extent() -> None: - service = JobService(MagicMock()) mock_service = MockJobService() - service.service = mock_service + service = MagicMock(spec=JobServiceClientSync, wraps=mock_service) job_client = JobClient(service, MagicMock(), NoopWorkflowTracer()) job_client.query(job_states=JobState.RUNNING) filters = mock_service.query_requests[-1].filters - assert not filters.HasField("time_interval") - assert not filters.HasField("id_interval") + assert filters is not None + assert not filters.has_field("time_interval") + assert not filters.has_field("id_interval") def test_query_rejects_empty_cluster_slug() -> None: - service = JobService(MagicMock()) - service.service = MockJobService() + service = MagicMock(spec=JobServiceClientSync, wraps=MockJobService()) job_client = JobClient(service, MagicMock(), NoopWorkflowTracer()) with pytest.raises(ValueError, match="explicit cluster slugs"): @@ -322,8 +326,7 @@ class JobOperations(RuleBasedStateMachine): def __init__(self) -> None: super().__init__() - service = JobService(MagicMock()) - service.service = MockJobService() # mock the gRPC service + service = MagicMock(spec=JobServiceClientSync, wraps=MockJobService()) self.job_client = JobClient(service, MagicMock(), NoopWorkflowTracer()) self.count_total_submitted = 0 diff --git a/tilebox-workflows/tests/log_values.py b/tilebox-workflows/tests/log_values.py new file mode 100644 index 0000000..17a07b2 --- /dev/null +++ b/tilebox-workflows/tests/log_values.py @@ -0,0 +1,9 @@ +from tilebox.workflows._otel.opentelemetry.proto.common.v1.common_pb import AnyValue + + +def string_value(value: AnyValue | None) -> str: + """Assert that a native OTEL value contains the string variant and return it.""" + assert value is not None + assert value.value is not None + assert value.value.field == "string_value" + return value.value.value diff --git a/tilebox-workflows/tests/observability/test_logging.py b/tilebox-workflows/tests/observability/test_logging.py index f46d4c3..abc36ab 100644 --- a/tilebox-workflows/tests/observability/test_logging.py +++ b/tilebox-workflows/tests/observability/test_logging.py @@ -16,8 +16,11 @@ from opentelemetry._logs import SeverityNumber from opentelemetry.sdk._logs.export import InMemoryLogRecordExporter, SimpleLogRecordProcessor from opentelemetry.sdk.trace import Span, TracerProvider +from protobuf import Oneof +from tests.log_values import string_value from tilebox.workflows._codec import registry +from tilebox.workflows._otel.opentelemetry.proto.common.v1.common_pb import AnyValue, ArrayValue, KeyValue, KeyValueList from tilebox.workflows.observability import _logging as structured_logging from tilebox.workflows.observability import logging as observability from tilebox.workflows.observability import tracing @@ -189,9 +192,10 @@ def test_export_attributes_capture_context_once_without_mutating_record(structur assert metadata == {"task_id": "task-123", "details": {"attempt": 2}} +@pytest.mark.asyncio @pytest.mark.parametrize("stdlib", [False, True]) @pytest.mark.parametrize("stream_first", [False, True]) -def test_codecs_run_once_per_record_across_handlers(stdlib: bool, stream_first: bool) -> None: +async def test_codecs_run_once_per_record_across_handlers(stdlib: bool, stream_first: bool) -> None: codec = registry.find(Affine) assert codec is not None encode = MagicMock(wraps=codec.encode) @@ -227,13 +231,33 @@ def test_codecs_run_once_per_record_across_handlers(stdlib: bool, stream_first: # Records hold snapshots, not references to task-owned mutable containers. value.clear() queue.seal() - stream_records = list(queue.subscribe()) - assert [record.body.string_value for record in stream_records] == ["record-0", "record-1"] + stream_records = [record async for record in queue.subscribe_async()] + assert [string_value(record.body) for record in stream_records] == ["record-0", "record-1"] for record in stream_records: attributes = {item.key: item.value for item in record.attributes} - transform = attributes["input"].kvlist_value.values[0] - assert transform.key == "transform" - assert [item.double_value for item in transform.value.array_value.values] == [2, 3, 5, 7, 11, 13] + assert attributes["input"] == AnyValue( + value=Oneof( + "kvlist_value", + KeyValueList( + values=[ + KeyValue( + key="transform", + value=AnyValue( + value=Oneof( + "array_value", + ArrayValue( + values=[ + AnyValue(value=Oneof("double_value", number)) + for number in (2.0, 3.0, 5.0, 7.0, 11.0, 13.0) + ] + ), + ) + ), + ) + ] + ), + ) + ) for exporter in exporters: records = [item.log_record for item in exporter.get_finished_logs()] assert [record.body for record in records] == ["record-0", "record-1"] @@ -246,9 +270,10 @@ def test_codecs_run_once_per_record_across_handlers(stdlib: bool, stream_first: provider.shutdown() +@pytest.mark.asyncio @pytest.mark.usefixtures("isolated_logging") @pytest.mark.parametrize("external_first", [False, True]) -def test_external_exports_and_console_do_not_replace_api_or_stream(external_first: bool) -> None: +async def test_external_exports_and_console_do_not_replace_api_or_stream(external_first: bool) -> None: queue = _LogQueue() root_logger.addHandler(_OTLPQueueHandler(queue)) api, external = InMemoryLogRecordExporter(), InMemoryLogRecordExporter() @@ -275,7 +300,7 @@ def test_external_exports_and_console_do_not_replace_api_or_stream(external_firs for exporter in (api, external): assert [item.log_record.body for item in exporter.get_finished_logs()] == ["all outputs", "exports only"] queue.seal() - assert [record.body.string_value for record in queue.subscribe()] == ["all outputs", "exports only"] + assert [string_value(record.body) async for record in queue.subscribe_async()] == ["all outputs", "exports only"] @pytest.mark.usefixtures("isolated_logging") @@ -302,8 +327,9 @@ def test_log_level_override_defaults_reset_both_levels() -> None: assert internal_logger.level == logging.ERROR +@pytest.mark.asyncio @pytest.mark.usefixtures("isolated_logging") -def test_task_values_and_unserializable_attributes_reach_both_outputs(monkeypatch: pytest.MonkeyPatch) -> None: +async def test_task_values_and_unserializable_attributes_reach_both_outputs(monkeypatch: pytest.MonkeyPatch) -> None: @dataclass class Input: transform: Affine @@ -330,17 +356,40 @@ def __str__(self) -> str: unsupported=Broken(), ) queue.seal() - [record] = queue.subscribe() + [record] = [record async for record in queue.subscribe_async()] stream_attributes = {item.key: item.value for item in record.attributes} - input_attributes = {item.key: item.value for item in stream_attributes["input"].kvlist_value.values} - assert [item.double_value for item in input_attributes["transform"].array_value.values] == [2, 3, 5, 7, 11, 13] - assert input_attributes["path"].string_value == "image.tif" - counts = stream_attributes["counts"].kvlist_value.values - assert len(counts) == 1 - assert counts[0].key == "2026-09-16" - assert counts[0].value.int_value == 3 - assert stream_attributes["cyclic"].string_value == "[[...]]" - assert stream_attributes["unsupported"].string_value == "" + assert stream_attributes["input"] == AnyValue( + value=Oneof( + "kvlist_value", + KeyValueList( + values=[ + KeyValue( + key="transform", + value=AnyValue( + value=Oneof( + "array_value", + ArrayValue( + values=[ + AnyValue(value=Oneof("double_value", number)) + for number in (2.0, 3.0, 5.0, 7.0, 11.0, 13.0) + ] + ), + ) + ), + ), + KeyValue(key="path", value=AnyValue(value=Oneof("string_value", "image.tif"))), + ] + ), + ) + ) + assert stream_attributes["counts"] == AnyValue( + value=Oneof( + "kvlist_value", + KeyValueList(values=[KeyValue(key="2026-09-16", value=AnyValue(value=Oneof("int_value", 3)))]), + ) + ) + assert string_value(stream_attributes["cyclic"]) == "[[...]]" + assert string_value(stream_attributes["unsupported"]) == "" attributes = exporter.get_finished_logs()[0].log_record.attributes assert attributes is not None assert isinstance(attributes["input"], str) diff --git a/tilebox-workflows/tests/observability/test_tracing.py b/tilebox-workflows/tests/observability/test_tracing.py index 8f1014e..9096389 100644 --- a/tilebox-workflows/tests/observability/test_tracing.py +++ b/tilebox-workflows/tests/observability/test_tracing.py @@ -1,7 +1,9 @@ +import gc import json import os import subprocess import sys +import weakref from collections.abc import Iterator from importlib.metadata import version from unittest.mock import patch @@ -10,9 +12,10 @@ from opentelemetry.context import Context from opentelemetry.sdk.resources import Resource from opentelemetry.sdk.trace import ReadableSpan, Span, TracerProvider -from opentelemetry.sdk.trace.export import SimpleSpanProcessor, SpanProcessor +from opentelemetry.sdk.trace.export import SimpleSpanProcessor, SpanExportResult, SpanProcessor from opentelemetry.sdk.trace.export.in_memory_span_exporter import InMemorySpanExporter +from tilebox.workflows import Client from tilebox.workflows.observability import logging as observability_logging from tilebox.workflows.observability import tracing @@ -62,10 +65,13 @@ def force_flush(self, timeout_millis: int = 30000) -> bool: # noqa: ARG002 @pytest.fixture(autouse=True) def reset_tilebox_tracing() -> Iterator[None]: original_provider = tracing._get_tilebox_tracer_provider() + original_tracers = tracing._workflow_tracers[:] tracing._workflow_tracers.clear() tracing._set_tilebox_tracer_provider(TracerProvider(resource=observability_logging._get_default_resource())) yield - tracing._workflow_tracers.clear() + for tracer in tracing._workflow_tracers[:]: + tracer.close() + tracing._workflow_tracers.extend(original_tracers) tracing._set_tilebox_tracer_provider(original_provider) @@ -82,6 +88,39 @@ def create_processor(*args: object, **kwargs: object) -> RecordingSpanProcessor: return processors +def test_client_close_flushes_owned_traces_and_preserves_shared_exporters() -> None: + shared = InMemorySpanExporter() + tracing._get_tilebox_tracer_provider().add_span_processor(SimpleSpanProcessor(shared)) + first_exporter, second_exporter = InMemorySpanExporter(), InMemorySpanExporter() + with ( + patch.object(tracing, "OTLPSpanExporter", side_effect=[first_exporter, second_exporter]), + Client(url="http://localhost:9999", token="test-key") as first, # noqa: S106 + Client(url="http://localhost:9999", token="test-key") as second, # noqa: S106 + ): + tracer_ref = weakref.ref(first._tracer) + with first._tracer.span("first"): + pass + first.close() + first.close() + assert [span.name for span in first_exporter.get_finished_spans()] == ["first"] + assert first_exporter.export([]) is SpanExportResult.FAILURE + assert second_exporter.export([]) is SpanExportResult.SUCCESS + assert shared.export([]) is SpanExportResult.SUCCESS + + # Reconfiguration must not resurrect a closed client or retain it in an old provider. + with patch.object(tracing, "_otel_span_exporter", return_value=SimpleSpanProcessor(shared)): + tracing.configure_otel_tracing(service="updated", endpoint="https://collector.example") + with second._tracer.span("second"): + pass + + assert [span.name for span in second_exporter.get_finished_spans()] == ["second"] + assert second_exporter.export([]) is SpanExportResult.FAILURE + assert shared.export([]) is SpanExportResult.SUCCESS + del first + gc.collect() + assert tracer_ref() is None + + @pytest.mark.parametrize("otel_service_name", ["unknown_service:python", "unknown_service:python.exe", "other-default"]) def test_exported_traces_use_tilebox_resource_defaults(otel_service_name: str) -> None: # Exercise actual module initialization, independent of OTEL's platform-specific defaults. diff --git a/tilebox-workflows/tests/proto/test_pb.py b/tilebox-workflows/tests/proto/test_pb.py new file mode 100644 index 0000000..6345b88 --- /dev/null +++ b/tilebox-workflows/tests/proto/test_pb.py @@ -0,0 +1,64 @@ +# Generated from test.proto. DO NOT EDIT. +# Generated by protoc-gen-py v0.6.0 with parameter "init_files=false". +# ruff: noqa: PGH004 +# ruff: noqa +# fmt: off + +from __future__ import annotations + +from typing import Literal, TYPE_CHECKING, TypeAlias + +from protobuf import Message +from protobuf._codegen import file_desc + +if TYPE_CHECKING: + from protobuf import DescFile + + +_SampleArgsFields: TypeAlias = Literal["some_string", "some_int"] + +class SampleArgs(Message[_SampleArgsFields]): + """ + ```proto + message SampleArgs + ``` + + Attributes: + some_string: + ```proto + optional string some_string = 1; + ``` + some_int: + ```proto + optional int64 some_int = 2; + ``` + """ + + __slots__ = ("some_string", "some_int") + + if TYPE_CHECKING: + + def __init__( + self, + *, + some_string: str | None = None, + some_int: int | None = None, + ) -> None: + pass + + some_string: str + some_int: int + + +_DESC = file_desc( + b'\n\ntest.proto"H\n\nSampleArgs\x12\x1f\n\x0bsome_string\x18\x01 \x01(\tR\nsomeString\x12\x19\n\x08some_int\x18\x02 \x01(\x03R\x07someIntb\x08editionsp\xe8\x07', + [], + { + "SampleArgs": SampleArgs, + }, +) + + +def desc() -> DescFile: + """Returns the descriptor for the file `test.proto`.""" + return _DESC diff --git a/tilebox-workflows/tests/proto/test_pb2.py b/tilebox-workflows/tests/proto/test_pb2.py deleted file mode 100644 index bbdf6bf..0000000 --- a/tilebox-workflows/tests/proto/test_pb2.py +++ /dev/null @@ -1,27 +0,0 @@ -# Generated by the protocol buffer compiler. DO NOT EDIT! -# source: datasets/v1/test.proto # noqa: ERA001 -# Protobuf Python Version: 5.26.1 -"""Generated protocol buffer code.""" - -from google.protobuf import descriptor as _descriptor -from google.protobuf import descriptor_pool as _descriptor_pool -from google.protobuf import symbol_database as _symbol_database -from google.protobuf.internal import builder as _builder - -# @@protoc_insertion_point(imports) - -_sym_db = _symbol_database.Default() - - -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile( - b'\n\ntest.proto"3\n\nSampleArgs\x12\x13\n\x0bsome_string\x18\x01 \x01(\t\x12\x10\n\x08some_int\x18\x02 \x01(\x03\x62\x08\x65\x64itionsp\xe8\x07' -) - -_globals = globals() -_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) -_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, "test_pb2", _globals) -if not _descriptor._USE_C_DESCRIPTORS: - DESCRIPTOR._loaded_options = None - _globals["_SAMPLEARGS"]._serialized_start = 14 - _globals["_SAMPLEARGS"]._serialized_end = 65 -# @@protoc_insertion_point(module_scope) diff --git a/tilebox-workflows/tests/proto/test_pb2.pyi b/tilebox-workflows/tests/proto/test_pb2.pyi deleted file mode 100644 index 61ceabe..0000000 --- a/tilebox-workflows/tests/proto/test_pb2.pyi +++ /dev/null @@ -1,14 +0,0 @@ -from typing import ClassVar as _ClassVar - -from google.protobuf import descriptor as _descriptor -from google.protobuf import message as _message - -DESCRIPTOR: _descriptor.FileDescriptor - -class SampleArgs(_message.Message): - __slots__ = ("some_int", "some_string") - SOME_STRING_FIELD_NUMBER: _ClassVar[int] - SOME_INT_FIELD_NUMBER: _ClassVar[int] - some_string: str - some_int: int - def __init__(self, some_string: str | None = ..., some_int: int | None = ...) -> None: ... diff --git a/tilebox-workflows/tests/runner/test_log_stream.py b/tilebox-workflows/tests/runner/test_log_stream.py index 7ea531a..699cbf4 100644 --- a/tilebox-workflows/tests/runner/test_log_stream.py +++ b/tilebox-workflows/tests/runner/test_log_stream.py @@ -1,86 +1,63 @@ +import asyncio import logging import sys -import threading import time import pytest -from opentelemetry.proto.logs.v1.logs_pb2 import LogRecord, SeverityNumber +from protobuf import Oneof +from tests.log_values import string_value +from tilebox.workflows._otel.opentelemetry.proto.common.v1.common_pb import AnyValue, ArrayValue, KeyValue, KeyValueList +from tilebox.workflows._otel.opentelemetry.proto.logs.v1.logs_pb import LogRecord, SeverityNumber from tilebox.workflows.observability._log_stream import _LogQueue, _OTLPQueueHandler def _record(message: str) -> LogRecord: - record = LogRecord() - record.body.string_value = message - return record + return LogRecord(body=AnyValue(value=Oneof("string_value", message))) -def test_full_sealed_queue_drains_without_sentinel() -> None: +@pytest.mark.asyncio +async def test_full_sealed_queue_drains_without_sentinel() -> None: records = _LogQueue(capacity=2) records.submit(_record("one")) records.submit(_record("two")) records.seal() - assert [record.body.string_value for record in records.subscribe()] == ["one", "two"] - assert list(records.subscribe()) == [] + assert [string_value(record.body) async for record in records.subscribe_async()] == ["one", "two"] + assert [record async for record in records.subscribe_async()] == [] -def test_overflow_notice_is_emitted_without_another_submission() -> None: +@pytest.mark.asyncio +async def test_overflow_notice_is_emitted_without_another_submission() -> None: records = _LogQueue(capacity=1) records.submit(_record("kept")) records.submit(_record("lost-one")) records.submit(_record("lost-two")) records.seal() - assert [record.body.string_value for record in records.subscribe()] == [ + assert [string_value(record.body) async for record in records.subscribe_async()] == [ "kept", "Dropped 2 local log records", ] -def test_cancelled_subscription_reconnects_and_does_not_consume_full_queue() -> None: - records = _LogQueue(capacity=1) - records.submit(_record("buffered")) - assert list(records.subscribe(lambda: False)) == [] - records.seal() - assert [record.body.string_value for record in records.subscribe()] == ["buffered"] - - -def test_overflow_notice_precedes_new_records_after_delivery_resumes() -> None: +@pytest.mark.asyncio +async def test_overflow_notice_precedes_new_records_after_delivery_resumes() -> None: records = _LogQueue(capacity=2) records.submit(_record("first")) records.submit(_record("second")) records.submit(_record("lost")) - subscriber = records.subscribe() - assert next(subscriber).body.string_value == "first" - assert next(subscriber).body.string_value == "second" + subscriber = records.subscribe_async() + assert string_value((await anext(subscriber)).body) == "first" + assert string_value((await anext(subscriber)).body) == "second" records.submit(_record("after loss")) records.seal() - assert [record.body.string_value for record in subscriber] == [ + assert [string_value(record.body) async for record in subscriber] == [ "Dropped 1 local log records", "after loss", ] -def test_concurrent_subscription_is_rejected() -> None: - records = _LogQueue() - subscribed = threading.Event() - release = threading.Event() - - def active() -> bool: - subscribed.set() - return not release.is_set() - - subscriber = threading.Thread(target=lambda: list(records.subscribe(active))) - subscriber.start() - assert subscribed.wait(timeout=1) - with pytest.raises(RuntimeError, match="already connected"): - next(records.subscribe()) - release.set() - subscriber.join(timeout=1) - assert not subscriber.is_alive() - - def test_wait_empty_is_bounded_without_consumer() -> None: records = _LogQueue() records.submit(_record("buffered")) @@ -89,13 +66,15 @@ def test_wait_empty_is_bounded_without_consumer() -> None: assert time.monotonic() - started < 0.5 -def test_oversize_record_is_replaced_by_drop_notice() -> None: +@pytest.mark.asyncio +async def test_oversize_record_is_replaced_by_drop_notice() -> None: records = _LogQueue(max_record_size=32) records.submit(_record("x" * 100)) records.seal() - assert [record.body.string_value for record in records.subscribe()] == ["Dropped 1 local log records"] + assert [string_value(record.body) async for record in records.subscribe_async()] == ["Dropped 1 local log records"] +@pytest.mark.asyncio @pytest.mark.parametrize( ("value", "field"), [ @@ -111,7 +90,7 @@ def test_oversize_record_is_replaced_by_drop_notice() -> None: (-(2**80), "string_value"), ], ) -def test_handler_preserves_integer_boundaries(value: int, field: str) -> None: +async def test_handler_preserves_integer_boundaries(value: int, field: str) -> None: records = _LogQueue() handler = _OTLPQueueHandler(records) record = logging.LogRecord("test", logging.INFO, __file__, 1, "integer attribute", (), None) @@ -120,18 +99,33 @@ def test_handler_preserves_integer_boundaries(value: int, field: str) -> None: handler.emit(logging.LogRecord("test", logging.INFO, __file__, 1, "next record", (), None)) records.seal() - first, second = records.subscribe() - assert first.body.string_value == "integer attribute" - assert second.body.string_value == "next record" + first, second = [record async for record in records.subscribe_async()] + assert string_value(first.body) == "integer attribute" + assert string_value(second.body) == "next record" attributes = {item.key: item.value for item in first.attributes} - nested = attributes["nested"].kvlist_value.values[0] - assert nested.key == "items" - for result in (attributes["value"], nested.value.array_value.values[0]): - assert result.WhichOneof("value") == field - assert getattr(result, field) == (str(value) if field == "string_value" else value) - - -def test_handler_preserves_structured_attributes_exception_trace_and_level() -> None: + if field == "string_value": + expected = AnyValue(value=Oneof("string_value", str(value))) + elif field == "bool_value": + assert isinstance(value, bool) + expected = AnyValue(value=Oneof("bool_value", value)) + else: + assert field == "int_value" + expected = AnyValue(value=Oneof("int_value", value)) + assert attributes["value"] == expected + assert attributes["nested"] == AnyValue( + value=Oneof( + "kvlist_value", + KeyValueList( + values=[ + KeyValue(key="items", value=AnyValue(value=Oneof("array_value", ArrayValue(values=[expected])))) + ] + ), + ) + ) + + +@pytest.mark.asyncio +async def test_handler_preserves_structured_attributes_exception_trace_and_level() -> None: records = _LogQueue() handler = _OTLPQueueHandler(records) @@ -151,14 +145,45 @@ def fail() -> None: } handler.emit(record) records.seal() - [result] = records.subscribe() + [result] = [record async for record in records.subscribe_async()] attributes = {item.key: item.value for item in result.attributes} - assert result.severity_number == SeverityNumber.SEVERITY_NUMBER_WARN + assert result.severity_number == SeverityNumber.WARN assert result.severity_text == "WARN" assert result.trace_id == b"\x01" * 16 assert result.span_id == b"\x02" * 8 - assert [item.int_value or item.string_value for item in attributes["items"].array_value.values] == [1, "two"] - assert attributes["mapping"].kvlist_value.values[0].value.bool_value - assert attributes["missing"].WhichOneof("value") is None - assert attributes["exception.type"].string_value == "ValueError" - assert "ValueError: bad value" in attributes["exception.stacktrace"].string_value + assert attributes["items"] == AnyValue( + value=Oneof( + "array_value", + ArrayValue(values=[AnyValue(value=Oneof("int_value", 1)), AnyValue(value=Oneof("string_value", "two"))]), + ) + ) + assert attributes["mapping"] == AnyValue( + value=Oneof( + "kvlist_value", + KeyValueList(values=[KeyValue(key="nested", value=AnyValue(value=Oneof("bool_value", True)))]), + ) + ) + assert attributes["missing"] == AnyValue() + assert string_value(attributes["exception.type"]) == "ValueError" + assert "ValueError: bad value" in string_value(attributes["exception.stacktrace"]) + + +@pytest.mark.asyncio +async def test_async_subscription_cancellation_releases_single_subscriber() -> None: + """Cancel an idle async subscriber without consuming future records or retaining a thread.""" + records = _LogQueue(capacity=1) + subscriber = records.subscribe_async() + pending = asyncio.create_task(anext(subscriber)) + await asyncio.sleep(0.02) + with pytest.raises(RuntimeError, match="already connected"): + await anext(records.subscribe_async()) + pending.cancel() + with pytest.raises(asyncio.CancelledError): + await pending + records.submit(_record("after cancellation")) + records.submit(_record("overflow")) + records.seal() + assert [string_value(record.body) async for record in records.subscribe_async()] == [ + "after cancellation", + "Dropped 1 local log records", + ] diff --git a/tilebox-workflows/tests/runner/test_runner.py b/tilebox-workflows/tests/runner/test_runner.py index fb63bfb..45468d1 100644 --- a/tilebox-workflows/tests/runner/test_runner.py +++ b/tilebox-workflows/tests/runner/test_runner.py @@ -9,7 +9,7 @@ import pytest -from _tilebox.grpc.replay import open_recording_channel, open_replay_channel +from _tilebox.replay import Record, Replay from tilebox.workflows import ExecutionContext, Runner, Task from tilebox.workflows.cache import InMemoryCache, JobCache from tilebox.workflows.client import Client @@ -51,7 +51,7 @@ def test_task_authoring_imports_are_lazy(monkeypatch: pytest.MonkeyPatch, grpc_v "import os\n" "import sys\n" "import tilebox.workflows as workflows\n" - f"assert os.environ['GRPC_VERBOSITY'] == {grpc_verbosity or 'ERROR'!r}\n" + f"assert os.environ.get('GRPC_VERBOSITY') == {grpc_verbosity!r}\n" "assert 'grpc' not in sys.modules\n" "heavy = {'pandas', 'xarray', 'boto3', 'google.cloud.storage', 'google.auth', 'azure.identity', 'obstore', 'ipywidgets', 'opentelemetry.sdk'}\n" "assert not heavy & sys.modules.keys()\n" @@ -206,39 +206,26 @@ def test_runner_with_workflow_tracking_progress() -> None: def replay_client(replay_file: str, assert_request_matches: bool = True) -> Client: replay = Path(__file__).parent / "testdata" / "recordings" / replay_file - replay_channel = open_replay_channel(replay, assert_request_matches) + interceptor = Replay(replay, assert_request_matches) with ( - patch("tilebox.workflows.client.open_channel") as open_channel_mock, patch("tilebox.workflows.client.WorkflowTracer", return_value=NoopWorkflowTracer()), ): - open_channel_mock.return_value = replay_channel - # url/token doesn't matter since its a mocked channel - client = Client( + return Client( url="https://api.tilebox.com", token="token", # noqa: S106 + interceptors=[interceptor], ) - open_channel_mock.assert_called_once() - - return client def record_client(recording_file: str) -> Client: recording = Path(__file__).parent / "testdata" / "recordings" / recording_file - # this will open a channel to api.tilebox.com, which will send real requests to the server, and record them - # for later offline replay - recording_channel = open_recording_channel( - "https://api.tilebox.com", os.environ["TILEBOX_OPENDATA_ONLY_API_KEY"], recording + return Client( + url="https://api.tilebox.com", + token=os.environ["TILEBOX_OPENDATA_ONLY_API_KEY"], + interceptors=[Record(recording)], ) - with patch("tilebox.workflows.client.open_channel") as open_channel_mock: - open_channel_mock.return_value = recording_channel - # url/token doesn't matter since its a mocked channel - client = Client(url="https://api.tilebox.com", token="token") # noqa: S106 - open_channel_mock.assert_called_once() - - return client - class ExplicitIdentifierTaskV1(Task): @classmethod diff --git a/tilebox-workflows/tests/runner/test_runtime_logging.py b/tilebox-workflows/tests/runner/test_runtime_logging.py index 69b4257..cc35171 100644 --- a/tilebox-workflows/tests/runner/test_runtime_logging.py +++ b/tilebox-workflows/tests/runner/test_runtime_logging.py @@ -1,3 +1,4 @@ +import asyncio import logging import os import subprocess @@ -9,14 +10,17 @@ import pytest from opentelemetry.sdk._logs.export import InMemoryLogRecordExporter, SimpleLogRecordProcessor +from protobuf import Oneof +from tests.log_values import string_value from tilebox.workflows import Client, Runner +from tilebox.workflows._otel.opentelemetry.proto.common.v1.common_pb import AnyValue from tilebox.workflows.observability import logging as observability from tilebox.workflows.observability._log_stream import _LogQueue, _OTLPQueueHandler from tilebox.workflows.observability._logging import internal_logger, logger, root_logger, task_logger from tilebox.workflows.observability.logging import _configure_runtime_logging, configure_console_logging from tilebox.workflows.runner.worker_service import WorkerServiceServicer -from tilebox.workflows.workflows.v1 import worker_pb2 +from tilebox.workflows.v1 import worker_pb @pytest.fixture(autouse=True) @@ -32,17 +36,17 @@ def isolated_handlers(monkeypatch: pytest.MonkeyPatch, caplog: pytest.LogCapture def test_worker_initialization_fallback_is_idempotent() -> None: exporter = InMemoryLogRecordExporter() - request = worker_pb2.InitializeRunnerRequest( - api_connection=worker_pb2.TileboxAPIConnection(url="https://legacy.example", token="legacy-key") # noqa: S106 + request = worker_pb.InitializeRunnerRequest( + api_connection=worker_pb.TileboxAPIConnection(url="https://legacy.example", token="legacy-key") # noqa: S106 ) with ( - patch("tilebox.workflows.client.open_channel"), + patch("tilebox.workflows.client.Connection"), patch("tilebox.workflows.client.WorkflowTracer"), patch.object(observability, "_otel_log_exporter", return_value=SimpleLogRecordProcessor(exporter)) as factory, ): for _ in range(2): service = WorkerServiceServicer(Runner(tasks=[]), lambda: None) - service.InitializeWorker(request, MagicMock()) + asyncio.run(service.initialize_worker(request, MagicMock())) factory.assert_called_once_with(endpoint="https://legacy.example", headers={"Authorization": "Bearer legacy-key"}) logger.error("legacy-runtime") assert [item.log_record.body for item in exporter.get_finished_logs()] == ["legacy-runtime"] @@ -51,7 +55,7 @@ def test_worker_initialization_fallback_is_idempotent() -> None: def test_direct_runner_initializes_on_connect_not_client_creation(capsys: pytest.CaptureFixture[str]) -> None: exporter = InMemoryLogRecordExporter() with ( - patch("tilebox.workflows.client.open_channel"), + patch("tilebox.workflows.client.Connection"), patch("tilebox.workflows.client.WorkflowTracer"), patch("tilebox.workflows.client.TaskRunner"), patch("tilebox.workflows.client._LeaseRenewer"), @@ -185,8 +189,9 @@ def test_console_opt_out_persists(before_startup: bool, capsys: pytest.CaptureFi assert output.getvalue().count("enabled again") == 1 +@pytest.mark.asyncio @pytest.mark.parametrize(("debug", "level"), [("false", "debug"), ("true", "error")]) -def test_independent_stream_levels(caplog: pytest.LogCaptureFixture, debug: str, level: str) -> None: +async def test_independent_stream_levels(caplog: pytest.LogCaptureFixture, debug: str, level: str) -> None: caplog.set_level(level.upper(), logger=task_logger.name) caplog.set_level(logging.DEBUG if debug == "true" else logging.ERROR, logger=internal_logger.name) records = _LogQueue() @@ -197,23 +202,25 @@ def test_independent_stream_levels(caplog: pytest.LogCaptureFixture, debug: str, logger.log(severity, f"internal-{severity}", task_id="preserved") workflow.log(severity, f"workflow-{severity}") records.seal() - exported = list(records.subscribe()) + exported = [record async for record in records.subscribe_async()] expected_internal = ( {"internal-10", "internal-20", "internal-30", "internal-40"} if debug == "true" else {"internal-40"} ) expected_workflow = ( {"workflow-10", "workflow-20", "workflow-30", "workflow-40"} if level == "debug" else {"workflow-40"} ) - assert {record.body.string_value for record in exported} == expected_internal | expected_workflow + assert {string_value(record.body) for record in exported} == expected_internal | expected_workflow assert len(exported) == len(expected_internal | expected_workflow) - internal = [record for record in exported if record.body.string_value.startswith("internal-")] + internal = [record for record in exported if string_value(record.body).startswith("internal-")] assert all( - {item.key: item.value.string_value for item in record.attributes}["task_id"] == "preserved" + {item.key: item.value for item in record.attributes}["task_id"] + == AnyValue(value=Oneof("string_value", "preserved")) for record in internal ) -def test_structured_stream_preserves_threaded_records(caplog: pytest.LogCaptureFixture) -> None: +@pytest.mark.asyncio +async def test_structured_stream_preserves_threaded_records(caplog: pytest.LogCaptureFixture) -> None: caplog.set_level(logging.WARNING, logger=task_logger.name) records = _LogQueue() root_logger.addHandler(_OTLPQueueHandler(records)) @@ -235,16 +242,16 @@ def log_failure() -> None: local_logger.exception("failed", extra={"tilebox_structured_log_attributes": {"task": 1}}) records.seal() - exported = list(records.subscribe()) - assert {record.body.string_value for record in exported} == { + exported = [record async for record in records.subscribe_async()] + assert {string_value(record.body) for record in exported} == { *(f"message-{index}" for index in range(20)), "failed", } - failure = next(record for record in exported if record.body.string_value == "failed") + failure = next(record for record in exported if string_value(record.body) == "failed") assert failure.severity_text == "ERROR" attributes = {item.key: item.value for item in failure.attributes} - assert "ValueError: first line\nsecond line" in attributes["exception.stacktrace"].string_value - assert attributes["task"].int_value == 1 + assert "ValueError: first line\nsecond line" in string_value(attributes["exception.stacktrace"]) + assert attributes["task"] == AnyValue(value=Oneof("int_value", 1)) @pytest.mark.parametrize("level", ["info", "error"]) @@ -263,6 +270,7 @@ def test_bootstrap_before_import(tmp_path: Path, level: str) -> None: sys.executable, "-c", """ +import asyncio from unittest.mock import MagicMock, patch from opentelemetry.sdk._logs.export import InMemoryLogRecordExporter exporter = InMemoryLogRecordExporter() @@ -288,7 +296,9 @@ def test_bootstrap_before_import(tmp_path: Path, level: str) -> None: ['import-info', 'import-error'] if task_logger.level == 20 else ['import-error'] ) managed_log_queue.seal() - assert [record.body.string_value for record in managed_log_queue.subscribe()] == ( + async def drain(): + return [record.body.value.value async for record in managed_log_queue.subscribe_async()] + assert asyncio.run(drain()) == ( ['import-info', 'import-error'] if task_logger.level == 20 else ['import-error'] ) """, diff --git a/tilebox-workflows/tests/runner/test_worker_concurrency.py b/tilebox-workflows/tests/runner/test_worker_concurrency.py index 551425b..1769a25 100644 --- a/tilebox-workflows/tests/runner/test_worker_concurrency.py +++ b/tilebox-workflows/tests/runner/test_worker_concurrency.py @@ -1,15 +1,15 @@ import asyncio import logging -import socket import threading +from concurrent.futures import ThreadPoolExecutor from datetime import UTC, datetime, timedelta +from pathlib import Path from typing import ClassVar from unittest.mock import MagicMock, patch from uuid import UUID, uuid4 -import grpc import pytest -from google.protobuf.empty_pb2 import Empty +from protobuf.wkt.google.protobuf.empty_pb import Empty from tilebox.datasets.uuid import must_uuid_to_uuid_message from tilebox.workflows import ExecutionContext, Runner, Task @@ -19,14 +19,16 @@ from tilebox.workflows.observability._logging import StructuredLogger from tilebox.workflows.observability.tracing import NoopWorkflowTracer from tilebox.workflows.runner.executor import LazyStorageLocations -from tilebox.workflows.runner.worker_server import serve_runner +from tilebox.workflows.runner.worker_server import WorkerServer from tilebox.workflows.task import TaskMeta -from tilebox.workflows.workflows.v1 import core_pb2, worker_pb2, worker_pb2_grpc +from tilebox.workflows.v1 import core_pb, worker_pb +from tilebox.workflows.v1.worker_connect import WorkerServiceClientSync def test_worker_executes_tasks_concurrently_with_isolated_execution_state( caplog: pytest.LogCaptureFixture, monkeypatch: pytest.MonkeyPatch, + tmp_path: Path, ) -> None: # Runtime identity is independent of the runner ID supplied by initialization. monkeypatch.setenv("TILEBOX_RUNTIME_ID", str(uuid4())) @@ -67,43 +69,43 @@ async def execute(self, context: ExecutionContext) -> None: fake_client._task_logger = StructuredLogger(logging.getLogger("tilebox.workflows.tests.shared-worker")) caplog.set_level(logging.INFO, logger="tilebox.workflows.tests.shared-worker") - with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as free_socket: - free_socket.bind(("127.0.0.1", 0)) - address = f"127.0.0.1:{free_socket.getsockname()[1]}" - - server_thread = threading.Thread(target=serve_runner, args=(runner, address), daemon=True) + monkeypatch.setenv("TILEBOX_RUNTIME_DIR", str(tmp_path)) + monkeypatch.setenv("TILEBOX_RUNTIME_TOKEN", "test-token") + server = WorkerServer(runner, "127.0.0.1:0") + headers = {"authorization": "Bearer test-token"} with ( patch("tilebox.workflows.runner.worker_service.Client", return_value=fake_client), patch("tilebox.workflows.runner.worker_service.initialize_logging") as initialize_logging, ): - server_thread.start() - channel = grpc.insecure_channel(address) - grpc.channel_ready_future(channel).result(timeout=5) - stub = worker_pb2_grpc.WorkerServiceStub(channel) - stub.InitializeWorker( - worker_pb2.InitializeRunnerRequest(runner_id=must_uuid_to_uuid_message(uuid4())), - timeout=5, + server.start() + stub = WorkerServiceClientSync(f"http://127.0.0.1:{server._port}", timeout_ms=5000) + stub.initialize_worker( + worker_pb.InitializeRunnerRequest(runner_id=must_uuid_to_uuid_message(uuid4())), + headers=headers, ) initialize_logging.assert_called_once_with(url="https://worker.example", token="worker-key") # noqa: S106 job = _job() tasks = [_task_message(ConcurrentTask(label), job) for label in ("first", "second")] - responses = [stub.ExecuteTask.future(task, timeout=5) for task in tasks] - - try: - results = [response.result() for response in responses] - finally: - stub.ShutdownWorker(Empty(), timeout=5) - channel.close() - server_thread.join(timeout=10) - - assert not server_thread.is_alive() + with ThreadPoolExecutor(max_workers=2) as pool: + responses = [pool.submit(stub.execute_task, task, headers=headers) for task in tasks] + try: + results = [response.result() for response in responses] + finally: + stub.shutdown_worker(Empty(), headers=headers) + stub.close() + server.wait() + + assert not server._thread.is_alive() assert len(SharedRunnerContext.instances) == 1 - assert all(result.HasField("computed_task") for result in results) - assert [result.computed_task.progress_updates[0].label for result in results] == ["first", "second"] - assert all(result.computed_task.progress_updates[0].total == 1 for result in results) - assert all(result.computed_task.progress_updates[0].done == 1 for result in results) + computed_tasks = [] + for result in results: + assert result.computed_task is not None + computed_tasks.append(result.computed_task) + assert [task.progress_updates[0].label for task in computed_tasks] == ["first", "second"] + assert all(task.progress_updates[0].total == 1 for task in computed_tasks) + assert all(task.progress_updates[0].done == 1 for task in computed_tasks) assert {observation[0] for observation in ConcurrentTask.observations} == {"first", "second"} assert len({observation[1] for observation in ConcurrentTask.observations}) == 2 @@ -118,7 +120,11 @@ async def execute(self, context: ExecutionContext) -> None: if record.message == "Concurrent task executing" ] assert {attributes["label"] for attributes in log_attributes} == {"first", "second"} - assert {attributes["task_id"] for attributes in log_attributes} == {str(UUID(bytes=task.id.uuid)) for task in tasks} + task_ids = set() + for task in tasks: + assert task.id is not None + task_ids.add(str(UUID(bytes=task.id.uuid))) + assert {attributes["task_id"] for attributes in log_attributes} == task_ids def test_lazy_storage_locations_are_loaded_once_during_concurrent_access() -> None: @@ -173,7 +179,7 @@ def _job() -> Job: ) -def _task_message(task: Task, job: Job) -> core_pb2.Task: +def _task_message(task: Task, job: Job) -> core_pb.Task: identifier = TaskMeta.for_task(task).identifier return TaskData( id=uuid4(), diff --git a/tilebox-workflows/tests/runner/test_worker_server.py b/tilebox-workflows/tests/runner/test_worker_server.py index 6c90278..0e8d478 100644 --- a/tilebox-workflows/tests/runner/test_worker_server.py +++ b/tilebox-workflows/tests/runner/test_worker_server.py @@ -1,75 +1,31 @@ +import http.client import json import os +import socket import subprocess import sys import tempfile import threading import time +from concurrent.futures import ThreadPoolExecutor from pathlib import Path -from typing import cast -from unittest.mock import MagicMock -import grpc import pytest -from google.protobuf.empty_pb2 import Empty +from connectrpc.code import Code +from connectrpc.errors import ConnectError +from protobuf.wkt.google.protobuf.empty_pb import Empty +from tests.log_values import string_value from tilebox.workflows import Runner +from tilebox.workflows._otel.opentelemetry.proto.logs.v1.logs_pb import LogRecord +from tilebox.workflows.observability._log_stream import _any_value, _LogQueue +from tilebox.workflows.runner import worker_server from tilebox.workflows.runner.worker_server import WorkerServer -from tilebox.workflows.runner.worker_service import WorkerServiceServicer -from tilebox.workflows.workflows.v1 import core_pb2, worker_pb2, worker_pb2_grpc +from tilebox.workflows.v1 import core_pb, worker_pb +from tilebox.workflows.v1.worker_connect import WorkerServiceClientSync _TOKEN = "test-runtime-token" # noqa: S105 -- local test credential -_AUTH = (("authorization", f"Bearer {_TOKEN}"),) - - -@pytest.mark.parametrize("address_from_environment", [False, True]) -def test_unmanaged_worker_serves_without_authentication_or_log_stream( - monkeypatch: pytest.MonkeyPatch, address_from_environment: bool -) -> None: - monkeypatch.delenv("TILEBOX_RUNTIME_DIR", raising=False) - monkeypatch.delenv("TILEBOX_RUNTIME_TOKEN", raising=False) - if address_from_environment: - monkeypatch.setenv("TILEBOX_WORKER_ADDRESS", "127.0.0.1:0") - server = WorkerServer(Runner(tasks=[]), None if address_from_environment else "127.0.0.1:0") - server.start() - try: - with grpc.insecure_channel(f"127.0.0.1:{server._port}") as channel: - worker = worker_pb2_grpc.WorkerServiceStub(channel) - assert not worker.ListRegisteredTasks(Empty(), timeout=2).identifiers - with pytest.raises(grpc.RpcError) as error: - next(worker.WatchLogs(Empty(), timeout=2)) - assert cast(grpc.Call, error.value).code() == grpc.StatusCode.UNAVAILABLE - worker.ShutdownWorker(Empty(), timeout=2) - finally: - server.shutdown() - server.wait() - - -def test_shutdown_waits_for_active_task_cleanup(monkeypatch: pytest.MonkeyPatch) -> None: - started, release, stopped = threading.Event(), threading.Event(), threading.Event() - service = WorkerServiceServicer(Runner(tasks=[]), stopped.set) - - def execute(_request: core_pb2.Task) -> worker_pb2.ExecuteTaskResponse: - started.set() - assert release.wait(timeout=2) - assert not stopped.is_set() - return worker_pb2.ExecuteTaskResponse() - - monkeypatch.setattr(service, "_execute_task", execute) - task = threading.Thread(target=service.ExecuteTask, args=(core_pb2.Task(), MagicMock())) - shutdown = threading.Thread(target=service.ShutdownWorker, args=(Empty(), MagicMock())) - task.start() - assert started.wait(timeout=1) - shutdown.start() - try: - assert not stopped.wait(timeout=0.05) - finally: - release.set() - task.join(timeout=2) - shutdown.join(timeout=2) - assert not task.is_alive() - assert not shutdown.is_alive() - assert stopped.is_set() +_AUTH = {"authorization": f"Bearer {_TOKEN}"} def _wait_for_file(path: Path, process: subprocess.Popen[str]) -> None: @@ -82,113 +38,214 @@ def _wait_for_file(path: Path, process: subprocess.Popen[str]) -> None: time.sleep(0.01) -def _assert_loading_rpc_contract(worker: worker_pb2_grpc.WorkerServiceStub) -> None: - for method, request in ( - (worker.ListRegisteredTasks, Empty()), - (worker.InitializeWorker, worker_pb2.InitializeRunnerRequest()), - (worker.ExecuteTask, core_pb2.Task()), - (worker.ShutdownWorker, Empty()), - ): - with pytest.raises(grpc.RpcError) as error: - method(request, timeout=2) - assert cast(grpc.Call, error.value).code() == grpc.StatusCode.UNAUTHENTICATED - with pytest.raises(grpc.RpcError) as error: - next(worker.WatchLogs(Empty(), timeout=2)) - assert cast(grpc.Call, error.value).code() == grpc.StatusCode.UNAUTHENTICATED - for method, request in ( - (worker.ListRegisteredTasks, Empty()), - (worker.InitializeWorker, worker_pb2.InitializeRunnerRequest()), - (worker.ExecuteTask, core_pb2.Task()), - ): - with pytest.raises(grpc.RpcError) as error: - method(request, metadata=_AUTH, timeout=2) - assert cast(grpc.Call, error.value).code() == grpc.StatusCode.UNAVAILABLE - - -def _wait_until_loaded(worker: worker_pb2_grpc.WorkerServiceStub) -> None: - deadline = time.monotonic() + 5 - while True: - try: - assert not worker.ListRegisteredTasks(Empty(), metadata=_AUTH, timeout=2).identifiers - except grpc.RpcError as error: - if cast(grpc.Call, error).code() != grpc.StatusCode.UNAVAILABLE: - raise - assert time.monotonic() < deadline - time.sleep(0.01) - else: - return - - -@pytest.mark.parametrize( - "transport", ["tcp", pytest.param("unix", marks=pytest.mark.skipif(os.name == "nt", reason="Unix socket"))] -) @pytest.mark.parametrize("fail_import", [False, True]) -def test_managed_worker_bootstrap_logs_and_shutdown(tmp_path: Path, transport: str, fail_import: bool) -> None: - # Short Unix paths fit sockaddr_un even when pytest's temporary path is long. - with tempfile.TemporaryDirectory(prefix="tbw-", dir="/tmp" if transport == "unix" else None) as directory: # noqa: S108 -- private random directory, short enough for sockaddr_un - runtime_dir = Path(directory) - address = f"unix://{runtime_dir / 'worker.sock'}" if transport == "unix" else "127.0.0.1:0" - source = """ +def test_managed_worker_bootstrap_logs_and_shutdown(tmp_path: Path, fail_import: bool) -> None: # noqa: PLR0915 -- full process lifecycle + """Exercise authenticated real HTTP/1 RPCs while workflow imports block the main thread.""" + source = """ import time from pathlib import Path from tilebox.workflows import Runner from tilebox.workflows.observability.logging import get_logger - get_logger().warning("before workflow loaded") print("raw stdout", flush=True) while not Path("load").exists(): time.sleep(0.01) """ - source += 'raise RuntimeError("broken workflow")\n' if fail_import else "runner = Runner(tasks=[])\n" - (tmp_path / "fixture.py").write_text(source) - environment = {key: value for key, value in os.environ.items() if not key.startswith("TILEBOX_")} - environment.update( - TILEBOX_WORKER_ADDRESS=address, - TILEBOX_RUNTIME_DIR=directory, - TILEBOX_RUNTIME_TOKEN=_TOKEN, - ) - with subprocess.Popen( - [sys.executable, "-m", "tilebox.workflows.runner", "fixture:runner"], - cwd=tmp_path, - env=environment, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - text=True, - ) as process: + source += 'raise RuntimeError("broken workflow")\n' if fail_import else "runner = Runner(tasks=[])\n" + (tmp_path / "fixture.py").write_text(source) + environment = {key: value for key, value in os.environ.items() if not key.startswith("TILEBOX_")} + environment.update( + TILEBOX_WORKER_ADDRESS="127.0.0.1:0", TILEBOX_RUNTIME_DIR=str(tmp_path), TILEBOX_RUNTIME_TOKEN=_TOKEN + ) + with subprocess.Popen( + [sys.executable, "-m", "tilebox.workflows.runner", "fixture:runner"], + cwd=tmp_path, + env=environment, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + ) as process: + try: + announcement = tmp_path / "endpoint.json" + _wait_for_file(announcement, process) + endpoint = json.loads(announcement.read_text()) + assert endpoint["version"] == 2 + assert endpoint["protocol"] == "connect" + assert _TOKEN not in announcement.read_text() + assert not (tmp_path / "endpoint.json.tmp").exists() + with WorkerServiceClientSync(f"http://{endpoint['address']}", timeout_ms=3000) as worker: + for call in ( + lambda: worker.list_registered_tasks(Empty()), + lambda: worker.initialize_worker(worker_pb.InitializeRunnerRequest()), + lambda: worker.execute_task(core_pb.Task()), + lambda: worker.shutdown_worker(Empty()), + ): + with pytest.raises(ConnectError) as error: + call() + assert error.value.code == Code.UNAUTHENTICATED + with pytest.raises(ConnectError) as error: + next(worker.watch_logs(Empty())) + assert error.value.code == Code.UNAUTHENTICATED + for call in ( + lambda: worker.list_registered_tasks(Empty(), headers=_AUTH), + lambda: worker.initialize_worker(worker_pb.InitializeRunnerRequest(), headers=_AUTH), + lambda: worker.execute_task(core_pb.Task(), headers=_AUTH), + ): + with pytest.raises(ConnectError) as error: + call() + assert error.value.code == Code.UNAVAILABLE + logs = worker.watch_logs(Empty(), headers=_AUTH, timeout_ms=10000) + assert string_value(next(logs).body) == "before workflow loaded" + with pytest.raises(ConnectError) as error: + next(worker.watch_logs(Empty(), headers=_AUTH)) + assert error.value.code == Code.ALREADY_EXISTS + (tmp_path / "load").touch() + if not fail_import: + deadline = time.monotonic() + 5 + while True: + try: + assert not worker.list_registered_tasks(Empty(), headers=_AUTH).identifiers + break + except ConnectError as error: + if error.code != Code.UNAVAILABLE: + raise + assert time.monotonic() < deadline + time.sleep(0.01) + worker.shutdown_worker(Empty(), headers=_AUTH) + assert list(logs) == [] + stdout, stderr = process.communicate(timeout=10) + assert stdout == "raw stdout\n" + if fail_import: + assert process.returncode != 0 + assert "broken workflow" in stderr + else: + assert process.returncode == 0, stderr + finally: + if process.poll() is None: + process.kill() + process.communicate(timeout=5) + + +@pytest.mark.skipif(os.name == "nt", reason="Unix socket") +def test_unix_worker_authenticates_real_connect_requests(monkeypatch: pytest.MonkeyPatch) -> None: + """Verify Unix permissions, endpoint discovery, and auth without a gRPC transport.""" + with tempfile.TemporaryDirectory(prefix="tbw-", dir="/tmp") as directory: + path = Path(directory) + monkeypatch.setenv("TILEBOX_RUNTIME_DIR", directory) + monkeypatch.setenv("TILEBOX_RUNTIME_TOKEN", _TOKEN) + server = WorkerServer(Runner(tasks=[]), f"unix://{path / 'worker.sock'}") + server.start() + try: + assert (path / "worker.sock").stat().st_mode & 0o777 == 0o600 + assert json.loads((path / "endpoint.json").read_text())["protocol"] == "connect" + for authorized in (False, True): + connection = http.client.HTTPConnection("localhost", timeout=3) + connection.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + connection.sock.settimeout(3) + connection.sock.connect(str(path / "worker.sock")) + try: + headers = {"Content-Type": "application/proto", **(_AUTH if authorized else {})} + connection.request("POST", "/workflows.v1.WorkerService/ListRegisteredTasks", b"", headers) + response = connection.getresponse() + assert response.status == (200 if authorized else 401) + if authorized: + assert not core_pb.UnboundTaskIdentifiers.from_binary(response.read()).identifiers + finally: + connection.close() + finally: + server.shutdown() + server.wait() + + +def test_tcp_worker_requires_authentication(monkeypatch: pytest.MonkeyPatch) -> None: + """Reject unsecured TCP listeners and non-loopback addresses before binding.""" + monkeypatch.delenv("TILEBOX_RUNTIME_DIR", raising=False) + monkeypatch.delenv("TILEBOX_RUNTIME_TOKEN", raising=False) + with pytest.raises(RuntimeError, match="authentication"): + WorkerServer(address="127.0.0.1:0") + with pytest.raises(RuntimeError, match="loopback"): + WorkerServer(address="0.0.0.0:0") + + +def test_log_cancellation_allows_reconnection_without_stopping_worker( + monkeypatch: pytest.MonkeyPatch, + tmp_path: Path, +) -> None: + """Cancel an HTTP/1 log stream, reconnect, and retain unary readiness.""" + monkeypatch.setenv("TILEBOX_RUNTIME_DIR", str(tmp_path)) + monkeypatch.setenv("TILEBOX_RUNTIME_TOKEN", _TOKEN) + records = _LogQueue() + monkeypatch.setattr(worker_server, "managed_log_queue", records) + records.submit(LogRecord(body=_any_value("first"))) + server = WorkerServer(Runner(tasks=[]), "127.0.0.1:0") + server.start() + try: + # Disposing the transport cancels the response without assuming that the + # public Iterator return type exposes generator-specific close methods. + with WorkerServiceClientSync(f"http://127.0.0.1:{server._port}", timeout_ms=2000) as initial_client: + stream = initial_client.watch_logs(Empty(), headers=_AUTH) + assert string_value(next(stream).body) == "first" + with WorkerServiceClientSync(f"http://127.0.0.1:{server._port}", timeout_ms=2000) as client: + deadline = time.monotonic() + 2 + # Do not enqueue a record to wake the old stream: disconnection must + # release even an idle subscriber, and delivery across cancel races + # is best-effort rather than an acknowledgement protocol. + while True: + stream = client.watch_logs(Empty(), headers=_AUTH, timeout_ms=100) + try: + next(stream) + pytest.fail("Idle WatchLogs unexpectedly returned a record") + except ConnectError as error: + if error.code == Code.DEADLINE_EXCEEDED: + break # Accepted a new subscription without any queued records. + if error.code != Code.ALREADY_EXISTS: + raise + assert time.monotonic() < deadline + time.sleep(0.01) + assert not client.list_registered_tasks(Empty(), headers=_AUTH).identifiers + client.shutdown_worker(Empty(), headers=_AUTH) + finally: + server.shutdown() + server.wait() + + +def test_task_deadline_does_not_skip_cleanup_on_shutdown(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None: + """A canceled execution keeps cleanup active while unrelated unary RPCs remain responsive.""" + monkeypatch.setenv("TILEBOX_RUNTIME_DIR", str(tmp_path)) + monkeypatch.setenv("TILEBOX_RUNTIME_TOKEN", _TOKEN) + started, release, cleaned = threading.Event(), threading.Event(), threading.Event() + server = WorkerServer(Runner(tasks=[]), "127.0.0.1:0") + + def execute(_request: core_pb.Task) -> worker_pb.ExecuteTaskResponse: + started.set() + try: + assert release.wait(timeout=3) + return worker_pb.ExecuteTaskResponse() + finally: + cleaned.set() + + monkeypatch.setattr(server._service, "_execute_task", execute) + server.start() + try: + with ( + WorkerServiceClientSync(f"http://127.0.0.1:{server._port}", timeout_ms=2000) as client, + ThreadPoolExecutor(max_workers=2) as pool, + ): + task = pool.submit(client.execute_task, core_pb.Task(), headers=_AUTH, timeout_ms=100) + assert started.wait(timeout=1) + with pytest.raises(ConnectError) as error: + task.result(timeout=2) + assert error.value.code == Code.DEADLINE_EXCEEDED + assert not client.list_registered_tasks(Empty(), headers=_AUTH).identifiers + shutdown = pool.submit(client.shutdown_worker, Empty(), headers=_AUTH) try: - announcement = runtime_dir / "endpoint.json" - _wait_for_file(announcement, process) - endpoint = json.loads(announcement.read_text()) - assert endpoint["version"] == 1 - assert _TOKEN not in announcement.read_text() - assert not (runtime_dir / "endpoint.json.tmp").exists() - if transport == "unix": - assert endpoint["address"] == address - else: - assert endpoint["address"].startswith("127.0.0.1:") - assert int(endpoint["address"].split(":")[1]) > 0 - with grpc.insecure_channel(endpoint["address"]) as channel: - worker = worker_pb2_grpc.WorkerServiceStub(channel) - _assert_loading_rpc_contract(worker) - logs = worker.WatchLogs(Empty(), metadata=_AUTH, timeout=10) - record = next(logs) - assert record.body.string_value == "before workflow loaded" - with pytest.raises(grpc.RpcError) as error: - next(worker.WatchLogs(Empty(), metadata=_AUTH, timeout=2)) - assert cast(grpc.Call, error.value).code() == grpc.StatusCode.ALREADY_EXISTS - (tmp_path / "load").touch() - if not fail_import: - _wait_until_loaded(worker) - worker.ShutdownWorker(Empty(), metadata=_AUTH, timeout=5) - assert list(logs) == [] - stdout, stderr = process.communicate(timeout=10) - assert stdout == "raw stdout\n" - if fail_import: - assert process.returncode != 0 - assert "broken workflow" in stderr - else: - assert process.returncode == 0, stderr + time.sleep(0.05) + assert not shutdown.done() finally: - if process.poll() is None: - process.kill() - process.communicate(timeout=5) + release.set() + shutdown.result(timeout=2) + assert cleaned.is_set() + finally: + release.set() + server.shutdown() + server.wait() diff --git a/tilebox-workflows/tests/storage_locations/test_client.py b/tilebox-workflows/tests/storage_locations/test_client.py index 8284f36..a60e579 100644 --- a/tilebox-workflows/tests/storage_locations/test_client.py +++ b/tilebox-workflows/tests/storage_locations/test_client.py @@ -1,15 +1,16 @@ from dataclasses import replace from datetime import UTC, datetime +from unittest.mock import MagicMock from uuid import UUID import pytest -from google.protobuf.empty_pb2 import Empty +from protobuf.wkt.google.protobuf.empty_pb import Empty -from _tilebox.grpc.error import NotFoundError +from _tilebox.errors import NotFoundError +from _tilebox.proto.tilebox.v1.id_pb import ID +from _tilebox.proto.tilebox.v1.query_pb import Pagination as PaginationMessage from tilebox.datasets.query.pagination import Pagination from tilebox.datasets.query.time_interval import datetime_to_timestamp -from tilebox.datasets.tilebox.v1.id_pb2 import ID -from tilebox.datasets.tilebox.v1.query_pb2 import Pagination as PaginationMessage from tilebox.datasets.uuid import uuid_message_to_uuid, uuid_to_uuid_message from tilebox.workflows.data import ( AWSS3BucketReference, @@ -29,8 +30,8 @@ StorageSubscriptionEvent, TriggeredJob, ) -from tilebox.workflows.storage_locations.service import StorageLocationService -from tilebox.workflows.workflows.v1 import storage_location_pb2 as storage_pb +from tilebox.workflows.v1 import storage_location_pb as storage_pb +from tilebox.workflows.v1.storage_location_connect import StorageLocationServiceClientSync _ID = UUID(int=1) _OTHER_ID = UUID(int=2) @@ -42,7 +43,7 @@ def __init__(self) -> None: self.locations: dict[UUID, storage_pb.StorageLocation] = {} self.events: dict[UUID, list[storage_pb.StorageSubscriptionEvent]] = {} - def CreateStorageLocation(self, request: storage_pb.CreateStorageLocationRequest) -> storage_pb.StorageLocation: # noqa: N802 + def create_storage_location(self, request: storage_pb.CreateStorageLocationRequest) -> storage_pb.StorageLocation: location_id = UUID(int=len(self.locations) + 1) location = storage_pb.StorageLocation( id=uuid_to_uuid_message(location_id), name=request.name, reference=request.reference @@ -50,28 +51,30 @@ def CreateStorageLocation(self, request: storage_pb.CreateStorageLocationRequest self.locations[location_id] = location return location - def GetStorageLocation(self, request: ID) -> storage_pb.StorageLocation: # noqa: N802 + def get_storage_location(self, request: ID) -> storage_pb.StorageLocation: try: return self.locations[uuid_message_to_uuid(request)] except KeyError: raise NotFoundError("Unknown storage location") from None - def ListStorageLocations(self, request: Empty) -> storage_pb.StorageLocations: # noqa: N802, ARG002 - return storage_pb.StorageLocations(locations=self.locations.values()) + def list_storage_locations(self, request: Empty) -> storage_pb.StorageLocations: # noqa: ARG002 + return storage_pb.StorageLocations(locations=list(self.locations.values())) - def UpdateStorageLocation(self, request: storage_pb.UpdateStorageLocationRequest) -> storage_pb.StorageLocation: # noqa: N802 - location = self.GetStorageLocation(request.storage_location_id) + def update_storage_location(self, request: storage_pb.UpdateStorageLocationRequest) -> storage_pb.StorageLocation: + assert request.storage_location_id is not None + location = self.get_storage_location(request.storage_location_id) location.name = request.name return location - def ListStorageSubscriptionEvents( # noqa: N802 + def list_storage_subscription_events( self, request: storage_pb.ListStorageSubscriptionEventsRequest ) -> storage_pb.StorageSubscriptionEvents: events = self.events.get(uuid_message_to_uuid(request.storage_location_id), []) - if request.page.HasField("starting_after"): - index = next(i for i, event in enumerate(events) if event.id == request.page.starting_after) + 1 + page = request.page or PaginationMessage() + if page.has_field("starting_after"): + index = next(i for i, event in enumerate(events) if event.id == page.starting_after) + 1 events = events[index:] - limit = request.page.limit or len(events) + limit = page.limit or len(events) return storage_pb.StorageSubscriptionEvents( events=events[:limit], next_page=PaginationMessage(limit=limit, starting_after=events[limit - 1].id) @@ -117,7 +120,7 @@ def ListStorageSubscriptionEvents( # noqa: N802 def test_storage_location_operations( reference: StorageLocationReference, expected: StorageLocation, expected_string: str ) -> None: - client = StorageLocationClient(StorageLocationService(MockStorageLocationService())) + client = StorageLocationClient(MagicMock(spec=StorageLocationServiceClientSync, wraps=MockStorageLocationService())) assert client.all() == [] created = client.create("Source", reference) other = client.create("Other", LocalReference("/data/other")) @@ -125,10 +128,10 @@ def test_storage_location_operations( assert str(created) == expected_string message = created.to_message() assert message.location == "" - assert message.type == storage_pb.STORAGE_TYPE_UNSPECIFIED + assert message.type == storage_pb.StorageType.UNSPECIFIED assert StorageLocation.from_message(message) == expected message.location = "ignored-legacy-location" - message.type = storage_pb.STORAGE_TYPE_LOCAL if expected.type != StorageType.LOCAL else storage_pb.STORAGE_TYPE_GCS + message.type = storage_pb.StorageType.LOCAL if expected.type != StorageType.LOCAL else storage_pb.StorageType.GCS assert StorageLocation.from_message(message) == expected assert client.all() == [expected, other] for identifier in (created, created.id, str(created.id)): @@ -142,7 +145,7 @@ def test_storage_location_operations( def test_storage_location_requires_reference() -> None: message = storage_pb.StorageLocation( - id=uuid_to_uuid_message(_ID), location="legacy-bucket", type=storage_pb.STORAGE_TYPE_AWS_S3 + id=uuid_to_uuid_message(_ID), location="legacy-bucket", type=storage_pb.StorageType.AWS_S3 ) with pytest.raises(ValueError, match="Unsupported storage reference type"): StorageLocation.from_message(message) @@ -150,14 +153,14 @@ def test_storage_location_requires_reference() -> None: def test_storage_event_service_returns_one_page_and_cursor() -> None: service = MockStorageLocationService() - storage_service = StorageLocationService(service) + storage_service = StorageLocationClient(MagicMock(spec=StorageLocationServiceClientSync, wraps=service)) event_time = datetime(2026, 10, 7, 12, 34, 56, 123456, tzinfo=UTC) received_at = datetime(2026, 10, 8, 1, 2, 3, 654321, tzinfo=UTC) service.events[_ID] = [ storage_pb.StorageSubscriptionEvent( id=uuid_to_uuid_message(UUID(int=10)), object_key="nested/first.txt", - type=storage_pb.STORAGE_EVENT_TYPE_CREATED, + type=storage_pb.StorageEventType.CREATED, event_time=datetime_to_timestamp(event_time), received_at=datetime_to_timestamp(received_at), storage_subscription_id=uuid_to_uuid_message(UUID(int=20)), @@ -172,7 +175,7 @@ def test_storage_event_service_returns_one_page_and_cursor() -> None: storage_pb.StorageSubscriptionEvent( id=uuid_to_uuid_message(UUID(int=11)), object_key="second.bin", - type=storage_pb.STORAGE_EVENT_TYPE_CREATED, + type=storage_pb.StorageEventType.CREATED, event_time=datetime_to_timestamp(received_at), received_at=datetime_to_timestamp(received_at), storage_subscription_id=uuid_to_uuid_message(UUID(int=21)), diff --git a/tilebox-workflows/tests/test_client.py b/tilebox-workflows/tests/test_client.py index d92887d..5977195 100644 --- a/tilebox-workflows/tests/test_client.py +++ b/tilebox-workflows/tests/test_client.py @@ -1,16 +1,47 @@ +from pathlib import Path from unittest.mock import patch +from uuid import UUID import pytest +from _tilebox.errors import NotFoundError +from _tilebox.replay import Replay from tilebox.workflows import Client -def test_http1_trusts_system_certificates() -> None: - from pyqwest import HTTPVersion # noqa: PLC0415 +def test_client_closes_connection() -> None: + with patch("tilebox.workflows.client.Connection") as connection, patch("tilebox.workflows.client.WorkflowTracer"): + with Client(url="https://api.tilebox.com", token="test-key"): # noqa: S106 + connection.return_value.close.assert_not_called() + connection.return_value.close.assert_called_once() - with patch("pyqwest.SyncHTTPTransport") as transport, patch("tilebox.workflows.client.WorkflowTracer"): - Client(url="https://api.tilebox.com", token="test-key", transport="http1") # noqa: S106 - transport.assert_called_once_with(http_version=HTTPVersion.HTTP1, tls_include_system_certs=True) + +@pytest.mark.parametrize( + ("client_name", "rpc_method", "identifier"), + [ + ("jobs", "/workflows.v1.JobService/GetJob", UUID(int=1)), + ("clusters", "/workflows.v1.WorkflowsService/GetCluster", "missing"), + ("workflows", "/workflows.v1.WorkflowsService/GetWorkflow", "missing"), + ("automations", "/workflows.v1.AutomationService/GetAutomation", UUID(int=1)), + ("storage_locations", "/workflows.v1.StorageLocationService/GetStorageLocation", UUID(int=1)), + ], +) +def test_public_clients_translate_connect_errors( + tmp_path: Path, client_name: str, rpc_method: str, identifier: UUID | str +) -> None: + """Propagate replayed Connect failures through the shared connection as public errors.""" + recording = tmp_path / "not-found.rpcs.bin" + recording.write_text(f"{rpc_method}\n\n5\nResource not found\n") + with ( + patch("tilebox.workflows.client.WorkflowTracer"), + Client( + url="https://api.tilebox.com", + token="test-key", # noqa: S106 + interceptors=[Replay(recording, assert_request_matches=False)], + ) as client, + pytest.raises(NotFoundError, match="Resource not found"), + ): + getattr(client, client_name)().find(identifier) @pytest.mark.parametrize( @@ -34,10 +65,10 @@ def test_client_url_environment( monkeypatch.setenv("TILEBOX_API_URL", environment_url) monkeypatch.setenv("TILEBOX_API_KEY", "runner-key") with ( - patch("tilebox.workflows.client.open_channel") as open_channel_mock, + patch("tilebox.workflows.client.Connection") as connection_mock, patch("tilebox.workflows.client.WorkflowTracer") as tracer_mock, ): client = Client() if explicit_url is None else Client(url=explicit_url) - open_channel_mock.assert_called_once_with(expected_url, "runner-key") + connection_mock.assert_called_once_with(expected_url, "runner-key", transport="http2", interceptors=()) tracer_mock.assert_called_once_with(service=None, url=expected_url, token="runner-key") # noqa: S106 assert client._auth == {"url": expected_url, "token": "runner-key"} diff --git a/tilebox-workflows/tests/test_data.py b/tilebox-workflows/tests/test_data.py index bc66297..6e55257 100644 --- a/tilebox-workflows/tests/test_data.py +++ b/tilebox-workflows/tests/test_data.py @@ -4,6 +4,7 @@ import pytest from hypothesis import given +from protobuf import Oneof from tests.tasks_data import ( artifacts, @@ -27,6 +28,11 @@ workflow_releases, workflows, ) +from tilebox.workflows._otel.opentelemetry.proto.common.v1.common_pb import AnyValue, ArrayValue, KeyValue +from tilebox.workflows._otel.opentelemetry.proto.logs.v1.logs_pb import LogRecord as NativeLogRecord +from tilebox.workflows._otel.opentelemetry.proto.logs.v1.logs_pb import ResourceLogs, ScopeLogs +from tilebox.workflows._otel.opentelemetry.proto.trace.v1.trace_pb import ResourceSpans, ScopeSpans +from tilebox.workflows._otel.opentelemetry.proto.trace.v1.trace_pb import Span as NativeSpan from tilebox.workflows.data import ( Artifact, AutomationPrototype, @@ -37,10 +43,12 @@ Idling, Job, JobState, + LogRecord, ProgressIndicator, QueryFilters, ReleaseContent, SingleTaskSubmission, + Span, StorageLocation, Task, TaskIdentifier, @@ -52,7 +60,7 @@ WorkflowRelease, ) from tilebox.workflows.formatting.job import JobWidget, RichDisplayJob -from tilebox.workflows.workflows.v1 import workflows_pb2 +from tilebox.workflows.v1 import workflows_pb @given(task_identifiers()) @@ -182,19 +190,19 @@ def test_workflows_to_message_and_back(workflow: Workflow) -> None: ], ) def test_workflow_summary_and_presentation(presentation: WorkflowPresentation | None) -> None: - message = workflows_pb2.Workflow( - slug="flood-extent", + message = workflows_pb.Workflow( + reference="flood-extent", name="Flood extent", summary="Map flooded areas", description="A longer description of the workflow.", - presentation=workflows_pb2.WorkflowPresentation(**vars(presentation)) if presentation is not None else None, + presentation=workflows_pb.WorkflowPresentation(**vars(presentation)) if presentation is not None else None, ) workflow = Workflow.from_message(message) assert workflow.summary == "Map flooded areas" assert workflow.description == "A longer description of the workflow." assert workflow.presentation == presentation assert workflow.to_message() == message - assert workflow.to_message().HasField("presentation") == (presentation is not None) + assert workflow.to_message().has_field("presentation") == (presentation is not None) @given(task_submissions()) @@ -235,3 +243,49 @@ def test_automation_to_message_and_back(automation: AutomationPrototype) -> None @given(query_filters()) def test_query_filters_to_message_and_back(filters: QueryFilters) -> None: assert QueryFilters.from_message(filters.to_message()) == filters + + +def test_native_log_values_preserve_false_and_nested_values() -> None: + """Decode native OTEL oneofs directly without losing false, zero, or nested values.""" + native = ResourceLogs( + scope_logs=[ + ScopeLogs( + log_records=[ + NativeLogRecord( + body=AnyValue(value=Oneof("string_value", "hello")), + attributes=[ + KeyValue( + key="values", + value=AnyValue( + value=Oneof( + "array_value", + ArrayValue( + values=[ + AnyValue(value=Oneof("bool_value", False)), + AnyValue(value=Oneof("int_value", 0)), + AnyValue(value=Oneof("bytes_value", b"\x00\xff")), + ] + ), + ) + ), + ) + ], + ) + ] + ) + ] + ) + record = LogRecord.from_message(ResourceLogs.from_binary(native.to_binary())) + assert record.body == "hello" + assert record.attributes == {"values": [False, 0, b"\x00\xff"]} + assert record.runner_attributes == {} + assert LogRecord.from_message(record.to_message()) == record + + +def test_native_span_without_status_or_resource() -> None: + """Treat omitted OTEL status and resource messages as their domain defaults.""" + span = Span.from_message(ResourceSpans(scope_spans=[ScopeSpans(spans=[NativeSpan(name="task")])])) + assert span.status_code == "STATUS_CODE_UNSET" + assert span.status_message == "" + assert span.runner_attributes == {} + assert Span.from_message(span.to_message()) == span diff --git a/tilebox-workflows/tests/test_task.py b/tilebox-workflows/tests/test_task.py index d92e6ce..e3a6557 100644 --- a/tilebox-workflows/tests/test_task.py +++ b/tilebox-workflows/tests/test_task.py @@ -5,7 +5,7 @@ import pytest -from tests.proto.test_pb2 import SampleArgs +from tests.proto.test_pb import SampleArgs from tilebox.workflows.cache import InMemoryCache from tilebox.workflows.data import TaskIdentifier from tilebox.workflows.runner.task_runner import ExecutionContext as RunnerExecutionContext diff --git a/tilebox-workflows/tests/test_task_serialization.py b/tilebox-workflows/tests/test_task_serialization.py index ac2de74..d020f5f 100644 --- a/tilebox-workflows/tests/test_task_serialization.py +++ b/tilebox-workflows/tests/test_task_serialization.py @@ -27,7 +27,7 @@ Polygon, ) -from tests.proto.test_pb2 import SampleArgs +from tests.proto.test_pb import SampleArgs from tilebox.datasets.data.data_access import SpatialFilter from tilebox.datasets.query.id_interval import IDInterval from tilebox.datasets.query.time_interval import TimeInterval @@ -175,10 +175,8 @@ class OptionalSingleProtobufTask(Task): def test_optional_single_protobuf_preserves_raw_envelope() -> None: message = SampleArgs(some_string="hello", some_int=123) - assert serialize_task(OptionalSingleProtobufTask(message)) == message.SerializeToString() - assert deserialize_task(OptionalSingleProtobufTask, message.SerializeToString()) == OptionalSingleProtobufTask( - message - ) + assert serialize_task(OptionalSingleProtobufTask(message)) == message.to_binary() + assert deserialize_task(OptionalSingleProtobufTask, message.to_binary()) == OptionalSingleProtobufTask(message) assert deserialize_task(OptionalSingleProtobufTask, b"null") == OptionalSingleProtobufTask(None) @@ -343,16 +341,27 @@ class WindowTask(Task): def test_optional_integrations_are_not_imported_by_task_module() -> None: script = """ import builtins +from datetime import UTC, datetime +from uuid import UUID original_import = builtins.__import__ def guarded_import(name, *args, **kwargs): - if name.startswith(('odc.geo', 'pyproj', 'affine', 'rasterio', 'async_geotiff')): + if name.startswith(('tilebox.datasets', 'pandas', 'numpy', 'xarray', 'shapely', 'odc.geo', 'pyproj', 'affine', 'rasterio', 'async_geotiff')): raise ModuleNotFoundError(name) return original_import(name, *args, **kwargs) builtins.__import__ = guarded_import -from tilebox.workflows.task import Task +from tilebox.query import IDInterval, TimeInterval +from tilebox.workflows.task import Task, serialize_task, deserialize_task class Example(Task): value: str assert Example('works')._serialize() == b'\"works\"' +class Intervals(Task): + time: TimeInterval + ids: IDInterval +task = Intervals( + TimeInterval.parse((datetime(2026, 1, 1, tzinfo=UTC), datetime(2026, 1, 2, tzinfo=UTC))), + IDInterval.parse((UUID(int=1), UUID(int=9))), +) +assert deserialize_task(Intervals, serialize_task(task)) == task """ subprocess.run([sys.executable, "-c", script], check=True) # noqa: S603 diff --git a/tilebox-workflows/tests/workflows/test_client.py b/tilebox-workflows/tests/workflows/test_client.py index 3f99762..d4c23b7 100644 --- a/tilebox-workflows/tests/workflows/test_client.py +++ b/tilebox-workflows/tests/workflows/test_client.py @@ -2,7 +2,7 @@ from unittest.mock import MagicMock from uuid import UUID, uuid4 -from _tilebox.grpc.error import NotFoundError +from _tilebox.errors import NotFoundError from tilebox.datasets.query.time_interval import datetime_to_timestamp from tilebox.datasets.uuid import uuid_message_to_uuid, uuid_to_uuid_message from tilebox.workflows.data import ( @@ -14,12 +14,11 @@ Workflow, WorkflowRelease, ) -from tilebox.workflows.workflows.client import WorkflowClient -from tilebox.workflows.workflows.service import WorkflowService -from tilebox.workflows.workflows.v1.workflows_pb2 import ( +from tilebox.workflows.v1.workflows_connect import WorkflowsServiceClientSync +from tilebox.workflows.v1.workflows_pb import ( Cluster as ClusterMessage, ) -from tilebox.workflows.workflows.v1.workflows_pb2 import ( +from tilebox.workflows.v1.workflows_pb import ( CreateWorkflowRequest, DeleteWorkflowRequest, DeployWorkflowReleaseRequest, @@ -32,17 +31,17 @@ UnpublishWorkflowReleaseRequest, UpdateWorkflowRequest, ) -from tilebox.workflows.workflows.v1.workflows_pb2 import ( +from tilebox.workflows.v1.workflows_pb import ( Workflow as WorkflowMessage, ) -from tilebox.workflows.workflows.v1.workflows_pb2 import ( +from tilebox.workflows.v1.workflows_pb import ( WorkflowRelease as WorkflowReleaseMessage, ) -from tilebox.workflows.workflows.v1.workflows_pb2_grpc import WorkflowsServiceStub +from tilebox.workflows.workflows.client import WorkflowClient -class MockWorkflowService(WorkflowsServiceStub): - """A mock implementation of the gRPC workflow service, that stores workflows in memory as a dict.""" +class MockWorkflowService: + """Store workflows in memory for client behavior tests.""" def __init__(self) -> None: self.workflows: dict[str, WorkflowMessage] = {} @@ -51,53 +50,53 @@ def __init__(self) -> None: self.undeploy_requests: list[UndeployWorkflowReleaseRequest] = [] self.update_requests: list[UpdateWorkflowRequest] = [] - def CreateWorkflow(self, req: CreateWorkflowRequest) -> WorkflowMessage: # noqa: N802 + def create_workflow(self, req: CreateWorkflowRequest) -> WorkflowMessage: slug = req.name.lower().replace(" ", "-") - workflow = WorkflowMessage(slug=slug, name=req.name, description=req.description, summary=req.summary) - self.workflows[workflow.slug] = workflow + workflow = WorkflowMessage(reference=slug, name=req.name, description=req.description, summary=req.summary) + self.workflows[workflow.reference] = workflow return workflow - def ListWorkflows(self, req: ListWorkflowsRequest) -> ListWorkflowsResponse: # noqa: N802 + def list_workflows(self, req: ListWorkflowsRequest) -> ListWorkflowsResponse: _ = req return ListWorkflowsResponse(workflows=list(self.workflows.values())) - def GetWorkflow(self, req: GetWorkflowRequest) -> WorkflowMessage: # noqa: N802 - if req.workflow_slug in self.workflows: - return self.workflows[req.workflow_slug] - raise NotFoundError(f"Workflow {req.workflow_slug} not found") + def get_workflow(self, req: GetWorkflowRequest) -> WorkflowMessage: + if req.workflow_reference in self.workflows: + return self.workflows[req.workflow_reference] + raise NotFoundError(f"Workflow {req.workflow_reference} not found") - def UpdateWorkflow(self, req: UpdateWorkflowRequest) -> WorkflowMessage: # noqa: N802 + def update_workflow(self, req: UpdateWorkflowRequest) -> WorkflowMessage: self.update_requests.append(req) - if req.workflow_slug not in self.workflows: - raise NotFoundError(f"Workflow {req.workflow_slug} not found") + if req.workflow_reference not in self.workflows: + raise NotFoundError(f"Workflow {req.workflow_reference} not found") - workflow = WorkflowMessage.FromString(self.workflows[req.workflow_slug].SerializeToString()) - if req.HasField("name"): + workflow = WorkflowMessage.from_binary(self.workflows[req.workflow_reference].to_binary()) + if req.has_field("name"): workflow.name = req.name - if req.HasField("description"): + if req.has_field("description"): workflow.description = req.description - if req.HasField("summary"): + if req.has_field("summary"): workflow.summary = req.summary - self.workflows[workflow.slug] = workflow + self.workflows[workflow.reference] = workflow return workflow - def DeleteWorkflow(self, req: DeleteWorkflowRequest) -> None: # noqa: N802 - if req.workflow_slug in self.workflows: - del self.workflows[req.workflow_slug] + def delete_workflow(self, req: DeleteWorkflowRequest) -> None: + if req.workflow_reference in self.workflows: + del self.workflows[req.workflow_reference] else: - raise NotFoundError(f"Workflow {req.workflow_slug} not found") + raise NotFoundError(f"Workflow {req.workflow_reference} not found") - def UnpublishWorkflowRelease(self, req: UnpublishWorkflowReleaseRequest) -> None: # noqa: N802 - self.unpublished_releases.append((req.workflow_slug, uuid_message_to_uuid(req.release_id))) + def unpublish_workflow_release(self, req: UnpublishWorkflowReleaseRequest) -> None: + self.unpublished_releases.append((req.workflow_reference, uuid_message_to_uuid(req.release_id))) - def DeployWorkflowRelease(self, req: DeployWorkflowReleaseRequest) -> DeployWorkflowReleaseResponse: # noqa: N802 + def deploy_workflow_release(self, req: DeployWorkflowReleaseRequest) -> DeployWorkflowReleaseResponse: self.deploy_requests.append(req) return DeployWorkflowReleaseResponse( release=WorkflowReleaseMessage(id=req.release_id), clusters=[ClusterMessage(slug=slug, display_name=slug) for slug in req.cluster_slugs], ) - def UndeployWorkflowRelease(self, req: UndeployWorkflowReleaseRequest) -> UndeployWorkflowReleaseResponse: # noqa: N802 + def undeploy_workflow_release(self, req: UndeployWorkflowReleaseRequest) -> UndeployWorkflowReleaseResponse: self.undeploy_requests.append(req) return UndeployWorkflowReleaseResponse( release=WorkflowReleaseMessage(id=req.release_id), @@ -106,8 +105,7 @@ def UndeployWorkflowRelease(self, req: UndeployWorkflowReleaseRequest) -> Undepl def test_create_workflow() -> None: - service = WorkflowService(MagicMock()) - service.service = MockWorkflowService() + service = MagicMock(spec=WorkflowsServiceClientSync, wraps=MockWorkflowService()) workflow_client = WorkflowClient(service) workflow = workflow_client.create("Agentic Workflow", "Short summary", "Description") @@ -119,9 +117,8 @@ def test_create_workflow() -> None: def test_list_find_and_delete_workflows() -> None: - service = WorkflowService(MagicMock()) mock_service = MockWorkflowService() - service.service = mock_service + service = MagicMock(spec=WorkflowsServiceClientSync, wraps=mock_service) workflow_client = WorkflowClient(service) workflow = workflow_client.create("Agentic Workflow", "Short summary") @@ -135,18 +132,17 @@ def test_list_find_and_delete_workflows() -> None: def test_update_workflow() -> None: - service = WorkflowService(MagicMock()) mock_service = MockWorkflowService() - service.service = mock_service + service = MagicMock(spec=WorkflowsServiceClientSync, wraps=mock_service) workflow_client = WorkflowClient(service) workflow = workflow_client.create("Agentic Workflow", "Short summary", "Description") updated_workflow = workflow_client.update(workflow, "Updated Workflow", "Updated summary", "Updated description") request = mock_service.update_requests[-1] - assert request.workflow_slug == workflow.slug - assert request.HasField("name") - assert request.HasField("description") + assert request.workflow_reference == workflow.slug + assert request.has_field("name") + assert request.has_field("description") assert request.name == "Updated Workflow" assert request.description == "Updated description" assert updated_workflow.name == "Updated Workflow" @@ -155,27 +151,26 @@ def test_update_workflow() -> None: def test_update_workflow_preserves_optional_presence() -> None: - service = WorkflowService(MagicMock()) mock_service = MockWorkflowService() - service.service = mock_service + service = MagicMock(spec=WorkflowsServiceClientSync, wraps=mock_service) workflow_client = WorkflowClient(service) workflow = workflow_client.create("Agentic Workflow", description="Description", summary="Short summary") unchanged = workflow_client.update(workflow.slug) assert unchanged == workflow request = mock_service.update_requests[-1] - assert not request.HasField("name") - assert not request.HasField("description") - assert not request.HasField("summary") + assert not request.has_field("name") + assert not request.has_field("description") + assert not request.has_field("summary") unchanged = workflow_client.update(workflow.slug, name=None, summary=None, description=None) assert unchanged == workflow updated_workflow = workflow_client.update(workflow.slug, summary=None, description="") request = mock_service.update_requests[-1] - assert not request.HasField("name") - assert not request.HasField("summary") - assert request.HasField("description") + assert not request.has_field("name") + assert not request.has_field("summary") + assert request.has_field("description") assert request.description == "" assert updated_workflow.description == "" assert updated_workflow.summary == "Short summary" @@ -191,9 +186,8 @@ def test_update_workflow_preserves_optional_presence() -> None: def test_unpublish_release() -> None: - service = WorkflowService(MagicMock()) mock_service = MockWorkflowService() - service.service = mock_service + service = MagicMock(spec=WorkflowsServiceClientSync, wraps=mock_service) workflow_client = WorkflowClient(service) release_id = uuid4() @@ -203,16 +197,15 @@ def test_unpublish_release() -> None: def test_deploy_release() -> None: - service = WorkflowService(MagicMock()) mock_service = MockWorkflowService() - service.service = mock_service + service = MagicMock(spec=WorkflowsServiceClientSync, wraps=mock_service) workflow_client = WorkflowClient(service) release_id = uuid4() deployment = workflow_client.deploy_release("agentic-workflow", release_id, clusters=["dev", "prod"]) request = mock_service.deploy_requests[-1] - assert request.workflow_slug == "agentic-workflow" + assert request.workflow_reference == "agentic-workflow" assert uuid_message_to_uuid(request.release_id) == release_id assert list(request.cluster_slugs) == ["dev", "prod"] assert deployment.release.id == release_id @@ -220,16 +213,15 @@ def test_deploy_release() -> None: def test_undeploy_release() -> None: - service = WorkflowService(MagicMock()) mock_service = MockWorkflowService() - service.service = mock_service + service = MagicMock(spec=WorkflowsServiceClientSync, wraps=mock_service) workflow_client = WorkflowClient(service) release_id = uuid4() deployment = workflow_client.undeploy_release("agentic-workflow", release_id, clusters="dev") request = mock_service.undeploy_requests[-1] - assert request.workflow_slug == "agentic-workflow" + assert request.workflow_reference == "agentic-workflow" assert uuid_message_to_uuid(request.release_id) == release_id assert list(request.cluster_slugs) == ["dev"] assert deployment.release.id == release_id diff --git a/tilebox-workflows/tilebox/workflows/__init__.py b/tilebox-workflows/tilebox/workflows/__init__.py index 511ed60..39a6b66 100644 --- a/tilebox-workflows/tilebox/workflows/__init__.py +++ b/tilebox-workflows/tilebox/workflows/__init__.py @@ -1,9 +1,6 @@ import os from typing import TYPE_CHECKING, Any -# Apply native logging defaults before any submodule can initialize gRPC. -import _tilebox.grpc # noqa: F401 - if TYPE_CHECKING: from tilebox.workflows.client import Client from tilebox.workflows.data import Job diff --git a/tilebox-workflows/tilebox/workflows/_codec.py b/tilebox-workflows/tilebox/workflows/_codec.py index ba033a9..706535c 100644 --- a/tilebox-workflows/tilebox/workflows/_codec.py +++ b/tilebox-workflows/tilebox/workflows/_codec.py @@ -33,6 +33,7 @@ class Codec: _PROVIDERS = ( + ("tilebox.query.", "tilebox.workflows._codecs.tilebox"), ("tilebox.datasets.", "tilebox.workflows._codecs.tilebox"), ("shapely.", "tilebox.workflows._codecs.shapely"), ("pyproj.", "tilebox.workflows._codecs.pyproj"), diff --git a/tilebox-workflows/tilebox/workflows/_codecs/tilebox.py b/tilebox-workflows/tilebox/workflows/_codecs/tilebox.py index b2ceb95..ff42db3 100644 --- a/tilebox-workflows/tilebox/workflows/_codecs/tilebox.py +++ b/tilebox-workflows/tilebox/workflows/_codecs/tilebox.py @@ -1,18 +1,18 @@ from datetime import datetime from typing import Any -from google.protobuf.json_format import MessageToDict, ParseDict -from google.protobuf.message import Message +import msgspec +from protobuf import Message from tilebox.workflows._codec import Codec, CodecRegistry def _protobuf_codec(python_type: type, message_type: type[Message]) -> Codec: def encode(value: Any) -> dict[str, Any]: - return MessageToDict(value.to_message(), preserving_proto_field_name=True) + return msgspec.json.decode(value.to_message().to_json(use_proto_field_name=True)) def decode(value: dict[str, Any]) -> Any: - return python_type.from_message(ParseDict(value, message_type())) # ty: ignore[unresolved-attribute] + return python_type.from_message(message_type.from_json(msgspec.json.encode(value))) # ty: ignore[unresolved-attribute] return Codec(python_type, encode, decode, overrides_native=True) @@ -26,24 +26,25 @@ def _decode_time_interval(time_interval_type: type, message_type: type[Message], start_exclusive=value.get("start_exclusive", False), end_inclusive=value.get("end_inclusive", False), ) - return time_interval_type.from_message(ParseDict(value, message_type())) # ty: ignore[unresolved-attribute] + return time_interval_type.from_message(message_type.from_json(msgspec.json.encode(value))) # ty: ignore[unresolved-attribute] -def register(registry: CodecRegistry, _: type) -> None: - from tilebox.datasets.data.data_access import SpatialFilter # noqa: PLC0415 - from tilebox.datasets.datasets.v1.data_access_pb2 import SpatialFilter as SpatialFilterMessage # noqa: PLC0415 - from tilebox.datasets.query.id_interval import IDInterval # noqa: PLC0415 - from tilebox.datasets.query.time_interval import TimeInterval # noqa: PLC0415 - from tilebox.datasets.tilebox.v1.query_pb2 import IDInterval as IDIntervalMessage # noqa: PLC0415 - from tilebox.datasets.tilebox.v1.query_pb2 import TimeInterval as TimeIntervalMessage # noqa: PLC0415 +def register(registry: CodecRegistry, python_type: type) -> None: + from _tilebox.proto.tilebox.v1.query_pb import IDInterval as IDIntervalMessage # noqa: PLC0415 + from _tilebox.proto.tilebox.v1.query_pb import TimeInterval as TimeIntervalMessage # noqa: PLC0415 + from tilebox.query import IDInterval, TimeInterval # noqa: PLC0415 registry.register( Codec( TimeInterval, - lambda value: MessageToDict(value.to_message(), preserving_proto_field_name=True), + lambda value: msgspec.json.decode(value.to_message().to_json(use_proto_field_name=True)), lambda value: _decode_time_interval(TimeInterval, TimeIntervalMessage, value), overrides_native=True, ) ) registry.register(_protobuf_codec(IDInterval, IDIntervalMessage)) - registry.register(_protobuf_codec(SpatialFilter, SpatialFilterMessage)) + if any(base.__module__.startswith("tilebox.datasets.") for base in python_type.__mro__): + from tilebox.datasets.data.data_access import SpatialFilter # noqa: PLC0415 + from tilebox.datasets.v1.data_access_pb import SpatialFilter as SpatialFilterMessage # noqa: PLC0415 + + registry.register(_protobuf_codec(SpatialFilter, SpatialFilterMessage)) diff --git a/tilebox-workflows/tilebox/workflows/_otel/LICENSE b/tilebox-workflows/tilebox/workflows/_otel/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/tilebox-workflows/tilebox/workflows/_otel/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/tilebox-workflows/tilebox/workflows/_otel/NOTICE b/tilebox-workflows/tilebox/workflows/_otel/NOTICE new file mode 100644 index 0000000..c8418cb --- /dev/null +++ b/tilebox-workflows/tilebox/workflows/_otel/NOTICE @@ -0,0 +1,45 @@ +OpenTelemetry Protocol +https://github.com/open-telemetry/opentelemetry-proto + +The following bundled protobuf-py modules are generated derivatives of +OpenTelemetry Protocol definitions: + + opentelemetry/proto/common/v1/common_pb.py + opentelemetry/proto/resource/v1/resource_pb.py + opentelemetry/proto/logs/v1/logs_pb.py + opentelemetry/proto/trace/v1/trace_pb.py + +Upstream copyright notices: + common.proto, resource.proto, trace.proto: + Copyright 2019, OpenTelemetry Authors + logs.proto: + Copyright 2020, OpenTelemetry Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use these files except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Generation provenance: + buf.gen.yaml pins the schema input to + buf.build/opentelemetry/opentelemetry:5f2c7d4f740541589805e0816dad4bb0 + and uses protoc-gen-py 0.6.0. The definitions are translated to Python + and their imports relocated under tilebox.workflows._otel; the upstream + .proto sources are not bundled here. + +License and attribution provenance: + LICENSE is an unmodified copy of the authoritative upstream LICENSE: + https://github.com/open-telemetry/opentelemetry-proto/blob/4300f2fcef73bde66dfe6cfd56529f06555a90ba/LICENSE + Copyright notices were checked in the upstream definitions at: + https://github.com/open-telemetry/opentelemetry-proto/tree/4300f2fcef73bde66dfe6cfd56529f06555a90ba/opentelemetry/proto + This Git revision identifies the license/attribution reference, not a + claimed mapping of the pinned Buf schema commit to a Git commit. + Upstream has no root NOTICE file at that revision. This NOTICE was + added by Tilebox to retain attribution and document generation changes. diff --git a/tilebox-workflows/tilebox/workflows/_otel/opentelemetry/proto/common/v1/common_pb.py b/tilebox-workflows/tilebox/workflows/_otel/opentelemetry/proto/common/v1/common_pb.py new file mode 100644 index 0000000..314526c --- /dev/null +++ b/tilebox-workflows/tilebox/workflows/_otel/opentelemetry/proto/common/v1/common_pb.py @@ -0,0 +1,343 @@ +# Generated from opentelemetry/proto/common/v1/common.proto. DO NOT EDIT. +# Generated by protoc-gen-py v0.6.0 with parameter "init_files=false,map_imports=datasets/**:tilebox,map_imports=workflows/**:tilebox,map_imports=tilebox/**:_tilebox.proto,map_imports=opentelemetry/**:tilebox.workflows._otel". +# ruff: noqa: PGH004 +# ruff: noqa +# fmt: off + +from __future__ import annotations + +from typing import Literal, TYPE_CHECKING, TypeAlias + +from protobuf import Message +from protobuf._codegen import file_desc + +if TYPE_CHECKING: + from protobuf import DescFile, Oneof + + +_AnyValueFields: TypeAlias = Literal["string_value", "bool_value", "int_value", "double_value", "array_value", "kvlist_value", "bytes_value", "string_value_strindex"] + +class AnyValue(Message[_AnyValueFields]): + """ + Represents any type of attribute value. AnyValue may contain a + primitive value such as a string or integer or it may contain an arbitrary nested + object containing arrays, key-value lists and primitives. + + ```proto + message opentelemetry.proto.common.v1.AnyValue + ``` + + Attributes: + value: + The value is one of the listed fields. It is valid for all values to be unspecified + in which case this AnyValue is considered to be "empty". + + ```proto + oneof value + ``` + """ + + __slots__ = ("value",) + + if TYPE_CHECKING: + + def __init__( + self, + *, + value: Oneof[Literal["string_value"], str] | Oneof[Literal["bool_value"], bool] | Oneof[Literal["int_value"], int] | Oneof[Literal["double_value"], float] | Oneof[Literal["array_value"], ArrayValue] | Oneof[Literal["kvlist_value"], KeyValueList] | Oneof[Literal["bytes_value"], bytes] | Oneof[Literal["string_value_strindex"], int] | None = None, + ) -> None: + pass + + value: Oneof[Literal["string_value"], str] | Oneof[Literal["bool_value"], bool] | Oneof[Literal["int_value"], int] | Oneof[Literal["double_value"], float] | Oneof[Literal["array_value"], ArrayValue] | Oneof[Literal["kvlist_value"], KeyValueList] | Oneof[Literal["bytes_value"], bytes] | Oneof[Literal["string_value_strindex"], int] | None + +_ArrayValueFields: TypeAlias = Literal["values"] + +class ArrayValue(Message[_ArrayValueFields]): + """ + ArrayValue is a list of AnyValue messages. We need ArrayValue as a message + since oneof in AnyValue does not allow repeated fields. + + ```proto + message opentelemetry.proto.common.v1.ArrayValue + ``` + + Attributes: + values: + Array of values. The array may be empty (contain 0 elements). + + ```proto + repeated opentelemetry.proto.common.v1.AnyValue values = 1; + ``` + """ + + __slots__ = ("values",) + + if TYPE_CHECKING: + + def __init__( + self, + *, + values: list[AnyValue] | None = None, + ) -> None: + pass + + values: list[AnyValue] + +_KeyValueListFields: TypeAlias = Literal["values"] + +class KeyValueList(Message[_KeyValueListFields]): + """ + KeyValueList is a list of KeyValue messages. We need KeyValueList as a message + since `oneof` in AnyValue does not allow repeated fields. Everywhere else where we need + a list of KeyValue messages (e.g. in Span) we use `repeated KeyValue` directly to + avoid unnecessary extra wrapping (which slows down the protocol). The 2 approaches + are semantically equivalent. + + ```proto + message opentelemetry.proto.common.v1.KeyValueList + ``` + + Attributes: + values: + A collection of key/value pairs of key-value pairs. The list may be empty (may + contain 0 elements). + + The keys MUST be unique (it is not allowed to have more than one + value with the same key). + The behavior of software that receives duplicated keys can be unpredictable. + + ```proto + repeated opentelemetry.proto.common.v1.KeyValue values = 1; + ``` + """ + + __slots__ = ("values",) + + if TYPE_CHECKING: + + def __init__( + self, + *, + values: list[KeyValue] | None = None, + ) -> None: + pass + + values: list[KeyValue] + +_KeyValueFields: TypeAlias = Literal["key", "value", "key_strindex"] + +class KeyValue(Message[_KeyValueFields]): + """ + Represents a key-value pair that is used to store Span attributes, Link + attributes, etc. + + ```proto + message opentelemetry.proto.common.v1.KeyValue + ``` + + Attributes: + key: + The key name of the pair. + key_ref MUST NOT be set if key is used. + + ```proto + string key = 1; + ``` + value: + The value of the pair. + + ```proto + optional opentelemetry.proto.common.v1.AnyValue value = 2; + ``` + key_strindex: + Reference to the string key in ProfilesDictionary.string_table. + key MUST NOT be set if key_strindex is used. + + Note: This is currently used exclusively in the Profiling signal. + Implementers of OTLP receivers for signals other than Profiling should + treat the presence of this key as a non-fatal issue. + Log an error or warning indicating an unexpected field intended for the + Profiling signal and process the data as if this value were absent or + empty, ignoring its semantic content for the non-Profiling signal. + + Status: [Development] + + ```proto + int32 key_strindex = 3; + ``` + """ + + __slots__ = ("key", "value", "key_strindex") + + if TYPE_CHECKING: + + def __init__( + self, + *, + key: str = "", + value: AnyValue | None = None, + key_strindex: int = 0, + ) -> None: + pass + + key: str + value: AnyValue | None + key_strindex: int + +_InstrumentationScopeFields: TypeAlias = Literal["name", "version", "attributes", "dropped_attributes_count"] + +class InstrumentationScope(Message[_InstrumentationScopeFields]): + """ + InstrumentationScope is a message representing the instrumentation scope information + such as the fully qualified name and version. + + ```proto + message opentelemetry.proto.common.v1.InstrumentationScope + ``` + + Attributes: + name: + A name denoting the Instrumentation scope. + An empty instrumentation scope name means the name is unknown. + + ```proto + string name = 1; + ``` + version: + Defines the version of the instrumentation scope. + An empty instrumentation scope version means the version is unknown. + + ```proto + string version = 2; + ``` + attributes: + Additional attributes that describe the scope. [Optional]. + Attribute keys MUST be unique (it is not allowed to have more than one + attribute with the same key). + The behavior of software that receives duplicated keys can be unpredictable. + + ```proto + repeated opentelemetry.proto.common.v1.KeyValue attributes = 3; + ``` + dropped_attributes_count: + The number of attributes that were discarded. Attributes + can be discarded because their keys are too long or because there are too many + attributes. If this value is 0, then no attributes were dropped. + + ```proto + uint32 dropped_attributes_count = 4; + ``` + """ + + __slots__ = ("name", "version", "attributes", "dropped_attributes_count") + + if TYPE_CHECKING: + + def __init__( + self, + *, + name: str = "", + version: str = "", + attributes: list[KeyValue] | None = None, + dropped_attributes_count: int = 0, + ) -> None: + pass + + name: str + version: str + attributes: list[KeyValue] + dropped_attributes_count: int + +_EntityRefFields: TypeAlias = Literal["schema_url", "type", "id_keys", "description_keys"] + +class EntityRef(Message[_EntityRefFields]): + """ + A reference to an Entity. + Entity represents an object of interest associated with produced telemetry: e.g spans, metrics, profiles, or logs. + + Status: [Development] + + ```proto + message opentelemetry.proto.common.v1.EntityRef + ``` + + Attributes: + schema_url: + The Schema URL, if known. This is the identifier of the Schema that the entity data + is recorded in. To learn more about Schema URL see + https://opentelemetry.io/docs/specs/otel/schemas/#schema-url + + This schema_url applies to the data in this message and to the Resource attributes + referenced by id_keys and description_keys. + TODO: discuss if we are happy with this somewhat complicated definition of what + the schema_url applies to. + + This field obsoletes the schema_url field in ResourceMetrics/ResourceSpans/ResourceLogs. + + ```proto + string schema_url = 1; + ``` + type: + Defines the type of the entity. MUST not change during the lifetime of the entity. + For example: "service" or "host". This field is required and MUST not be empty + for valid entities. + + ```proto + string type = 2; + ``` + id_keys: + Attribute Keys that identify the entity. + MUST not change during the lifetime of the entity. The Id must contain at least one attribute. + These keys MUST exist in the containing {message}.attributes. + + ```proto + repeated string id_keys = 3; + ``` + description_keys: + Descriptive (non-identifying) attribute keys of the entity. + MAY change over the lifetime of the entity. MAY be empty. + These attribute keys are not part of entity's identity. + These keys MUST exist in the containing {message}.attributes. + + ```proto + repeated string description_keys = 4; + ``` + """ + + __slots__ = ("schema_url", "type", "id_keys", "description_keys") + + if TYPE_CHECKING: + + def __init__( + self, + *, + schema_url: str = "", + type: str = "", + id_keys: list[str] | None = None, + description_keys: list[str] | None = None, + ) -> None: + pass + + schema_url: str + type: str + id_keys: list[str] + description_keys: list[str] + + +_DESC = file_desc( + b'\n*opentelemetry/proto/common/v1/common.proto\x12\x1dopentelemetry.proto.common.v1"\x96\x03\n\x08AnyValue\x12#\n\x0cstring_value\x18\x01 \x01(\tH\x00R\x0bstringValue\x12\x1f\n\nbool_value\x18\x02 \x01(\x08H\x00R\tboolValue\x12\x1d\n\tint_value\x18\x03 \x01(\x03H\x00R\x08intValue\x12#\n\x0cdouble_value\x18\x04 \x01(\x01H\x00R\x0bdoubleValue\x12L\n\x0barray_value\x18\x05 \x01(\x0b2).opentelemetry.proto.common.v1.ArrayValueH\x00R\narrayValue\x12P\n\x0ckvlist_value\x18\x06 \x01(\x0b2+.opentelemetry.proto.common.v1.KeyValueListH\x00R\x0bkvlistValue\x12!\n\x0bbytes_value\x18\x07 \x01(\x0cH\x00R\nbytesValue\x124\n\x15string_value_strindex\x18\x08 \x01(\x05H\x00R\x13stringValueStrindexB\x07\n\x05value"M\n\nArrayValue\x12?\n\x06values\x18\x01 \x03(\x0b2\'.opentelemetry.proto.common.v1.AnyValueR\x06values"O\n\x0cKeyValueList\x12?\n\x06values\x18\x01 \x03(\x0b2\'.opentelemetry.proto.common.v1.KeyValueR\x06values"~\n\x08KeyValue\x12\x10\n\x03key\x18\x01 \x01(\tR\x03key\x12=\n\x05value\x18\x02 \x01(\x0b2\'.opentelemetry.proto.common.v1.AnyValueR\x05value\x12!\n\x0ckey_strindex\x18\x03 \x01(\x05R\x0bkeyStrindex"\xc7\x01\n\x14InstrumentationScope\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\x12\x18\n\x07version\x18\x02 \x01(\tR\x07version\x12G\n\nattributes\x18\x03 \x03(\x0b2\'.opentelemetry.proto.common.v1.KeyValueR\nattributes\x128\n\x18dropped_attributes_count\x18\x04 \x01(\rR\x16droppedAttributesCount"\x82\x01\n\tEntityRef\x12\x1d\n\nschema_url\x18\x01 \x01(\tR\tschemaUrl\x12\x12\n\x04type\x18\x02 \x01(\tR\x04type\x12\x17\n\x07id_keys\x18\x03 \x03(\tR\x06idKeys\x12)\n\x10description_keys\x18\x04 \x03(\tR\x0fdescriptionKeysB{\n io.opentelemetry.proto.common.v1B\x0bCommonProtoP\x01Z(go.opentelemetry.io/proto/otlp/common/v1\xaa\x02\x1dOpenTelemetry.Proto.Common.V1b\x06proto3', + [], + { + "AnyValue": AnyValue, + "ArrayValue": ArrayValue, + "KeyValueList": KeyValueList, + "KeyValue": KeyValue, + "InstrumentationScope": InstrumentationScope, + "EntityRef": EntityRef, + }, +) + + +def desc() -> DescFile: + """Returns the descriptor for the file `opentelemetry/proto/common/v1/common.proto`.""" + return _DESC diff --git a/tilebox-workflows/tilebox/workflows/_otel/opentelemetry/proto/logs/v1/logs_pb.py b/tilebox-workflows/tilebox/workflows/_otel/opentelemetry/proto/logs/v1/logs_pb.py new file mode 100644 index 0000000..2e9dcee --- /dev/null +++ b/tilebox-workflows/tilebox/workflows/_otel/opentelemetry/proto/logs/v1/logs_pb.py @@ -0,0 +1,534 @@ +# Generated from opentelemetry/proto/logs/v1/logs.proto. DO NOT EDIT. +# Generated by protoc-gen-py v0.6.0 with parameter "init_files=false,map_imports=datasets/**:tilebox,map_imports=workflows/**:tilebox,map_imports=tilebox/**:_tilebox.proto,map_imports=opentelemetry/**:tilebox.workflows._otel". +# ruff: noqa: PGH004 +# ruff: noqa +# fmt: off + +from __future__ import annotations + +from typing import Literal, TYPE_CHECKING, TypeAlias + +from protobuf import Enum, Message +from protobuf._codegen import file_desc +from tilebox.workflows._otel.opentelemetry.proto.common.v1 import common_pb +from tilebox.workflows._otel.opentelemetry.proto.resource.v1 import resource_pb + +if TYPE_CHECKING: + from protobuf import DescFile + from tilebox.workflows._otel.opentelemetry.proto.common.v1.common_pb import AnyValue, InstrumentationScope, KeyValue + from tilebox.workflows._otel.opentelemetry.proto.resource.v1.resource_pb import Resource + + +_LogsDataFields: TypeAlias = Literal["resource_logs"] + +class LogsData(Message[_LogsDataFields]): + """ + LogsData represents the logs data that can be stored in a persistent storage, + OR can be embedded by other protocols that transfer OTLP logs data but do not + implement the OTLP protocol. + + The main difference between this message and collector protocol is that + in this message there will not be any "control" or "metadata" specific to + OTLP protocol. + + When new fields are added into this message, the OTLP request MUST be updated + as well. + + ```proto + message opentelemetry.proto.logs.v1.LogsData + ``` + + Attributes: + resource_logs: + An array of ResourceLogs. + For data coming from a single resource this array will typically contain + one element. Intermediary nodes that receive data from multiple origins + typically batch the data before forwarding further and in that case this + array will contain multiple elements. + + ```proto + repeated opentelemetry.proto.logs.v1.ResourceLogs resource_logs = 1; + ``` + """ + + __slots__ = ("resource_logs",) + + if TYPE_CHECKING: + + def __init__( + self, + *, + resource_logs: list[ResourceLogs] | None = None, + ) -> None: + pass + + resource_logs: list[ResourceLogs] + +_ResourceLogsFields: TypeAlias = Literal["resource", "scope_logs", "schema_url"] + +class ResourceLogs(Message[_ResourceLogsFields]): + """ + A collection of ScopeLogs from a Resource. + + ```proto + message opentelemetry.proto.logs.v1.ResourceLogs + ``` + + Attributes: + resource: + The resource for the logs in this message. + If this field is not set then resource info is unknown. + + ```proto + optional opentelemetry.proto.resource.v1.Resource resource = 1; + ``` + scope_logs: + A list of ScopeLogs that originate from a resource. + + ```proto + repeated opentelemetry.proto.logs.v1.ScopeLogs scope_logs = 2; + ``` + schema_url: + The Schema URL, if known. This is the identifier of the Schema that the resource data + is recorded in. Notably, the last part of the URL path is the version number of the + schema: http[s]://server[:port]/path/. To learn more about Schema URL see + https://opentelemetry.io/docs/specs/otel/schemas/#schema-url + This schema_url applies to the data in the "resource" field. It does not apply + to the data in the "scope_logs" field which have their own schema_url field. + + ```proto + string schema_url = 3; + ``` + """ + + __slots__ = ("resource", "scope_logs", "schema_url") + + if TYPE_CHECKING: + + def __init__( + self, + *, + resource: Resource | None = None, + scope_logs: list[ScopeLogs] | None = None, + schema_url: str = "", + ) -> None: + pass + + resource: Resource | None + scope_logs: list[ScopeLogs] + schema_url: str + +_ScopeLogsFields: TypeAlias = Literal["scope", "log_records", "schema_url"] + +class ScopeLogs(Message[_ScopeLogsFields]): + """ + A collection of Logs produced by a Scope. + + ```proto + message opentelemetry.proto.logs.v1.ScopeLogs + ``` + + Attributes: + scope: + The instrumentation scope information for the logs in this message. + Semantically when InstrumentationScope isn't set, it is equivalent with + an empty instrumentation scope name (unknown). + + ```proto + optional opentelemetry.proto.common.v1.InstrumentationScope scope = 1; + ``` + log_records: + A list of log records. + + ```proto + repeated opentelemetry.proto.logs.v1.LogRecord log_records = 2; + ``` + schema_url: + The Schema URL, if known. This is the identifier of the Schema that the log data + is recorded in. Notably, the last part of the URL path is the version number of the + schema: http[s]://server[:port]/path/. To learn more about Schema URL see + https://opentelemetry.io/docs/specs/otel/schemas/#schema-url + This schema_url applies to the data in the "scope" field and all logs in the + "log_records" field. + + ```proto + string schema_url = 3; + ``` + """ + + __slots__ = ("scope", "log_records", "schema_url") + + if TYPE_CHECKING: + + def __init__( + self, + *, + scope: InstrumentationScope | None = None, + log_records: list[LogRecord] | None = None, + schema_url: str = "", + ) -> None: + pass + + scope: InstrumentationScope | None + log_records: list[LogRecord] + schema_url: str + +_LogRecordFields: TypeAlias = Literal["time_unix_nano", "observed_time_unix_nano", "severity_number", "severity_text", "body", "attributes", "dropped_attributes_count", "flags", "trace_id", "span_id", "event_name"] + +class LogRecord(Message[_LogRecordFields]): + """ + A log record according to OpenTelemetry Log Data Model: + https://github.com/open-telemetry/oteps/blob/main/text/logs/0097-log-data-model.md + + ```proto + message opentelemetry.proto.logs.v1.LogRecord + ``` + + Attributes: + time_unix_nano: + time_unix_nano is the time when the event occurred. + Value is UNIX Epoch time in nanoseconds since 00:00:00 UTC on 1 January 1970. + Value of 0 indicates unknown or missing timestamp. + + ```proto + fixed64 time_unix_nano = 1; + ``` + observed_time_unix_nano: + Time when the event was observed by the collection system. + For events that originate in OpenTelemetry (e.g. using OpenTelemetry Logging SDK) + this timestamp is typically set at the generation time and is equal to Timestamp. + For events originating externally and collected by OpenTelemetry (e.g. using + Collector) this is the time when OpenTelemetry's code observed the event measured + by the clock of the OpenTelemetry code. This field MUST be set once the event is + observed by OpenTelemetry. + + For converting OpenTelemetry log data to formats that support only one timestamp or + when receiving OpenTelemetry log data by recipients that support only one timestamp + internally the following logic is recommended: + - Use time_unix_nano if it is present, otherwise use observed_time_unix_nano. + + Value is UNIX Epoch time in nanoseconds since 00:00:00 UTC on 1 January 1970. + Value of 0 indicates unknown or missing timestamp. + + ```proto + fixed64 observed_time_unix_nano = 11; + ``` + severity_number: + Numerical value of the severity, normalized to values described in Log Data Model. + [Optional]. + + ```proto + opentelemetry.proto.logs.v1.SeverityNumber severity_number = 2; + ``` + severity_text: + The severity text (also known as log level). The original string representation as + it is known at the source. [Optional]. + + ```proto + string severity_text = 3; + ``` + body: + A value containing the body of the log record. Can be for example a human-readable + string message (including multi-line) describing the event in a free form or it can + be a structured data composed of arrays and maps of other values. [Optional]. + + ```proto + optional opentelemetry.proto.common.v1.AnyValue body = 5; + ``` + attributes: + Additional attributes that describe the specific event occurrence. [Optional]. + Attribute keys MUST be unique (it is not allowed to have more than one + attribute with the same key). + The behavior of software that receives duplicated keys can be unpredictable. + + ```proto + repeated opentelemetry.proto.common.v1.KeyValue attributes = 6; + ``` + dropped_attributes_count: + ```proto + uint32 dropped_attributes_count = 7; + ``` + flags: + Flags, a bit field. 8 least significant bits are the trace flags as + defined in W3C Trace Context specification. 24 most significant bits are reserved + and must be set to 0. Readers must not assume that 24 most significant bits + will be zero and must correctly mask the bits when reading 8-bit trace flag (use + flags & LOG_RECORD_FLAGS_TRACE_FLAGS_MASK). [Optional]. + + ```proto + fixed32 flags = 8; + ``` + trace_id: + A unique identifier for a trace. All logs from the same trace share + the same `trace_id`. The ID is a 16-byte array. An ID with all zeroes OR + of length other than 16 bytes is considered invalid (empty string in OTLP/JSON + is zero-length and thus is also invalid). + + This field is optional. + + The receivers SHOULD assume that the log record is not associated with a + trace if any of the following is true: + - the field is not present, + - the field contains an invalid value. + + ```proto + bytes trace_id = 9; + ``` + span_id: + A unique identifier for a span within a trace, assigned when the span + is created. The ID is an 8-byte array. An ID with all zeroes OR of length + other than 8 bytes is considered invalid (empty string in OTLP/JSON + is zero-length and thus is also invalid). + + This field is optional. If the sender specifies a valid span_id then it SHOULD also + specify a valid trace_id. + + The receivers SHOULD assume that the log record is not associated with a + span if any of the following is true: + - the field is not present, + - the field contains an invalid value. + + ```proto + bytes span_id = 10; + ``` + event_name: + A unique identifier of event category/type. + All events with the same event_name are expected to conform to the same + schema for both their attributes and their body. + + Recommended to be fully qualified and short (no longer than 256 characters). + + Presence of event_name on the log record identifies this record + as an event. + + [Optional]. + + ```proto + string event_name = 12; + ``` + """ + + __slots__ = ("time_unix_nano", "observed_time_unix_nano", "severity_number", "severity_text", "body", "attributes", "dropped_attributes_count", "flags", "trace_id", "span_id", "event_name") + + if TYPE_CHECKING: + + def __init__( + self, + *, + time_unix_nano: int = 0, + observed_time_unix_nano: int = 0, + severity_number: SeverityNumber | None = None, + severity_text: str = "", + body: AnyValue | None = None, + attributes: list[KeyValue] | None = None, + dropped_attributes_count: int = 0, + flags: int = 0, + trace_id: bytes = b"", + span_id: bytes = b"", + event_name: str = "", + ) -> None: + pass + + time_unix_nano: int + observed_time_unix_nano: int + severity_number: SeverityNumber + severity_text: str + body: AnyValue | None + attributes: list[KeyValue] + dropped_attributes_count: int + flags: int + trace_id: bytes + span_id: bytes + event_name: str + +class SeverityNumber(Enum): + """ + Possible values for LogRecord.SeverityNumber. + + ```proto + enum opentelemetry.proto.logs.v1.SeverityNumber + ``` + + Attributes: + UNSPECIFIED: + ```proto + SEVERITY_NUMBER_UNSPECIFIED = 0 + ``` + TRACE: + ```proto + SEVERITY_NUMBER_TRACE = 1 + ``` + TRACE2: + ```proto + SEVERITY_NUMBER_TRACE2 = 2 + ``` + TRACE3: + ```proto + SEVERITY_NUMBER_TRACE3 = 3 + ``` + TRACE4: + ```proto + SEVERITY_NUMBER_TRACE4 = 4 + ``` + DEBUG: + ```proto + SEVERITY_NUMBER_DEBUG = 5 + ``` + DEBUG2: + ```proto + SEVERITY_NUMBER_DEBUG2 = 6 + ``` + DEBUG3: + ```proto + SEVERITY_NUMBER_DEBUG3 = 7 + ``` + DEBUG4: + ```proto + SEVERITY_NUMBER_DEBUG4 = 8 + ``` + INFO: + ```proto + SEVERITY_NUMBER_INFO = 9 + ``` + INFO2: + ```proto + SEVERITY_NUMBER_INFO2 = 10 + ``` + INFO3: + ```proto + SEVERITY_NUMBER_INFO3 = 11 + ``` + INFO4: + ```proto + SEVERITY_NUMBER_INFO4 = 12 + ``` + WARN: + ```proto + SEVERITY_NUMBER_WARN = 13 + ``` + WARN2: + ```proto + SEVERITY_NUMBER_WARN2 = 14 + ``` + WARN3: + ```proto + SEVERITY_NUMBER_WARN3 = 15 + ``` + WARN4: + ```proto + SEVERITY_NUMBER_WARN4 = 16 + ``` + ERROR: + ```proto + SEVERITY_NUMBER_ERROR = 17 + ``` + ERROR2: + ```proto + SEVERITY_NUMBER_ERROR2 = 18 + ``` + ERROR3: + ```proto + SEVERITY_NUMBER_ERROR3 = 19 + ``` + ERROR4: + ```proto + SEVERITY_NUMBER_ERROR4 = 20 + ``` + FATAL: + ```proto + SEVERITY_NUMBER_FATAL = 21 + ``` + FATAL2: + ```proto + SEVERITY_NUMBER_FATAL2 = 22 + ``` + FATAL3: + ```proto + SEVERITY_NUMBER_FATAL3 = 23 + ``` + FATAL4: + ```proto + SEVERITY_NUMBER_FATAL4 = 24 + ``` + """ + + UNSPECIFIED = 0 + TRACE = 1 + TRACE2 = 2 + TRACE3 = 3 + TRACE4 = 4 + DEBUG = 5 + DEBUG2 = 6 + DEBUG3 = 7 + DEBUG4 = 8 + INFO = 9 + INFO2 = 10 + INFO3 = 11 + INFO4 = 12 + WARN = 13 + WARN2 = 14 + WARN3 = 15 + WARN4 = 16 + ERROR = 17 + ERROR2 = 18 + ERROR3 = 19 + ERROR4 = 20 + FATAL = 21 + FATAL2 = 22 + FATAL3 = 23 + FATAL4 = 24 + +class LogRecordFlags(Enum): + """ + LogRecordFlags represents constants used to interpret the + LogRecord.flags field, which is protobuf 'fixed32' type and is to + be used as bit-fields. Each non-zero value defined in this enum is + a bit-mask. To extract the bit-field, for example, use an + expression like: + + (logRecord.flags & LOG_RECORD_FLAGS_TRACE_FLAGS_MASK) + + ```proto + enum opentelemetry.proto.logs.v1.LogRecordFlags + ``` + + Attributes: + DO_NOT_USE: + The zero value for the enum. Should not be used for comparisons. + Instead use bitwise "and" with the appropriate mask as shown above. + + ```proto + LOG_RECORD_FLAGS_DO_NOT_USE = 0 + ``` + TRACE_FLAGS_MASK: + Bits 0-7 are used for trace flags. + + ```proto + LOG_RECORD_FLAGS_TRACE_FLAGS_MASK = 255 + ``` + """ + + DO_NOT_USE = 0 + TRACE_FLAGS_MASK = 255 + + +_DESC = file_desc( + b'\n&opentelemetry/proto/logs/v1/logs.proto\x12\x1bopentelemetry.proto.logs.v1\x1a*opentelemetry/proto/common/v1/common.proto\x1a.opentelemetry/proto/resource/v1/resource.proto"Z\n\x08LogsData\x12N\n\rresource_logs\x18\x01 \x03(\x0b2).opentelemetry.proto.logs.v1.ResourceLogsR\x0cresourceLogs"\xc3\x01\n\x0cResourceLogs\x12E\n\x08resource\x18\x01 \x01(\x0b2).opentelemetry.proto.resource.v1.ResourceR\x08resource\x12E\n\nscope_logs\x18\x02 \x03(\x0b2&.opentelemetry.proto.logs.v1.ScopeLogsR\tscopeLogs\x12\x1d\n\nschema_url\x18\x03 \x01(\tR\tschemaUrlJ\x06\x08\xe8\x07\x10\xe9\x07"\xbe\x01\n\tScopeLogs\x12I\n\x05scope\x18\x01 \x01(\x0b23.opentelemetry.proto.common.v1.InstrumentationScopeR\x05scope\x12G\n\x0blog_records\x18\x02 \x03(\x0b2&.opentelemetry.proto.logs.v1.LogRecordR\nlogRecords\x12\x1d\n\nschema_url\x18\x03 \x01(\tR\tschemaUrl"\x92\x04\n\tLogRecord\x12$\n\x0etime_unix_nano\x18\x01 \x01(\x06R\x0ctimeUnixNano\x125\n\x17observed_time_unix_nano\x18\x0b \x01(\x06R\x14observedTimeUnixNano\x12T\n\x0fseverity_number\x18\x02 \x01(\x0e2+.opentelemetry.proto.logs.v1.SeverityNumberR\x0eseverityNumber\x12#\n\rseverity_text\x18\x03 \x01(\tR\x0cseverityText\x12;\n\x04body\x18\x05 \x01(\x0b2\'.opentelemetry.proto.common.v1.AnyValueR\x04body\x12G\n\nattributes\x18\x06 \x03(\x0b2\'.opentelemetry.proto.common.v1.KeyValueR\nattributes\x128\n\x18dropped_attributes_count\x18\x07 \x01(\rR\x16droppedAttributesCount\x12\x14\n\x05flags\x18\x08 \x01(\x07R\x05flags\x12\x19\n\x08trace_id\x18\t \x01(\x0cR\x07traceId\x12\x17\n\x07span_id\x18\n \x01(\x0cR\x06spanId\x12\x1d\n\nevent_name\x18\x0c \x01(\tR\teventNameJ\x04\x08\x04\x10\x05*\xc3\x05\n\x0eSeverityNumber\x12\x1f\n\x1bSEVERITY_NUMBER_UNSPECIFIED\x10\x00\x12\x19\n\x15SEVERITY_NUMBER_TRACE\x10\x01\x12\x1a\n\x16SEVERITY_NUMBER_TRACE2\x10\x02\x12\x1a\n\x16SEVERITY_NUMBER_TRACE3\x10\x03\x12\x1a\n\x16SEVERITY_NUMBER_TRACE4\x10\x04\x12\x19\n\x15SEVERITY_NUMBER_DEBUG\x10\x05\x12\x1a\n\x16SEVERITY_NUMBER_DEBUG2\x10\x06\x12\x1a\n\x16SEVERITY_NUMBER_DEBUG3\x10\x07\x12\x1a\n\x16SEVERITY_NUMBER_DEBUG4\x10\x08\x12\x18\n\x14SEVERITY_NUMBER_INFO\x10\t\x12\x19\n\x15SEVERITY_NUMBER_INFO2\x10\n\x12\x19\n\x15SEVERITY_NUMBER_INFO3\x10\x0b\x12\x19\n\x15SEVERITY_NUMBER_INFO4\x10\x0c\x12\x18\n\x14SEVERITY_NUMBER_WARN\x10\r\x12\x19\n\x15SEVERITY_NUMBER_WARN2\x10\x0e\x12\x19\n\x15SEVERITY_NUMBER_WARN3\x10\x0f\x12\x19\n\x15SEVERITY_NUMBER_WARN4\x10\x10\x12\x19\n\x15SEVERITY_NUMBER_ERROR\x10\x11\x12\x1a\n\x16SEVERITY_NUMBER_ERROR2\x10\x12\x12\x1a\n\x16SEVERITY_NUMBER_ERROR3\x10\x13\x12\x1a\n\x16SEVERITY_NUMBER_ERROR4\x10\x14\x12\x19\n\x15SEVERITY_NUMBER_FATAL\x10\x15\x12\x1a\n\x16SEVERITY_NUMBER_FATAL2\x10\x16\x12\x1a\n\x16SEVERITY_NUMBER_FATAL3\x10\x17\x12\x1a\n\x16SEVERITY_NUMBER_FATAL4\x10\x18*Y\n\x0eLogRecordFlags\x12\x1f\n\x1bLOG_RECORD_FLAGS_DO_NOT_USE\x10\x00\x12&\n!LOG_RECORD_FLAGS_TRACE_FLAGS_MASK\x10\xff\x01Bs\n\x1eio.opentelemetry.proto.logs.v1B\tLogsProtoP\x01Z&go.opentelemetry.io/proto/otlp/logs/v1\xaa\x02\x1bOpenTelemetry.Proto.Logs.V1b\x06proto3', + [ + common_pb.desc(), + resource_pb.desc(), + ], + { + "LogsData": LogsData, + "ResourceLogs": ResourceLogs, + "ScopeLogs": ScopeLogs, + "LogRecord": LogRecord, + "SeverityNumber": SeverityNumber, + "LogRecordFlags": LogRecordFlags, + }, +) + + +def desc() -> DescFile: + """Returns the descriptor for the file `opentelemetry/proto/logs/v1/logs.proto`.""" + return _DESC diff --git a/tilebox-workflows/tilebox/workflows/_otel/opentelemetry/proto/resource/v1/resource_pb.py b/tilebox-workflows/tilebox/workflows/_otel/opentelemetry/proto/resource/v1/resource_pb.py new file mode 100644 index 0000000..254513b --- /dev/null +++ b/tilebox-workflows/tilebox/workflows/_otel/opentelemetry/proto/resource/v1/resource_pb.py @@ -0,0 +1,90 @@ +# Generated from opentelemetry/proto/resource/v1/resource.proto. DO NOT EDIT. +# Generated by protoc-gen-py v0.6.0 with parameter "init_files=false,map_imports=datasets/**:tilebox,map_imports=workflows/**:tilebox,map_imports=tilebox/**:_tilebox.proto,map_imports=opentelemetry/**:tilebox.workflows._otel". +# ruff: noqa: PGH004 +# ruff: noqa +# fmt: off + +from __future__ import annotations + +from typing import Literal, TYPE_CHECKING, TypeAlias + +from protobuf import Message +from protobuf._codegen import file_desc +from tilebox.workflows._otel.opentelemetry.proto.common.v1 import common_pb + +if TYPE_CHECKING: + from protobuf import DescFile + from tilebox.workflows._otel.opentelemetry.proto.common.v1.common_pb import EntityRef, KeyValue + + +_ResourceFields: TypeAlias = Literal["attributes", "dropped_attributes_count", "entity_refs"] + +class Resource(Message[_ResourceFields]): + """ + Resource information. + + ```proto + message opentelemetry.proto.resource.v1.Resource + ``` + + Attributes: + attributes: + Set of attributes that describe the resource. + Attribute keys MUST be unique (it is not allowed to have more than one + attribute with the same key). + The behavior of software that receives duplicated keys can be unpredictable. + + ```proto + repeated opentelemetry.proto.common.v1.KeyValue attributes = 1; + ``` + dropped_attributes_count: + The number of dropped attributes. If the value is 0, then + no attributes were dropped. + + ```proto + uint32 dropped_attributes_count = 2; + ``` + entity_refs: + Set of entities that participate in this Resource. + + Note: keys in the references MUST exist in attributes of this message. + + Status: [Development] + + ```proto + repeated opentelemetry.proto.common.v1.EntityRef entity_refs = 3; + ``` + """ + + __slots__ = ("attributes", "dropped_attributes_count", "entity_refs") + + if TYPE_CHECKING: + + def __init__( + self, + *, + attributes: list[KeyValue] | None = None, + dropped_attributes_count: int = 0, + entity_refs: list[EntityRef] | None = None, + ) -> None: + pass + + attributes: list[KeyValue] + dropped_attributes_count: int + entity_refs: list[EntityRef] + + +_DESC = file_desc( + b'\n.opentelemetry/proto/resource/v1/resource.proto\x12\x1fopentelemetry.proto.resource.v1\x1a*opentelemetry/proto/common/v1/common.proto"\xd8\x01\n\x08Resource\x12G\n\nattributes\x18\x01 \x03(\x0b2\'.opentelemetry.proto.common.v1.KeyValueR\nattributes\x128\n\x18dropped_attributes_count\x18\x02 \x01(\rR\x16droppedAttributesCount\x12I\n\x0bentity_refs\x18\x03 \x03(\x0b2(.opentelemetry.proto.common.v1.EntityRefR\nentityRefsB\x83\x01\n"io.opentelemetry.proto.resource.v1B\rResourceProtoP\x01Z*go.opentelemetry.io/proto/otlp/resource/v1\xaa\x02\x1fOpenTelemetry.Proto.Resource.V1b\x06proto3', + [ + common_pb.desc(), + ], + { + "Resource": Resource, + }, +) + + +def desc() -> DescFile: + """Returns the descriptor for the file `opentelemetry/proto/resource/v1/resource.proto`.""" + return _DESC diff --git a/tilebox-workflows/tilebox/workflows/_otel/opentelemetry/proto/trace/v1/trace_pb.py b/tilebox-workflows/tilebox/workflows/_otel/opentelemetry/proto/trace/v1/trace_pb.py new file mode 100644 index 0000000..f9d7f24 --- /dev/null +++ b/tilebox-workflows/tilebox/workflows/_otel/opentelemetry/proto/trace/v1/trace_pb.py @@ -0,0 +1,776 @@ +# Generated from opentelemetry/proto/trace/v1/trace.proto. DO NOT EDIT. +# Generated by protoc-gen-py v0.6.0 with parameter "init_files=false,map_imports=datasets/**:tilebox,map_imports=workflows/**:tilebox,map_imports=tilebox/**:_tilebox.proto,map_imports=opentelemetry/**:tilebox.workflows._otel". +# ruff: noqa: PGH004 +# ruff: noqa +# fmt: off + +from __future__ import annotations + +from typing import Literal, TYPE_CHECKING, TypeAlias + +from protobuf import Enum, Message +from protobuf._codegen import file_desc +from tilebox.workflows._otel.opentelemetry.proto.common.v1 import common_pb +from tilebox.workflows._otel.opentelemetry.proto.resource.v1 import resource_pb + +if TYPE_CHECKING: + from protobuf import DescFile + from tilebox.workflows._otel.opentelemetry.proto.common.v1.common_pb import InstrumentationScope, KeyValue + from tilebox.workflows._otel.opentelemetry.proto.resource.v1.resource_pb import Resource + + +_TracesDataFields: TypeAlias = Literal["resource_spans"] + +class TracesData(Message[_TracesDataFields]): + """ + TracesData represents the traces data that can be stored in a persistent storage, + OR can be embedded by other protocols that transfer OTLP traces data but do + not implement the OTLP protocol. + + The main difference between this message and collector protocol is that + in this message there will not be any "control" or "metadata" specific to + OTLP protocol. + + When new fields are added into this message, the OTLP request MUST be updated + as well. + + ```proto + message opentelemetry.proto.trace.v1.TracesData + ``` + + Attributes: + resource_spans: + An array of ResourceSpans. + For data coming from a single resource this array will typically contain + one element. Intermediary nodes that receive data from multiple origins + typically batch the data before forwarding further and in that case this + array will contain multiple elements. + + ```proto + repeated opentelemetry.proto.trace.v1.ResourceSpans resource_spans = 1; + ``` + """ + + __slots__ = ("resource_spans",) + + if TYPE_CHECKING: + + def __init__( + self, + *, + resource_spans: list[ResourceSpans] | None = None, + ) -> None: + pass + + resource_spans: list[ResourceSpans] + +_ResourceSpansFields: TypeAlias = Literal["resource", "scope_spans", "schema_url"] + +class ResourceSpans(Message[_ResourceSpansFields]): + """ + A collection of ScopeSpans from a Resource. + + ```proto + message opentelemetry.proto.trace.v1.ResourceSpans + ``` + + Attributes: + resource: + The resource for the spans in this message. + If this field is not set then no resource info is known. + + ```proto + optional opentelemetry.proto.resource.v1.Resource resource = 1; + ``` + scope_spans: + A list of ScopeSpans that originate from a resource. + + ```proto + repeated opentelemetry.proto.trace.v1.ScopeSpans scope_spans = 2; + ``` + schema_url: + The Schema URL, if known. This is the identifier of the Schema that the resource data + is recorded in. Notably, the last part of the URL path is the version number of the + schema: http[s]://server[:port]/path/. To learn more about Schema URL see + https://opentelemetry.io/docs/specs/otel/schemas/#schema-url + This schema_url applies to the data in the "resource" field. It does not apply + to the data in the "scope_spans" field which have their own schema_url field. + + ```proto + string schema_url = 3; + ``` + """ + + __slots__ = ("resource", "scope_spans", "schema_url") + + if TYPE_CHECKING: + + def __init__( + self, + *, + resource: Resource | None = None, + scope_spans: list[ScopeSpans] | None = None, + schema_url: str = "", + ) -> None: + pass + + resource: Resource | None + scope_spans: list[ScopeSpans] + schema_url: str + +_ScopeSpansFields: TypeAlias = Literal["scope", "spans", "schema_url"] + +class ScopeSpans(Message[_ScopeSpansFields]): + """ + A collection of Spans produced by an InstrumentationScope. + + ```proto + message opentelemetry.proto.trace.v1.ScopeSpans + ``` + + Attributes: + scope: + The instrumentation scope information for the spans in this message. + Semantically when InstrumentationScope isn't set, it is equivalent with + an empty instrumentation scope name (unknown). + + ```proto + optional opentelemetry.proto.common.v1.InstrumentationScope scope = 1; + ``` + spans: + A list of Spans that originate from an instrumentation scope. + + ```proto + repeated opentelemetry.proto.trace.v1.Span spans = 2; + ``` + schema_url: + The Schema URL, if known. This is the identifier of the Schema that the span data + is recorded in. Notably, the last part of the URL path is the version number of the + schema: http[s]://server[:port]/path/. To learn more about Schema URL see + https://opentelemetry.io/docs/specs/otel/schemas/#schema-url + This schema_url applies to the data in the "scope" field and all spans and span + events in the "spans" field. + + ```proto + string schema_url = 3; + ``` + """ + + __slots__ = ("scope", "spans", "schema_url") + + if TYPE_CHECKING: + + def __init__( + self, + *, + scope: InstrumentationScope | None = None, + spans: list[Span] | None = None, + schema_url: str = "", + ) -> None: + pass + + scope: InstrumentationScope | None + spans: list[Span] + schema_url: str + +_SpanFields: TypeAlias = Literal["trace_id", "span_id", "trace_state", "parent_span_id", "flags", "name", "kind", "start_time_unix_nano", "end_time_unix_nano", "attributes", "dropped_attributes_count", "events", "dropped_events_count", "links", "dropped_links_count", "status"] + +class Span(Message[_SpanFields]): + """ + A Span represents a single operation performed by a single component of the system. + + The next available field id is 17. + + ```proto + message opentelemetry.proto.trace.v1.Span + ``` + + Attributes: + trace_id: + A unique identifier for a trace. All spans from the same trace share + the same `trace_id`. The ID is a 16-byte array. An ID with all zeroes OR + of length other than 16 bytes is considered invalid (empty string in OTLP/JSON + is zero-length and thus is also invalid). + + This field is required. + + ```proto + bytes trace_id = 1; + ``` + span_id: + A unique identifier for a span within a trace, assigned when the span + is created. The ID is an 8-byte array. An ID with all zeroes OR of length + other than 8 bytes is considered invalid (empty string in OTLP/JSON + is zero-length and thus is also invalid). + + This field is required. + + ```proto + bytes span_id = 2; + ``` + trace_state: + trace_state conveys information about request position in multiple distributed tracing graphs. + It is a trace_state in w3c-trace-context format: https://www.w3.org/TR/trace-context/#tracestate-header + See also https://github.com/w3c/distributed-tracing for more details about this field. + + ```proto + string trace_state = 3; + ``` + parent_span_id: + The `span_id` of this span's parent span. If this is a root span, then this + field must be empty. The ID is an 8-byte array. + + ```proto + bytes parent_span_id = 4; + ``` + flags: + Flags, a bit field. + + Bits 0-7 (8 least significant bits) are the trace flags as defined in W3C Trace + Context specification. To read the 8-bit W3C trace flag, use + `flags & SPAN_FLAGS_TRACE_FLAGS_MASK`. + + See https://www.w3.org/TR/trace-context-2/#trace-flags for the flag definitions. + + Bits 8 and 9 represent the 3 states of whether a span's parent + is remote. The states are (unknown, is not remote, is remote). + To read whether the value is known, use `(flags & SPAN_FLAGS_CONTEXT_HAS_IS_REMOTE_MASK) != 0`. + To read whether the span is remote, use `(flags & SPAN_FLAGS_CONTEXT_IS_REMOTE_MASK) != 0`. + + When creating span messages, if the message is logically forwarded from another source + with an equivalent flags fields (i.e., usually another OTLP span message), the field SHOULD + be copied as-is. If creating from a source that does not have an equivalent flags field + (such as a runtime representation of an OpenTelemetry span), the high 22 bits MUST + be set to zero. + Readers MUST NOT assume that bits 10-31 (22 most significant bits) will be zero. + + [Optional]. + + ```proto + fixed32 flags = 16; + ``` + name: + A description of the span's operation. + + For example, the name can be a qualified method name or a file name + and a line number where the operation is called. A best practice is to use + the same display name at the same call point in an application. + This makes it easier to correlate spans in different traces. + + This field is semantically required to be set to non-empty string. + Empty value is equivalent to an unknown span name. + + This field is required. + + ```proto + string name = 5; + ``` + kind: + Distinguishes between spans generated in a particular context. For example, + two spans with the same name may be distinguished using `CLIENT` (caller) + and `SERVER` (callee) to identify queueing latency associated with the span. + + ```proto + opentelemetry.proto.trace.v1.Span.SpanKind kind = 6; + ``` + start_time_unix_nano: + The start time of the span. On the client side, this is the time + kept by the local machine where the span execution starts. On the server side, this + is the time when the server's application handler starts running. + Value is UNIX Epoch time in nanoseconds since 00:00:00 UTC on 1 January 1970. + + This field is semantically required and it is expected that end_time >= start_time. + + ```proto + fixed64 start_time_unix_nano = 7; + ``` + end_time_unix_nano: + The end time of the span. On the client side, this is the time + kept by the local machine where the span execution ends. On the server side, this + is the time when the server application handler stops running. + Value is UNIX Epoch time in nanoseconds since 00:00:00 UTC on 1 January 1970. + + This field is semantically required and it is expected that end_time >= start_time. + + ```proto + fixed64 end_time_unix_nano = 8; + ``` + attributes: + A collection of key/value pairs. Note, global attributes + like server name can be set using the resource API. Examples of attributes: + + "/http/user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36" + "/http/server_latency": 300 + "example.com/myattribute": true + "example.com/score": 10.239 + + Attribute keys MUST be unique (it is not allowed to have more than one + attribute with the same key). + The behavior of software that receives duplicated keys can be unpredictable. + + ```proto + repeated opentelemetry.proto.common.v1.KeyValue attributes = 9; + ``` + dropped_attributes_count: + The number of attributes that were discarded. Attributes + can be discarded because their keys are too long or because there are too many + attributes. If this value is 0, then no attributes were dropped. + + ```proto + uint32 dropped_attributes_count = 10; + ``` + events: + A collection of Event items. + + ```proto + repeated opentelemetry.proto.trace.v1.Span.Event events = 11; + ``` + dropped_events_count: + The number of dropped events. If the value is 0, then no + events were dropped. + + ```proto + uint32 dropped_events_count = 12; + ``` + links: + A collection of Links, which are references from this span to a span + in the same or different trace. + + ```proto + repeated opentelemetry.proto.trace.v1.Span.Link links = 13; + ``` + dropped_links_count: + The number of dropped links after the maximum size was + enforced. If this value is 0, then no links were dropped. + + ```proto + uint32 dropped_links_count = 14; + ``` + status: + An optional final status for this span. Semantically when Status isn't set, it means + span's status code is unset, i.e. assume STATUS_CODE_UNSET (code = 0). + + ```proto + optional opentelemetry.proto.trace.v1.Status status = 15; + ``` + """ + + __slots__ = ("trace_id", "span_id", "trace_state", "parent_span_id", "flags", "name", "kind", "start_time_unix_nano", "end_time_unix_nano", "attributes", "dropped_attributes_count", "events", "dropped_events_count", "links", "dropped_links_count", "status") + + if TYPE_CHECKING: + + def __init__( + self, + *, + trace_id: bytes = b"", + span_id: bytes = b"", + trace_state: str = "", + parent_span_id: bytes = b"", + flags: int = 0, + name: str = "", + kind: Span.SpanKind | None = None, + start_time_unix_nano: int = 0, + end_time_unix_nano: int = 0, + attributes: list[KeyValue] | None = None, + dropped_attributes_count: int = 0, + events: list[Span.Event] | None = None, + dropped_events_count: int = 0, + links: list[Span.Link] | None = None, + dropped_links_count: int = 0, + status: Status | None = None, + ) -> None: + pass + + trace_id: bytes + span_id: bytes + trace_state: str + parent_span_id: bytes + flags: int + name: str + kind: Span.SpanKind + start_time_unix_nano: int + end_time_unix_nano: int + attributes: list[KeyValue] + dropped_attributes_count: int + events: list[Span.Event] + dropped_events_count: int + links: list[Span.Link] + dropped_links_count: int + status: Status | None + + _EventFields: TypeAlias = Literal["time_unix_nano", "name", "attributes", "dropped_attributes_count"] + + class Event(Message[_EventFields]): + """ + Event is a time-stamped annotation of the span, consisting of user-supplied + text description and key-value pairs. + + ```proto + message opentelemetry.proto.trace.v1.Span.Event + ``` + + Attributes: + time_unix_nano: + The time the event occurred. + + ```proto + fixed64 time_unix_nano = 1; + ``` + name: + The name of the event. + This field is semantically required to be set to non-empty string. + + ```proto + string name = 2; + ``` + attributes: + A collection of attribute key/value pairs on the event. + Attribute keys MUST be unique (it is not allowed to have more than one + attribute with the same key). + The behavior of software that receives duplicated keys can be unpredictable. + + ```proto + repeated opentelemetry.proto.common.v1.KeyValue attributes = 3; + ``` + dropped_attributes_count: + The number of dropped attributes. If the value is 0, + then no attributes were dropped. + + ```proto + uint32 dropped_attributes_count = 4; + ``` + """ + + __slots__ = ("time_unix_nano", "name", "attributes", "dropped_attributes_count") + + if TYPE_CHECKING: + + def __init__( + self, + *, + time_unix_nano: int = 0, + name: str = "", + attributes: list[KeyValue] | None = None, + dropped_attributes_count: int = 0, + ) -> None: + pass + + time_unix_nano: int + name: str + attributes: list[KeyValue] + dropped_attributes_count: int + + _LinkFields: TypeAlias = Literal["trace_id", "span_id", "trace_state", "attributes", "dropped_attributes_count", "flags"] + + class Link(Message[_LinkFields]): + """ + A pointer from the current span to another span in the same trace or in a + different trace. For example, this can be used in batching operations, + where a single batch handler processes multiple requests from different + traces or when the handler receives a request from a different project. + + ```proto + message opentelemetry.proto.trace.v1.Span.Link + ``` + + Attributes: + trace_id: + A unique identifier of a trace that this linked span is part of. The ID is a + 16-byte array. + + ```proto + bytes trace_id = 1; + ``` + span_id: + A unique identifier for the linked span. The ID is an 8-byte array. + + ```proto + bytes span_id = 2; + ``` + trace_state: + The trace_state associated with the link. + + ```proto + string trace_state = 3; + ``` + attributes: + A collection of attribute key/value pairs on the link. + Attribute keys MUST be unique (it is not allowed to have more than one + attribute with the same key). + The behavior of software that receives duplicated keys can be unpredictable. + + ```proto + repeated opentelemetry.proto.common.v1.KeyValue attributes = 4; + ``` + dropped_attributes_count: + The number of dropped attributes. If the value is 0, + then no attributes were dropped. + + ```proto + uint32 dropped_attributes_count = 5; + ``` + flags: + Flags, a bit field. + + Bits 0-7 (8 least significant bits) are the trace flags as defined in W3C Trace + Context specification. To read the 8-bit W3C trace flag, use + `flags & SPAN_FLAGS_TRACE_FLAGS_MASK`. + + See https://www.w3.org/TR/trace-context-2/#trace-flags for the flag definitions. + + Bits 8 and 9 represent the 3 states of whether the link is remote. + The states are (unknown, is not remote, is remote). + To read whether the value is known, use `(flags & SPAN_FLAGS_CONTEXT_HAS_IS_REMOTE_MASK) != 0`. + To read whether the link is remote, use `(flags & SPAN_FLAGS_CONTEXT_IS_REMOTE_MASK) != 0`. + + Readers MUST NOT assume that bits 10-31 (22 most significant bits) will be zero. + When creating new spans, bits 10-31 (most-significant 22-bits) MUST be zero. + + [Optional]. + + ```proto + fixed32 flags = 6; + ``` + """ + + __slots__ = ("trace_id", "span_id", "trace_state", "attributes", "dropped_attributes_count", "flags") + + if TYPE_CHECKING: + + def __init__( + self, + *, + trace_id: bytes = b"", + span_id: bytes = b"", + trace_state: str = "", + attributes: list[KeyValue] | None = None, + dropped_attributes_count: int = 0, + flags: int = 0, + ) -> None: + pass + + trace_id: bytes + span_id: bytes + trace_state: str + attributes: list[KeyValue] + dropped_attributes_count: int + flags: int + + class SpanKind(Enum): + """ + SpanKind is the type of span. Can be used to specify additional relationships between spans + in addition to a parent/child relationship. + + ```proto + enum opentelemetry.proto.trace.v1.Span.SpanKind + ``` + + Attributes: + UNSPECIFIED: + Unspecified. Do NOT use as default. + Implementations MAY assume SpanKind to be INTERNAL when receiving UNSPECIFIED. + + ```proto + SPAN_KIND_UNSPECIFIED = 0 + ``` + INTERNAL: + Indicates that the span represents an internal operation within an application, + as opposed to an operation happening at the boundaries. Default value. + + ```proto + SPAN_KIND_INTERNAL = 1 + ``` + SERVER: + Indicates that the span covers server-side handling of an RPC or other + remote network request. + + ```proto + SPAN_KIND_SERVER = 2 + ``` + CLIENT: + Indicates that the span describes a request to some remote service. + + ```proto + SPAN_KIND_CLIENT = 3 + ``` + PRODUCER: + Indicates that the span describes a producer sending a message to a broker. + Unlike CLIENT and SERVER, there is often no direct critical path latency relationship + between producer and consumer spans. A PRODUCER span ends when the message was accepted + by the broker while the logical processing of the message might span a much longer time. + + ```proto + SPAN_KIND_PRODUCER = 4 + ``` + CONSUMER: + Indicates that the span describes consumer receiving a message from a broker. + Like the PRODUCER kind, there is often no direct critical path latency relationship + between producer and consumer spans. + + ```proto + SPAN_KIND_CONSUMER = 5 + ``` + """ + + UNSPECIFIED = 0 + INTERNAL = 1 + SERVER = 2 + CLIENT = 3 + PRODUCER = 4 + CONSUMER = 5 + +_StatusFields: TypeAlias = Literal["message", "code"] + +class Status(Message[_StatusFields]): + """ + The Status type defines a logical error model that is suitable for different + programming environments, including REST APIs and RPC APIs. + + ```proto + message opentelemetry.proto.trace.v1.Status + ``` + + Attributes: + message: + A developer-facing human readable error message. + + ```proto + string message = 2; + ``` + code: + The status code. + + ```proto + opentelemetry.proto.trace.v1.Status.StatusCode code = 3; + ``` + """ + + __slots__ = ("message", "code") + + if TYPE_CHECKING: + + def __init__( + self, + *, + message: str = "", + code: Status.StatusCode | None = None, + ) -> None: + pass + + message: str + code: Status.StatusCode + + class StatusCode(Enum): + """ + For the semantics of status codes see + https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/api.md#set-status + + ```proto + enum opentelemetry.proto.trace.v1.Status.StatusCode + ``` + + Attributes: + UNSET: + The default status. + + ```proto + STATUS_CODE_UNSET = 0 + ``` + OK: + The Span has been validated by an Application developer or Operator to + have completed successfully. + + ```proto + STATUS_CODE_OK = 1 + ``` + ERROR: + The Span contains an error. + + ```proto + STATUS_CODE_ERROR = 2 + ``` + """ + + UNSET = 0 + OK = 1 + ERROR = 2 + +class SpanFlags(Enum): + """ + SpanFlags represents constants used to interpret the + Span.flags field, which is protobuf 'fixed32' type and is to + be used as bit-fields. Each non-zero value defined in this enum is + a bit-mask. To extract the bit-field, for example, use an + expression like: + + (span.flags & SPAN_FLAGS_TRACE_FLAGS_MASK) + + See https://www.w3.org/TR/trace-context-2/#trace-flags for the flag definitions. + + Note that Span flags were introduced in version 1.1 of the + OpenTelemetry protocol. Older Span producers do not set this + field, consequently consumers should not rely on the absence of a + particular flag bit to indicate the presence of a particular feature. + + ```proto + enum opentelemetry.proto.trace.v1.SpanFlags + ``` + + Attributes: + DO_NOT_USE: + The zero value for the enum. Should not be used for comparisons. + Instead use bitwise "and" with the appropriate mask as shown above. + + ```proto + SPAN_FLAGS_DO_NOT_USE = 0 + ``` + TRACE_FLAGS_MASK: + Bits 0-7 are used for trace flags. + + ```proto + SPAN_FLAGS_TRACE_FLAGS_MASK = 255 + ``` + CONTEXT_HAS_IS_REMOTE_MASK: + Bits 8 and 9 are used to indicate that the parent span or link span is remote. + Bit 8 (`HAS_IS_REMOTE`) indicates whether the value is known. + Bit 9 (`IS_REMOTE`) indicates whether the span or link is remote. + + ```proto + SPAN_FLAGS_CONTEXT_HAS_IS_REMOTE_MASK = 256 + ``` + CONTEXT_IS_REMOTE_MASK: + ```proto + SPAN_FLAGS_CONTEXT_IS_REMOTE_MASK = 512 + ``` + """ + + DO_NOT_USE = 0 + TRACE_FLAGS_MASK = 255 + CONTEXT_HAS_IS_REMOTE_MASK = 256 + CONTEXT_IS_REMOTE_MASK = 512 + + +_DESC = file_desc( + b'\n(opentelemetry/proto/trace/v1/trace.proto\x12\x1copentelemetry.proto.trace.v1\x1a*opentelemetry/proto/common/v1/common.proto\x1a.opentelemetry/proto/resource/v1/resource.proto"`\n\nTracesData\x12R\n\x0eresource_spans\x18\x01 \x03(\x0b2+.opentelemetry.proto.trace.v1.ResourceSpansR\rresourceSpans"\xc8\x01\n\rResourceSpans\x12E\n\x08resource\x18\x01 \x01(\x0b2).opentelemetry.proto.resource.v1.ResourceR\x08resource\x12I\n\x0bscope_spans\x18\x02 \x03(\x0b2(.opentelemetry.proto.trace.v1.ScopeSpansR\nscopeSpans\x12\x1d\n\nschema_url\x18\x03 \x01(\tR\tschemaUrlJ\x06\x08\xe8\x07\x10\xe9\x07"\xb0\x01\n\nScopeSpans\x12I\n\x05scope\x18\x01 \x01(\x0b23.opentelemetry.proto.common.v1.InstrumentationScopeR\x05scope\x128\n\x05spans\x18\x02 \x03(\x0b2".opentelemetry.proto.trace.v1.SpanR\x05spans\x12\x1d\n\nschema_url\x18\x03 \x01(\tR\tschemaUrl"\xc8\n\n\x04Span\x12\x19\n\x08trace_id\x18\x01 \x01(\x0cR\x07traceId\x12\x17\n\x07span_id\x18\x02 \x01(\x0cR\x06spanId\x12\x1f\n\x0btrace_state\x18\x03 \x01(\tR\ntraceState\x12$\n\x0eparent_span_id\x18\x04 \x01(\x0cR\x0cparentSpanId\x12\x14\n\x05flags\x18\x10 \x01(\x07R\x05flags\x12\x12\n\x04name\x18\x05 \x01(\tR\x04name\x12?\n\x04kind\x18\x06 \x01(\x0e2+.opentelemetry.proto.trace.v1.Span.SpanKindR\x04kind\x12/\n\x14start_time_unix_nano\x18\x07 \x01(\x06R\x11startTimeUnixNano\x12+\n\x12end_time_unix_nano\x18\x08 \x01(\x06R\x0fendTimeUnixNano\x12G\n\nattributes\x18\t \x03(\x0b2\'.opentelemetry.proto.common.v1.KeyValueR\nattributes\x128\n\x18dropped_attributes_count\x18\n \x01(\rR\x16droppedAttributesCount\x12@\n\x06events\x18\x0b \x03(\x0b2(.opentelemetry.proto.trace.v1.Span.EventR\x06events\x120\n\x14dropped_events_count\x18\x0c \x01(\rR\x12droppedEventsCount\x12=\n\x05links\x18\r \x03(\x0b2\'.opentelemetry.proto.trace.v1.Span.LinkR\x05links\x12.\n\x13dropped_links_count\x18\x0e \x01(\rR\x11droppedLinksCount\x12<\n\x06status\x18\x0f \x01(\x0b2$.opentelemetry.proto.trace.v1.StatusR\x06status\x1a\xc4\x01\n\x05Event\x12$\n\x0etime_unix_nano\x18\x01 \x01(\x06R\x0ctimeUnixNano\x12\x12\n\x04name\x18\x02 \x01(\tR\x04name\x12G\n\nattributes\x18\x03 \x03(\x0b2\'.opentelemetry.proto.common.v1.KeyValueR\nattributes\x128\n\x18dropped_attributes_count\x18\x04 \x01(\rR\x16droppedAttributesCount\x1a\xf4\x01\n\x04Link\x12\x19\n\x08trace_id\x18\x01 \x01(\x0cR\x07traceId\x12\x17\n\x07span_id\x18\x02 \x01(\x0cR\x06spanId\x12\x1f\n\x0btrace_state\x18\x03 \x01(\tR\ntraceState\x12G\n\nattributes\x18\x04 \x03(\x0b2\'.opentelemetry.proto.common.v1.KeyValueR\nattributes\x128\n\x18dropped_attributes_count\x18\x05 \x01(\rR\x16droppedAttributesCount\x12\x14\n\x05flags\x18\x06 \x01(\x07R\x05flags"\x99\x01\n\x08SpanKind\x12\x19\n\x15SPAN_KIND_UNSPECIFIED\x10\x00\x12\x16\n\x12SPAN_KIND_INTERNAL\x10\x01\x12\x14\n\x10SPAN_KIND_SERVER\x10\x02\x12\x14\n\x10SPAN_KIND_CLIENT\x10\x03\x12\x16\n\x12SPAN_KIND_PRODUCER\x10\x04\x12\x16\n\x12SPAN_KIND_CONSUMER\x10\x05"\xbd\x01\n\x06Status\x12\x18\n\x07message\x18\x02 \x01(\tR\x07message\x12C\n\x04code\x18\x03 \x01(\x0e2/.opentelemetry.proto.trace.v1.Status.StatusCodeR\x04code"N\n\nStatusCode\x12\x15\n\x11STATUS_CODE_UNSET\x10\x00\x12\x12\n\x0eSTATUS_CODE_OK\x10\x01\x12\x15\n\x11STATUS_CODE_ERROR\x10\x02J\x04\x08\x01\x10\x02*\x9c\x01\n\tSpanFlags\x12\x19\n\x15SPAN_FLAGS_DO_NOT_USE\x10\x00\x12 \n\x1bSPAN_FLAGS_TRACE_FLAGS_MASK\x10\xff\x01\x12*\n%SPAN_FLAGS_CONTEXT_HAS_IS_REMOTE_MASK\x10\x80\x02\x12&\n!SPAN_FLAGS_CONTEXT_IS_REMOTE_MASK\x10\x80\x04Bw\n\x1fio.opentelemetry.proto.trace.v1B\nTraceProtoP\x01Z\'go.opentelemetry.io/proto/otlp/trace/v1\xaa\x02\x1cOpenTelemetry.Proto.Trace.V1b\x06proto3', + [ + common_pb.desc(), + resource_pb.desc(), + ], + { + "TracesData": TracesData, + "ResourceSpans": ResourceSpans, + "ScopeSpans": ScopeSpans, + "Span": Span, + "Span.Event": Span.Event, + "Span.Link": Span.Link, + "Span.SpanKind": Span.SpanKind, + "Status": Status, + "Status.StatusCode": Status.StatusCode, + "SpanFlags": SpanFlags, + }, +) + + +def desc() -> DescFile: + """Returns the descriptor for the file `opentelemetry/proto/trace/v1/trace.proto`.""" + return _DESC diff --git a/tilebox-workflows/tilebox/workflows/_serialization.py b/tilebox-workflows/tilebox/workflows/_serialization.py index bf86717..aaee88c 100644 --- a/tilebox-workflows/tilebox/workflows/_serialization.py +++ b/tilebox-workflows/tilebox/workflows/_serialization.py @@ -9,7 +9,7 @@ from zoneinfo import ZoneInfo import msgspec -from google.protobuf.message import Message +from protobuf import Message from tilebox.workflows._codec import registry @@ -193,7 +193,7 @@ def _matches_annotation(field_type: Any, value: Any) -> bool: def _encode_hook(value: Any) -> Any: if isinstance(value, Message): - return b64encode(value.SerializeToString()).decode("ascii") + return b64encode(value.to_binary()).decode("ascii") codec = registry.find(type(value)) if codec is not None: return codec.encode(value) @@ -239,8 +239,8 @@ def _typed_decoder(field_type: Any) -> msgspec.json.Decoder: def _decode_hook(field_type: type, value: Any) -> Any: - if _is_protobuf_type(field_type): - return field_type.FromString(b64decode(value)) # ty: ignore[unresolved-attribute] + if issubclass(field_type, Message): + return field_type.from_binary(b64decode(value)) codec = registry.find(field_type) if codec is not None: return codec.decode(value) diff --git a/tilebox-workflows/tilebox/workflows/automations/client.py b/tilebox-workflows/tilebox/workflows/automations/client.py index 42dec93..73b068b 100644 --- a/tilebox-workflows/tilebox/workflows/automations/client.py +++ b/tilebox-workflows/tilebox/workflows/automations/client.py @@ -1,7 +1,9 @@ from uuid import UUID +from protobuf.wkt.google.protobuf.empty_pb import Empty + +from _tilebox.uuid import must_uuid_to_uuid_message, uuid_to_uuid_message from tilebox.workflows.automations.cron import CronTask -from tilebox.workflows.automations.service import AutomationService from tilebox.workflows.automations.storage_event import StorageEventTask from tilebox.workflows.clusters.client import ClusterSlugLike, to_cluster_slug from tilebox.workflows.data import ( @@ -12,10 +14,12 @@ StorageLocation, ) from tilebox.workflows.task import _task_meta +from tilebox.workflows.v1.automation_connect import AutomationServiceClientSync +from tilebox.workflows.v1.automation_pb import DeleteAutomationRequest class AutomationClient: - def __init__(self, service: AutomationService) -> None: + def __init__(self, service: AutomationServiceClientSync) -> None: self._service = service def all(self) -> list[AutomationPrototype]: @@ -24,7 +28,8 @@ def all(self) -> list[AutomationPrototype]: Returns: A list of all registered automations. """ - return self._service.list() + response = self._service.list_automations(Empty()) + return [AutomationPrototype.from_message(automation) for automation in response.automations] def find(self, automation_id: UUID | str) -> AutomationPrototype: """Find an automation by id. @@ -37,7 +42,7 @@ def find(self, automation_id: UUID | str) -> AutomationPrototype: """ if isinstance(automation_id, str): automation_id = UUID(automation_id) - return self._service.get_by_id(automation_id) + return AutomationPrototype.from_message(self._service.get_automation(must_uuid_to_uuid_message(automation_id))) def create_cron_automation( self, @@ -79,7 +84,7 @@ def create_cron_automation( storage_event_triggers=[], cron_triggers=[CronTrigger(id=UUID(int=0), schedule=s) for s in cron_schedules], ) - return self._service.create(automation) + return AutomationPrototype.from_message(self._service.create_automation(automation.to_message())) def create_storage_event_automation( self, @@ -123,7 +128,7 @@ def create_storage_event_automation( ], cron_triggers=[], ) - return self._service.create(automation) + return AutomationPrototype.from_message(self._service.create_automation(automation.to_message())) def delete(self, automation_or_id: AutomationPrototype | UUID | str, cancel_jobs: bool = False) -> None: """Delete an automation by id. @@ -139,4 +144,6 @@ def delete(self, automation_or_id: AutomationPrototype | UUID | str, cancel_jobs else: automation_id = automation_or_id - self._service.delete(automation_id, cancel_jobs) + self._service.delete_automation( + DeleteAutomationRequest(automation_id=uuid_to_uuid_message(automation_id), cancel_jobs=cancel_jobs) + ) diff --git a/tilebox-workflows/tilebox/workflows/automations/cron.py b/tilebox-workflows/tilebox/workflows/automations/cron.py index 0d065ee..d1bf5f1 100644 --- a/tilebox-workflows/tilebox/workflows/automations/cron.py +++ b/tilebox-workflows/tilebox/workflows/automations/cron.py @@ -4,8 +4,8 @@ from tilebox.workflows.data import TriggeredCronEvent from tilebox.workflows.task import RunnerContext, Task, deserialize_task, serialize_task -from tilebox.workflows.workflows.v1.automation_pb2 import Automation as AutomationMessage -from tilebox.workflows.workflows.v1.automation_pb2 import TriggeredCronEvent as TriggeredCronEventMessage +from tilebox.workflows.v1.automation_pb import Automation as AutomationMessage +from tilebox.workflows.v1.automation_pb import TriggeredCronEvent as TriggeredCronEventMessage _NOT_TRIGGERED = TriggeredCronEvent(datetime.min.replace(tzinfo=UTC)) @@ -23,20 +23,18 @@ def _serialize(self) -> bytes: event = self.trigger.to_message() args = self._serialize_args() message = AutomationMessage( - trigger_event=event.SerializeToString(), + trigger_event=event.to_binary(), args=args, ) - return message.SerializeToString() + return message.to_binary() @classmethod def _deserialize(cls, task_input: bytes, context: RunnerContext | None = None) -> Self: # noqa: ARG003 - message = AutomationMessage() - message.ParseFromString(task_input) + message = AutomationMessage.from_binary(task_input) task = deserialize_task(cls, message.args) - event_message = TriggeredCronEventMessage() - event_message.ParseFromString(message.trigger_event) + event_message = TriggeredCronEventMessage.from_binary(message.trigger_event) event = TriggeredCronEvent.from_message(event_message) task.trigger = event return task diff --git a/tilebox-workflows/tilebox/workflows/automations/service.py b/tilebox-workflows/tilebox/workflows/automations/service.py deleted file mode 100644 index 4b6f163..0000000 --- a/tilebox-workflows/tilebox/workflows/automations/service.py +++ /dev/null @@ -1,50 +0,0 @@ -import builtins -from typing import Any -from uuid import UUID - -from google.protobuf.empty_pb2 import Empty -from grpc import Channel - -from _tilebox.grpc.error import with_pythonic_errors -from tilebox.datasets.uuid import uuid_to_uuid_message -from tilebox.workflows.data import ( - AutomationPrototype, -) -from tilebox.workflows.workflows.v1.automation_pb2 import AutomationPrototype as AutomationPrototypeMessage -from tilebox.workflows.workflows.v1.automation_pb2 import Automations, DeleteAutomationRequest -from tilebox.workflows.workflows.v1.automation_pb2_grpc import AutomationServiceStub - - -class AutomationService: - def __init__(self, channel: Channel | Any) -> None: - """ - A wrapper around the AutomationServiceStub that provides a more pythonic interface and converts the protobuf - messages to and from the data classes used in the rest of the tilebox-workflows codebase. - - Args: - channel: The gRPC channel to use for the service. - """ - self.service = ( - with_pythonic_errors(AutomationServiceStub(channel)) if hasattr(channel, "unary_unary") else channel - ) - - def list(self) -> builtins.list[AutomationPrototype]: - response: Automations = self.service.ListAutomations(Empty()) - return [AutomationPrototype.from_message(automation) for automation in response.automations] - - def get_by_id(self, automation_id: UUID) -> AutomationPrototype: - response: AutomationPrototypeMessage = self.service.GetAutomation(uuid_to_uuid_message(automation_id)) - return AutomationPrototype.from_message(response) - - def create(self, automation: AutomationPrototype) -> AutomationPrototype: - response: AutomationPrototypeMessage = self.service.CreateAutomation(automation.to_message()) - return AutomationPrototype.from_message(response) - - def update(self, automation: AutomationPrototype) -> AutomationPrototype: - response: AutomationPrototypeMessage = self.service.UpdateAutomation(automation.to_message()) - return AutomationPrototype.from_message(response) - - def delete(self, automation_id: UUID, cancel_jobs: bool = False) -> None: - self.service.DeleteAutomation( - DeleteAutomationRequest(automation_id=uuid_to_uuid_message(automation_id), cancel_jobs=cancel_jobs) - ) diff --git a/tilebox-workflows/tilebox/workflows/automations/storage_event.py b/tilebox-workflows/tilebox/workflows/automations/storage_event.py index 355c8e3..cddef1a 100644 --- a/tilebox-workflows/tilebox/workflows/automations/storage_event.py +++ b/tilebox-workflows/tilebox/workflows/automations/storage_event.py @@ -9,8 +9,8 @@ TriggeredStorageEvent, ) from tilebox.workflows.task import RunnerContext, Task, deserialize_task, serialize_task -from tilebox.workflows.workflows.v1.automation_pb2 import Automation as AutomationMessage -from tilebox.workflows.workflows.v1.storage_location_pb2 import TriggeredStorageEvent as TriggeredStorageEventMessage +from tilebox.workflows.v1.automation_pb import Automation as AutomationMessage +from tilebox.workflows.v1.storage_location_pb import TriggeredStorageEvent as TriggeredStorageEventMessage _NOT_TRIGGERED = TriggeredStorageEvent( StorageLocation(UUID(int=0), "", StorageType.LOCAL), @@ -32,20 +32,18 @@ def _serialize(self) -> bytes: event = self.trigger.to_message() args = self._serialize_args() message = AutomationMessage( - trigger_event=event.SerializeToString(), + trigger_event=event.to_binary(), args=args, ) - return message.SerializeToString() + return message.to_binary() @classmethod def _deserialize(cls, task_input: bytes, context: RunnerContext | None = None) -> Self: - message = AutomationMessage() - message.ParseFromString(task_input) + message = AutomationMessage.from_binary(task_input) task = deserialize_task(cls, message.args) - event_message = TriggeredStorageEventMessage() - event_message.ParseFromString(message.trigger_event) + event_message = TriggeredStorageEventMessage.from_binary(message.trigger_event) if context is None: raise ValueError("No storage locations available in runner context, cannot deserialize storage event task.") event = TriggeredStorageEvent.from_message(event_message, context.storage_locations) diff --git a/tilebox-workflows/tilebox/workflows/client.py b/tilebox-workflows/tilebox/workflows/client.py index d24b80c..5a47ed2 100644 --- a/tilebox-workflows/tilebox/workflows/client.py +++ b/tilebox-workflows/tilebox/workflows/client.py @@ -1,20 +1,21 @@ import logging import os import warnings -from typing import TypedDict +from collections.abc import Sequence +from typing import Self, TypedDict +from urllib.parse import urlsplit from uuid import UUID, uuid4 -from _tilebox.grpc.channel import ConnectStubAdapter, Transport, connect_address, open_channel, parse_channel_info -from tilebox.workflows.automations.client import AutomationClient, AutomationService +from connectrpc.interceptor import InterceptorSync + +from _tilebox.client import Connection, Transport +from tilebox.workflows.automations.client import AutomationClient from tilebox.workflows.cache import JobCache, NoCache from tilebox.workflows.clusters.client import ClusterClient, ClusterSlugLike, to_cluster_slug -from tilebox.workflows.clusters.service import ClusterService from tilebox.workflows.data import ( RunnerContext, ) from tilebox.workflows.jobs.client import JobClient -from tilebox.workflows.jobs.service import JobService -from tilebox.workflows.jobs.telemetry_service import TelemetryService from tilebox.workflows.observability._logging import StructuredLogger, internal_logger, task_logger from tilebox.workflows.observability.logging import ( initialize_logging, @@ -24,10 +25,14 @@ from tilebox.workflows.runner.task_runner import TaskRunner, _LeaseRenewer from tilebox.workflows.runner.task_service import TaskService from tilebox.workflows.storage_locations.client import StorageLocationClient -from tilebox.workflows.storage_locations.service import StorageLocationService from tilebox.workflows.task import Task +from tilebox.workflows.v1.automation_connect import AutomationServiceClientSync +from tilebox.workflows.v1.job_connect import JobServiceClientSync +from tilebox.workflows.v1.storage_location_connect import StorageLocationServiceClientSync +from tilebox.workflows.v1.task_connect import TaskServiceClientSync +from tilebox.workflows.v1.telemetry_connect import TelemetryQueryServiceClientSync +from tilebox.workflows.v1.workflows_connect import WorkflowsServiceClientSync from tilebox.workflows.workflows.client import WorkflowClient -from tilebox.workflows.workflows.service import WorkflowService class _ClientAuth(TypedDict): @@ -36,14 +41,15 @@ class _ClientAuth(TypedDict): class Client: - def __init__( + def __init__( # noqa: PLR0913 self, *, url: str | None = None, token: str | None = None, name: str | None = None, client_id: UUID | None = None, - transport: Transport = "grpc", + transport: Transport = "http2", + interceptors: Sequence[InterceptorSync] = (), ) -> None: """ Create a Tilebox workflows client. @@ -56,62 +62,22 @@ def __init__( the service name provided by `tilebox.workflows.observability.tracing.configure_otel_tracing`, or "tilebox-python" if no external tracer is configured. client_id: An optional client identifier. Logging is shared across the process. - transport: Network transport to use for API requests. Defaults to "grpc". Use "http1" to force - the Connect protocol over HTTP/1.1 for networks that do not support gRPC over HTTP/2 correctly. + transport: Connect HTTP version: "http2" (default), or "http1" for proxies that block HTTP/2. + The legacy "grpc" spelling is an alias for "http2". + interceptors: Connect interceptors applied to API requests. """ if url is None: url = os.environ.get("TILEBOX_API_URL") or "https://api.tilebox.com" token = _token_from_env(url, token) self._auth: _ClientAuth = {"token": token, "url": url} - match transport: - case "grpc": - self._job_service = open_channel(url, token) - self._telemetry_service = self._job_service - self._cluster_service = self._job_service - self._workflow_service = self._job_service - self._automation_service = self._job_service - self._storage_location_service = self._job_service - self._task_service = self._job_service - case "http1": - from pyqwest import HTTPVersion, SyncClient, SyncHTTPTransport # noqa: PLC0415 - - from tilebox.workflows.workflows.v1.automation_connect import ( # noqa: PLC0415 - AutomationServiceClientSync, - ) - from tilebox.workflows.workflows.v1.job_connect import JobServiceClientSync # noqa: PLC0415 - from tilebox.workflows.workflows.v1.storage_location_connect import ( # noqa: PLC0415 - StorageLocationServiceClientSync, - ) - from tilebox.workflows.workflows.v1.task_connect import TaskServiceClientSync # noqa: PLC0415 - from tilebox.workflows.workflows.v1.telemetry_connect import ( # noqa: PLC0415 - TelemetryQueryServiceClientSync, - ) - from tilebox.workflows.workflows.v1.workflows_connect import WorkflowsServiceClientSync # noqa: PLC0415 - - address = connect_address(url) - http_client = SyncClient( - SyncHTTPTransport(http_version=HTTPVersion.HTTP1, tls_include_system_certs=True) - ) - headers = None if token is None else {"authorization": f"Bearer {token}"} - self._job_service = ConnectStubAdapter(JobServiceClientSync(address, http_client=http_client), headers) - self._telemetry_service = ConnectStubAdapter( - TelemetryQueryServiceClientSync(address, http_client=http_client), headers - ) - self._cluster_service = ConnectStubAdapter( - WorkflowsServiceClientSync(address, http_client=http_client), headers - ) - self._workflow_service = self._cluster_service - self._automation_service = ConnectStubAdapter( - AutomationServiceClientSync(address, http_client=http_client), headers - ) - self._storage_location_service = ConnectStubAdapter( - StorageLocationServiceClientSync(address, http_client=http_client), headers - ) - self._task_service = ConnectStubAdapter( - TaskServiceClientSync(address, http_client=http_client), headers - ) - case _: - raise ValueError(f"Unsupported transport: {transport}") + self._connection = Connection(url, token, transport=transport, interceptors=interceptors) + self._job_service = self._connection.client(JobServiceClientSync) + self._telemetry_service = self._connection.client(TelemetryQueryServiceClientSync) + self._cluster_service = self._connection.client(WorkflowsServiceClientSync) + self._workflow_service = self._cluster_service + self._automation_service = self._connection.client(AutomationServiceClientSync) + self._storage_location_service = self._connection.client(StorageLocationServiceClientSync) + self._task_service = self._connection.client(TaskServiceClientSync) self._client_id = client_id or uuid4() self._name = name @@ -119,6 +85,19 @@ def __init__( self._runner_logger = StructuredLogger(internal_logger) self._tracer = WorkflowTracer(service=name, url=url, token=token) + def close(self) -> None: + """Close the API connection and flush this client's tracing exporter.""" + try: + self._tracer.close() + finally: + self._connection.close() + + def __enter__(self) -> Self: + return self + + def __exit__(self, *_: object) -> None: + self.close() + def configure_logging(self, level: int | logging.Logger, runner_level: int | None = None) -> None: # noqa: ARG002 """Deprecated compatibility no-op. Runner logging is initialized automatically.""" warnings.warn( @@ -136,7 +115,7 @@ def jobs(self) -> JobClient: A client for the jobs service. """ - return JobClient(JobService(self._job_service), TelemetryService(self._telemetry_service), self._tracer) + return JobClient(self._job_service, self._telemetry_service, self._tracer) def runner( self, @@ -173,7 +152,7 @@ def runner( found_cluster.slug, cache, self._tracer, - _LeaseRenewer(**self._auth), + _LeaseRenewer(**self._auth, transport=self._connection.transport), runner_context, task_logger=self._task_logger, runner_logger=self._runner_logger, @@ -191,7 +170,7 @@ def clusters(self) -> ClusterClient: Returns: A client for the clusters service. """ - return ClusterClient(ClusterService(self._cluster_service)) + return ClusterClient(self._cluster_service) def workflows(self) -> WorkflowClient: """ @@ -200,7 +179,7 @@ def workflows(self) -> WorkflowClient: Returns: A client for the workflows service. """ - return WorkflowClient(WorkflowService(self._workflow_service)) + return WorkflowClient(self._workflow_service) def automations(self) -> AutomationClient: """ @@ -209,18 +188,18 @@ def automations(self) -> AutomationClient: Returns: A client for the automations service. """ - return AutomationClient(AutomationService(self._automation_service)) + return AutomationClient(self._automation_service) def storage_locations(self) -> StorageLocationClient: """Get a client for storage locations.""" - return StorageLocationClient(StorageLocationService(self._storage_location_service)) + return StorageLocationClient(self._storage_location_service) def _token_from_env(url: str, token: str | None) -> str | None: if token is None: # if no token is provided, try to get it from the environment token = os.environ.get("TILEBOX_API_KEY", None) - if token is None and parse_channel_info(url).address == "api.tilebox.com": + if token is None and urlsplit(url if "://" in url else f"https://{url}").hostname == "api.tilebox.com": raise ValueError( "No API key provided and no TILEBOX_API_KEY environment variable set. Please specify an API key using " "the token argument. For example: `Client(token='YOUR_TILEBOX_API_KEY')`" diff --git a/tilebox-workflows/tilebox/workflows/clusters/client.py b/tilebox-workflows/tilebox/workflows/clusters/client.py index 88c9ffb..9e11aee 100644 --- a/tilebox-workflows/tilebox/workflows/clusters/client.py +++ b/tilebox-workflows/tilebox/workflows/clusters/client.py @@ -1,13 +1,20 @@ from typing import TypeAlias -from tilebox.workflows.clusters.service import ClusterService from tilebox.workflows.data import Cluster +from tilebox.workflows.v1.workflows_connect import WorkflowsServiceClientSync +from tilebox.workflows.v1.workflows_pb import ( + CreateClusterRequest, + DeleteClusterRequest, + GetClusterRequest, + ListClustersRequest, + UpdateClusterRequest, +) ClusterSlugLike: TypeAlias = Cluster | str class ClusterClient: - def __init__(self, service: ClusterService) -> None: + def __init__(self, service: WorkflowsServiceClientSync) -> None: """Create a new cluster client. Args: @@ -28,7 +35,8 @@ def create(self, name: str, description: str | None = None, slug: str | None = N Returns: Cluster: The created cluster. """ - return self._service.create(name, description, slug) + request = CreateClusterRequest(name=name, description=description or "", slug=slug or "") + return Cluster.from_message(self._service.create_cluster(request)) def update( self, @@ -46,7 +54,10 @@ def update( Returns: The updated cluster. """ - return self._service.update(to_cluster_slug(cluster_or_slug), name, description) + request = UpdateClusterRequest( + cluster_slug=to_cluster_slug(cluster_or_slug), name=name, description=description + ) + return Cluster.from_message(self._service.update_cluster(request)) def all(self) -> list[Cluster]: """List all available clusters. @@ -54,7 +65,8 @@ def all(self) -> list[Cluster]: Returns: All available clusters. """ - return self._service.list() + response = self._service.list_clusters(ListClustersRequest()) + return [Cluster.from_message(cluster) for cluster in response.clusters] def find(self, cluster_or_slug: ClusterSlugLike) -> Cluster: """Find a cluster by slug. @@ -65,7 +77,9 @@ def find(self, cluster_or_slug: ClusterSlugLike) -> Cluster: Returns: The cluster for the given cluster_slug. """ - return self._service.get_by_slug(to_cluster_slug(cluster_or_slug)) + return Cluster.from_message( + self._service.get_cluster(GetClusterRequest(cluster_slug=to_cluster_slug(cluster_or_slug))) + ) def delete(self, cluster_or_slug: ClusterSlugLike) -> None: """Delete a cluster by slug. @@ -73,7 +87,7 @@ def delete(self, cluster_or_slug: ClusterSlugLike) -> None: Args: cluster_slug: The slug of the cluster to delete. """ - self._service.delete(to_cluster_slug(cluster_or_slug)) + self._service.delete_cluster(DeleteClusterRequest(cluster_slug=to_cluster_slug(cluster_or_slug))) def to_cluster_slug(cluster: Cluster | str) -> str: diff --git a/tilebox-workflows/tilebox/workflows/clusters/service.py b/tilebox-workflows/tilebox/workflows/clusters/service.py deleted file mode 100644 index 9341271..0000000 --- a/tilebox-workflows/tilebox/workflows/clusters/service.py +++ /dev/null @@ -1,53 +0,0 @@ -import builtins -from typing import Any - -from grpc import Channel - -from _tilebox.grpc.error import with_pythonic_errors -from tilebox.workflows.data import ( - Cluster, -) -from tilebox.workflows.workflows.v1.workflows_pb2 import ( - CreateClusterRequest, - DeleteClusterRequest, - GetClusterRequest, - ListClustersRequest, - ListClustersResponse, - UpdateClusterRequest, -) -from tilebox.workflows.workflows.v1.workflows_pb2_grpc import WorkflowsServiceStub - - -class ClusterService: - def __init__(self, channel: Channel | Any) -> None: - """ - A wrapper around the WorkflowsServiceStub that provides a more pythonic interface and converts the protobuf - messages to and from the data classes used in the rest of the tilebox-workflows codebase. - - Args: - channel: The gRPC channel to use for the service. - """ - self.service = ( - with_pythonic_errors(WorkflowsServiceStub(channel)) if hasattr(channel, "unary_unary") else channel - ) - - def create(self, cluster_name: str, description: str | None = None, slug: str | None = None) -> Cluster: - request = CreateClusterRequest(name=cluster_name, description=description, slug=slug or "") - return Cluster.from_message(self.service.CreateCluster(request)) - - def update(self, cluster_slug: str, name: str | None = None, description: str | None = None) -> Cluster: - request = UpdateClusterRequest(cluster_slug=cluster_slug, name=name, description=description) - return Cluster.from_message(self.service.UpdateCluster(request)) - - def get_by_slug(self, cluster_slug: str) -> Cluster: - request = GetClusterRequest(cluster_slug=cluster_slug) - return Cluster.from_message(self.service.GetCluster(request)) - - def delete(self, cluster_slug: str) -> None: - request = DeleteClusterRequest(cluster_slug=cluster_slug) - self.service.DeleteCluster(request) - - def list(self) -> builtins.list[Cluster]: - request = ListClustersRequest() - response: ListClustersResponse = self.service.ListClusters(request) - return [Cluster.from_message(cluster) for cluster in response.clusters] diff --git a/tilebox-workflows/tilebox/workflows/data.py b/tilebox-workflows/tilebox/workflows/data.py index cf00588..3472295 100644 --- a/tilebox-workflows/tilebox/workflows/data.py +++ b/tilebox-workflows/tilebox/workflows/data.py @@ -1,7 +1,7 @@ import os import re import warnings -from collections.abc import Callable, Mapping, MutableMapping +from collections.abc import Callable, Iterable, Mapping, MutableMapping from contextlib import closing from dataclasses import asdict, dataclass, field, replace from datetime import UTC, datetime, timedelta @@ -11,22 +11,23 @@ from typing import TYPE_CHECKING, Any, Literal, Self, TypeAlias, cast from uuid import UUID -from google.protobuf.duration_pb2 import Duration -from opentelemetry.proto.common.v1 import common_pb2 -from opentelemetry.proto.logs.v1 import logs_pb2 -from opentelemetry.proto.resource.v1 import resource_pb2 -from opentelemetry.proto.trace.v1 import trace_pb2 +from protobuf import Oneof +from protobuf.wkt.google.protobuf.duration_pb import Duration -from tilebox.datasets.query.id_interval import IDInterval -from tilebox.datasets.query.pagination import Pagination -from tilebox.datasets.query.time_interval import ( - TimeInterval, +from _tilebox.time import ( datetime_to_timestamp, duration_to_timedelta, timedelta_to_duration, timestamp_to_datetime, ) -from tilebox.datasets.uuid import must_uuid_to_uuid_message, uuid_message_to_uuid, uuid_to_uuid_message +from _tilebox.uuid import must_uuid_to_uuid_message, uuid_message_to_uuid, uuid_to_uuid_message +from tilebox.query.id_interval import IDInterval +from tilebox.query.pagination import Pagination +from tilebox.query.time_interval import TimeInterval +from tilebox.workflows._otel.opentelemetry.proto.common.v1 import common_pb +from tilebox.workflows._otel.opentelemetry.proto.logs.v1 import logs_pb +from tilebox.workflows._otel.opentelemetry.proto.resource.v1 import resource_pb +from tilebox.workflows._otel.opentelemetry.proto.trace.v1 import trace_pb if TYPE_CHECKING: from obstore.store import AzureStore, GCSStore, S3Store @@ -35,9 +36,8 @@ else: WorkflowTracer = Any -from tilebox.workflows.workflows.v1 import automation_pb2 as automation_pb -from tilebox.workflows.workflows.v1 import core_pb2, job_pb2, task_pb2, telemetry_pb2, workflows_pb2 -from tilebox.workflows.workflows.v1 import storage_location_pb2 as storage_pb +from tilebox.workflows.v1 import automation_pb, core_pb, job_pb, task_pb, telemetry_pb, workflows_pb +from tilebox.workflows.v1 import storage_location_pb as storage_pb _VERSION_PATTERN = re.compile(r"^v(\d+)\.(\d+)$") # matches a version string in the format "v3.2" @@ -61,11 +61,11 @@ class TaskIdentifier: version: str @classmethod - def from_message(cls, identifier: core_pb2.TaskIdentifier) -> Self: + def from_message(cls, identifier: core_pb.TaskIdentifier | core_pb.UnboundTaskIdentifier) -> Self: return cls(name=identifier.name, version=identifier.version) - def to_message(self) -> core_pb2.TaskIdentifier: - return core_pb2.TaskIdentifier(name=self.name, version=self.version) + def to_message(self) -> core_pb.TaskIdentifier: + return core_pb.TaskIdentifier(name=self.name, version=self.version) @classmethod def from_name_and_version(cls, name: str, version: str) -> Self: @@ -98,16 +98,18 @@ class TaskLease: recommended_wait_until_next_extension: int @classmethod - def from_message(cls, computed_task: core_pb2.TaskLease) -> "TaskLease": + def from_message(cls, computed_task: core_pb.TaskLease) -> "TaskLease": """Convert a TaskLease protobuf message to a TaskLease object.""" return cls( - lease=computed_task.lease.ToSeconds(), - recommended_wait_until_next_extension=computed_task.recommended_wait_until_next_extension.ToSeconds(), + lease=int((computed_task.lease or Duration()).to_seconds()), + recommended_wait_until_next_extension=int( + (computed_task.recommended_wait_until_next_extension or Duration()).to_seconds() + ), ) - def to_message(self) -> core_pb2.TaskLease: + def to_message(self) -> core_pb.TaskLease: """Convert a TaskLease object to a TaskLease protobuf message.""" - return core_pb2.TaskLease( + return core_pb.TaskLease( lease=Duration(seconds=self.lease), recommended_wait_until_next_extension=Duration(seconds=self.recommended_wait_until_next_extension), ) @@ -120,13 +122,13 @@ class ProgressIndicator: done: int @classmethod - def from_message(cls, progress_indicator: core_pb2.Progress) -> "ProgressIndicator": + def from_message(cls, progress_indicator: core_pb.Progress) -> "ProgressIndicator": """Convert a ProgressIndicator protobuf message to a ProgressIndicator object.""" return cls(label=progress_indicator.label or None, total=progress_indicator.total, done=progress_indicator.done) - def to_message(self) -> core_pb2.Progress: + def to_message(self) -> core_pb.Progress: """Convert a ProgressIndicator object to a ProgressIndicator protobuf message.""" - return core_pb2.Progress(label=self.label, total=self.total, done=self.done) + return core_pb.Progress(label=self.label or "", total=self.total, done=self.done) @dataclass(order=True) @@ -143,30 +145,30 @@ class Task: retry_count: int = field(default=0) @classmethod - def from_message(cls, task: core_pb2.Task) -> Self: + def from_message(cls, task: core_pb.Task) -> Self: """Convert a Task protobuf message to a Task object.""" return cls( id=uuid_message_to_uuid(task.id), - identifier=TaskIdentifier.from_message(task.identifier), + identifier=TaskIdentifier.from_message(task.identifier or core_pb.TaskIdentifier()), state=_TASK_STATES[task.state], input=task.input, - display=task.display, + display=task.display if task.has_field("display") else None, job=Job.from_message(task.job) if task.job else None, - parent_id=uuid_message_to_uuid(task.parent_id) if task.parent_id.uuid else None, + parent_id=uuid_message_to_uuid(task.parent_id) if task.parent_id and task.parent_id.uuid else None, depends_on=[uuid_message_to_uuid(uuid) for uuid in task.depends_on], lease=TaskLease.from_message(task.lease) if task.lease else None, retry_count=task.retry_count, ) - def to_message(self) -> core_pb2.Task: + def to_message(self) -> core_pb.Task: """Convert a Task object to a Task protobuf message.""" depends_on = [uuid_to_uuid_message(uuid) for uuid in self.depends_on] depends_on_valid = [uuid for uuid in depends_on if uuid is not None] - return core_pb2.Task( + return core_pb.Task( id=uuid_to_uuid_message(self.id), identifier=self.identifier.to_message(), - state=cast(core_pb2.TaskState, self.state.value), + state=cast(core_pb.TaskState, self.state.value), input=self.input, display=self.display, job=self.job.to_message() if self.job else None, @@ -182,13 +184,13 @@ class Idling: suggested_idling_duration: timedelta @classmethod - def from_message(cls, idling: task_pb2.IdlingResponse) -> "Idling": + def from_message(cls, idling: task_pb.IdlingResponse) -> "Idling": """Convert a Idling protobuf message to a Idling object.""" - return cls(suggested_idling_duration=duration_to_timedelta(idling.suggested_idling_duration)) + return cls(suggested_idling_duration=duration_to_timedelta(idling.suggested_idling_duration or Duration())) - def to_message(self) -> task_pb2.IdlingResponse: + def to_message(self) -> task_pb.IdlingResponse: """Convert a Idling object to a Idling protobuf message.""" - return task_pb2.IdlingResponse(suggested_idling_duration=timedelta_to_duration(self.suggested_idling_duration)) + return task_pb.IdlingResponse(suggested_idling_duration=timedelta_to_duration(self.suggested_idling_duration)) class JobState(Enum): @@ -218,25 +220,25 @@ class ExecutionStats: tasks_by_state: dict[TaskState, int] @classmethod - def from_message(cls, execution_stats: core_pb2.ExecutionStats) -> "ExecutionStats": + def from_message(cls, execution_stats: core_pb.ExecutionStats) -> "ExecutionStats": """Convert a ExecutionStats protobuf message to a ExecutionStats object.""" return cls( first_task_started_at=timestamp_to_datetime(execution_stats.first_task_started_at) - if execution_stats.HasField("first_task_started_at") + if execution_stats.has_field("first_task_started_at") else None, last_task_stopped_at=timestamp_to_datetime(execution_stats.last_task_stopped_at) - if execution_stats.HasField("last_task_stopped_at") + if execution_stats.has_field("last_task_stopped_at") else None, - compute_time=duration_to_timedelta(execution_stats.compute_time), - elapsed_time=duration_to_timedelta(execution_stats.elapsed_time), + compute_time=duration_to_timedelta(execution_stats.compute_time or Duration()), + elapsed_time=duration_to_timedelta(execution_stats.elapsed_time or Duration()), parallelism=execution_stats.parallelism, total_tasks=execution_stats.total_tasks, tasks_by_state={_TASK_STATES[state.state]: state.count for state in execution_stats.tasks_by_state}, ) - def to_message(self) -> core_pb2.ExecutionStats: + def to_message(self) -> core_pb.ExecutionStats: """Convert a ExecutionStats object to a ExecutionStats protobuf message.""" - return core_pb2.ExecutionStats( + return core_pb.ExecutionStats( first_task_started_at=datetime_to_timestamp(self.first_task_started_at) if self.first_task_started_at else None, @@ -248,7 +250,7 @@ def to_message(self) -> core_pb2.ExecutionStats: parallelism=self.parallelism, total_tasks=self.total_tasks, tasks_by_state=[ - core_pb2.TaskStateCount(state=cast(core_pb2.TaskState, state.value), count=count) + core_pb.TaskStateCount(state=cast(core_pb.TaskState, state.value), count=count) for state, count in self.tasks_by_state.items() ], ) @@ -276,7 +278,7 @@ def __lt__(self, other: object) -> bool: ) @classmethod - def from_message(cls, job: core_pb2.Job, **extra_kwargs: Any) -> Self: + def from_message(cls, job: core_pb.Job, **extra_kwargs: Any) -> Self: """Convert a Job protobuf message to a Job object.""" return cls( id=uuid_message_to_uuid(job.id), @@ -285,17 +287,17 @@ def from_message(cls, job: core_pb2.Job, **extra_kwargs: Any) -> Self: state=_JOB_STATES[job.state], submitted_at=timestamp_to_datetime(job.submitted_at), progress=[ProgressIndicator.from_message(progress) for progress in job.progress], - execution_stats=ExecutionStats.from_message(job.execution_stats), + execution_stats=ExecutionStats.from_message(job.execution_stats or core_pb.ExecutionStats()), **extra_kwargs, ) - def to_message(self) -> core_pb2.Job: + def to_message(self) -> core_pb.Job: """Convert a Job object to a Job protobuf message.""" - return core_pb2.Job( + return core_pb.Job( id=uuid_to_uuid_message(self.id), name=self.name, trace_parent=self.trace_parent, - state=cast(core_pb2.JobState, self.state.value), + state=cast(core_pb.JobState, self.state.value), submitted_at=datetime_to_timestamp(self.submitted_at), progress=[progress.to_message() for progress in self.progress], execution_stats=self.execution_stats.to_message(), @@ -332,7 +334,7 @@ class Cluster: deployed_workflows: list["Workflow"] = field(default_factory=list) @classmethod - def from_message(cls, cluster: workflows_pb2.Cluster, *, include_deployed_workflows: bool = True) -> Self: + def from_message(cls, cluster: workflows_pb.Cluster, *, include_deployed_workflows: bool = True) -> Self: """Convert a Cluster protobuf message to a Cluster object.""" return cls( slug=cluster.slug, @@ -344,13 +346,13 @@ def from_message(cls, cluster: workflows_pb2.Cluster, *, include_deployed_workfl else [], ) - def to_message(self) -> workflows_pb2.Cluster: + def to_message(self) -> workflows_pb.Cluster: """Convert a Cluster object to a Cluster protobuf message.""" - return workflows_pb2.Cluster( + return workflows_pb.Cluster( slug=self.slug, display_name=self.display_name, deletable=self.deletable, - description=self.description, + description=self.description or "", deployed_releases=[workflow.to_message() for workflow in self.deployed_workflows], ) @@ -361,13 +363,13 @@ class Artifact: digest: str @classmethod - def from_message(cls, artifact: workflows_pb2.Artifact) -> "Artifact": + def from_message(cls, artifact: workflows_pb.Artifact) -> "Artifact": """Convert an Artifact protobuf message to an Artifact object.""" return cls(id=uuid_message_to_uuid(artifact.id), digest=artifact.digest) - def to_message(self) -> workflows_pb2.Artifact: + def to_message(self) -> workflows_pb.Artifact: """Convert an Artifact object to an Artifact protobuf message.""" - return workflows_pb2.Artifact(id=uuid_to_uuid_message(self.id), digest=self.digest) + return workflows_pb.Artifact(id=uuid_to_uuid_message(self.id), digest=self.digest) @dataclass(order=True, frozen=True) @@ -377,7 +379,7 @@ class FilesystemNode: children: list["FilesystemNode"] = field(default_factory=list) @classmethod - def from_message(cls, path: workflows_pb2.Path) -> "FilesystemNode": + def from_message(cls, path: workflows_pb.Path) -> "FilesystemNode": """Convert a Path protobuf message to a FilesystemNode object.""" return cls( path=path.path, @@ -385,9 +387,9 @@ def from_message(cls, path: workflows_pb2.Path) -> "FilesystemNode": children=[cls.from_message(child) for child in path.children], ) - def to_message(self) -> workflows_pb2.Path: + def to_message(self) -> workflows_pb.Path: """Convert a FilesystemNode object to a Path protobuf message.""" - return workflows_pb2.Path( + return workflows_pb.Path( path=self.path, directory=self.directory, children=[child.to_message() for child in self.children], @@ -403,7 +405,7 @@ class ReleaseContent: command_override: list[str] = field(default_factory=list) @classmethod - def from_message(cls, content: workflows_pb2.ReleaseContent) -> "ReleaseContent": + def from_message(cls, content: workflows_pb.ReleaseContent) -> "ReleaseContent": """Convert a ReleaseContent protobuf message to a ReleaseContent object.""" return cls( fingerprint=content.fingerprint, @@ -413,11 +415,11 @@ def from_message(cls, content: workflows_pb2.ReleaseContent) -> "ReleaseContent" command_override=list(content.command_override), ) - def to_message(self) -> workflows_pb2.ReleaseContent: + def to_message(self) -> workflows_pb.ReleaseContent: """Convert a ReleaseContent object to a ReleaseContent protobuf message.""" - return workflows_pb2.ReleaseContent( + return workflows_pb.ReleaseContent( fingerprint=self.fingerprint, - tasks=[task.to_message() for task in self.tasks], + tasks=[core_pb.UnboundTaskIdentifier(name=task.name, version=task.version) for task in self.tasks], files=[path.to_message() for path in self.files], runner_object_path=self.runner_object_path, command_override=self.command_override, @@ -433,7 +435,7 @@ class WorkflowPresentation: dataset_label: list[str] = field(default_factory=list) @classmethod - def from_message(cls, presentation: workflows_pb2.WorkflowPresentation) -> "WorkflowPresentation": + def from_message(cls, presentation: workflows_pb.WorkflowPresentation) -> "WorkflowPresentation": return cls( image_url=presentation.image_url, image_alt=presentation.image_alt, @@ -442,8 +444,8 @@ def from_message(cls, presentation: workflows_pb2.WorkflowPresentation) -> "Work dataset_label=list(presentation.dataset_label), ) - def to_message(self) -> workflows_pb2.WorkflowPresentation: - return workflows_pb2.WorkflowPresentation( + def to_message(self) -> workflows_pb.WorkflowPresentation: + return workflows_pb.WorkflowPresentation( image_url=self.image_url, image_alt=self.image_alt, imagery_type=self.imagery_type, @@ -462,23 +464,23 @@ class Workflow: presentation: WorkflowPresentation | None = field(default=None, compare=False) @classmethod - def from_message(cls, workflow: workflows_pb2.Workflow) -> "Workflow": + def from_message(cls, workflow: workflows_pb.Workflow) -> "Workflow": """Convert a Workflow protobuf message to a Workflow object.""" return cls( - slug=workflow.slug, + slug=workflow.reference, name=workflow.name, description=workflow.description, releases=[WorkflowRelease.from_message(release) for release in workflow.releases], summary=workflow.summary, presentation=WorkflowPresentation.from_message(workflow.presentation) - if workflow.HasField("presentation") + if workflow.presentation is not None else None, ) - def to_message(self) -> workflows_pb2.Workflow: + def to_message(self) -> workflows_pb.Workflow: """Convert a Workflow object to a Workflow protobuf message.""" - return workflows_pb2.Workflow( - slug=self.slug, + return workflows_pb.Workflow( + reference=self.slug, name=self.name, description=self.description, releases=[release.to_message() for release in self.releases], @@ -496,19 +498,19 @@ class WorkflowRelease: clusters: list[Cluster] = field(default_factory=list) @classmethod - def from_message(cls, release: workflows_pb2.WorkflowRelease) -> "WorkflowRelease": + def from_message(cls, release: workflows_pb.WorkflowRelease) -> "WorkflowRelease": """Convert a WorkflowRelease protobuf message to a WorkflowRelease object.""" return cls( id=uuid_message_to_uuid(release.id), - artifact=Artifact.from_message(release.artifact) if release.HasField("artifact") else None, - content=ReleaseContent.from_message(release.content) if release.HasField("content") else None, - created_at=timestamp_to_datetime(release.created_at) if release.HasField("created_at") else None, + artifact=Artifact.from_message(release.artifact) if release.artifact is not None else None, + content=ReleaseContent.from_message(release.content) if release.content is not None else None, + created_at=timestamp_to_datetime(release.created_at) if release.has_field("created_at") else None, clusters=[Cluster.from_message(cluster, include_deployed_workflows=False) for cluster in release.clusters], ) - def to_message(self) -> workflows_pb2.WorkflowRelease: + def to_message(self) -> workflows_pb.WorkflowRelease: """Convert a WorkflowRelease object to a WorkflowRelease protobuf message.""" - return workflows_pb2.WorkflowRelease( + return workflows_pb.WorkflowRelease( id=uuid_to_uuid_message(self.id), artifact=self.artifact.to_message() if self.artifact is not None else None, content=self.content.to_message() if self.content is not None else None, @@ -523,22 +525,20 @@ class WorkflowReleaseDeployment: clusters: list[Cluster] @classmethod - def from_deploy_message( - cls, deployment: workflows_pb2.DeployWorkflowReleaseResponse - ) -> "WorkflowReleaseDeployment": + def from_deploy_message(cls, deployment: workflows_pb.DeployWorkflowReleaseResponse) -> "WorkflowReleaseDeployment": """Convert a DeployWorkflowReleaseResponse protobuf message to a WorkflowReleaseDeployment object.""" return cls( - release=WorkflowRelease.from_message(deployment.release), + release=WorkflowRelease.from_message(deployment.release or workflows_pb.WorkflowRelease()), clusters=[Cluster.from_message(cluster) for cluster in deployment.clusters], ) @classmethod def from_undeploy_message( - cls, deployment: workflows_pb2.UndeployWorkflowReleaseResponse + cls, deployment: workflows_pb.UndeployWorkflowReleaseResponse ) -> "WorkflowReleaseDeployment": """Convert an UndeployWorkflowReleaseResponse protobuf message to a WorkflowReleaseDeployment object.""" return cls( - release=WorkflowRelease.from_message(deployment.release), + release=WorkflowRelease.from_message(deployment.release or workflows_pb.WorkflowRelease()), clusters=[Cluster.from_message(cluster) for cluster in deployment.clusters], ) @@ -550,9 +550,9 @@ class NextTaskToRun: # from message not needed, as we never return this from the server - def to_message(self) -> task_pb2.NextTaskToRun: + def to_message(self) -> task_pb.NextTaskToRun: """Convert a NextTaskToRun object to a NextTaskToRun protobuf message.""" - return task_pb2.NextTaskToRun( + return task_pb.NextTaskToRun( cluster_slug=self.cluster_slug, identifiers=[identifier.to_message() for identifier in self.identifiers], ) @@ -569,7 +569,7 @@ class TaskSubmissionGroup: optional_values: list[bool] = field(default_factory=list) @classmethod - def from_message(cls, group: core_pb2.TaskSubmissionGroup) -> "TaskSubmissionGroup": + def from_message(cls, group: core_pb.TaskSubmissionGroup) -> "TaskSubmissionGroup": """Convert a TaskSubmissionGroup protobuf message to a TaskSubmissionGroup object.""" return cls( dependencies_on_other_groups=list(group.dependencies_on_other_groups), @@ -581,9 +581,9 @@ def from_message(cls, group: core_pb2.TaskSubmissionGroup) -> "TaskSubmissionGro optional_values=list(group.optional_values), ) - def to_message(self) -> core_pb2.TaskSubmissionGroup: + def to_message(self) -> core_pb.TaskSubmissionGroup: """Convert a TaskSubmissionGroup object to a TaskSubmissionGroup protobuf message.""" - return core_pb2.TaskSubmissionGroup( + return core_pb.TaskSubmissionGroup( dependencies_on_other_groups=self.dependencies_on_other_groups, inputs=self.inputs, identifier_pointers=self.identifier_pointers, @@ -602,7 +602,7 @@ class TaskSubmissions: display_lookup: list[str] @classmethod - def from_message(cls, sub_task: core_pb2.TaskSubmissions) -> "TaskSubmissions": + def from_message(cls, sub_task: core_pb.TaskSubmissions) -> "TaskSubmissions": """Convert a TaskSubmission protobuf message to a TaskSubmission object.""" return cls( task_groups=[TaskSubmissionGroup.from_message(group) for group in sub_task.task_groups], @@ -611,9 +611,9 @@ def from_message(cls, sub_task: core_pb2.TaskSubmissions) -> "TaskSubmissions": display_lookup=list(sub_task.display_lookup), ) - def to_message(self) -> core_pb2.TaskSubmissions: + def to_message(self) -> core_pb.TaskSubmissions: """Convert a TaskSubmissions object to a TaskSubmissions protobuf message.""" - return core_pb2.TaskSubmissions( + return core_pb.TaskSubmissions( task_groups=[group.to_message() for group in self.task_groups], cluster_slug_lookup=self.cluster_slug_lookup, identifier_lookup=[identifier.to_message() for identifier in self.identifier_lookup], @@ -633,20 +633,20 @@ class SingleTaskSubmission: max_retries: int = 0 @classmethod - def from_message(cls, sub_task: core_pb2.SingleTaskSubmission) -> "SingleTaskSubmission": + def from_message(cls, sub_task: core_pb.SingleTaskSubmission) -> "SingleTaskSubmission": """Convert a TaskSubmission protobuf message to a SingleTaskSubmission object.""" return cls( cluster_slug=sub_task.cluster_slug, - identifier=TaskIdentifier.from_message(sub_task.identifier), + identifier=TaskIdentifier.from_message(sub_task.identifier or core_pb.TaskIdentifier()), input=sub_task.input, dependencies=list(sub_task.dependencies), display=sub_task.display, max_retries=sub_task.max_retries, ) - def to_message(self) -> core_pb2.SingleTaskSubmission: + def to_message(self) -> core_pb.SingleTaskSubmission: """Convert a SingleTaskSubmission object to a TaskSubmission protobuf message.""" - return core_pb2.SingleTaskSubmission( + return core_pb.SingleTaskSubmission( cluster_slug=self.cluster_slug, identifier=self.identifier.to_message(), input=self.input, @@ -664,22 +664,22 @@ class ComputedTask: progress_updates: list[ProgressIndicator] @classmethod - def from_message(cls, computed_task: task_pb2.ComputedTask) -> "ComputedTask": + def from_message(cls, computed_task: task_pb.ComputedTask) -> "ComputedTask": """Convert a ComputedTask protobuf message to a ComputedTask object.""" return cls( id=uuid_message_to_uuid(computed_task.id), display=computed_task.display, sub_tasks=TaskSubmissions.from_message(computed_task.sub_tasks) - if computed_task.HasField("sub_tasks") + if computed_task.sub_tasks is not None else None, progress_updates=[ProgressIndicator.from_message(progress) for progress in computed_task.progress_updates], ) - def to_message(self) -> task_pb2.ComputedTask: + def to_message(self) -> task_pb.ComputedTask: """Convert a ComputedTask object to a ComputedTask protobuf message.""" - return task_pb2.ComputedTask( + return task_pb.ComputedTask( id=uuid_to_uuid_message(self.id), - display=self.display, + display=self.display or "", sub_tasks=self.sub_tasks.to_message() if self.sub_tasks else None, progress_updates=[progress.to_message() for progress in self.progress_updates], ) @@ -693,7 +693,7 @@ class FailedTask: progress_updates: list[ProgressIndicator] @classmethod - def from_message(cls, failed_task: task_pb2.TaskFailedRequest) -> "FailedTask": + def from_message(cls, failed_task: task_pb.TaskFailedRequest) -> "FailedTask": """Convert a TaskFailedRequest protobuf message to a FailedTask object.""" return cls( task_id=uuid_message_to_uuid(failed_task.task_id), @@ -720,11 +720,11 @@ def from_task_error( progress_updates=progress_updates, ) - def to_message(self) -> task_pb2.TaskFailedRequest: + def to_message(self) -> task_pb.TaskFailedRequest: """Convert a FailedTask object to a TaskFailedRequest protobuf message.""" - return task_pb2.TaskFailedRequest( + return task_pb.TaskFailedRequest( task_id=uuid_to_uuid_message(self.task_id), - display=self.display, + display=self.display or "", was_workflow_error=self.was_workflow_error, progress_updates=[progress.to_message() for progress in self.progress_updates], ) @@ -747,11 +747,11 @@ def _parse_version(version: str) -> tuple[int, int]: class LogSeverity(Enum): - TRACE = telemetry_pb2.LOG_SEVERITY_GROUP_TRACE - DEBUG = telemetry_pb2.LOG_SEVERITY_GROUP_DEBUG - INFO = telemetry_pb2.LOG_SEVERITY_GROUP_INFO - WARNING = telemetry_pb2.LOG_SEVERITY_GROUP_WARNING - ERROR = telemetry_pb2.LOG_SEVERITY_GROUP_ERROR + TRACE = telemetry_pb.LogSeverityGroup.TRACE + DEBUG = telemetry_pb.LogSeverityGroup.DEBUG + INFO = telemetry_pb.LogSeverityGroup.INFO + WARNING = telemetry_pb.LogSeverityGroup.WARNING + ERROR = telemetry_pb.LogSeverityGroup.ERROR LogSeverityLiteral: TypeAlias = Literal["trace", "debug", "info", "warning", "error"] @@ -769,13 +769,13 @@ class LogRecord: runner_attributes: dict[str, Any] @classmethod - def from_message(cls, log_record: logs_pb2.ResourceLogs) -> "LogRecord": + def from_message(cls, log_record: logs_pb.ResourceLogs) -> "LogRecord": try: log_record_message = log_record.scope_logs[0].log_records[0] except IndexError: raise ValueError("ResourceLogs does not contain a log record.") from None - body = _any_value_to_python(log_record_message.body) if log_record_message.HasField("body") else "" + body = _any_value_to_python(log_record_message.body) if log_record_message.body is not None else "" return cls( time=_datetime_from_unix_nanos(log_record_message.time_unix_nano), severity_number=int(log_record_message.severity_number), @@ -785,19 +785,19 @@ def from_message(cls, log_record: logs_pb2.ResourceLogs) -> "LogRecord": span_id=log_record_message.span_id.hex() or None, attributes=_key_values_to_dict(log_record_message.attributes), runner_attributes=_key_values_to_dict(log_record.resource.attributes) - if log_record.HasField("resource") + if log_record.resource is not None else {}, ) - def to_message(self) -> logs_pb2.ResourceLogs: - return logs_pb2.ResourceLogs( - resource=resource_pb2.Resource(attributes=_dict_to_key_values(self.runner_attributes)), + def to_message(self) -> logs_pb.ResourceLogs: + return logs_pb.ResourceLogs( + resource=resource_pb.Resource(attributes=_dict_to_key_values(self.runner_attributes)), scope_logs=[ - logs_pb2.ScopeLogs( + logs_pb.ScopeLogs( log_records=[ - logs_pb2.LogRecord( + logs_pb.LogRecord( time_unix_nano=_datetime_to_unix_nanos(self.time), - severity_number=cast(logs_pb2.SeverityNumber.ValueType, self.severity_number), + severity_number=logs_pb.SeverityNumber(self.severity_number), severity_text=self.severity_text, body=_python_to_any_value(self.body), attributes=_dict_to_key_values(self.attributes), @@ -837,7 +837,7 @@ def duration(self) -> timedelta: return self.end_time - self.start_time @classmethod - def from_message(cls, span: trace_pb2.ResourceSpans) -> "Span": + def from_message(cls, span: trace_pb.ResourceSpans) -> "Span": try: span_message = span.scope_spans[0].spans[0] except IndexError: @@ -850,10 +850,10 @@ def from_message(cls, span: trace_pb2.ResourceSpans) -> "Span": span_id=span_message.span_id.hex(), parent_span_id=span_message.parent_span_id.hex() or None, name=span_message.name, - status_code=trace_pb2.Status.StatusCode.Name(span_message.status.code), - status_message=span_message.status.message, + status_code="STATUS_CODE_" + (span_message.status or trace_pb.Status()).code.name, + status_message=(span_message.status or trace_pb.Status()).message, attributes=_key_values_to_dict(span_message.attributes), - runner_attributes=_key_values_to_dict(span.resource.attributes) if span.HasField("resource") else {}, + runner_attributes=_key_values_to_dict(span.resource.attributes) if span.resource is not None else {}, events=[ { "time": _datetime_from_unix_nanos(event.time_unix_nano), @@ -864,20 +864,20 @@ def from_message(cls, span: trace_pb2.ResourceSpans) -> "Span": ], ) - def to_message(self) -> trace_pb2.ResourceSpans: - return trace_pb2.ResourceSpans( - resource=resource_pb2.Resource(attributes=_dict_to_key_values(self.runner_attributes)), + def to_message(self) -> trace_pb.ResourceSpans: + return trace_pb.ResourceSpans( + resource=resource_pb.Resource(attributes=_dict_to_key_values(self.runner_attributes)), scope_spans=[ - trace_pb2.ScopeSpans( + trace_pb.ScopeSpans( spans=[ - trace_pb2.Span( + trace_pb.Span( start_time_unix_nano=_datetime_to_unix_nanos(self.start_time), end_time_unix_nano=_datetime_to_unix_nanos(self.end_time), trace_id=bytes.fromhex(self.trace_id), span_id=bytes.fromhex(self.span_id), parent_span_id=bytes.fromhex(self.parent_span_id) if self.parent_span_id else b"", name=self.name, - status=trace_pb2.Status( + status=trace_pb.Status( code=_status_code_from_name(self.status_code), message=self.status_message, ), @@ -911,67 +911,60 @@ def _datetime_to_unix_nanos(value: datetime) -> int: return int(value.timestamp() * 1_000_000_000) -def _any_value_to_python(value: common_pb2.AnyValue) -> Any: # noqa: PLR0911 - match value.WhichOneof("value"): - case "string_value": - return value.string_value - case "bool_value": - return value.bool_value - case "int_value": - return value.int_value - case "double_value": - return value.double_value - case "bytes_value": - return value.bytes_value - case "array_value": - return [_any_value_to_python(item) for item in value.array_value.values] # noqa: PD011 - case "kvlist_value": - return _key_values_to_dict(value.kvlist_value.values) - case _: - return None - - -def _python_to_any_value(value: Any) -> common_pb2.AnyValue: # noqa: PLR0911 +def _any_value_to_python(value: common_pb.AnyValue | None) -> Any: + if value is None or value.value is None: + return None + selected = value.value + if selected.field == "array_value": + return [_any_value_to_python(item) for item in selected.value.values] # noqa: PD011 + if selected.field == "kvlist_value": + return _key_values_to_dict(selected.value.values) + return selected.value + + +def _python_to_any_value(value: Any) -> common_pb.AnyValue: # noqa: PLR0911 if isinstance(value, bool): - return common_pb2.AnyValue(bool_value=value) + return common_pb.AnyValue(value=Oneof("bool_value", value)) if isinstance(value, str): - return common_pb2.AnyValue(string_value=value) + return common_pb.AnyValue(value=Oneof("string_value", value)) if isinstance(value, int): - return common_pb2.AnyValue(int_value=value) + return common_pb.AnyValue(value=Oneof("int_value", value)) if isinstance(value, float): - return common_pb2.AnyValue(double_value=value) + return common_pb.AnyValue(value=Oneof("double_value", value)) if isinstance(value, bytes): - return common_pb2.AnyValue(bytes_value=value) + return common_pb.AnyValue(value=Oneof("bytes_value", value)) if isinstance(value, list | tuple): - return common_pb2.AnyValue( - array_value=common_pb2.ArrayValue(values=[_python_to_any_value(item) for item in value]) + return common_pb.AnyValue( + value=Oneof("array_value", common_pb.ArrayValue(values=[_python_to_any_value(item) for item in value])) ) if isinstance(value, dict): - return common_pb2.AnyValue(kvlist_value=common_pb2.KeyValueList(values=_dict_to_key_values(value))) - return common_pb2.AnyValue(string_value=str(value)) + return common_pb.AnyValue( + value=Oneof("kvlist_value", common_pb.KeyValueList(values=_dict_to_key_values(value))) + ) + return common_pb.AnyValue(value=Oneof("string_value", str(value))) -def _key_values_to_dict(key_values: Any) -> dict[str, Any]: +def _key_values_to_dict(key_values: Iterable[common_pb.KeyValue]) -> dict[str, Any]: return {key_value.key: _any_value_to_python(key_value.value) for key_value in key_values} -def _dict_to_key_values(attributes: dict[str, Any]) -> list[common_pb2.KeyValue]: - return [common_pb2.KeyValue(key=key, value=_python_to_any_value(value)) for key, value in attributes.items()] +def _dict_to_key_values(attributes: dict[str, Any]) -> list[common_pb.KeyValue]: + return [common_pb.KeyValue(key=key, value=_python_to_any_value(value)) for key, value in attributes.items()] -def _status_code_from_name(status_code: str) -> trace_pb2.Status.StatusCode.ValueType: +def _status_code_from_name(status_code: str) -> trace_pb.Status.StatusCode: try: - return trace_pb2.Status.StatusCode.Value(status_code) - except ValueError: - return trace_pb2.Status.STATUS_CODE_UNSET + return trace_pb.Status.StatusCode[status_code.removeprefix("STATUS_CODE_")] + except KeyError: + return trace_pb.Status.StatusCode.UNSET -def _span_event_to_message(event: dict[str, Any]) -> trace_pb2.Span.Event: +def _span_event_to_message(event: dict[str, Any]) -> trace_pb.Span.Event: time = event.get("time") attributes = event.get("attributes", {}) if not isinstance(attributes, dict): attributes = {} - return trace_pb2.Span.Event( + return trace_pb.Span.Event( time_unix_nano=_datetime_to_unix_nanos(time) if isinstance(time, datetime) else 0, name=str(event.get("name", "")), attributes=_dict_to_key_values(attributes), @@ -984,7 +977,7 @@ class QueryJobLogsResponse: next_page: Pagination @classmethod - def from_message(cls, page: Any) -> "QueryJobLogsResponse": + def from_message(cls, page: telemetry_pb.PaginatedLogsData) -> "QueryJobLogsResponse": return cls( logs=LogRecords(LogRecord.from_message(log_record) for log_record in page.resource_logs), next_page=Pagination.from_message(page.next_page), @@ -997,7 +990,7 @@ class QueryJobSpansResponse: next_page: Pagination @classmethod - def from_message(cls, page: Any) -> "QueryJobSpansResponse": + def from_message(cls, page: telemetry_pb.PaginatedSpansData) -> "QueryJobSpansResponse": return cls( spans=Spans(Span.from_message(span) for span in page.resource_spans), next_page=Pagination.from_message(page.next_page), @@ -1005,10 +998,10 @@ def from_message(cls, page: Any) -> "QueryJobSpansResponse": class StorageType(Enum): - GCS = storage_pb.STORAGE_TYPE_GCS # Google Cloud Storage - AWS_S3 = storage_pb.STORAGE_TYPE_AWS_S3 # Amazon Web Services S3 - LOCAL = storage_pb.STORAGE_TYPE_LOCAL # Local Filesystem - AZURE_BLOB = storage_pb.STORAGE_TYPE_AZURE_BLOB # Azure Blob Storage + GCS = storage_pb.StorageType.GCS # Google Cloud Storage + AWS_S3 = storage_pb.StorageType.AWS_S3 # Amazon Web Services S3 + LOCAL = storage_pb.StorageType.LOCAL # Local Filesystem + AZURE_BLOB = storage_pb.StorageType.AZURE_BLOB # Azure Blob Storage @dataclass(frozen=True) @@ -1018,7 +1011,7 @@ class AWSS3BucketReference: def to_message(self) -> storage_pb.StorageLocationReference: return storage_pb.StorageLocationReference( - type=storage_pb.STORAGE_TYPE_AWS_S3, + type=storage_pb.StorageType.AWS_S3, aws_s3_bucket=storage_pb.AWSS3BucketReference(bucket=self.bucket, region=self.region), ) @@ -1031,7 +1024,7 @@ class GCSBucketReference: def to_message(self) -> storage_pb.StorageLocationReference: return storage_pb.StorageLocationReference( - type=storage_pb.STORAGE_TYPE_GCS, + type=storage_pb.StorageType.GCS, gcs_bucket=storage_pb.GCSBucketReference( bucket=self.bucket, project_id=self.project_id, location=self.location ), @@ -1046,7 +1039,7 @@ class AzureBlobReference: def to_message(self) -> storage_pb.StorageLocationReference: return storage_pb.StorageLocationReference( - type=storage_pb.STORAGE_TYPE_AZURE_BLOB, + type=storage_pb.StorageType.AZURE_BLOB, azure_blob=storage_pb.AzureBlobReference( storage_account_resource_id=self.storage_account_resource_id, container=self.container, @@ -1061,7 +1054,7 @@ class LocalReference: def to_message(self) -> storage_pb.StorageLocationReference: return storage_pb.StorageLocationReference( - type=storage_pb.STORAGE_TYPE_LOCAL, local=storage_pb.LocalReference(path=self.path) + type=storage_pb.StorageType.LOCAL, local=storage_pb.LocalReference(path=self.path) ) @@ -1096,39 +1089,42 @@ def __str__(self) -> str: @classmethod def from_message(cls, storage_location: storage_pb.StorageLocation) -> "StorageLocation": """Convert a StorageLocation protobuf message to a StorageLocation object.""" - reference = storage_location.reference + reference = storage_location.reference or storage_pb.StorageLocationReference() match reference.type: - case storage_pb.STORAGE_TYPE_AZURE_BLOB: + case storage_pb.StorageType.AZURE_BLOB: + azure = reference.azure_blob or storage_pb.AzureBlobReference() return AzureBlobStorageLocation( id=uuid_message_to_uuid(storage_location.id), name=storage_location.name, - location=reference.azure_blob.container, + location=azure.container, type=StorageType.AZURE_BLOB, - storage_account_resource_id=reference.azure_blob.storage_account_resource_id, - region=reference.azure_blob.region, + storage_account_resource_id=azure.storage_account_resource_id, + region=azure.region, ) - case storage_pb.STORAGE_TYPE_GCS: + case storage_pb.StorageType.GCS: + gcs = reference.gcs_bucket or storage_pb.GCSBucketReference() return GCSStorageLocation( id=uuid_message_to_uuid(storage_location.id), name=storage_location.name, - location=reference.gcs_bucket.bucket, + location=gcs.bucket, type=StorageType.GCS, - project_id=reference.gcs_bucket.project_id, - bucket_location=reference.gcs_bucket.location, + project_id=gcs.project_id, + bucket_location=gcs.location, ) - case storage_pb.STORAGE_TYPE_AWS_S3: + case storage_pb.StorageType.AWS_S3: + s3 = reference.aws_s3_bucket or storage_pb.AWSS3BucketReference() return AWSS3StorageLocation( id=uuid_message_to_uuid(storage_location.id), name=storage_location.name, - location=reference.aws_s3_bucket.bucket, + location=s3.bucket, type=StorageType.AWS_S3, - region=reference.aws_s3_bucket.region or None, + region=s3.region or None, ) - case storage_pb.STORAGE_TYPE_LOCAL: + case storage_pb.StorageType.LOCAL: return cls( id=uuid_message_to_uuid(storage_location.id), name=storage_location.name, - location=reference.local.path, + location=(reference.local or storage_pb.LocalReference()).path, type=StorageType.LOCAL, ) case _: @@ -1142,13 +1138,13 @@ def to_message(self) -> storage_pb.StorageLocation: reference = storage_pb.StorageLocationReference(type=self.type.value) match self.type: case StorageType.AWS_S3: - reference.aws_s3_bucket.bucket = self.location + reference.aws_s3_bucket = storage_pb.AWSS3BucketReference(bucket=self.location) case StorageType.GCS: - reference.gcs_bucket.bucket = self.location + reference.gcs_bucket = storage_pb.GCSBucketReference(bucket=self.location) case StorageType.AZURE_BLOB: - reference.azure_blob.container = self.location + reference.azure_blob = storage_pb.AzureBlobReference(container=self.location) case StorageType.LOCAL: - reference.local.path = self.location + reference.local = storage_pb.LocalReference(path=self.location) return storage_pb.StorageLocation(id=uuid_to_uuid_message(self.id), name=self.name, reference=reference) def read(self, path: str) -> bytes: @@ -1193,7 +1189,7 @@ class AWSS3StorageLocation(StorageLocation): def to_message(self) -> storage_pb.StorageLocation: """Include the S3 bucket and its region in the storage reference.""" message = super().to_message() - message.reference.aws_s3_bucket.region = self.region or "" + message.reference = AWSS3BucketReference(self.location, self.region or "").to_message() return message @@ -1204,8 +1200,7 @@ class GCSStorageLocation(StorageLocation): def to_message(self) -> storage_pb.StorageLocation: message = super().to_message() - message.reference.gcs_bucket.project_id = self.project_id - message.reference.gcs_bucket.location = self.bucket_location + message.reference = GCSBucketReference(self.location, self.project_id, self.bucket_location).to_message() return message @@ -1220,8 +1215,9 @@ def __str__(self) -> str: def to_message(self) -> storage_pb.StorageLocation: """Include the Azure account and container in the storage reference.""" message = super().to_message() - message.reference.azure_blob.storage_account_resource_id = self.storage_account_resource_id - message.reference.azure_blob.region = self.region + message.reference = AzureBlobReference( + self.storage_account_resource_id, self.location, self.region + ).to_message() return message @@ -1236,7 +1232,7 @@ def from_message(cls, trigger: automation_pb.StorageEventTrigger) -> "StorageEve """Convert a StorageEventTrigger protobuf message to a StorageEventTrigger object.""" return cls( id=uuid_message_to_uuid(trigger.id), - storage_location=StorageLocation.from_message(trigger.storage_location), + storage_location=StorageLocation.from_message(trigger.storage_location or storage_pb.StorageLocation()), glob_pattern=trigger.glob_pattern, ) @@ -1250,7 +1246,7 @@ def to_message(self) -> automation_pb.StorageEventTrigger: class StorageEventType(Enum): - CREATED = storage_pb.STORAGE_EVENT_TYPE_CREATED + CREATED = storage_pb.StorageEventType.CREATED _STORAGE_EVENT_TYPE_TO_ENUM = {storage_event_type.value: storage_event_type for storage_event_type in StorageEventType} @@ -1332,7 +1328,7 @@ def from_message(cls, task: automation_pb.AutomationPrototype) -> "AutomationPro return cls( id=uuid_message_to_uuid(task.id), name=task.name, - prototype=SingleTaskSubmission.from_message(task.prototype), + prototype=SingleTaskSubmission.from_message(task.prototype or core_pb.SingleTaskSubmission()), storage_event_triggers=[ StorageEventTrigger.from_message(trigger) for trigger in task.storage_event_triggers ], @@ -1466,16 +1462,16 @@ class QueryJobsResponse: @classmethod def from_message( cls, - page: job_pb2.QueryJobsResponse, - job_factory: Callable[[core_pb2.Job], Job] = Job.from_message, + page: job_pb.QueryJobsResponse, + job_factory: Callable[[core_pb.Job], Job] = Job.from_message, ) -> "QueryJobsResponse": return cls( jobs=[job_factory(job) for job in page.jobs], next_page=Pagination.from_message(page.next_page), ) - def to_message(self) -> job_pb2.QueryJobsResponse: - return job_pb2.QueryJobsResponse( + def to_message(self) -> job_pb.QueryJobsResponse: + return job_pb.QueryJobsResponse( jobs=[job.to_message() for job in self.jobs], next_page=self.next_page.to_message(), ) @@ -1492,12 +1488,12 @@ class QueryFilters: cluster_slugs: list[str] = field(default_factory=list) @classmethod - def from_message(cls, filters: job_pb2.QueryFilters) -> "QueryFilters": + def from_message(cls, filters: job_pb.QueryFilters) -> "QueryFilters": return cls( time_interval=TimeInterval.from_message(filters.time_interval) - if filters.HasField("time_interval") + if filters.time_interval is not None else None, - id_interval=IDInterval.from_message(filters.id_interval) if filters.HasField("id_interval") else None, + id_interval=IDInterval.from_message(filters.id_interval) if filters.id_interval is not None else None, automation_ids=[uuid_message_to_uuid(uuid) for uuid in filters.automation_ids], job_states=[_JOB_STATES[state] for state in filters.states], name=filters.name or None, @@ -1505,16 +1501,16 @@ def from_message(cls, filters: job_pb2.QueryFilters) -> "QueryFilters": cluster_slugs=list(filters.cluster_slugs), ) - def to_message(self) -> job_pb2.QueryFilters: - return job_pb2.QueryFilters( + def to_message(self) -> job_pb.QueryFilters: + return job_pb.QueryFilters( time_interval=self.time_interval.to_message() if self.time_interval else None, id_interval=self.id_interval.to_message() if self.id_interval else None, automation_ids=[must_uuid_to_uuid_message(uuid) for uuid in self.automation_ids] if self.automation_ids else None, - states=[cast(core_pb2.JobState, state.value) for state in self.job_states] if self.job_states else None, - name=self.name or None, # empty string becomes None - task_states=[cast(core_pb2.TaskState, state.value) for state in self.task_states] + states=[cast(core_pb.JobState, state.value) for state in self.job_states] if self.job_states else None, + name=self.name or "", + task_states=[cast(core_pb.TaskState, state.value) for state in self.task_states] if self.task_states else None, cluster_slugs=self.cluster_slugs or None, diff --git a/tilebox-workflows/tilebox/workflows/jobs/client.py b/tilebox-workflows/tilebox/workflows/jobs/client.py index f91b161..7d4fb55 100644 --- a/tilebox-workflows/tilebox/workflows/jobs/client.py +++ b/tilebox-workflows/tilebox/workflows/jobs/client.py @@ -1,11 +1,12 @@ from typing import Any, TypeAlias from uuid import UUID -from _tilebox.grpc.pagination import Pagination as PaginationProtocol -from _tilebox.grpc.pagination import paginated_request -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 +from _tilebox.pagination import Pagination as PaginationProtocol +from _tilebox.pagination import paginated_request +from _tilebox.uuid import uuid_to_uuid_message +from tilebox.query.id_interval import IDInterval, IDIntervalLike +from tilebox.query.pagination import Pagination +from tilebox.query.time_interval import TimeInterval, TimeIntervalLike from tilebox.workflows.clusters.client import ClusterSlugLike, to_cluster_slug from tilebox.workflows.data import ( Job, @@ -20,11 +21,23 @@ Spans, TaskState, ) -from tilebox.workflows.jobs.service import JobService -from tilebox.workflows.jobs.telemetry_service import TelemetryService +from tilebox.workflows.formatting.job import JobWidget, RichDisplayJob from tilebox.workflows.observability.tracing import WorkflowTracer, get_trace_parent_of_current_span, start_job_span from tilebox.workflows.task import FutureTask, merge_future_tasks_to_submissions from tilebox.workflows.task import Task as TaskInstance +from tilebox.workflows.v1.core_pb import Job as JobMessage +from tilebox.workflows.v1.diagram_pb import RenderOptions +from tilebox.workflows.v1.job_connect import JobServiceClientSync +from tilebox.workflows.v1.job_pb import ( + CancelJobRequest, + GetJobRequest, + QueryJobsRequest, + RetryJobRequest, + SubmitJobRequest, + VisualizeJobRequest, +) +from tilebox.workflows.v1.telemetry_connect import TelemetryQueryServiceClientSync +from tilebox.workflows.v1.telemetry_pb import LogQueryFilters, QueryJobLogsRequest, QueryJobSpansRequest try: from IPython.display import HTML, display @@ -41,13 +54,15 @@ def display(*_args: Any, **_kwargs: Any) -> None: class JobClient: - def __init__(self, service: JobService, telemetry_service: TelemetryService, tracer: WorkflowTracer) -> None: + def __init__( + self, service: JobServiceClientSync, telemetry_service: TelemetryQueryServiceClientSync, tracer: WorkflowTracer + ) -> None: """Create a new job client. - z - Args: - service: The service to use for job operations. - telemetry_service: The service to use for job telemetry queries. - tracer: The tracer to use for tracing. + + Args: + service: The native client to use for job operations. + telemetry_service: The native client to use for job telemetry queries. + tracer: The tracer to use for tracing. """ self._service = service self._telemetry_service = telemetry_service @@ -96,7 +111,10 @@ def submit( with self._tracer.span(f"job/{job_name}"): trace_parent = get_trace_parent_of_current_span() - return self._service.submit(job_name, trace_parent, submissions_merged) + request = SubmitJobRequest( + job_name=job_name, trace_parent=trace_parent, tasks=submissions_merged.to_message() + ) + return self._job_from_message(self._service.submit_job(request)) def retry(self, job_or_id: JobIDLike) -> int: """Retry a job. @@ -111,7 +129,9 @@ def retry(self, job_or_id: JobIDLike) -> int: job = self.find(_to_uuid(job_or_id)) if not isinstance(job_or_id, Job) else job_or_id with start_job_span(self._tracer, job, "retry_job") as span: - num_rescheduled = self._service.retry(job.id) + num_rescheduled = self._service.retry_job( + RetryJobRequest(job_id=uuid_to_uuid_message(job.id)) + ).num_tasks_rescheduled span.add_event("retried_succeeded", {"num_tasks_rescheduled": num_rescheduled}) return num_rescheduled @@ -125,7 +145,7 @@ def cancel(self, job_or_id: JobIDLike) -> None: job = self.find(_to_uuid(job_or_id)) if not isinstance(job_or_id, Job) else job_or_id with start_job_span(self._tracer, job, "cancel_job") as cancel_job: - self._service.cancel(job.id) + self._service.cancel_job(CancelJobRequest(job_id=uuid_to_uuid_message(job.id))) cancel_job.add_event("cancel_succeeded") def find(self, job_id: JobIDLike) -> Job: @@ -138,7 +158,13 @@ def find(self, job_id: JobIDLike) -> Job: Returns: The job details for the given job_id. """ - return self._service.get_by_id(_to_uuid(job_id)) + return self._job_from_message( + self._service.get_job(GetJobRequest(job_id=uuid_to_uuid_message(_to_uuid(job_id)))) + ) + + def _job_from_message(self, message: JobMessage) -> RichDisplayJob: + """Attach notebook formatting and refresh through this client's find operation.""" + return RichDisplayJob.from_message(message, _widget=JobWidget(self.find)) def query_logs( self, @@ -169,7 +195,15 @@ def query_logs( def request(page: PaginationProtocol) -> QueryJobLogsResponse: query_page = Pagination(page.limit, page.starting_after) - return self._telemetry_service.query_job_logs(_to_uuid(job_id), query_page, task_uuid, severity_levels) + request = QueryJobLogsRequest( + job_id=uuid_to_uuid_message(_to_uuid(job_id)), + page=query_page.to_message(), + task_id=uuid_to_uuid_message(task_uuid) if task_uuid is not None else None, + filters=LogQueryFilters(severity_levels=[severity.value for severity in severity_levels]) + if severity_levels + else None, + ) + return QueryJobLogsResponse.from_message(self._telemetry_service.query_job_logs(request)) pages = paginated_request(request, Pagination()) @@ -193,7 +227,12 @@ def query_spans(self, job_id: JobIDLike, *, task_id: UUID | str | None = None) - def request(page: PaginationProtocol) -> QueryJobSpansResponse: query_page = Pagination(page.limit, page.starting_after) - return self._telemetry_service.query_job_spans(_to_uuid(job_id), query_page, task_uuid) + request = QueryJobSpansRequest( + job_id=uuid_to_uuid_message(_to_uuid(job_id)), + page=query_page.to_message(), + task_id=uuid_to_uuid_message(task_uuid) if task_uuid is not None else None, + ) + return QueryJobSpansResponse.from_message(self._telemetry_service.query_job_spans(request)) pages = paginated_request(request, Pagination()) @@ -229,7 +268,11 @@ def visualize(self, job: JobIDLike, direction: str = "down", layout: str = "dagr Returns: Rendered SVG of the diagram. """ - return self._service.visualize(_to_uuid(job), direction, layout, sketchy) + request = VisualizeJobRequest( + job_id=uuid_to_uuid_message(_to_uuid(job)), + render_options=RenderOptions(sketchy=sketchy, padding=5, layout=layout, direction=direction), + ) + return self._service.visualize_job(request).svg.decode("utf-8") def query( # noqa: C901, PLR0913, PLR0917 self, @@ -327,7 +370,8 @@ def query( # noqa: C901, PLR0913, PLR0917 def request(page: PaginationProtocol) -> QueryJobsResponse: query_page = Pagination(page.limit, page.starting_after) - return self._service.query(filters, query_page) + request = QueryJobsRequest(filters=filters.to_message(), page=query_page.to_message()) + return QueryJobsResponse.from_message(self._service.query_jobs(request), job_factory=self._job_from_message) initial_page = Pagination() pages = paginated_request(request, initial_page) diff --git a/tilebox-workflows/tilebox/workflows/jobs/service.py b/tilebox-workflows/tilebox/workflows/jobs/service.py deleted file mode 100644 index 9fad323..0000000 --- a/tilebox-workflows/tilebox/workflows/jobs/service.py +++ /dev/null @@ -1,81 +0,0 @@ -from typing import Any -from uuid import UUID - -from grpc import Channel - -from _tilebox.grpc.error import with_pythonic_errors -from tilebox.datasets.query.pagination import Pagination -from tilebox.workflows.data import ( - Job, - QueryFilters, - QueryJobsResponse, - TaskSubmissions, - uuid_to_uuid_message, -) -from tilebox.workflows.formatting.job import JobWidget, RichDisplayJob -from tilebox.workflows.workflows.v1.core_pb2 import Job as JobMessage -from tilebox.workflows.workflows.v1.diagram_pb2 import Diagram, RenderOptions -from tilebox.workflows.workflows.v1.job_pb2 import ( - CancelJobRequest, - GetJobRequest, - QueryJobsRequest, - RetryJobRequest, - RetryJobResponse, - SubmitJobRequest, - VisualizeJobRequest, -) -from tilebox.workflows.workflows.v1.job_pb2 import QueryJobsResponse as QueryJobsResponseMessage -from tilebox.workflows.workflows.v1.job_pb2_grpc import JobServiceStub - - -class JobService: - def __init__(self, channel: Channel | Any) -> None: - """ - A wrapper around the JobServiceStub that provides a more pythonic interface and converts the protobuf messages - to and from the data classes used in the rest of the tilebox-workflows codebase. - - Args: - channel: The gRPC channel to use for the service. - """ - self.service = with_pythonic_errors(JobServiceStub(channel)) if hasattr(channel, "unary_unary") else channel - - def submit(self, job_name: str, trace_parent: str, tasks: TaskSubmissions) -> Job: - request = SubmitJobRequest( - tasks=tasks.to_message(), - job_name=job_name, - trace_parent=trace_parent, - ) - return RichDisplayJob.from_message(self.service.SubmitJob(request), _widget=JobWidget(self.get_by_id)) - - def get_by_id(self, job_id: UUID) -> Job: - request = GetJobRequest(job_id=uuid_to_uuid_message(job_id)) - response: JobMessage = self.service.GetJob(request) - return RichDisplayJob.from_message(response, _widget=JobWidget(self.get_by_id)) - - def retry(self, job_id: UUID) -> int: - request = RetryJobRequest(job_id=uuid_to_uuid_message(job_id)) - response: RetryJobResponse = self.service.RetryJob(request) - return response.num_tasks_rescheduled - - def cancel(self, job_id: UUID) -> None: - request = CancelJobRequest(job_id=uuid_to_uuid_message(job_id)) - self.service.CancelJob(request) - - def visualize(self, job_id: UUID, direction: str, layout: str = "dagre", sketchy: bool = True) -> str: - request = VisualizeJobRequest( - job_id=uuid_to_uuid_message(job_id), - render_options=RenderOptions(sketchy=sketchy, padding=5, layout=layout, direction=direction), - ) - response: Diagram = self.service.VisualizeJob(request) - return response.svg.decode("utf-8") - - def query(self, filters: QueryFilters, page: Pagination | None = None) -> QueryJobsResponse: - request = QueryJobsRequest( - filters=filters.to_message(), - page=page.to_message() if page is not None else None, - ) - response: QueryJobsResponseMessage = self.service.QueryJobs(request) - - return QueryJobsResponse.from_message( - response, job_factory=lambda job: RichDisplayJob.from_message(job, _widget=JobWidget(self.get_by_id)) - ) diff --git a/tilebox-workflows/tilebox/workflows/jobs/telemetry_service.py b/tilebox-workflows/tilebox/workflows/jobs/telemetry_service.py deleted file mode 100644 index 9dbd56e..0000000 --- a/tilebox-workflows/tilebox/workflows/jobs/telemetry_service.py +++ /dev/null @@ -1,64 +0,0 @@ -from typing import Any -from uuid import UUID - -from grpc import Channel - -from _tilebox.grpc.error import with_pythonic_errors -from tilebox.datasets.query.pagination import Pagination -from tilebox.workflows.data import ( - LogSeverity, - QueryJobLogsResponse, - QueryJobSpansResponse, - uuid_to_uuid_message, -) -from tilebox.workflows.workflows.v1.telemetry_pb2 import ( - LogQueryFilters, - PaginatedLogsData, - PaginatedSpansData, - QueryJobLogsRequest, - QueryJobSpansRequest, -) -from tilebox.workflows.workflows.v1.telemetry_pb2_grpc import TelemetryQueryServiceStub - - -class TelemetryService: - def __init__(self, channel: Channel | Any) -> None: - """ - A wrapper around the TelemetryQueryServiceStub that provides a more pythonic interface and converts the - protobuf messages to and from the data classes used in the rest of the tilebox-workflows codebase. - - Args: - channel: The gRPC channel to use for the service. - """ - self.service = ( - with_pythonic_errors(TelemetryQueryServiceStub(channel)) if hasattr(channel, "unary_unary") else channel - ) - - def query_job_logs( - self, - job_id: UUID, - page: Pagination | None = None, - task_id: UUID | None = None, - severity_levels: list[LogSeverity] | None = None, - ) -> QueryJobLogsResponse: - request = QueryJobLogsRequest( - job_id=uuid_to_uuid_message(job_id), - page=page.to_message() if page is not None else None, - task_id=uuid_to_uuid_message(task_id) if task_id is not None else None, - filters=LogQueryFilters(severity_levels=[severity.value for severity in severity_levels]) - if severity_levels - else None, - ) - response: PaginatedLogsData = self.service.QueryJobLogs(request) - return QueryJobLogsResponse.from_message(response) - - def query_job_spans( - self, job_id: UUID, page: Pagination | None = None, task_id: UUID | None = None - ) -> QueryJobSpansResponse: - request = QueryJobSpansRequest( - job_id=uuid_to_uuid_message(job_id), - page=page.to_message() if page is not None else None, - task_id=uuid_to_uuid_message(task_id) if task_id is not None else None, - ) - response: PaginatedSpansData = self.service.QueryJobSpans(request) - return QueryJobSpansResponse.from_message(response) diff --git a/tilebox-workflows/tilebox/workflows/observability/_log_stream.py b/tilebox-workflows/tilebox/workflows/observability/_log_stream.py index 9103539..bc2f9f3 100644 --- a/tilebox-workflows/tilebox/workflows/observability/_log_stream.py +++ b/tilebox-workflows/tilebox/workflows/observability/_log_stream.py @@ -1,19 +1,21 @@ """Bounded OTLP log streaming for CLI-managed workflow runtimes.""" +import asyncio import logging import os import threading import time import traceback from collections import deque -from collections.abc import Callable, Iterator +from collections.abc import AsyncGenerator from typing import Any from opentelemetry.instrumentation.log_utils import std_to_otel -from opentelemetry.proto.common.v1.common_pb2 import AnyValue, ArrayValue, KeyValue, KeyValueList -from opentelemetry.proto.logs.v1.logs_pb2 import LogRecord, SeverityNumber from opentelemetry.semconv.attributes import exception_attributes +from protobuf import Oneof +from tilebox.workflows._otel.opentelemetry.proto.common.v1.common_pb import AnyValue, ArrayValue, KeyValue, KeyValueList +from tilebox.workflows._otel.opentelemetry.proto.logs.v1.logs_pb import LogRecord, SeverityNumber from tilebox.workflows.observability._logging import _record_attributes _MAX_RECORD_SIZE = 1024 * 1024 @@ -25,20 +27,23 @@ def _any_value(value: Any) -> AnyValue: # noqa: PLR0911 -- each protobuf union if value is None: return AnyValue() if isinstance(value, bool): - return AnyValue(bool_value=value) + return AnyValue(value=Oneof("bool_value", value)) if isinstance(value, int): if abs(value) > 2**63 - 1: - return AnyValue(string_value=str(value)) - return AnyValue(int_value=value) + return AnyValue(value=Oneof("string_value", str(value))) + return AnyValue(value=Oneof("int_value", value)) if isinstance(value, float): - return AnyValue(double_value=value) + return AnyValue(value=Oneof("double_value", value)) if isinstance(value, dict): return AnyValue( - kvlist_value=KeyValueList(values=[KeyValue(key=key, value=_any_value(item)) for key, item in value.items()]) + value=Oneof( + "kvlist_value", + KeyValueList(values=[KeyValue(key=key, value=_any_value(item)) for key, item in value.items()]), + ) ) if isinstance(value, list): - return AnyValue(array_value=ArrayValue(values=[_any_value(item) for item in value])) - return AnyValue(string_value=value) + return AnyValue(value=Oneof("array_value", ArrayValue(values=[_any_value(item) for item in value]))) + return AnyValue(value=Oneof("string_value", value)) class _LogQueue: @@ -57,7 +62,7 @@ def submit(self, record: LogRecord) -> None: with self._condition: if self._sealed: return - if record.ByteSize() > self._max_record_size or len(self._records) >= self._capacity: + if len(record.to_binary()) > self._max_record_size or len(self._records) >= self._capacity: self._dropped += 1 self._condition.notify_all() return @@ -71,27 +76,28 @@ def submit(self, record: LogRecord) -> None: self._records.append(record) self._condition.notify_all() - def subscribe(self, active: Callable[[], bool] = lambda: True) -> Iterator[LogRecord]: + async def subscribe_async(self) -> AsyncGenerator[LogRecord]: + """Stream without blocking the ASGI loop or leaving a thread behind on cancellation.""" with self._condition: if self._subscriber: raise RuntimeError("A log subscriber is already connected") self._subscriber = True try: - while active(): + while True: + record = None with self._condition: if self._records: record = self._records.popleft() - self._condition.notify_all() elif self._dropped: record = _drop_record(self._dropped) self._dropped = 0 - self._condition.notify_all() elif self._sealed: return - else: - self._condition.wait(timeout=0.1) - continue - yield record + self._condition.notify_all() + if record is not None: + yield record + else: + await asyncio.sleep(0.01) finally: with self._condition: self._subscriber = False @@ -118,7 +124,7 @@ def _drop_record(count: int) -> LogRecord: time_unix_nano=time.time_ns(), severity_number=_severity_number(logging.WARNING), severity_text="WARN", - body=AnyValue(string_value=f"Dropped {count} local log records"), + body=_any_value(f"Dropped {count} local log records"), ) @@ -165,8 +171,8 @@ def _hex_id(value: Any, size: int) -> bytes: return result if len(result) == size else b"" -def _severity_number(level: int) -> SeverityNumber.ValueType: - return SeverityNumber.Value(f"SEVERITY_NUMBER_{std_to_otel(level).name}") +def _severity_number(level: int) -> SeverityNumber: + return SeverityNumber(std_to_otel(level).value) managed_log_queue = _LogQueue() if _MANAGED_RUNTIME else None diff --git a/tilebox-workflows/tilebox/workflows/observability/tracing.py b/tilebox-workflows/tilebox/workflows/observability/tracing.py index 32e0fda..b3cb391 100644 --- a/tilebox-workflows/tilebox/workflows/observability/tracing.py +++ b/tilebox-workflows/tilebox/workflows/observability/tracing.py @@ -1,3 +1,4 @@ +import atexit import logging import os from collections.abc import Iterator @@ -83,6 +84,7 @@ def __init__(self, service: str | Resource | None, url: str, token: str | None) authentication information. """ self._service = service + self._closed = False self._tilebox_span_exporter = _otel_span_exporter( endpoint=url, headers={"Authorization": f"Bearer {token}"} if token is not None else None, @@ -94,13 +96,27 @@ def __init__(self, service: str | Resource | None, url: str, token: str | None) # keep track of all workflow tracers, to be able to update them in case the # global tracer provider is replaced. _workflow_tracers.append(self) + atexit.register(self.close) + + def close(self) -> None: + """Flush and shut down this client's exporter, leaving shared exporters running.""" + if self._closed: + return + self._closed = True + _workflow_tracers.remove(self) + atexit.unregister(self.close) + self._tilebox_span_exporter.shutdown() def _configure_provider(self, source_provider: TracerProvider) -> None: """ A callback function that get's invoked in case a new global tracer provider is configured, to make sure existing workflow tracers are updated when the user changes global tracing configuration. """ - provider = TracerProvider(resource=_get_default_resource(self._service or source_provider.resource)) + # The client owns its exporter; copied processors belong to the global provider. + # Provider shutdown would close both, including shared exporters still in use. + provider = TracerProvider( + resource=_get_default_resource(self._service or source_provider.resource), shutdown_on_exit=False + ) _copy_configured_span_processors(source_provider, provider) provider.add_span_processor(self._tilebox_span_exporter) @@ -125,6 +141,9 @@ def __init__(self, service: str | Resource | None = None) -> None: provider = TracerProvider(resource=_get_default_resource(service)) self._tracer = provider.get_tracer(_INSTRUMENTATION_MODULE_NAME) + def close(self) -> None: + pass + def _configure_provider(self, source_provider: TracerProvider) -> None: # noop tracer doesn't need to be reconfigured pass diff --git a/tilebox-workflows/tilebox/workflows/runner/task_runner.py b/tilebox-workflows/tilebox/workflows/runner/task_runner.py index c5390e0..dbb692e 100644 --- a/tilebox-workflows/tilebox/workflows/runner/task_runner.py +++ b/tilebox-workflows/tilebox/workflows/runner/task_runner.py @@ -17,8 +17,8 @@ from tenacity import retry, retry_if_exception_type, stop_when_event_set, wait_random_exponential from tenacity.stop import stop_base -from _tilebox.grpc.channel import open_channel -from _tilebox.grpc.error import InternalServerError +from _tilebox.client import Connection, Transport +from _tilebox.errors import InternalServerError from tilebox.workflows.cache import JobCache from tilebox.workflows.data import ComputedTask, FailedTask, Idling, NextTaskToRun, Task, TaskLease from tilebox.workflows.observability._logging import StructuredLogger, logger @@ -76,12 +76,17 @@ def _retry_backoff(func: Callable[..., WrappedFnReturnT], stop: stop_base) -> Ca def lease_renewer( - url: str, token: str | None, new_leases: "Queue[tuple[UUID, TaskLease]]", done_tasks: "Queue[UUID]" + url: str, + token: str | None, + new_leases: "Queue[tuple[UUID, TaskLease]]", + done_tasks: "Queue[UUID]", + *, + transport: Transport = "http2", ) -> None: # The direct runner's spawned lease-renewal process needs its own stage-3 setup; # it cannot inherit the parent's logging handlers or exporter thread. initialize_logging(url=url, token=token) - channel = open_channel(url, token) + channel = Connection(url, token, transport=transport) service = TaskService(channel) while True: @@ -143,10 +148,11 @@ def _extend_lease_while_task_is_running( class _LeaseRenewer(SpawnProcess): """Daemon process to extend the lease for a task whenever necessary.""" - def __init__(self, url: str, token: str | None) -> None: + def __init__(self, url: str, token: str | None, *, transport: Transport = "http2") -> None: super().__init__(daemon=True) self._url = url self._token = token + self._transport = transport # we don't want to fork the current process, but instead spawn a new one # therefore we need to use the spawn context to create the queues @@ -155,7 +161,7 @@ def __init__(self, url: str, token: str | None) -> None: self._done_tasks: Queue[UUID] = ctx.Queue() def run(self) -> None: - lease_renewer(self._url, self._token, self._new_leases, self._done_tasks) + lease_renewer(self._url, self._token, self._new_leases, self._done_tasks, transport=self._transport) @contextmanager def lease_extension(self, task_id: UUID, task_lease: TaskLease) -> Iterator[None]: diff --git a/tilebox-workflows/tilebox/workflows/runner/task_service.py b/tilebox-workflows/tilebox/workflows/runner/task_service.py index 4ff9d54..7cbb1f1 100644 --- a/tilebox-workflows/tilebox/workflows/runner/task_service.py +++ b/tilebox-workflows/tilebox/workflows/runner/task_service.py @@ -1,10 +1,9 @@ from typing import Any from uuid import UUID -from google.protobuf.duration_pb2 import Duration -from grpc import Channel +from protobuf.wkt.google.protobuf.duration_pb import Duration -from _tilebox.grpc.error import with_pythonic_errors +from _tilebox.client import Connection from tilebox.workflows.data import ( ComputedTask, FailedTask, @@ -15,35 +14,29 @@ TaskLease, uuid_to_uuid_message, ) -from tilebox.workflows.workflows.v1.core_pb2 import TaskLease as TaskLeaseMessage -from tilebox.workflows.workflows.v1.task_pb2 import ( +from tilebox.workflows.v1.core_pb import TaskLease as TaskLeaseMessage +from tilebox.workflows.v1.task_connect import TaskServiceClientSync +from tilebox.workflows.v1.task_pb import ( NextTaskRequest, NextTaskResponse, TaskLeaseRequest, ) -from tilebox.workflows.workflows.v1.task_pb2_grpc import TaskServiceStub class TaskService: - def __init__(self, channel: Channel | Any) -> None: - """ - A wrapper around the TaskServiceStub that provides a more pythonic interface and converts the protobuf messages - to and from the data classes used in the rest of the tilebox-workflows codebase. - - Args: - channel: The gRPC channel to use for the service. - """ - self.service = with_pythonic_errors(TaskServiceStub(channel)) if hasattr(channel, "unary_unary") else channel + def __init__(self, channel: Connection | Any) -> None: + """Wrap a native synchronous task client or construct one from a shared connection.""" + self.service = channel.client(TaskServiceClientSync) if isinstance(channel, Connection) else channel def next_task(self, task_to_run: NextTaskToRun | None, computed_task: ComputedTask | None) -> Task | Idling | None: computed_task_message = None if computed_task is None else computed_task.to_message() task_to_run_message = None if task_to_run is None else task_to_run.to_message() - response: NextTaskResponse = self.service.NextTask( + response: NextTaskResponse = self.service.next_task( NextTaskRequest(computed_task=computed_task_message, next_task_to_run=task_to_run_message) ) - if response.next_task is not None and response.next_task.id.uuid: + if response.next_task is not None and response.next_task.id is not None and response.next_task.id.uuid: return Task.from_message(response.next_task) if response.idling is not None: return Idling.from_message(response.idling) @@ -62,11 +55,11 @@ def task_failed( if error is None or was_workflow_error is None or progress_updates is None: raise ValueError("error, was_workflow_error, and progress_updates are required when passing a Task") request = FailedTask.from_task_error(task, error, was_workflow_error, progress_updates).to_message() - self.service.TaskFailed(request) + self.service.task_failed(request) def extend_task_lease(self, task_id: UUID, requested_lease: int) -> TaskLease: request = TaskLeaseRequest( task_id=uuid_to_uuid_message(task_id), requested_lease=Duration(seconds=requested_lease) ) - response: TaskLeaseMessage = self.service.ExtendTaskLease(request) + response: TaskLeaseMessage = self.service.extend_task_lease(request) return TaskLease.from_message(response) diff --git a/tilebox-workflows/tilebox/workflows/runner/worker_server.py b/tilebox-workflows/tilebox/workflows/runner/worker_server.py index 9d749b0..6840c15 100644 --- a/tilebox-workflows/tilebox/workflows/runner/worker_server.py +++ b/tilebox-workflows/tilebox/workflows/runner/worker_server.py @@ -1,78 +1,124 @@ +from __future__ import annotations + +import asyncio import json import os +import socket +import stat import threading -from concurrent import futures +import time from pathlib import Path +from typing import TYPE_CHECKING + +import uvicorn -import grpc +if TYPE_CHECKING: + from uvicorn._types import ASGIReceiveCallable, ASGIReceiveEvent, ASGISendCallable, Scope from tilebox.workflows.observability._log_stream import managed_log_queue -from tilebox.workflows.observability._logging import logger from tilebox.workflows.observability.logging import flush_api_logging from tilebox.workflows.runner.runner import Runner from tilebox.workflows.runner.worker_service import WorkerServiceServicer -from tilebox.workflows.workflows.v1 import worker_pb2_grpc +from tilebox.workflows.v1.worker_connect import WorkerServiceASGIApplication WORKER_ADDRESS_ENV = "TILEBOX_WORKER_ADDRESS" RUNTIME_DIR_ENV = "TILEBOX_RUNTIME_DIR" -RUNTIME_TOKEN_ENV = "TILEBOX_RUNTIME_TOKEN" # noqa: S105 -- environment variable name, not a credential +RUNTIME_TOKEN_ENV = "TILEBOX_RUNTIME_TOKEN" # noqa: S105 -class WorkerServer: - """Python worker server for a runner started from the Tilebox CLI. +class _DisconnectAwareApplication: + """Cancel idle WatchLogs calls when the ASGI transport reports disconnection.""" + + def __init__(self, application: WorkerServiceASGIApplication) -> None: + self._application = application + + async def __call__(self, scope: Scope, receive: ASGIReceiveCallable, send: ASGISendCallable) -> None: + request_task = asyncio.current_task() + + async def receive_with_cancellation() -> ASGIReceiveEvent: + event = await receive() + # Connect checks disconnects only after the next yielded record. An + # idle stream has no next record, so cancel it to run iterator cleanup. + if event["type"] == "http.disconnect" and request_task is not None: + request_task.cancel() + return event + + watch_logs = scope["type"] == "http" and scope["path"] == "/workflows.v1.WorkerService/WatchLogs" + await self._application(scope, receive_with_cancellation if watch_logs else receive, send) + - This server is the interface the CLI uses to communicate with the Python process. - """ +class WorkerServer: + """Serve native Connect RPCs over a private Unix socket or authenticated loopback HTTP/1.""" def __init__(self, runner: Runner | None = None, address: str | None = None) -> None: - # Older CLIs and direct serve_runner() callers use the configured address - # without runtime-token authentication, endpoint discovery, or live log streaming. - self._legacy_cli = not (os.environ.get(RUNTIME_DIR_ENV) and os.environ.get(RUNTIME_TOKEN_ENV)) - configured_address = address or os.environ.get(WORKER_ADDRESS_ENV) - if not configured_address: - raise RuntimeError( - f"{WORKER_ADDRESS_ENV} is not set. Set it to a local gRPC address, for example " - f"'unix:///tmp/tilebox-worker.sock'." - ) - self._address = configured_address + self._address = address or os.environ.get(WORKER_ADDRESS_ENV, "") + if not self._address: + raise RuntimeError(f"{WORKER_ADDRESS_ENV} is not set") runtime_dir = os.environ.get(RUNTIME_DIR_ENV) - runtime_token = os.environ.get(RUNTIME_TOKEN_ENV) - if bool(runtime_dir) != bool(runtime_token): + token = os.environ.get(RUNTIME_TOKEN_ENV) + if bool(runtime_dir) != bool(token): raise RuntimeError(f"{RUNTIME_DIR_ENV} and {RUNTIME_TOKEN_ENV} must be set together") - self._bind_address = _normalize_grpc_address(configured_address) - if not self._legacy_cli and not configured_address.startswith("unix://"): - host, _, port = configured_address.rpartition(":") + self._runtime_dir = Path(runtime_dir) if runtime_dir else None + if self._address.startswith("unix://"): + path = Path(self._address.removeprefix("unix://")) + if not path.is_absolute(): + raise RuntimeError("Worker Unix socket path must be absolute") + if path.exists(): + if not stat.S_ISSOCK(path.lstat().st_mode): + raise RuntimeError("Refusing to remove a non-socket worker endpoint") + path.unlink() + self._socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + self._socket.bind(str(path)) + path.chmod(0o600) + self._port = 0 + else: + host, _, port = self._address.rpartition(":") if host != "127.0.0.1" or not port.isdecimal() or int(port) > 65535: - raise RuntimeError("Managed worker TCP addresses must use the 127.0.0.1 loopback interface") - _unlink_stale_unix_socket(self._bind_address) - self._server = grpc.server(futures.ThreadPoolExecutor()) - self._service = WorkerServiceServicer( - runner, - self.shutdown, - None if self._legacy_cli else managed_log_queue, - None if self._legacy_cli else os.environ.get(RUNTIME_TOKEN_ENV), + raise RuntimeError("Worker TCP addresses must use the 127.0.0.1 loopback interface") + if not token: + raise RuntimeError("Worker TCP requires runtime-token authentication") + self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self._socket.bind((host, int(port))) + self._port = self._socket.getsockname()[1] + self._socket.listen(128) + self._socket.setblocking(False) + self._service = WorkerServiceServicer(runner, self.shutdown, managed_log_queue, token) + self._server = uvicorn.Server( + uvicorn.Config( + _DisconnectAwareApplication(WorkerServiceASGIApplication(self._service, read_max_bytes=None)), + http="h11", + ws="none", + lifespan="off", + log_config=None, + access_log=False, + timeout_graceful_shutdown=5, + ) ) - worker_pb2_grpc.add_WorkerServiceServicer_to_server(self._service, self._server) - self._port = self._server.add_insecure_port(self._bind_address) - if self._port == 0: - raise RuntimeError(f"Failed to bind worker server to {configured_address!r}") + self._thread = threading.Thread(target=self._server.run, kwargs={"sockets": [self._socket]}, daemon=True) self._shutdown_lock = threading.Lock() self._shutdown_started = False def start(self) -> None: - self._server.start() - try: - if not self._legacy_cli: + self._thread.start() + deadline = time.monotonic() + 5 + while not self._server.started: + if not self._thread.is_alive() or time.monotonic() >= deadline: + self.shutdown() + raise RuntimeError("Worker HTTP server failed to start") + time.sleep(0.005) + if self._runtime_dir is not None: + try: self._announce() - except BaseException: - self._server.stop(0).wait() - raise + except BaseException: + self.shutdown() + self.wait() + raise def set_runner(self, runner: Runner) -> None: self._service.set_runner(runner) def wait(self) -> None: - self._server.wait_for_termination() + self._thread.join() def shutdown(self) -> None: with self._shutdown_lock: @@ -82,22 +128,23 @@ def shutdown(self) -> None: threading.Thread(target=self._shutdown, name="tilebox-worker-shutdown", daemon=True).start() def _shutdown(self) -> None: - try: - flush_api_logging() - finally: - if not self._legacy_cli and managed_log_queue is not None: - managed_log_queue.seal() - managed_log_queue.wait_empty(timeout=1.0) - self._server.stop(5).wait() + # Exporters are third-party code: do not let a stalled flush pin shutdown. + flush = threading.Thread(target=flush_api_logging, daemon=True) + flush.start() + flush.join(timeout=2) + if managed_log_queue is not None: + managed_log_queue.seal() + managed_log_queue.wait_empty(timeout=1) + self._server.should_exit = True def _announce(self) -> None: - runtime_dir = Path(os.environ[RUNTIME_DIR_ENV]) - address = self._address - if not address.startswith("unix://"): - address = f"127.0.0.1:{self._port}" - temporary = runtime_dir / "endpoint.json.tmp" - temporary.write_text(json.dumps({"version": 1, "address": address}, separators=(",", ":"))) - temporary.replace(runtime_dir / "endpoint.json") + assert self._runtime_dir is not None + address = self._address if self._address.startswith("unix://") else f"127.0.0.1:{self._port}" + temporary = self._runtime_dir / "endpoint.json.tmp" + temporary.write_text( + json.dumps({"version": 2, "protocol": "connect", "address": address}, separators=(",", ":")) + ) + temporary.replace(self._runtime_dir / "endpoint.json") def serve_runner(runner: Runner, address: str | None = None) -> None: @@ -108,21 +155,3 @@ def serve_runner(runner: Runner, address: str | None = None) -> None: finally: server.shutdown() server.wait() - - -def _normalize_grpc_address(address: str) -> str: - if address.startswith("unix://"): - return "unix:" + address.removeprefix("unix://") - return address - - -def _unlink_stale_unix_socket(address: str) -> None: - if not address.startswith("unix:"): - return - path = address.removeprefix("unix:") - if not path: - return - socket_path = Path(path) - if socket_path.exists(): - logger.debug(f"Removing stale worker Unix socket {socket_path}") - socket_path.unlink() diff --git a/tilebox-workflows/tilebox/workflows/runner/worker_service.py b/tilebox-workflows/tilebox/workflows/runner/worker_service.py index 9c2bd9d..bcb975b 100644 --- a/tilebox-workflows/tilebox/workflows/runner/worker_service.py +++ b/tilebox-workflows/tilebox/workflows/runner/worker_service.py @@ -1,12 +1,17 @@ +import asyncio import hmac import threading -from collections.abc import Callable, Iterator +from collections.abc import AsyncIterator, Callable +from contextlib import aclosing, suppress +from typing import TypeVar -import grpc -from google.protobuf.empty_pb2 import Empty -from opentelemetry.proto.logs.v1.logs_pb2 import LogRecord +from connectrpc.code import Code +from connectrpc.errors import ConnectError +from connectrpc.request import RequestContext +from protobuf.wkt.google.protobuf.empty_pb import Empty -from tilebox.datasets.uuid import uuid_message_to_uuid +from _tilebox.uuid import uuid_message_to_uuid +from tilebox.workflows._otel.opentelemetry.proto.logs.v1.logs_pb import LogRecord from tilebox.workflows.cache import NoCache from tilebox.workflows.client import Client from tilebox.workflows.data import Cluster, ComputedTask, FailedTask, Task @@ -16,10 +21,40 @@ from tilebox.workflows.runner.executor import LazyStorageLocations, TaskExecutor from tilebox.workflows.runner.runner import Runner from tilebox.workflows.task import RunnerContext -from tilebox.workflows.workflows.v1 import core_pb2, worker_pb2, worker_pb2_grpc +from tilebox.workflows.v1 import core_pb, worker_pb +from tilebox.workflows.v1.worker_connect import WorkerService +_Result = TypeVar("_Result") -class WorkerServiceServicer(worker_pb2_grpc.WorkerServiceServicer): + +async def _offload(function: Callable[[], _Result]) -> _Result: + """Run blocking user code without pinning process exit after the shutdown deadline.""" + loop = asyncio.get_running_loop() + future: asyncio.Future[_Result] = loop.create_future() + + def complete(result: _Result | None, error: BaseException | None) -> None: + if future.done(): + return + if error is not None: + future.set_exception(error) + else: + future.set_result(result) # ty: ignore[invalid-argument-type] + + def run() -> None: + result, error = None, None + try: + result = function() + except BaseException as caught: # noqa: BLE001 -- propagate user failures to the request + error = caught + # The bounded shutdown deadline may already have closed the loop. + with suppress(RuntimeError): + loop.call_soon_threadsafe(complete, result, error) + + threading.Thread(target=run, name="tilebox-worker-task", daemon=True).start() + return await future + + +class WorkerServiceServicer(WorkerService): def __init__( self, runner: Runner | None, @@ -35,47 +70,63 @@ def __init__( self._tasks = threading.Condition() self._active_tasks = 0 self._stopping = False + self._initialize_lock = threading.Lock() - def _authorize(self, context: grpc.ServicerContext) -> None: + def _authorize(self, context: RequestContext) -> None: if self._token is None: return - metadata = dict(context.invocation_metadata()) - supplied = metadata.get("authorization", "") + supplied = context.request_headers.get("authorization", "") if not hmac.compare_digest(supplied, f"Bearer {self._token}"): - context.abort(grpc.StatusCode.UNAUTHENTICATED, "Invalid runtime authorization") + raise ConnectError(Code.UNAUTHENTICATED, "Invalid runtime authorization") def set_runner(self, runner: Runner) -> None: self._runner = runner - def _loaded_runner(self, context: grpc.ServicerContext) -> Runner: + def _loaded_runner(self, context: RequestContext) -> Runner: # noqa: ARG002 runner = self._runner if runner is None: - context.abort(grpc.StatusCode.UNAVAILABLE, "Worker runtime is still loading") - raise RuntimeError("Worker runtime is still loading") + raise ConnectError(Code.UNAVAILABLE, "Worker runtime is still loading") return runner - def ListRegisteredTasks(self, request: Empty, context: grpc.ServicerContext) -> core_pb2.TaskIdentifiers: # noqa: ARG002, N802 + async def list_registered_tasks(self, request: Empty, context: RequestContext) -> core_pb.UnboundTaskIdentifiers: # noqa: ARG002 logger.debug("ListRegisteredTasks RPC called") self._authorize(context) runner = self._loaded_runner(context) - identifiers = [identifier.to_message() for identifier in runner.task_identifiers] + identifiers = [ + core_pb.UnboundTaskIdentifier(name=identifier.name, version=identifier.version) + for identifier in runner.task_identifiers + ] logger.debug(f"ListRegisteredTasks RPC returning {len(identifiers)} task identifier(s)") - return core_pb2.TaskIdentifiers(identifiers=identifiers) + return core_pb.UnboundTaskIdentifiers(identifiers=identifiers) - def InitializeWorker( # noqa: N802 + async def initialize_worker( + self, request: worker_pb.InitializeRunnerRequest, context: RequestContext + ) -> worker_pb.InitializeRunnerResponse: + self._authorize(context) + return await _offload(lambda: self._initialize_worker(request, context)) + + def _initialize_worker( + self, + request: worker_pb.InitializeRunnerRequest, + context: RequestContext, + ) -> worker_pb.InitializeRunnerResponse: + with self._initialize_lock: + return self._initialize(request, context) + + def _initialize( self, - request: worker_pb2.InitializeRunnerRequest, - context: grpc.ServicerContext, - ) -> worker_pb2.InitializeRunnerResponse: + request: worker_pb.InitializeRunnerRequest, + context: RequestContext, + ) -> worker_pb.InitializeRunnerResponse: logger.debug("InitializeWorker RPC called") self._authorize(context) runner = self._loaded_runner(context) runner_id = uuid_message_to_uuid(request.runner_id) if self._executor is not None: - context.abort(grpc.StatusCode.FAILED_PRECONDITION, "Worker is already initialized") - cluster = Cluster.from_message(request.cluster) if request.HasField("cluster") else None + raise ConnectError(Code.FAILED_PRECONDITION, "Worker is already initialized") + cluster = Cluster.from_message(request.cluster) if request.cluster is not None else None - api_connection = request.api_connection if request.HasField("api_connection") else None + api_connection = request.api_connection if request.has_field("api_connection") else None api_url = api_connection.url if api_connection and api_connection.url else None api_token = api_connection.token if api_connection and api_connection.token else None @@ -104,20 +155,25 @@ def InitializeWorker( # noqa: N802 logger.debug( f"InitializeWorker RPC returning for runner_id={runner_id}, cluster={cluster.slug if cluster is not None else None!r}" ) - return worker_pb2.InitializeRunnerResponse() + return worker_pb.InitializeRunnerResponse() - def ExecuteTask( # noqa: N802 + async def execute_task( self, - request: core_pb2.Task, - context: grpc.ServicerContext, - ) -> worker_pb2.ExecuteTaskResponse: + request: core_pb.Task, + context: RequestContext, + ) -> worker_pb.ExecuteTaskResponse: logger.debug("ExecuteTask RPC called") self._authorize(context) self._loaded_runner(context) with self._tasks: if self._stopping: - context.abort(grpc.StatusCode.UNAVAILABLE, "Worker is shutting down") + raise ConnectError(Code.UNAVAILABLE, "Worker is shutting down") self._active_tasks += 1 + # Cancellation of the HTTP request must not orphan task cleanup. + task = asyncio.create_task(_offload(lambda: self._execute_active_task(request))) + return await asyncio.shield(task) + + def _execute_active_task(self, request: core_pb.Task) -> worker_pb.ExecuteTaskResponse: try: return self._execute_task(request) finally: @@ -125,7 +181,7 @@ def ExecuteTask( # noqa: N802 self._active_tasks -= 1 self._tasks.notify_all() - def _execute_task(self, request: core_pb2.Task) -> worker_pb2.ExecuteTaskResponse: + def _execute_task(self, request: core_pb.Task) -> worker_pb.ExecuteTaskResponse: task = Task.from_message(request) executor = self._executor if executor is None: @@ -136,36 +192,42 @@ def _execute_task(self, request: core_pb2.Task) -> worker_pb2.ExecuteTaskRespons progress_updates=[], ) logger.debug(f"ExecuteTask RPC returning failed task for uninitialized worker, task_id={task.id}") - return worker_pb2.ExecuteTaskResponse(failed_task=failed_task.to_message()) + return worker_pb.ExecuteTaskResponse(failed_task=failed_task.to_message()) result = executor.execute_task(task) if isinstance(result, ComputedTask): logger.debug(f"ExecuteTask RPC returning computed task, task_id={task.id}") - return worker_pb2.ExecuteTaskResponse(computed_task=result.to_message()) + return worker_pb.ExecuteTaskResponse(computed_task=result.to_message()) if isinstance(result, FailedTask): logger.debug(f"ExecuteTask RPC returning failed task, task_id={task.id}") - return worker_pb2.ExecuteTaskResponse(failed_task=result.to_message()) + return worker_pb.ExecuteTaskResponse(failed_task=result.to_message()) raise TypeError(f"Unexpected task execution result: {type(result)}") - def WatchLogs(self, request: Empty, context: grpc.ServicerContext) -> Iterator[LogRecord]: # noqa: ARG002, N802 + async def watch_logs(self, request: Empty, context: RequestContext) -> AsyncIterator[LogRecord]: # noqa: ARG002 self._authorize(context) - context.send_initial_metadata(()) logs = self._logs if logs is None: - context.abort(grpc.StatusCode.UNAVAILABLE, "Local log streaming is unavailable") - raise RuntimeError("Local log streaming is unavailable") + raise ConnectError(Code.UNAVAILABLE, "Local log streaming is unavailable") try: - yield from logs.subscribe(context.is_active) + async with aclosing(logs.subscribe_async()) as records: + async for record in records: + yield record except RuntimeError as error: - context.abort(grpc.StatusCode.ALREADY_EXISTS, str(error)) + raise ConnectError(Code.ALREADY_EXISTS, str(error)) from error - def ShutdownWorker(self, request: Empty, context: grpc.ServicerContext) -> Empty: # noqa: ARG002, N802 + async def shutdown_worker(self, request: Empty, context: RequestContext) -> Empty: # noqa: ARG002 self._authorize(context) logger.debug("ShutdownWorker RPC called") with self._tasks: self._stopping = True - while self._active_tasks: - self._tasks.wait() + return await asyncio.shield(asyncio.create_task(self._finish_shutdown())) + + async def _finish_shutdown(self) -> Empty: + await asyncio.to_thread(self._wait_tasks) self._shutdown() logger.debug("ShutdownWorker RPC returning") return Empty() + + def _wait_tasks(self) -> None: + with self._tasks: + self._tasks.wait_for(lambda: self._active_tasks == 0, timeout=5) diff --git a/tilebox-workflows/tilebox/workflows/storage_locations/client.py b/tilebox-workflows/tilebox/workflows/storage_locations/client.py index e592c64..d83691b 100644 --- a/tilebox-workflows/tilebox/workflows/storage_locations/client.py +++ b/tilebox-workflows/tilebox/workflows/storage_locations/client.py @@ -1,23 +1,36 @@ from typing import TypeAlias from uuid import UUID +from protobuf.wkt.google.protobuf.empty_pb import Empty + +from _tilebox.uuid import must_uuid_to_uuid_message, uuid_to_uuid_message +from tilebox.query.pagination import Pagination from tilebox.workflows.data import StorageLocation, StorageLocationReference -from tilebox.workflows.storage_locations.service import StorageLocationService +from tilebox.workflows.storage_locations.data import StorageSubscriptionEvents +from tilebox.workflows.v1.storage_location_connect import StorageLocationServiceClientSync +from tilebox.workflows.v1.storage_location_pb import ( + CreateStorageLocationRequest, + ListStorageSubscriptionEventsRequest, + UpdateStorageLocationRequest, +) StorageLocationIDLike: TypeAlias = StorageLocation | UUID | str class StorageLocationClient: - def __init__(self, service: StorageLocationService) -> None: + def __init__(self, service: StorageLocationServiceClientSync) -> None: self._service = service def all(self) -> list[StorageLocation]: """List all available storage locations.""" - return self._service.list() + response = self._service.list_storage_locations(Empty()) + return [StorageLocation.from_message(location) for location in response.locations] def find(self, storage_location_id: StorageLocationIDLike) -> StorageLocation: """Find a storage location by object, UUID, or UUID string.""" - return self._service.get_by_id(_to_uuid(storage_location_id)) + return StorageLocation.from_message( + self._service.get_storage_location(must_uuid_to_uuid_message(_to_uuid(storage_location_id))) + ) def create(self, name: str, reference: StorageLocationReference) -> StorageLocation: """Create a storage location from an AWS S3, GCS, Azure Blob, or local reference. @@ -25,11 +38,28 @@ def create(self, name: str, reference: StorageLocationReference) -> StorageLocat Creating a location does not create the underlying bucket, container, or directory, or configure an event subscription. """ - return self._service.create(name, reference) + return StorageLocation.from_message( + self._service.create_storage_location( + CreateStorageLocationRequest(name=name, reference=reference.to_message()) + ) + ) def update(self, storage_location_id: StorageLocationIDLike, name: str) -> StorageLocation: """Rename a storage location. Its storage reference cannot be changed.""" - return self._service.update(_to_uuid(storage_location_id), name) + return StorageLocation.from_message( + self._service.update_storage_location( + UpdateStorageLocationRequest( + storage_location_id=uuid_to_uuid_message(_to_uuid(storage_location_id)), name=name + ) + ) + ) + + def list_events(self, storage_location_id: StorageLocationIDLike, page: Pagination) -> StorageSubscriptionEvents: + """Fetch one page of storage subscription events and its continuation cursor.""" + request = ListStorageSubscriptionEventsRequest( + storage_location_id=uuid_to_uuid_message(_to_uuid(storage_location_id)), page=page.to_message() + ) + return StorageSubscriptionEvents.from_message(self._service.list_storage_subscription_events(request)) def _to_uuid(location: StorageLocationIDLike) -> UUID: diff --git a/tilebox-workflows/tilebox/workflows/storage_locations/data.py b/tilebox-workflows/tilebox/workflows/storage_locations/data.py index d4a25d3..ee2c8a6 100644 --- a/tilebox-workflows/tilebox/workflows/storage_locations/data.py +++ b/tilebox-workflows/tilebox/workflows/storage_locations/data.py @@ -2,11 +2,11 @@ from datetime import datetime from uuid import UUID -from tilebox.datasets.query.pagination import Pagination -from tilebox.datasets.query.time_interval import timestamp_to_datetime -from tilebox.datasets.uuid import uuid_message_to_uuid +from _tilebox.time import timestamp_to_datetime +from _tilebox.uuid import uuid_message_to_uuid +from tilebox.query.pagination import Pagination from tilebox.workflows.data import StorageEventType -from tilebox.workflows.workflows.v1 import storage_location_pb2 as storage_pb +from tilebox.workflows.v1 import storage_location_pb as storage_pb @dataclass(frozen=True) diff --git a/tilebox-workflows/tilebox/workflows/storage_locations/service.py b/tilebox-workflows/tilebox/workflows/storage_locations/service.py deleted file mode 100644 index 61d4665..0000000 --- a/tilebox-workflows/tilebox/workflows/storage_locations/service.py +++ /dev/null @@ -1,46 +0,0 @@ -import builtins -from typing import Any -from uuid import UUID - -from google.protobuf.empty_pb2 import Empty -from grpc import Channel - -from _tilebox.grpc.error import with_pythonic_errors -from tilebox.datasets.query.pagination import Pagination -from tilebox.datasets.uuid import uuid_to_uuid_message -from tilebox.workflows.data import StorageLocation, StorageLocationReference -from tilebox.workflows.storage_locations.data import StorageSubscriptionEvents -from tilebox.workflows.workflows.v1.storage_location_pb2 import ( - CreateStorageLocationRequest, - ListStorageSubscriptionEventsRequest, - UpdateStorageLocationRequest, -) -from tilebox.workflows.workflows.v1.storage_location_pb2_grpc import StorageLocationServiceStub - - -class StorageLocationService: - def __init__(self, channel: Channel | Any) -> None: - self.service = ( - with_pythonic_errors(StorageLocationServiceStub(channel)) if hasattr(channel, "unary_unary") else channel - ) - - def list(self) -> builtins.list[StorageLocation]: - response = self.service.ListStorageLocations(Empty()) - return [StorageLocation.from_message(location) for location in response.locations] - - def get_by_id(self, storage_location_id: UUID) -> StorageLocation: - return StorageLocation.from_message(self.service.GetStorageLocation(uuid_to_uuid_message(storage_location_id))) - - def create(self, name: str, reference: StorageLocationReference) -> StorageLocation: - request = CreateStorageLocationRequest(name=name, reference=reference.to_message()) - return StorageLocation.from_message(self.service.CreateStorageLocation(request)) - - def update(self, storage_location_id: UUID, name: str) -> StorageLocation: - request = UpdateStorageLocationRequest(storage_location_id=uuid_to_uuid_message(storage_location_id), name=name) - return StorageLocation.from_message(self.service.UpdateStorageLocation(request)) - - def list_events(self, storage_location_id: UUID, page: Pagination) -> StorageSubscriptionEvents: - request = ListStorageSubscriptionEventsRequest( - storage_location_id=uuid_to_uuid_message(storage_location_id), page=page.to_message() - ) - return StorageSubscriptionEvents.from_message(self.service.ListStorageSubscriptionEvents(request)) diff --git a/tilebox-workflows/tilebox/workflows/task.py b/tilebox-workflows/tilebox/workflows/task.py index 8f3d3f9..2403b90 100644 --- a/tilebox-workflows/tilebox/workflows/task.py +++ b/tilebox-workflows/tilebox/workflows/task.py @@ -9,6 +9,8 @@ from typing import TYPE_CHECKING, Any, Generic, Protocol, TypeVar, cast, dataclass_transform, get_args, get_origin from uuid import UUID +from protobuf import Message + from tilebox.workflows._serialization import decode_json, encode_json_field, encode_json_fields from tilebox.workflows.data import RunnerContext, TaskIdentifier, TaskSubmissionGroup, TaskSubmissions @@ -478,8 +480,8 @@ def serialize_task(task: Task) -> bytes: return b"" # empty task if len(task_fields) == 1: value = getattr(task, task_fields[0].name) - if hasattr(value, "SerializeToString"): - return value.SerializeToString() + if isinstance(value, Message): + return value.to_binary() return encode_json_field(value, type(task), task_fields[0].name) return encode_json_fields(task, task_fields) @@ -503,8 +505,8 @@ def deserialize_task(task_cls: type[_TaskT], task_input: bytes) -> _TaskT: type_hints = typing.get_type_hints(task_cls, include_extras=True) field_type = type_hints.get(task_fields[0].name, task_fields[0].type) protobuf_type = _get_deserialization_field_type(field_type) - if hasattr(protobuf_type, "FromString"): # protobuf message - value = None if task_input == b"null" else protobuf_type.FromString(task_input) # ty: ignore[call-non-callable] + if isinstance(protobuf_type, type) and issubclass(protobuf_type, Message): + value = None if task_input == b"null" else protobuf_type.from_binary(task_input) else: value = decode_json(task_input, field_type) diff --git a/tilebox-workflows/tilebox/workflows/workflows/v1/automation_connect.py b/tilebox-workflows/tilebox/workflows/v1/automation_connect.py similarity index 52% rename from tilebox-workflows/tilebox/workflows/workflows/v1/automation_connect.py rename to tilebox-workflows/tilebox/workflows/v1/automation_connect.py index cedd0ed..7d39bdf 100644 --- a/tilebox-workflows/tilebox/workflows/workflows/v1/automation_connect.py +++ b/tilebox-workflows/tilebox/workflows/v1/automation_connect.py @@ -1,43 +1,84 @@ -# -*- coding: utf-8 -*- -# Generated by https://github.com/connectrpc/connect-python. DO NOT EDIT! -# source: workflows/v1/automation.proto +# Generated from workflows/v1/automation.proto. DO NOT EDIT. +# Generated by protoc-gen-connectrpc-py v0.12.1 with parameter "map_imports=datasets/**:tilebox,map_imports=workflows/**:tilebox,map_imports=tilebox/**:_tilebox.proto,map_imports=opentelemetry/**:tilebox.workflows._otel". +# ruff: noqa: PGH004 +# ruff: noqa +# fmt: off -from collections.abc import AsyncGenerator, AsyncIterator, Iterable, Iterator, Mapping -from typing import Protocol +from __future__ import annotations +from typing import Protocol, TYPE_CHECKING + +from _tilebox.proto.tilebox.v1.id_pb import ID from connectrpc.client import ConnectClient, ConnectClientSync from connectrpc.code import Code -from connectrpc.codec import Codec -from connectrpc.compression import Compression from connectrpc.errors import ConnectError -from connectrpc.interceptor import Interceptor, InterceptorSync from connectrpc.method import IdempotencyLevel, MethodInfo -from connectrpc.request import Headers, RequestContext -from connectrpc.server import ConnectASGIApplication, ConnectWSGIApplication, Endpoint, EndpointSync -import google.protobuf.empty_pb2 as google_dot_protobuf_dot_empty__pb2 -import tilebox.datasets.tilebox.v1.id_pb2 as tilebox_dot_v1_dot_id__pb2 -import tilebox.workflows.workflows.v1.automation_pb2 as workflows_dot_v1_dot_automation__pb2 +from connectrpc.server import ConnectASGIApplication, ConnectWSGIApplication, DEFAULT_READ_MAX_BYTES, Endpoint, EndpointSync +from protobuf import DescService +from protobuf.wkt.google.protobuf.empty_pb import Empty +from tilebox.workflows.v1 import automation_pb +from tilebox.workflows.v1.automation_pb import AutomationPrototype, Automations, DeleteAutomationRequest + +if TYPE_CHECKING: + from collections.abc import AsyncGenerator, Iterable, Mapping + + from connectrpc.codec import Codec + from connectrpc.compression import Compression + from connectrpc.interceptor import Interceptor, InterceptorSync + from connectrpc.request import Headers, RequestContext class AutomationService(Protocol): - async def list_automations(self, request: google_dot_protobuf_dot_empty__pb2.Empty, ctx: RequestContext) -> workflows_dot_v1_dot_automation__pb2.Automations: - raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") + """ + AutomationService is a service for managing automations. Currently, we support two types of triggers for automations: + - Bucket triggers, which triggers tasks when an object is uploaded to a storage bucket that matches a glob pattern + - Cron triggers, which triggers tasks on a schedule + """ + async def list_automations(self, request: Empty, ctx: RequestContext[Empty, Automations], /) -> Automations: + """ + ListAutomations lists all the automations that are currently registered in a namespace. + """ + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') - async def get_automation(self, request: tilebox_dot_v1_dot_id__pb2.ID, ctx: RequestContext) -> workflows_dot_v1_dot_automation__pb2.AutomationPrototype: - raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") + async def get_automation(self, request: ID, ctx: RequestContext[ID, AutomationPrototype], /) -> AutomationPrototype: + """ + GetAutomation gets an automation by its ID. + """ + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') - async def create_automation(self, request: workflows_dot_v1_dot_automation__pb2.AutomationPrototype, ctx: RequestContext) -> workflows_dot_v1_dot_automation__pb2.AutomationPrototype: - raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") + async def create_automation(self, request: AutomationPrototype, ctx: RequestContext[AutomationPrototype, AutomationPrototype], /) -> AutomationPrototype: + """ + CreateAutomation creates a new automation in a namespace. + """ + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') - async def update_automation(self, request: workflows_dot_v1_dot_automation__pb2.AutomationPrototype, ctx: RequestContext) -> workflows_dot_v1_dot_automation__pb2.AutomationPrototype: - raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") + async def update_automation(self, request: AutomationPrototype, ctx: RequestContext[AutomationPrototype, AutomationPrototype], /) -> AutomationPrototype: + """ + UpdateAutomation updates an automation in a namespace. + """ + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') - async def delete_automation(self, request: workflows_dot_v1_dot_automation__pb2.DeleteAutomationRequest, ctx: RequestContext) -> google_dot_protobuf_dot_empty__pb2.Empty: - raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") + async def delete_automation(self, request: DeleteAutomationRequest, ctx: RequestContext[DeleteAutomationRequest, Empty], /) -> Empty: + """ + DeleteAutomation deletes an automation from a namespace. + """ + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') + @classmethod + def desc(cls) -> DescService: + """Returns the descriptor for this service.""" + return next(s for s in automation_pb.desc().services if s.type_name == 'workflows.v1.AutomationService') class AutomationServiceASGIApplication(ConnectASGIApplication[AutomationService]): - def __init__(self, service: AutomationService | AsyncGenerator[AutomationService], *, interceptors: Iterable[Interceptor]=(), read_max_bytes: int | None = None, compressions: Iterable[Compression] | None = None, codecs: Iterable[Codec] | None = None) -> None: + def __init__( + self, + service: AutomationService | AsyncGenerator[AutomationService], + *, + interceptors: Iterable[Interceptor] = (), + read_max_bytes: int | None = DEFAULT_READ_MAX_BYTES, + compressions: Iterable[Compression] | None = None, + codecs: Iterable[Codec] | None = None, + ) -> None: super().__init__( service=service, endpoints=lambda svc: { @@ -45,8 +86,8 @@ def __init__(self, service: AutomationService | AsyncGenerator[AutomationService method=MethodInfo( name="ListAutomations", service_name="workflows.v1.AutomationService", - input=google_dot_protobuf_dot_empty__pb2.Empty, - output=workflows_dot_v1_dot_automation__pb2.Automations, + input=Empty, + output=Automations, idempotency_level=IdempotencyLevel.UNKNOWN, ), function=svc.list_automations, @@ -55,8 +96,8 @@ def __init__(self, service: AutomationService | AsyncGenerator[AutomationService method=MethodInfo( name="GetAutomation", service_name="workflows.v1.AutomationService", - input=tilebox_dot_v1_dot_id__pb2.ID, - output=workflows_dot_v1_dot_automation__pb2.AutomationPrototype, + input=ID, + output=AutomationPrototype, idempotency_level=IdempotencyLevel.UNKNOWN, ), function=svc.get_automation, @@ -65,8 +106,8 @@ def __init__(self, service: AutomationService | AsyncGenerator[AutomationService method=MethodInfo( name="CreateAutomation", service_name="workflows.v1.AutomationService", - input=workflows_dot_v1_dot_automation__pb2.AutomationPrototype, - output=workflows_dot_v1_dot_automation__pb2.AutomationPrototype, + input=AutomationPrototype, + output=AutomationPrototype, idempotency_level=IdempotencyLevel.UNKNOWN, ), function=svc.create_automation, @@ -75,8 +116,8 @@ def __init__(self, service: AutomationService | AsyncGenerator[AutomationService method=MethodInfo( name="UpdateAutomation", service_name="workflows.v1.AutomationService", - input=workflows_dot_v1_dot_automation__pb2.AutomationPrototype, - output=workflows_dot_v1_dot_automation__pb2.AutomationPrototype, + input=AutomationPrototype, + output=AutomationPrototype, idempotency_level=IdempotencyLevel.UNKNOWN, ), function=svc.update_automation, @@ -85,8 +126,8 @@ def __init__(self, service: AutomationService | AsyncGenerator[AutomationService method=MethodInfo( name="DeleteAutomation", service_name="workflows.v1.AutomationService", - input=workflows_dot_v1_dot_automation__pb2.DeleteAutomationRequest, - output=google_dot_protobuf_dot_empty__pb2.Empty, + input=DeleteAutomationRequest, + output=Empty, idempotency_level=IdempotencyLevel.UNKNOWN, ), function=svc.delete_automation, @@ -105,20 +146,28 @@ def path(self) -> str: class AutomationServiceClient(ConnectClient): + """ + AutomationService is a service for managing automations. Currently, we support two types of triggers for automations: + - Bucket triggers, which triggers tasks when an object is uploaded to a storage bucket that matches a glob pattern + - Cron triggers, which triggers tasks on a schedule + """ async def list_automations( self, - request: google_dot_protobuf_dot_empty__pb2.Empty, + request: Empty, *, headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, - ) -> workflows_dot_v1_dot_automation__pb2.Automations: + ) -> Automations: + """ + ListAutomations lists all the automations that are currently registered in a namespace. + """ return await self.execute_unary( request=request, method=MethodInfo( name="ListAutomations", service_name="workflows.v1.AutomationService", - input=google_dot_protobuf_dot_empty__pb2.Empty, - output=workflows_dot_v1_dot_automation__pb2.Automations, + input=Empty, + output=Automations, idempotency_level=IdempotencyLevel.UNKNOWN, ), headers=headers, @@ -127,18 +176,21 @@ async def list_automations( async def get_automation( self, - request: tilebox_dot_v1_dot_id__pb2.ID, + request: ID, *, headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, - ) -> workflows_dot_v1_dot_automation__pb2.AutomationPrototype: + ) -> AutomationPrototype: + """ + GetAutomation gets an automation by its ID. + """ return await self.execute_unary( request=request, method=MethodInfo( name="GetAutomation", service_name="workflows.v1.AutomationService", - input=tilebox_dot_v1_dot_id__pb2.ID, - output=workflows_dot_v1_dot_automation__pb2.AutomationPrototype, + input=ID, + output=AutomationPrototype, idempotency_level=IdempotencyLevel.UNKNOWN, ), headers=headers, @@ -147,18 +199,21 @@ async def get_automation( async def create_automation( self, - request: workflows_dot_v1_dot_automation__pb2.AutomationPrototype, + request: AutomationPrototype, *, headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, - ) -> workflows_dot_v1_dot_automation__pb2.AutomationPrototype: + ) -> AutomationPrototype: + """ + CreateAutomation creates a new automation in a namespace. + """ return await self.execute_unary( request=request, method=MethodInfo( name="CreateAutomation", service_name="workflows.v1.AutomationService", - input=workflows_dot_v1_dot_automation__pb2.AutomationPrototype, - output=workflows_dot_v1_dot_automation__pb2.AutomationPrototype, + input=AutomationPrototype, + output=AutomationPrototype, idempotency_level=IdempotencyLevel.UNKNOWN, ), headers=headers, @@ -167,18 +222,21 @@ async def create_automation( async def update_automation( self, - request: workflows_dot_v1_dot_automation__pb2.AutomationPrototype, + request: AutomationPrototype, *, headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, - ) -> workflows_dot_v1_dot_automation__pb2.AutomationPrototype: + ) -> AutomationPrototype: + """ + UpdateAutomation updates an automation in a namespace. + """ return await self.execute_unary( request=request, method=MethodInfo( name="UpdateAutomation", service_name="workflows.v1.AutomationService", - input=workflows_dot_v1_dot_automation__pb2.AutomationPrototype, - output=workflows_dot_v1_dot_automation__pb2.AutomationPrototype, + input=AutomationPrototype, + output=AutomationPrototype, idempotency_level=IdempotencyLevel.UNKNOWN, ), headers=headers, @@ -187,51 +245,85 @@ async def update_automation( async def delete_automation( self, - request: workflows_dot_v1_dot_automation__pb2.DeleteAutomationRequest, + request: DeleteAutomationRequest, *, headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, - ) -> google_dot_protobuf_dot_empty__pb2.Empty: + ) -> Empty: + """ + DeleteAutomation deletes an automation from a namespace. + """ return await self.execute_unary( request=request, method=MethodInfo( name="DeleteAutomation", service_name="workflows.v1.AutomationService", - input=workflows_dot_v1_dot_automation__pb2.DeleteAutomationRequest, - output=google_dot_protobuf_dot_empty__pb2.Empty, + input=DeleteAutomationRequest, + output=Empty, idempotency_level=IdempotencyLevel.UNKNOWN, ), headers=headers, timeout_ms=timeout_ms, ) +class AutomationServiceSync(Protocol): + """ + AutomationService is a service for managing automations. Currently, we support two types of triggers for automations: + - Bucket triggers, which triggers tasks when an object is uploaded to a storage bucket that matches a glob pattern + - Cron triggers, which triggers tasks on a schedule + """ + def list_automations(self, request: Empty, ctx: RequestContext[Empty, Automations], /) -> Automations: + """ + ListAutomations lists all the automations that are currently registered in a namespace. + """ + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') + def get_automation(self, request: ID, ctx: RequestContext[ID, AutomationPrototype], /) -> AutomationPrototype: + """ + GetAutomation gets an automation by its ID. + """ + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') + def create_automation(self, request: AutomationPrototype, ctx: RequestContext[AutomationPrototype, AutomationPrototype], /) -> AutomationPrototype: + """ + CreateAutomation creates a new automation in a namespace. + """ + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') + def update_automation(self, request: AutomationPrototype, ctx: RequestContext[AutomationPrototype, AutomationPrototype], /) -> AutomationPrototype: + """ + UpdateAutomation updates an automation in a namespace. + """ + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') -class AutomationServiceSync(Protocol): - def list_automations(self, request: google_dot_protobuf_dot_empty__pb2.Empty, ctx: RequestContext) -> workflows_dot_v1_dot_automation__pb2.Automations: - raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") - def get_automation(self, request: tilebox_dot_v1_dot_id__pb2.ID, ctx: RequestContext) -> workflows_dot_v1_dot_automation__pb2.AutomationPrototype: - raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") - def create_automation(self, request: workflows_dot_v1_dot_automation__pb2.AutomationPrototype, ctx: RequestContext) -> workflows_dot_v1_dot_automation__pb2.AutomationPrototype: - raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") - def update_automation(self, request: workflows_dot_v1_dot_automation__pb2.AutomationPrototype, ctx: RequestContext) -> workflows_dot_v1_dot_automation__pb2.AutomationPrototype: - raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") - def delete_automation(self, request: workflows_dot_v1_dot_automation__pb2.DeleteAutomationRequest, ctx: RequestContext) -> google_dot_protobuf_dot_empty__pb2.Empty: - raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") + def delete_automation(self, request: DeleteAutomationRequest, ctx: RequestContext[DeleteAutomationRequest, Empty], /) -> Empty: + """ + DeleteAutomation deletes an automation from a namespace. + """ + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') + @classmethod + def desc(cls) -> DescService: + """Returns the descriptor for this service.""" + return next(s for s in automation_pb.desc().services if s.type_name == 'workflows.v1.AutomationService') class AutomationServiceWSGIApplication(ConnectWSGIApplication): - def __init__(self, service: AutomationServiceSync, interceptors: Iterable[InterceptorSync]=(), read_max_bytes: int | None = None, compressions: Iterable[Compression] | None = None, codecs: Iterable[Codec] | None = None) -> None: + def __init__( + self, + service: AutomationServiceSync, + interceptors: Iterable[InterceptorSync] = (), + read_max_bytes: int | None = DEFAULT_READ_MAX_BYTES, + compressions: Iterable[Compression] | None = None, + codecs: Iterable[Codec] | None = None, + ) -> None: super().__init__( endpoints={ "/workflows.v1.AutomationService/ListAutomations": EndpointSync.unary( method=MethodInfo( name="ListAutomations", service_name="workflows.v1.AutomationService", - input=google_dot_protobuf_dot_empty__pb2.Empty, - output=workflows_dot_v1_dot_automation__pb2.Automations, + input=Empty, + output=Automations, idempotency_level=IdempotencyLevel.UNKNOWN, ), function=service.list_automations, @@ -240,8 +332,8 @@ def __init__(self, service: AutomationServiceSync, interceptors: Iterable[Interc method=MethodInfo( name="GetAutomation", service_name="workflows.v1.AutomationService", - input=tilebox_dot_v1_dot_id__pb2.ID, - output=workflows_dot_v1_dot_automation__pb2.AutomationPrototype, + input=ID, + output=AutomationPrototype, idempotency_level=IdempotencyLevel.UNKNOWN, ), function=service.get_automation, @@ -250,8 +342,8 @@ def __init__(self, service: AutomationServiceSync, interceptors: Iterable[Interc method=MethodInfo( name="CreateAutomation", service_name="workflows.v1.AutomationService", - input=workflows_dot_v1_dot_automation__pb2.AutomationPrototype, - output=workflows_dot_v1_dot_automation__pb2.AutomationPrototype, + input=AutomationPrototype, + output=AutomationPrototype, idempotency_level=IdempotencyLevel.UNKNOWN, ), function=service.create_automation, @@ -260,8 +352,8 @@ def __init__(self, service: AutomationServiceSync, interceptors: Iterable[Interc method=MethodInfo( name="UpdateAutomation", service_name="workflows.v1.AutomationService", - input=workflows_dot_v1_dot_automation__pb2.AutomationPrototype, - output=workflows_dot_v1_dot_automation__pb2.AutomationPrototype, + input=AutomationPrototype, + output=AutomationPrototype, idempotency_level=IdempotencyLevel.UNKNOWN, ), function=service.update_automation, @@ -270,8 +362,8 @@ def __init__(self, service: AutomationServiceSync, interceptors: Iterable[Interc method=MethodInfo( name="DeleteAutomation", service_name="workflows.v1.AutomationService", - input=workflows_dot_v1_dot_automation__pb2.DeleteAutomationRequest, - output=google_dot_protobuf_dot_empty__pb2.Empty, + input=DeleteAutomationRequest, + output=Empty, idempotency_level=IdempotencyLevel.UNKNOWN, ), function=service.delete_automation, @@ -290,100 +382,116 @@ def path(self) -> str: class AutomationServiceClientSync(ConnectClientSync): + """ + AutomationService is a service for managing automations. Currently, we support two types of triggers for automations: + - Bucket triggers, which triggers tasks when an object is uploaded to a storage bucket that matches a glob pattern + - Cron triggers, which triggers tasks on a schedule + """ def list_automations( self, - request: google_dot_protobuf_dot_empty__pb2.Empty, + request: Empty, *, headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, - ) -> workflows_dot_v1_dot_automation__pb2.Automations: + ) -> Automations: + """ + ListAutomations lists all the automations that are currently registered in a namespace. + """ return self.execute_unary( request=request, method=MethodInfo( name="ListAutomations", service_name="workflows.v1.AutomationService", - input=google_dot_protobuf_dot_empty__pb2.Empty, - output=workflows_dot_v1_dot_automation__pb2.Automations, + input=Empty, + output=Automations, idempotency_level=IdempotencyLevel.UNKNOWN, ), headers=headers, timeout_ms=timeout_ms, ) - def get_automation( self, - request: tilebox_dot_v1_dot_id__pb2.ID, + request: ID, *, headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, - ) -> workflows_dot_v1_dot_automation__pb2.AutomationPrototype: + ) -> AutomationPrototype: + """ + GetAutomation gets an automation by its ID. + """ return self.execute_unary( request=request, method=MethodInfo( name="GetAutomation", service_name="workflows.v1.AutomationService", - input=tilebox_dot_v1_dot_id__pb2.ID, - output=workflows_dot_v1_dot_automation__pb2.AutomationPrototype, + input=ID, + output=AutomationPrototype, idempotency_level=IdempotencyLevel.UNKNOWN, ), headers=headers, timeout_ms=timeout_ms, ) - def create_automation( self, - request: workflows_dot_v1_dot_automation__pb2.AutomationPrototype, + request: AutomationPrototype, *, headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, - ) -> workflows_dot_v1_dot_automation__pb2.AutomationPrototype: + ) -> AutomationPrototype: + """ + CreateAutomation creates a new automation in a namespace. + """ return self.execute_unary( request=request, method=MethodInfo( name="CreateAutomation", service_name="workflows.v1.AutomationService", - input=workflows_dot_v1_dot_automation__pb2.AutomationPrototype, - output=workflows_dot_v1_dot_automation__pb2.AutomationPrototype, + input=AutomationPrototype, + output=AutomationPrototype, idempotency_level=IdempotencyLevel.UNKNOWN, ), headers=headers, timeout_ms=timeout_ms, ) - def update_automation( self, - request: workflows_dot_v1_dot_automation__pb2.AutomationPrototype, + request: AutomationPrototype, *, headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, - ) -> workflows_dot_v1_dot_automation__pb2.AutomationPrototype: + ) -> AutomationPrototype: + """ + UpdateAutomation updates an automation in a namespace. + """ return self.execute_unary( request=request, method=MethodInfo( name="UpdateAutomation", service_name="workflows.v1.AutomationService", - input=workflows_dot_v1_dot_automation__pb2.AutomationPrototype, - output=workflows_dot_v1_dot_automation__pb2.AutomationPrototype, + input=AutomationPrototype, + output=AutomationPrototype, idempotency_level=IdempotencyLevel.UNKNOWN, ), headers=headers, timeout_ms=timeout_ms, ) - def delete_automation( self, - request: workflows_dot_v1_dot_automation__pb2.DeleteAutomationRequest, + request: DeleteAutomationRequest, *, headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, - ) -> google_dot_protobuf_dot_empty__pb2.Empty: + ) -> Empty: + """ + DeleteAutomation deletes an automation from a namespace. + """ return self.execute_unary( request=request, method=MethodInfo( name="DeleteAutomation", service_name="workflows.v1.AutomationService", - input=workflows_dot_v1_dot_automation__pb2.DeleteAutomationRequest, - output=google_dot_protobuf_dot_empty__pb2.Empty, + input=DeleteAutomationRequest, + output=Empty, idempotency_level=IdempotencyLevel.UNKNOWN, ), headers=headers, diff --git a/tilebox-workflows/tilebox/workflows/v1/automation_pb.py b/tilebox-workflows/tilebox/workflows/v1/automation_pb.py new file mode 100644 index 0000000..688ef96 --- /dev/null +++ b/tilebox-workflows/tilebox/workflows/v1/automation_pb.py @@ -0,0 +1,365 @@ +# Generated from workflows/v1/automation.proto. DO NOT EDIT. +# Generated by protoc-gen-py v0.6.0 with parameter "init_files=false,map_imports=datasets/**:tilebox,map_imports=workflows/**:tilebox,map_imports=tilebox/**:_tilebox.proto,map_imports=opentelemetry/**:tilebox.workflows._otel". +# ruff: noqa: PGH004 +# ruff: noqa +# fmt: off + +from __future__ import annotations + +from typing import Literal, TYPE_CHECKING, TypeAlias + +from _tilebox.proto.tilebox.v1 import id_pb +from protobuf import Message +from protobuf._codegen import file_desc +from protobuf.wkt.google.protobuf import empty_pb, timestamp_pb +from tilebox.workflows.v1 import core_pb, storage_location_pb + +if TYPE_CHECKING: + from _tilebox.proto.tilebox.v1.id_pb import ID + from protobuf import DescFile + from protobuf.wkt.google.protobuf.timestamp_pb import Timestamp + from tilebox.workflows.v1.core_pb import SingleTaskSubmission + from tilebox.workflows.v1.storage_location_pb import StorageLocation + + +_AutomationPrototypeFields: TypeAlias = Literal["id", "name", "prototype", "storage_event_triggers", "cron_triggers", "disabled"] + +class AutomationPrototype(Message[_AutomationPrototypeFields]): + """ + AutomationPrototype is a task prototype that can result in many submitted tasks. Task submissions are triggered by + NRT triggers, such as bucket triggers or cron triggers. + + ```proto + message workflows.v1.AutomationPrototype + ``` + + Attributes: + id: + Unique identifier for the trigger + + ```proto + optional tilebox.v1.ID id = 1; + ``` + name: + A human-readable name for the trigger + + ```proto + string name = 2; + ``` + prototype: + The task submission to trigger + + ```proto + optional workflows.v1.SingleTaskSubmission prototype = 3; + ``` + storage_event_triggers: + The storage event triggers that will trigger the task + + ```proto + repeated workflows.v1.StorageEventTrigger storage_event_triggers = 4; + ``` + cron_triggers: + The cron triggers that will trigger the task + + ```proto + repeated workflows.v1.CronTrigger cron_triggers = 5; + ``` + disabled: + Whether the automation is disabled (paused) or not. + the field is named disabled rather than enabled (with the semantics flipped) to make sure not setting the field at + all results in the automation being enabled by default. + + ```proto + bool disabled = 6; + ``` + """ + + __slots__ = ("id", "name", "prototype", "storage_event_triggers", "cron_triggers", "disabled") + + if TYPE_CHECKING: + + def __init__( + self, + *, + id: ID | None = None, + name: str = "", + prototype: SingleTaskSubmission | None = None, + storage_event_triggers: list[StorageEventTrigger] | None = None, + cron_triggers: list[CronTrigger] | None = None, + disabled: bool = False, + ) -> None: + pass + + id: ID | None + name: str + prototype: SingleTaskSubmission | None + storage_event_triggers: list[StorageEventTrigger] + cron_triggers: list[CronTrigger] + disabled: bool + +_AutomationsFields: TypeAlias = Literal["automations"] + +class Automations(Message[_AutomationsFields]): + """ + Automations is a list of automations + + ```proto + message workflows.v1.Automations + ``` + + Attributes: + automations: + ```proto + repeated workflows.v1.AutomationPrototype automations = 1; + ``` + """ + + __slots__ = ("automations",) + + if TYPE_CHECKING: + + def __init__( + self, + *, + automations: list[AutomationPrototype] | None = None, + ) -> None: + pass + + automations: list[AutomationPrototype] + +_StorageEventTriggerFields: TypeAlias = Literal["id", "storage_location", "glob_pattern"] + +class StorageEventTrigger(Message[_StorageEventTriggerFields]): + """ + StorageEventTrigger is a trigger that will trigger a task submission when an object matching the glob pattern is + created in a storage location. + + ```proto + message workflows.v1.StorageEventTrigger + ``` + + Attributes: + id: + Unique identifier for the trigger + + ```proto + optional tilebox.v1.ID id = 1; + ``` + storage_location: + The storage location to watch for events + + ```proto + optional workflows.v1.StorageLocation storage_location = 2; + ``` + glob_pattern: + A glob pattern to match objects/files in the storage location + + ```proto + string glob_pattern = 3; + ``` + """ + + __slots__ = ("id", "storage_location", "glob_pattern") + + if TYPE_CHECKING: + + def __init__( + self, + *, + id: ID | None = None, + storage_location: StorageLocation | None = None, + glob_pattern: str = "", + ) -> None: + pass + + id: ID | None + storage_location: StorageLocation | None + glob_pattern: str + +_CronTriggerFields: TypeAlias = Literal["id", "schedule", "next_scheduled_at"] + +class CronTrigger(Message[_CronTriggerFields]): + """ + CronTrigger is a trigger that will trigger a task submission on a schedule. + + ```proto + message workflows.v1.CronTrigger + ``` + + Attributes: + id: + Unique identifier for the trigger + + ```proto + optional tilebox.v1.ID id = 1; + ``` + schedule: + A cron schedule for the trigger, e.g. "0 0 * * *" (every day at midnight) + + ```proto + string schedule = 2; + ``` + next_scheduled_at: + The next time the cron schedule occurs, regardless of whether the automation is disabled. + + ```proto + optional google.protobuf.Timestamp next_scheduled_at = 3; + ``` + """ + + __slots__ = ("id", "schedule", "next_scheduled_at") + + if TYPE_CHECKING: + + def __init__( + self, + *, + id: ID | None = None, + schedule: str = "", + next_scheduled_at: Timestamp | None = None, + ) -> None: + pass + + id: ID | None + schedule: str + next_scheduled_at: Timestamp | None + +_AutomationFields: TypeAlias = Literal["trigger_event", "args"] + +class Automation(Message[_AutomationFields]): + """ + Automation is an actual submitted task that was triggered by a automation prototype. + It contains the trigger arguments of the event that triggered the task, and the custom, user-defined task arguments. + + ```proto + message workflows.v1.Automation + ``` + + Attributes: + trigger_event: + Details of the event that triggered the task. This is a serialized protobuf message. The type of the message + depends on the type of the trigger, either StorageEventTriggerEvent or CronTriggerEvent. + + ```proto + bytes trigger_event = 1; + ``` + args: + Additional, user-defined arguments for the task, to be deserialized by the task itself + + ```proto + bytes args = 2; + ``` + """ + + __slots__ = ("trigger_event", "args") + + if TYPE_CHECKING: + + def __init__( + self, + *, + trigger_event: bytes = b"", + args: bytes = b"", + ) -> None: + pass + + trigger_event: bytes + args: bytes + +_TriggeredCronEventFields: TypeAlias = Literal["trigger_time"] + +class TriggeredCronEvent(Message[_TriggeredCronEventFields]): + """ + TriggeredCronEvent contains the details of a concrete event that triggered a cron trigger. + + ```proto + message workflows.v1.TriggeredCronEvent + ``` + + Attributes: + trigger_time: + The time the cron trigger fired + + ```proto + optional google.protobuf.Timestamp trigger_time = 1; + ``` + """ + + __slots__ = ("trigger_time",) + + if TYPE_CHECKING: + + def __init__( + self, + *, + trigger_time: Timestamp | None = None, + ) -> None: + pass + + trigger_time: Timestamp | None + +_DeleteAutomationRequestFields: TypeAlias = Literal["automation_id", "cancel_jobs"] + +class DeleteAutomationRequest(Message[_DeleteAutomationRequestFields]): + """ + DeleteAutomationRequest requests the deletion of an automation. + + ```proto + message workflows.v1.DeleteAutomationRequest + ``` + + Attributes: + automation_id: + The ID of the automation to delete. + + ```proto + optional tilebox.v1.ID automation_id = 1; + ``` + cancel_jobs: + Whether to cancel all jobs that have been created by this automation. + + ```proto + bool cancel_jobs = 2; + ``` + """ + + __slots__ = ("automation_id", "cancel_jobs") + + if TYPE_CHECKING: + + def __init__( + self, + *, + automation_id: ID | None = None, + cancel_jobs: bool = False, + ) -> None: + pass + + automation_id: ID | None + cancel_jobs: bool + + +_DESC = file_desc( + b'\n\x1dworkflows/v1/automation.proto\x12\x0cworkflows.v1\x1a\x1bgoogle/protobuf/empty.proto\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\x13tilebox/v1/id.proto\x1a\x17workflows/v1/core.proto\x1a#workflows/v1/storage_location.proto"\xc0\x02\n\x13AutomationPrototype\x12\x1e\n\x02id\x18\x01 \x01(\x0b2\x0e.tilebox.v1.IDR\x02id\x12\x12\n\x04name\x18\x02 \x01(\tR\x04name\x12@\n\tprototype\x18\x03 \x01(\x0b2".workflows.v1.SingleTaskSubmissionR\tprototype\x12W\n\x16storage_event_triggers\x18\x04 \x03(\x0b2!.workflows.v1.StorageEventTriggerR\x14storageEventTriggers\x12>\n\rcron_triggers\x18\x05 \x03(\x0b2\x19.workflows.v1.CronTriggerR\x0ccronTriggers\x12\x1a\n\x08disabled\x18\x06 \x01(\x08R\x08disabled"R\n\x0bAutomations\x12C\n\x0bautomations\x18\x01 \x03(\x0b2!.workflows.v1.AutomationPrototypeR\x0bautomations"\xa2\x01\n\x13StorageEventTrigger\x12\x1e\n\x02id\x18\x01 \x01(\x0b2\x0e.tilebox.v1.IDR\x02id\x12H\n\x10storage_location\x18\x02 \x01(\x0b2\x1d.workflows.v1.StorageLocationR\x0fstorageLocation\x12!\n\x0cglob_pattern\x18\x03 \x01(\tR\x0bglobPattern"\x91\x01\n\x0bCronTrigger\x12\x1e\n\x02id\x18\x01 \x01(\x0b2\x0e.tilebox.v1.IDR\x02id\x12\x1a\n\x08schedule\x18\x02 \x01(\tR\x08schedule\x12F\n\x11next_scheduled_at\x18\x03 \x01(\x0b2\x1a.google.protobuf.TimestampR\x0fnextScheduledAt"E\n\nAutomation\x12#\n\rtrigger_event\x18\x01 \x01(\x0cR\x0ctriggerEvent\x12\x12\n\x04args\x18\x02 \x01(\x0cR\x04args"S\n\x12TriggeredCronEvent\x12=\n\x0ctrigger_time\x18\x01 \x01(\x0b2\x1a.google.protobuf.TimestampR\x0btriggerTime"o\n\x17DeleteAutomationRequest\x123\n\rautomation_id\x18\x01 \x01(\x0b2\x0e.tilebox.v1.IDR\x0cautomationId\x12\x1f\n\x0bcancel_jobs\x18\x02 \x01(\x08R\ncancelJobs2\xa4\x03\n\x11AutomationService\x12D\n\x0fListAutomations\x12\x16.google.protobuf.Empty\x1a\x19.workflows.v1.Automations\x12B\n\rGetAutomation\x12\x0e.tilebox.v1.ID\x1a!.workflows.v1.AutomationPrototype\x12X\n\x10CreateAutomation\x12!.workflows.v1.AutomationPrototype\x1a!.workflows.v1.AutomationPrototype\x12X\n\x10UpdateAutomation\x12!.workflows.v1.AutomationPrototype\x1a!.workflows.v1.AutomationPrototype\x12Q\n\x10DeleteAutomation\x12%.workflows.v1.DeleteAutomationRequest\x1a\x16.google.protobuf.EmptyB\x05\x92\x03\x02\x08\x02b\x08editionsp\xe8\x07', + [ + empty_pb.desc(), + timestamp_pb.desc(), + id_pb.desc(), + core_pb.desc(), + storage_location_pb.desc(), + ], + { + "AutomationPrototype": AutomationPrototype, + "Automations": Automations, + "StorageEventTrigger": StorageEventTrigger, + "CronTrigger": CronTrigger, + "Automation": Automation, + "TriggeredCronEvent": TriggeredCronEvent, + "DeleteAutomationRequest": DeleteAutomationRequest, + }, +) + + +def desc() -> DescFile: + """Returns the descriptor for the file `workflows/v1/automation.proto`.""" + return _DESC diff --git a/tilebox-workflows/tilebox/workflows/v1/core_pb.py b/tilebox-workflows/tilebox/workflows/v1/core_pb.py new file mode 100644 index 0000000..e6b5038 --- /dev/null +++ b/tilebox-workflows/tilebox/workflows/v1/core_pb.py @@ -0,0 +1,999 @@ +# Generated from workflows/v1/core.proto. DO NOT EDIT. +# Generated by protoc-gen-py v0.6.0 with parameter "init_files=false,map_imports=datasets/**:tilebox,map_imports=workflows/**:tilebox,map_imports=tilebox/**:_tilebox.proto,map_imports=opentelemetry/**:tilebox.workflows._otel". +# ruff: noqa: PGH004 +# ruff: noqa +# fmt: off + +from __future__ import annotations + +from typing import Literal, TYPE_CHECKING, TypeAlias + +from _tilebox.proto.tilebox.v1 import id_pb +from protobuf import Enum, Message +from protobuf._codegen import file_desc +from protobuf.wkt.google.protobuf import duration_pb, timestamp_pb + +if TYPE_CHECKING: + from _tilebox.proto.tilebox.v1.id_pb import ID + from protobuf import DescFile + from protobuf.wkt.google.protobuf.duration_pb import Duration + from protobuf.wkt.google.protobuf.timestamp_pb import Timestamp + + +_JobFields: TypeAlias = Literal["id", "name", "trace_parent", "canceled", "legacy_state", "submitted_at", "started_at", "automation_id", "progress", "state", "execution_stats"] + +class Job(Message[_JobFields]): + """ + A job is a logical grouping of tasks that are related. + + ```proto + message workflows.v1.Job + ``` + + Attributes: + id: + ```proto + optional tilebox.v1.ID id = 1; + ``` + name: + ```proto + string name = 2; + ``` + trace_parent: + ```proto + string trace_parent = 3; + ``` + canceled: + Whether the job has been canceled. Deprecated, part of the job status. + + ```proto + bool canceled = 5 [deprecated = true]; + ``` + legacy_state: + The current state of the job. Deprecated, use status instead. + + ```proto + workflows.v1.LegacyJobState legacy_state = 6 [deprecated = true]; + ``` + submitted_at: + The time the job was submitted. + + ```proto + optional google.protobuf.Timestamp submitted_at = 7; + ``` + started_at: + The time the job started running. Deprecated, use execution_stats.first_task_started_at instead. + + ```proto + optional google.protobuf.Timestamp started_at = 8 [deprecated = true]; + ``` + automation_id: + The automation that submitted the job. + + ```proto + optional tilebox.v1.ID automation_id = 10; + ``` + progress: + A list of progress indicators for the job. + + ```proto + repeated workflows.v1.Progress progress = 11; + ``` + state: + The status of the job. + + ```proto + workflows.v1.JobState state = 12; + ``` + execution_stats: + The execution stats of the job inferred from the tasks. + + ```proto + optional workflows.v1.ExecutionStats execution_stats = 13; + ``` + """ + + __slots__ = ("id", "name", "trace_parent", "canceled", "legacy_state", "submitted_at", "started_at", "automation_id", "progress", "state", "execution_stats") + + if TYPE_CHECKING: + + def __init__( + self, + *, + id: ID | None = None, + name: str = "", + trace_parent: str = "", + canceled: bool = False, + legacy_state: LegacyJobState | None = None, + submitted_at: Timestamp | None = None, + started_at: Timestamp | None = None, + automation_id: ID | None = None, + progress: list[Progress] | None = None, + state: JobState | None = None, + execution_stats: ExecutionStats | None = None, + ) -> None: + pass + + id: ID | None + name: str + trace_parent: str + canceled: bool + legacy_state: LegacyJobState + submitted_at: Timestamp | None + started_at: Timestamp | None + automation_id: ID | None + progress: list[Progress] + state: JobState + execution_stats: ExecutionStats | None + +_ExecutionStatsFields: TypeAlias = Literal["first_task_started_at", "last_task_stopped_at", "compute_time", "elapsed_time", "parallelism", "total_tasks", "tasks_by_state"] + +class ExecutionStats(Message[_ExecutionStatsFields]): + """ + ExecutionStats contains statistics about the execution of a job. + + ```proto + message workflows.v1.ExecutionStats + ``` + + Attributes: + first_task_started_at: + The time the first task of the job was started. + + ```proto + optional google.protobuf.Timestamp first_task_started_at = 1; + ``` + last_task_stopped_at: + The time the last task of the job was stopped. + + ```proto + optional google.protobuf.Timestamp last_task_stopped_at = 2; + ``` + compute_time: + The total compute time of the job, as sum of all task compute times. + + ```proto + optional google.protobuf.Duration compute_time = 3; + ``` + elapsed_time: + The elapsed time of the job (wall time), which is the time from first task started to last task stopped. + + ```proto + optional google.protobuf.Duration elapsed_time = 4; + ``` + parallelism: + The parallelism factor of the job, which is the average number of tasks running at any given time. + + ```proto + double parallelism = 5; + ``` + total_tasks: + Total number of tasks of the job. + + ```proto + uint64 total_tasks = 6; + ``` + tasks_by_state: + Number of tasks by their state. + + ```proto + repeated workflows.v1.TaskStateCount tasks_by_state = 7; + ``` + """ + + __slots__ = ("first_task_started_at", "last_task_stopped_at", "compute_time", "elapsed_time", "parallelism", "total_tasks", "tasks_by_state") + + if TYPE_CHECKING: + + def __init__( + self, + *, + first_task_started_at: Timestamp | None = None, + last_task_stopped_at: Timestamp | None = None, + compute_time: Duration | None = None, + elapsed_time: Duration | None = None, + parallelism: float = 0, + total_tasks: int = 0, + tasks_by_state: list[TaskStateCount] | None = None, + ) -> None: + pass + + first_task_started_at: Timestamp | None + last_task_stopped_at: Timestamp | None + compute_time: Duration | None + elapsed_time: Duration | None + parallelism: float + total_tasks: int + tasks_by_state: list[TaskStateCount] + +_TaskStateCountFields: TypeAlias = Literal["state", "count"] + +class TaskStateCount(Message[_TaskStateCountFields]): + """ + TaskStateCount is a message specifying the number of tasks in a certain state. + + ```proto + message workflows.v1.TaskStateCount + ``` + + Attributes: + state: + ```proto + workflows.v1.TaskState state = 1; + ``` + count: + ```proto + uint64 count = 2; + ``` + """ + + __slots__ = ("state", "count") + + if TYPE_CHECKING: + + def __init__( + self, + *, + state: TaskState | None = None, + count: int = 0, + ) -> None: + pass + + state: TaskState + count: int + +_ProgressFields: TypeAlias = Literal["label", "total", "done"] + +class Progress(Message[_ProgressFields]): + """ + Progress is an indicator of total and completed work units for a job. + + ```proto + message workflows.v1.Progress + ``` + + Attributes: + label: + A human-readable label for the progress indicator. Can also be empty. + + ```proto + string label = 1; + ``` + total: + The total number of work units to be processed. + + ```proto + uint64 total = 2; + ``` + done: + The number of work units that have already been processed. + + ```proto + uint64 done = 3; + ``` + """ + + __slots__ = ("label", "total", "done") + + if TYPE_CHECKING: + + def __init__( + self, + *, + label: str = "", + total: int = 0, + done: int = 0, + ) -> None: + pass + + label: str + total: int + done: int + +_TaskFields: TypeAlias = Literal["id", "identifier", "state", "input", "display", "job", "parent_id", "depends_on", "lease", "retry_count"] + +class Task(Message[_TaskFields]): + """ + A task is a single unit of work. + + ```proto + message workflows.v1.Task + ``` + + Attributes: + id: + The id of the task instance. Contains the submission timestamp as the time part of the ULID. + + ```proto + optional tilebox.v1.ID id = 1; + ``` + identifier: + Unique identifier for the task. Used by runners to match tasks to specific functions. + + ```proto + optional workflows.v1.TaskIdentifier identifier = 2; + ``` + state: + The current state of the task. + + ```proto + workflows.v1.TaskState state = 3; + ``` + input: + The serialized input parameters for the task in the format that this task expects. + + ```proto + optional bytes input = 4; + ``` + display: + Display is a human readable representation of the Task used for printing or visualizations + + ```proto + optional string display = 5; + ``` + job: + The job that this task belongs to. + + ```proto + optional workflows.v1.Job job = 6; + ``` + parent_id: + The id of the parent task. + + ```proto + optional tilebox.v1.ID parent_id = 7; + ``` + depends_on: + The ids of the tasks that this task depends on. + + ```proto + repeated tilebox.v1.ID depends_on = 8; + ``` + lease: + The lease of the task. + + ```proto + optional workflows.v1.TaskLease lease = 9; + ``` + retry_count: + The number of times this task has been retried. + + ```proto + int64 retry_count = 10; + ``` + """ + + __slots__ = ("id", "identifier", "state", "input", "display", "job", "parent_id", "depends_on", "lease", "retry_count") + + if TYPE_CHECKING: + + def __init__( + self, + *, + id: ID | None = None, + identifier: TaskIdentifier | None = None, + state: TaskState | None = None, + input: bytes | None = None, + display: str | None = None, + job: Job | None = None, + parent_id: ID | None = None, + depends_on: list[ID] | None = None, + lease: TaskLease | None = None, + retry_count: int = 0, + ) -> None: + pass + + id: ID | None + identifier: TaskIdentifier | None + state: TaskState + input: bytes + display: str + job: Job | None + parent_id: ID | None + depends_on: list[ID] + lease: TaskLease | None + retry_count: int + +_UnboundTaskIdentifierFields: TypeAlias = Literal["name", "version"] + +class UnboundTaskIdentifier(Message[_UnboundTaskIdentifierFields]): + """ + Identifies a task implementation before an execution scope is selected. + Release definitions and worker discovery use this type. Runners bind it to a workflow or advertise it unscoped. + + ```proto + message workflows.v1.UnboundTaskIdentifier + ``` + + Attributes: + name: + The local task name. + + ```proto + string name = 1; + ``` + version: + Version of the task. + + ```proto + string version = 2; + ``` + """ + + __slots__ = ("name", "version") + + if TYPE_CHECKING: + + def __init__( + self, + *, + name: str = "", + version: str = "", + ) -> None: + pass + + name: str + version: str + +_UnboundTaskIdentifiersFields: TypeAlias = Literal["identifiers"] + +class UnboundTaskIdentifiers(Message[_UnboundTaskIdentifiersFields]): + """ + A list of task identifiers without execution scope bindings. + + ```proto + message workflows.v1.UnboundTaskIdentifiers + ``` + + Attributes: + identifiers: + ```proto + repeated workflows.v1.UnboundTaskIdentifier identifiers = 1; + ``` + """ + + __slots__ = ("identifiers",) + + if TYPE_CHECKING: + + def __init__( + self, + *, + identifiers: list[UnboundTaskIdentifier] | None = None, + ) -> None: + pass + + identifiers: list[UnboundTaskIdentifier] + +_TaskIdentifierFields: TypeAlias = Literal["name", "version", "workflow_id"] + +class TaskIdentifier(Message[_TaskIdentifierFields]): + """ + Identifies a task implementation by name, version and by (optional) workflow scope. + + ```proto + message workflows.v1.TaskIdentifier + ``` + + Attributes: + name: + The task name within its workflow scope, or within the unscoped task space. + + ```proto + string name = 1; + ``` + version: + Version of the task. + + ```proto + string version = 2; + ``` + workflow_id: + The immutable ID of the workflow that scopes this task. + In task submissions and execution messages, omission means unscoped, never inheritance of a parent's scope. + Executable tasks returned by the API carry the resolved scope. + + ```proto + optional tilebox.v1.ID workflow_id = 3; + ``` + """ + + __slots__ = ("name", "version", "workflow_id") + + if TYPE_CHECKING: + + def __init__( + self, + *, + name: str = "", + version: str = "", + workflow_id: ID | None = None, + ) -> None: + pass + + name: str + version: str + workflow_id: ID | None + +_TasksFields: TypeAlias = Literal["tasks"] + +class Tasks(Message[_TasksFields]): + """ + A list of tasks. + + ```proto + message workflows.v1.Tasks + ``` + + Attributes: + tasks: + ```proto + repeated workflows.v1.Task tasks = 1; + ``` + """ + + __slots__ = ("tasks",) + + if TYPE_CHECKING: + + def __init__( + self, + *, + tasks: list[Task] | None = None, + ) -> None: + pass + + tasks: list[Task] + +_SingleTaskSubmissionFields: TypeAlias = Literal["cluster_slug", "identifier", "display", "dependencies", "max_retries", "input"] + +class SingleTaskSubmission(Message[_SingleTaskSubmissionFields]): + """ + SingleTaskSubmission is a message of a task that is just about to be submitted, currently in use by automations. + + ```proto + message workflows.v1.SingleTaskSubmission + ``` + + Attributes: + cluster_slug: + The cluster that this task should be run on + + ```proto + string cluster_slug = 1; + ``` + identifier: + The task identifier + + ```proto + optional workflows.v1.TaskIdentifier identifier = 2; + ``` + display: + A human-readable description of the task + + ```proto + string display = 4; + ``` + dependencies: + A list of indices, corresponding to tasks in the list of sub_tasks that this SubTask is part of. + + ```proto + repeated int64 dependencies = 5 [packed = true]; + ``` + max_retries: + The maximum number of retries for this task. + + ```proto + int64 max_retries = 6; + ``` + input: + The serialized task instance, if there is only a single instance. + + ```proto + bytes input = 3; + ``` + """ + + __slots__ = ("cluster_slug", "identifier", "display", "dependencies", "max_retries", "input") + + if TYPE_CHECKING: + + def __init__( + self, + *, + cluster_slug: str = "", + identifier: TaskIdentifier | None = None, + display: str = "", + dependencies: list[int] | None = None, + max_retries: int = 0, + input: bytes = b"", + ) -> None: + pass + + cluster_slug: str + identifier: TaskIdentifier | None + display: str + dependencies: list[int] + max_retries: int + input: bytes + +_TaskSubmissionsFields: TypeAlias = Literal["task_groups", "cluster_slug_lookup", "identifier_lookup", "display_lookup"] + +class TaskSubmissions(Message[_TaskSubmissionsFields]): + """ + TaskSubmissions is a structure for representing a set of tasks about to be submitted, either as a job or as subtasks. + It is optimized for efficient serialization for cases where a large number of very similar tasks are submitted, + with potentially only the individual input parameters varying. + To reduce the serialization size, we keep a separate list/lookup table of unique task properties, that can then be + referenced by their index. + + ```proto + message workflows.v1.TaskSubmissions + ``` + + Attributes: + task_groups: + Concrete instantiations of tasks, grouped by their dependencies and dependants. Each group is uniquely defined by + the set of groups that it depends on (dependencies_on_other_groups) and the set of groups that depend on it, + (which is implicitly given by the inverse of the dependencies on other groups). + + ```proto + repeated workflows.v1.TaskSubmissionGroup task_groups = 1; + ``` + cluster_slug_lookup: + Unique values of cluster slugs, referenced by index in the task instantiations. + + ```proto + repeated string cluster_slug_lookup = 2; + ``` + identifier_lookup: + Unique values of task identifiers, referenced by index in the task instantiations. + + ```proto + repeated workflows.v1.TaskIdentifier identifier_lookup = 3; + ``` + display_lookup: + Unique values of display names, referenced by index in the task instantiations. + + ```proto + repeated string display_lookup = 4; + ``` + """ + + __slots__ = ("task_groups", "cluster_slug_lookup", "identifier_lookup", "display_lookup") + + if TYPE_CHECKING: + + def __init__( + self, + *, + task_groups: list[TaskSubmissionGroup] | None = None, + cluster_slug_lookup: list[str] | None = None, + identifier_lookup: list[TaskIdentifier] | None = None, + display_lookup: list[str] | None = None, + ) -> None: + pass + + task_groups: list[TaskSubmissionGroup] + cluster_slug_lookup: list[str] + identifier_lookup: list[TaskIdentifier] + display_lookup: list[str] + +_TaskSubmissionGroupFields: TypeAlias = Literal["dependencies_on_other_groups", "inputs", "identifier_pointers", "cluster_slug_pointers", "display_pointers", "max_retries_values", "optional_values"] + +class TaskSubmissionGroup(Message[_TaskSubmissionGroupFields]): + """ + TaskSubmissionGroup is a structure for representing a list of submitted tasks, that all share the exact same + dependencies and dependants. Grouping tasks by their dependency edges, and then converting task dependencies to + group dependencies can help to drastically reduce the number of edges we need to serialize and transmit. + Dependants are not explicitly specified, since they can be inferred from the dependencies of the other groups + in the containing TaskSubmissions message. This means that the `dependencies_on_other_groups` field is not unique, + across groups, since there may be two groups sharing the same dependencies but having different dependants. + + ```proto + message workflows.v1.TaskSubmissionGroup + ``` + + Attributes: + dependencies_on_other_groups: + The indices of the groups that this submission group depends on. Indices refer to the groups field of the + containing TaskSubmissions message. + + ```proto + repeated uint32 dependencies_on_other_groups = 1 [packed = true]; + ``` + inputs: + The input parameters for each task. + We explicitly don't group the fields into a submessage and then have a single repeated field for that submessage, + to enable packed encoding of the repeated fields. + + ```proto + repeated bytes inputs = 2; + ``` + identifier_pointers: + Index of the task identifier in the identifier_lookup field of the containing TaskSubmissions message + for each task. + + ```proto + repeated uint64 identifier_pointers = 3 [packed = true]; + ``` + cluster_slug_pointers: + Index of the cluster slug in the cluster_slug_lookup field of the containing TaskSubmissions message for each task, + indicating the cluster that the task should be run on. + + ```proto + repeated uint64 cluster_slug_pointers = 4 [packed = true]; + ``` + display_pointers: + Index of the display name in the display_lookup field of the containing TaskSubmissions message for each task, + specifying a human-readable description of the task. + + ```proto + repeated uint64 display_pointers = 5 [packed = true]; + ``` + max_retries_values: + The maximum number of retries for each task. Not a pointer to a lookup table, since we just inline the values. + + ```proto + repeated int64 max_retries_values = 6 [packed = true]; + ``` + optional_values: + Flag indicating whether each task is optional. Not a pointer to a lookup table, since we just inline the values. + + ```proto + repeated bool optional_values = 7 [packed = true]; + ``` + """ + + __slots__ = ("dependencies_on_other_groups", "inputs", "identifier_pointers", "cluster_slug_pointers", "display_pointers", "max_retries_values", "optional_values") + + if TYPE_CHECKING: + + def __init__( + self, + *, + dependencies_on_other_groups: list[int] | None = None, + inputs: list[bytes] | None = None, + identifier_pointers: list[int] | None = None, + cluster_slug_pointers: list[int] | None = None, + display_pointers: list[int] | None = None, + max_retries_values: list[int] | None = None, + optional_values: list[bool] | None = None, + ) -> None: + pass + + dependencies_on_other_groups: list[int] + inputs: list[bytes] + identifier_pointers: list[int] + cluster_slug_pointers: list[int] + display_pointers: list[int] + max_retries_values: list[int] + optional_values: list[bool] + +_TaskLeaseFields: TypeAlias = Literal["lease", "recommended_wait_until_next_extension"] + +class TaskLease(Message[_TaskLeaseFields]): + """ + A lease for a task. + + ```proto + message workflows.v1.TaskLease + ``` + + Attributes: + lease: + ```proto + optional google.protobuf.Duration lease = 1; + ``` + recommended_wait_until_next_extension: + ```proto + optional google.protobuf.Duration recommended_wait_until_next_extension = 2; + ``` + """ + + __slots__ = ("lease", "recommended_wait_until_next_extension") + + if TYPE_CHECKING: + + def __init__( + self, + *, + lease: Duration | None = None, + recommended_wait_until_next_extension: Duration | None = None, + ) -> None: + pass + + lease: Duration | None + recommended_wait_until_next_extension: Duration | None + +class LegacyJobState(Enum): + """ + The state of a job, deprecated in favor of JobState. + + ```proto + enum workflows.v1.LegacyJobState + ``` + + Attributes: + UNSPECIFIED: + ```proto + LEGACY_JOB_STATE_UNSPECIFIED = 0 + ``` + QUEUED: + The job is queued and waiting to be run. + + ```proto + LEGACY_JOB_STATE_QUEUED = 1 + ``` + STARTED: + At least one task of the job has been started. + + ```proto + LEGACY_JOB_STATE_STARTED = 2 + ``` + COMPLETED: + All tasks of the job have been completed. + + ```proto + LEGACY_JOB_STATE_COMPLETED = 3 + ``` + """ + + UNSPECIFIED = 0 + QUEUED = 1 + STARTED = 2 + COMPLETED = 3 + +class JobState(Enum): + """ + The state of a job, which is computed from the state of each of the jobs tasks combined with the information + if the job has been canceled or not. + + ```proto + enum workflows.v1.JobState + ``` + + Attributes: + UNSPECIFIED: + ```proto + JOB_STATE_UNSPECIFIED = 0 + ``` + SUBMITTED: + The job has been submitted, queued and waiting for its first task to be run. + + ```proto + JOB_STATE_SUBMITTED = 1 + ``` + RUNNING: + The job is running, i.e. at least one task is running. + + ```proto + JOB_STATE_RUNNING = 2 + ``` + STARTED: + The job has started running, i.e. at least one task has been computed, but currently no tasks are running. + + ```proto + JOB_STATE_STARTED = 3 + ``` + COMPLETED: + The job has completed successfully. + + ```proto + JOB_STATE_COMPLETED = 4 + ``` + FAILED: + The job has failed. + + ```proto + JOB_STATE_FAILED = 5 + ``` + CANCELED: + The job has been canceled on user request. + + ```proto + JOB_STATE_CANCELED = 6 + ``` + """ + + UNSPECIFIED = 0 + SUBMITTED = 1 + RUNNING = 2 + STARTED = 3 + COMPLETED = 4 + FAILED = 5 + CANCELED = 6 + +class TaskState(Enum): + """ + The state of a task. + + ```proto + enum workflows.v1.TaskState + ``` + + Attributes: + UNSPECIFIED: + ```proto + TASK_STATE_UNSPECIFIED = 0 + ``` + QUEUED: + The task is queued and waiting to be run. + + ```proto + TASK_STATE_QUEUED = 1 + ``` + RUNNING: + The task is currently running on some task runner. + + ```proto + TASK_STATE_RUNNING = 2 + ``` + COMPUTED: + The task has been computed and the output is available. + If the task also has no more outstanding children, it is considered COMPLETED. + + ```proto + TASK_STATE_COMPUTED = 3 + ``` + FAILED: + The task has failed. + + ```proto + TASK_STATE_FAILED = 4 + ``` + SKIPPED: + The task has been skipped, because it was in an optional subbranch and one of its sibling tasks in the same + branch has failed. + + ```proto + TASK_STATE_SKIPPED = 5 + ``` + FAILED_OPTIONAL: + The task has failed, but was optional. + + ```proto + TASK_STATE_FAILED_OPTIONAL = 6 + ``` + """ + + UNSPECIFIED = 0 + QUEUED = 1 + RUNNING = 2 + COMPUTED = 3 + FAILED = 4 + SKIPPED = 5 + FAILED_OPTIONAL = 6 + + +_DESC = file_desc( + b'\n\x17workflows/v1/core.proto\x12\x0cworkflows.v1\x1a\x1egoogle/protobuf/duration.proto\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\x13tilebox/v1/id.proto"\xb9\x04\n\x03Job\x12\x1e\n\x02id\x18\x01 \x01(\x0b2\x0e.tilebox.v1.IDR\x02id\x12\x12\n\x04name\x18\x02 \x01(\tR\x04name\x12!\n\x0ctrace_parent\x18\x03 \x01(\tR\x0btraceParent\x12\x1e\n\x08canceled\x18\x05 \x01(\x08R\x08canceledB\x02\x18\x01\x12C\n\x0clegacy_state\x18\x06 \x01(\x0e2\x1c.workflows.v1.LegacyJobStateR\x0blegacyStateB\x02\x18\x01\x12=\n\x0csubmitted_at\x18\x07 \x01(\x0b2\x1a.google.protobuf.TimestampR\x0bsubmittedAt\x12=\n\nstarted_at\x18\x08 \x01(\x0b2\x1a.google.protobuf.TimestampR\tstartedAtB\x02\x18\x01\x123\n\rautomation_id\x18\n \x01(\x0b2\x0e.tilebox.v1.IDR\x0cautomationId\x122\n\x08progress\x18\x0b \x03(\x0b2\x16.workflows.v1.ProgressR\x08progress\x12,\n\x05state\x18\x0c \x01(\x0e2\x16.workflows.v1.JobStateR\x05state\x12E\n\x0fexecution_stats\x18\r \x01(\x0b2\x1c.workflows.v1.ExecutionStatsR\x0eexecutionStatsJ\x04\x08\x04\x10\x05J\x04\x08\t\x10\nR\x0etask_summaries"\xaf\x03\n\x0eExecutionStats\x12M\n\x15first_task_started_at\x18\x01 \x01(\x0b2\x1a.google.protobuf.TimestampR\x12firstTaskStartedAt\x12K\n\x14last_task_stopped_at\x18\x02 \x01(\x0b2\x1a.google.protobuf.TimestampR\x11lastTaskStoppedAt\x12<\n\x0ccompute_time\x18\x03 \x01(\x0b2\x19.google.protobuf.DurationR\x0bcomputeTime\x12<\n\x0celapsed_time\x18\x04 \x01(\x0b2\x19.google.protobuf.DurationR\x0belapsedTime\x12 \n\x0bparallelism\x18\x05 \x01(\x01R\x0bparallelism\x12\x1f\n\x0btotal_tasks\x18\x06 \x01(\x04R\ntotalTasks\x12B\n\x0etasks_by_state\x18\x07 \x03(\x0b2\x1c.workflows.v1.TaskStateCountR\x0ctasksByState"U\n\x0eTaskStateCount\x12-\n\x05state\x18\x01 \x01(\x0e2\x17.workflows.v1.TaskStateR\x05state\x12\x14\n\x05count\x18\x02 \x01(\x04R\x05count"J\n\x08Progress\x12\x14\n\x05label\x18\x01 \x01(\tR\x05label\x12\x14\n\x05total\x18\x02 \x01(\x04R\x05total\x12\x12\n\x04done\x18\x03 \x01(\x04R\x04done"\xa2\x03\n\x04Task\x12\x1e\n\x02id\x18\x01 \x01(\x0b2\x0e.tilebox.v1.IDR\x02id\x12<\n\nidentifier\x18\x02 \x01(\x0b2\x1c.workflows.v1.TaskIdentifierR\nidentifier\x12-\n\x05state\x18\x03 \x01(\x0e2\x17.workflows.v1.TaskStateR\x05state\x12\x1b\n\x05input\x18\x04 \x01(\x0cR\x05inputB\x05\xaa\x01\x02\x08\x01\x12\x1f\n\x07display\x18\x05 \x01(\tR\x07displayB\x05\xaa\x01\x02\x08\x01\x12#\n\x03job\x18\x06 \x01(\x0b2\x11.workflows.v1.JobR\x03job\x12+\n\tparent_id\x18\x07 \x01(\x0b2\x0e.tilebox.v1.IDR\x08parentId\x12-\n\ndepends_on\x18\x08 \x03(\x0b2\x0e.tilebox.v1.IDR\tdependsOn\x12-\n\x05lease\x18\t \x01(\x0b2\x17.workflows.v1.TaskLeaseR\x05lease\x12\x1f\n\x0bretry_count\x18\n \x01(\x03R\nretryCount"E\n\x15UnboundTaskIdentifier\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\x12\x18\n\x07version\x18\x02 \x01(\tR\x07version"_\n\x16UnboundTaskIdentifiers\x12E\n\x0bidentifiers\x18\x01 \x03(\x0b2#.workflows.v1.UnboundTaskIdentifierR\x0bidentifiers"v\n\x0eTaskIdentifier\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\x12\x18\n\x07version\x18\x02 \x01(\tR\x07version\x126\n\x0bworkflow_id\x18\x03 \x01(\x0b2\x0e.tilebox.v1.IDR\nworkflowIdB\x05\xaa\x01\x02\x08\x01"1\n\x05Tasks\x12(\n\x05tasks\x18\x01 \x03(\x0b2\x12.workflows.v1.TaskR\x05tasks"\xec\x01\n\x14SingleTaskSubmission\x12!\n\x0ccluster_slug\x18\x01 \x01(\tR\x0bclusterSlug\x12<\n\nidentifier\x18\x02 \x01(\x0b2\x1c.workflows.v1.TaskIdentifierR\nidentifier\x12\x18\n\x07display\x18\x04 \x01(\tR\x07display\x12"\n\x0cdependencies\x18\x05 \x03(\x03R\x0cdependencies\x12\x1f\n\x0bmax_retries\x18\x06 \x01(\x03R\nmaxRetries\x12\x14\n\x05input\x18\x03 \x01(\x0cR\x05input"\xf7\x01\n\x0fTaskSubmissions\x12B\n\x0btask_groups\x18\x01 \x03(\x0b2!.workflows.v1.TaskSubmissionGroupR\ntaskGroups\x12.\n\x13cluster_slug_lookup\x18\x02 \x03(\tR\x11clusterSlugLookup\x12I\n\x11identifier_lookup\x18\x03 \x03(\x0b2\x1c.workflows.v1.TaskIdentifierR\x10identifierLookup\x12%\n\x0edisplay_lookup\x18\x04 \x03(\tR\rdisplayLookup"\xd5\x02\n\x13TaskSubmissionGroup\x12?\n\x1cdependencies_on_other_groups\x18\x01 \x03(\rR\x19dependenciesOnOtherGroups\x12\x16\n\x06inputs\x18\x02 \x03(\x0cR\x06inputs\x12/\n\x13identifier_pointers\x18\x03 \x03(\x04R\x12identifierPointers\x122\n\x15cluster_slug_pointers\x18\x04 \x03(\x04R\x13clusterSlugPointers\x12)\n\x10display_pointers\x18\x05 \x03(\x04R\x0fdisplayPointers\x12,\n\x12max_retries_values\x18\x06 \x03(\x03R\x10maxRetriesValues\x12\'\n\x0foptional_values\x18\x07 \x03(\x08R\x0eoptionalValues"\xa9\x01\n\tTaskLease\x12/\n\x05lease\x18\x01 \x01(\x0b2\x19.google.protobuf.DurationR\x05lease\x12k\n%recommended_wait_until_next_extension\x18\x02 \x01(\x0b2\x19.google.protobuf.DurationR!recommendedWaitUntilNextExtension*\x8d\x01\n\x0eLegacyJobState\x12 \n\x1cLEGACY_JOB_STATE_UNSPECIFIED\x10\x00\x12\x1b\n\x17LEGACY_JOB_STATE_QUEUED\x10\x01\x12\x1c\n\x18LEGACY_JOB_STATE_STARTED\x10\x02\x12\x1e\n\x1aLEGACY_JOB_STATE_COMPLETED\x10\x03*\xb3\x01\n\x08JobState\x12\x19\n\x15JOB_STATE_UNSPECIFIED\x10\x00\x12\x17\n\x13JOB_STATE_SUBMITTED\x10\x01\x12\x15\n\x11JOB_STATE_RUNNING\x10\x02\x12\x15\n\x11JOB_STATE_STARTED\x10\x03\x12\x17\n\x13JOB_STATE_COMPLETED\x10\x04\x12\x14\n\x10JOB_STATE_FAILED\x10\x05\x12\x16\n\x12JOB_STATE_CANCELED\x10\x06*\xbe\x01\n\tTaskState\x12\x1a\n\x16TASK_STATE_UNSPECIFIED\x10\x00\x12\x15\n\x11TASK_STATE_QUEUED\x10\x01\x12\x16\n\x12TASK_STATE_RUNNING\x10\x02\x12\x17\n\x13TASK_STATE_COMPUTED\x10\x03\x12\x15\n\x11TASK_STATE_FAILED\x10\x04\x12\x16\n\x12TASK_STATE_SKIPPED\x10\x05\x12\x1e\n\x1aTASK_STATE_FAILED_OPTIONAL\x10\x06B\x05\x92\x03\x02\x08\x02b\x08editionsp\xe8\x07', + [ + duration_pb.desc(), + timestamp_pb.desc(), + id_pb.desc(), + ], + { + "Job": Job, + "ExecutionStats": ExecutionStats, + "TaskStateCount": TaskStateCount, + "Progress": Progress, + "Task": Task, + "UnboundTaskIdentifier": UnboundTaskIdentifier, + "UnboundTaskIdentifiers": UnboundTaskIdentifiers, + "TaskIdentifier": TaskIdentifier, + "Tasks": Tasks, + "SingleTaskSubmission": SingleTaskSubmission, + "TaskSubmissions": TaskSubmissions, + "TaskSubmissionGroup": TaskSubmissionGroup, + "TaskLease": TaskLease, + "LegacyJobState": LegacyJobState, + "JobState": JobState, + "TaskState": TaskState, + }, +) + + +def desc() -> DescFile: + """Returns the descriptor for the file `workflows/v1/core.proto`.""" + return _DESC diff --git a/tilebox-workflows/tilebox/workflows/workflows/v1/diagram_connect.py b/tilebox-workflows/tilebox/workflows/v1/diagram_connect.py similarity index 51% rename from tilebox-workflows/tilebox/workflows/workflows/v1/diagram_connect.py rename to tilebox-workflows/tilebox/workflows/v1/diagram_connect.py index 6ba154e..a77f02f 100644 --- a/tilebox-workflows/tilebox/workflows/workflows/v1/diagram_connect.py +++ b/tilebox-workflows/tilebox/workflows/v1/diagram_connect.py @@ -1,29 +1,53 @@ -# -*- coding: utf-8 -*- -# Generated by https://github.com/connectrpc/connect-python. DO NOT EDIT! -# source: workflows/v1/diagram.proto +# Generated from workflows/v1/diagram.proto. DO NOT EDIT. +# Generated by protoc-gen-connectrpc-py v0.12.1 with parameter "map_imports=datasets/**:tilebox,map_imports=workflows/**:tilebox,map_imports=tilebox/**:_tilebox.proto,map_imports=opentelemetry/**:tilebox.workflows._otel". +# ruff: noqa: PGH004 +# ruff: noqa +# fmt: off -from collections.abc import AsyncGenerator, AsyncIterator, Iterable, Iterator, Mapping -from typing import Protocol +from __future__ import annotations + +from typing import Protocol, TYPE_CHECKING from connectrpc.client import ConnectClient, ConnectClientSync from connectrpc.code import Code -from connectrpc.codec import Codec -from connectrpc.compression import Compression from connectrpc.errors import ConnectError -from connectrpc.interceptor import Interceptor, InterceptorSync from connectrpc.method import IdempotencyLevel, MethodInfo -from connectrpc.request import Headers, RequestContext -from connectrpc.server import ConnectASGIApplication, ConnectWSGIApplication, Endpoint, EndpointSync -import tilebox.workflows.workflows.v1.diagram_pb2 as workflows_dot_v1_dot_diagram__pb2 +from connectrpc.server import ConnectASGIApplication, ConnectWSGIApplication, DEFAULT_READ_MAX_BYTES, Endpoint, EndpointSync +from protobuf import DescService +from tilebox.workflows.v1 import diagram_pb +from tilebox.workflows.v1.diagram_pb import Diagram, RenderDiagramRequest + +if TYPE_CHECKING: + from collections.abc import AsyncGenerator, Iterable, Mapping + + from connectrpc.codec import Codec + from connectrpc.compression import Compression + from connectrpc.interceptor import Interceptor, InterceptorSync + from connectrpc.request import Headers, RequestContext class DiagramService(Protocol): - async def render(self, request: workflows_dot_v1_dot_diagram__pb2.RenderDiagramRequest, ctx: RequestContext) -> workflows_dot_v1_dot_diagram__pb2.Diagram: - raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") + """ + The diagram service + """ + async def render(self, request: RenderDiagramRequest, ctx: RequestContext[RenderDiagramRequest, Diagram], /) -> Diagram: + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') + @classmethod + def desc(cls) -> DescService: + """Returns the descriptor for this service.""" + return next(s for s in diagram_pb.desc().services if s.type_name == 'workflows.v1.DiagramService') class DiagramServiceASGIApplication(ConnectASGIApplication[DiagramService]): - def __init__(self, service: DiagramService | AsyncGenerator[DiagramService], *, interceptors: Iterable[Interceptor]=(), read_max_bytes: int | None = None, compressions: Iterable[Compression] | None = None, codecs: Iterable[Codec] | None = None) -> None: + def __init__( + self, + service: DiagramService | AsyncGenerator[DiagramService], + *, + interceptors: Iterable[Interceptor] = (), + read_max_bytes: int | None = DEFAULT_READ_MAX_BYTES, + compressions: Iterable[Compression] | None = None, + codecs: Iterable[Codec] | None = None, + ) -> None: super().__init__( service=service, endpoints=lambda svc: { @@ -31,8 +55,8 @@ def __init__(self, service: DiagramService | AsyncGenerator[DiagramService], *, method=MethodInfo( name="Render", service_name="workflows.v1.DiagramService", - input=workflows_dot_v1_dot_diagram__pb2.RenderDiagramRequest, - output=workflows_dot_v1_dot_diagram__pb2.Diagram, + input=RenderDiagramRequest, + output=Diagram, idempotency_level=IdempotencyLevel.UNKNOWN, ), function=svc.render, @@ -51,45 +75,58 @@ def path(self) -> str: class DiagramServiceClient(ConnectClient): + """ + The diagram service + """ async def render( self, - request: workflows_dot_v1_dot_diagram__pb2.RenderDiagramRequest, + request: RenderDiagramRequest, *, headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, - ) -> workflows_dot_v1_dot_diagram__pb2.Diagram: + ) -> Diagram: return await self.execute_unary( request=request, method=MethodInfo( name="Render", service_name="workflows.v1.DiagramService", - input=workflows_dot_v1_dot_diagram__pb2.RenderDiagramRequest, - output=workflows_dot_v1_dot_diagram__pb2.Diagram, + input=RenderDiagramRequest, + output=Diagram, idempotency_level=IdempotencyLevel.UNKNOWN, ), headers=headers, timeout_ms=timeout_ms, ) - - - - class DiagramServiceSync(Protocol): - def render(self, request: workflows_dot_v1_dot_diagram__pb2.RenderDiagramRequest, ctx: RequestContext) -> workflows_dot_v1_dot_diagram__pb2.Diagram: - raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") + """ + The diagram service + """ + def render(self, request: RenderDiagramRequest, ctx: RequestContext[RenderDiagramRequest, Diagram], /) -> Diagram: + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') + @classmethod + def desc(cls) -> DescService: + """Returns the descriptor for this service.""" + return next(s for s in diagram_pb.desc().services if s.type_name == 'workflows.v1.DiagramService') class DiagramServiceWSGIApplication(ConnectWSGIApplication): - def __init__(self, service: DiagramServiceSync, interceptors: Iterable[InterceptorSync]=(), read_max_bytes: int | None = None, compressions: Iterable[Compression] | None = None, codecs: Iterable[Codec] | None = None) -> None: + def __init__( + self, + service: DiagramServiceSync, + interceptors: Iterable[InterceptorSync] = (), + read_max_bytes: int | None = DEFAULT_READ_MAX_BYTES, + compressions: Iterable[Compression] | None = None, + codecs: Iterable[Codec] | None = None, + ) -> None: super().__init__( endpoints={ "/workflows.v1.DiagramService/Render": EndpointSync.unary( method=MethodInfo( name="Render", service_name="workflows.v1.DiagramService", - input=workflows_dot_v1_dot_diagram__pb2.RenderDiagramRequest, - output=workflows_dot_v1_dot_diagram__pb2.Diagram, + input=RenderDiagramRequest, + output=Diagram, idempotency_level=IdempotencyLevel.UNKNOWN, ), function=service.render, @@ -108,20 +145,23 @@ def path(self) -> str: class DiagramServiceClientSync(ConnectClientSync): + """ + The diagram service + """ def render( self, - request: workflows_dot_v1_dot_diagram__pb2.RenderDiagramRequest, + request: RenderDiagramRequest, *, headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, - ) -> workflows_dot_v1_dot_diagram__pb2.Diagram: + ) -> Diagram: return self.execute_unary( request=request, method=MethodInfo( name="Render", service_name="workflows.v1.DiagramService", - input=workflows_dot_v1_dot_diagram__pb2.RenderDiagramRequest, - output=workflows_dot_v1_dot_diagram__pb2.Diagram, + input=RenderDiagramRequest, + output=Diagram, idempotency_level=IdempotencyLevel.UNKNOWN, ), headers=headers, diff --git a/tilebox-workflows/tilebox/workflows/v1/diagram_pb.py b/tilebox-workflows/tilebox/workflows/v1/diagram_pb.py new file mode 100644 index 0000000..310c833 --- /dev/null +++ b/tilebox-workflows/tilebox/workflows/v1/diagram_pb.py @@ -0,0 +1,167 @@ +# Generated from workflows/v1/diagram.proto. DO NOT EDIT. +# Generated by protoc-gen-py v0.6.0 with parameter "init_files=false,map_imports=datasets/**:tilebox,map_imports=workflows/**:tilebox,map_imports=tilebox/**:_tilebox.proto,map_imports=opentelemetry/**:tilebox.workflows._otel". +# ruff: noqa: PGH004 +# ruff: noqa +# fmt: off + +from __future__ import annotations + +from typing import Literal, TYPE_CHECKING, TypeAlias + +from protobuf import Message +from protobuf._codegen import file_desc + +if TYPE_CHECKING: + from protobuf import DescFile + + +_RenderDiagramRequestFields: TypeAlias = Literal["diagram", "render_options"] + +class RenderDiagramRequest(Message[_RenderDiagramRequestFields]): + """ + Request to render a diagram + + ```proto + message workflows.v1.RenderDiagramRequest + ``` + + Attributes: + diagram: + The diagram graph in the D2 syntax + + ```proto + string diagram = 1; + ``` + render_options: + The options for rendering the diagram + + ```proto + optional workflows.v1.RenderOptions render_options = 2; + ``` + """ + + __slots__ = ("diagram", "render_options") + + if TYPE_CHECKING: + + def __init__( + self, + *, + diagram: str = "", + render_options: RenderOptions | None = None, + ) -> None: + pass + + diagram: str + render_options: RenderOptions | None + +_RenderOptionsFields: TypeAlias = Literal["layout", "theme_id", "sketchy", "padding", "direction"] + +class RenderOptions(Message[_RenderOptionsFields]): + """ + Options for rendering the diagram + + ```proto + message workflows.v1.RenderOptions + ``` + + Attributes: + layout: + The layout to use for rendering the diagram: https://d2lang.com/tour/layouts/. + "dagre" or "elk". Defaults to "dagre" + + ```proto + string layout = 1; + ``` + theme_id: + The D2 theme to use when rendering the diagram: https://d2lang.com/tour/themes/ + + ```proto + optional int64 theme_id = 2; + ``` + sketchy: + Whether to render the diagram in a sketchy (hand-drawn) style + + ```proto + bool sketchy = 3; + ``` + padding: + The padding around the diagram + + ```proto + int64 padding = 4; + ``` + direction: + Set explicitly the direction of the diagram: https://d2lang.com/tour/layouts/#direction. + + ```proto + string direction = 5; + ``` + """ + + __slots__ = ("layout", "theme_id", "sketchy", "padding", "direction") + + if TYPE_CHECKING: + + def __init__( + self, + *, + layout: str = "", + theme_id: int | None = None, + sketchy: bool = False, + padding: int = 0, + direction: str = "", + ) -> None: + pass + + layout: str + theme_id: int + sketchy: bool + padding: int + direction: str + +_DiagramFields: TypeAlias = Literal["svg"] + +class Diagram(Message[_DiagramFields]): + """ + A rendered diagram + + ```proto + message workflows.v1.Diagram + ``` + + Attributes: + svg: + ```proto + bytes svg = 1; + ``` + """ + + __slots__ = ("svg",) + + if TYPE_CHECKING: + + def __init__( + self, + *, + svg: bytes = b"", + ) -> None: + pass + + svg: bytes + + +_DESC = file_desc( + b'\n\x1aworkflows/v1/diagram.proto\x12\x0cworkflows.v1"t\n\x14RenderDiagramRequest\x12\x18\n\x07diagram\x18\x01 \x01(\tR\x07diagram\x12B\n\x0erender_options\x18\x02 \x01(\x0b2\x1b.workflows.v1.RenderOptionsR\rrenderOptions"\x9b\x01\n\rRenderOptions\x12\x16\n\x06layout\x18\x01 \x01(\tR\x06layout\x12 \n\x08theme_id\x18\x02 \x01(\x03R\x07themeIdB\x05\xaa\x01\x02\x08\x01\x12\x18\n\x07sketchy\x18\x03 \x01(\x08R\x07sketchy\x12\x18\n\x07padding\x18\x04 \x01(\x03R\x07padding\x12\x1c\n\tdirection\x18\x05 \x01(\tR\tdirection"\x1b\n\x07Diagram\x12\x10\n\x03svg\x18\x01 \x01(\x0cR\x03svg2U\n\x0eDiagramService\x12C\n\x06Render\x12".workflows.v1.RenderDiagramRequest\x1a\x15.workflows.v1.DiagramB\x05\x92\x03\x02\x08\x02b\x08editionsp\xe8\x07', + [], + { + "RenderDiagramRequest": RenderDiagramRequest, + "RenderOptions": RenderOptions, + "Diagram": Diagram, + }, +) + + +def desc() -> DescFile: + """Returns the descriptor for the file `workflows/v1/diagram.proto`.""" + return _DESC diff --git a/tilebox-workflows/tilebox/workflows/workflows/v1/job_connect.py b/tilebox-workflows/tilebox/workflows/v1/job_connect.py similarity index 57% rename from tilebox-workflows/tilebox/workflows/workflows/v1/job_connect.py rename to tilebox-workflows/tilebox/workflows/v1/job_connect.py index 23d631d..aa6183b 100644 --- a/tilebox-workflows/tilebox/workflows/workflows/v1/job_connect.py +++ b/tilebox-workflows/tilebox/workflows/v1/job_connect.py @@ -1,61 +1,85 @@ -# -*- coding: utf-8 -*- -# Generated by https://github.com/connectrpc/connect-python. DO NOT EDIT! -# source: workflows/v1/job.proto +# Generated from workflows/v1/job.proto. DO NOT EDIT. +# Generated by protoc-gen-connectrpc-py v0.12.1 with parameter "map_imports=datasets/**:tilebox,map_imports=workflows/**:tilebox,map_imports=tilebox/**:_tilebox.proto,map_imports=opentelemetry/**:tilebox.workflows._otel". +# ruff: noqa: PGH004 +# ruff: noqa +# fmt: off -from collections.abc import AsyncGenerator, AsyncIterator, Iterable, Iterator, Mapping -from typing import Protocol +from __future__ import annotations + +from typing import Protocol, TYPE_CHECKING from connectrpc.client import ConnectClient, ConnectClientSync from connectrpc.code import Code -from connectrpc.codec import Codec -from connectrpc.compression import Compression from connectrpc.errors import ConnectError -from connectrpc.interceptor import Interceptor, InterceptorSync from connectrpc.method import IdempotencyLevel, MethodInfo -from connectrpc.request import Headers, RequestContext -from connectrpc.server import ConnectASGIApplication, ConnectWSGIApplication, Endpoint, EndpointSync -import tilebox.workflows.workflows.v1.core_pb2 as workflows_dot_v1_dot_core__pb2 -import tilebox.workflows.workflows.v1.diagram_pb2 as workflows_dot_v1_dot_diagram__pb2 -import tilebox.workflows.workflows.v1.job_pb2 as workflows_dot_v1_dot_job__pb2 +from connectrpc.server import ConnectASGIApplication, ConnectWSGIApplication, DEFAULT_READ_MAX_BYTES, Endpoint, EndpointSync +from protobuf import DescService +from tilebox.workflows.v1 import job_pb +from tilebox.workflows.v1.core_pb import Job +from tilebox.workflows.v1.diagram_pb import Diagram +from tilebox.workflows.v1.job_pb import CancelJobRequest, CancelJobResponse, CloneJobRequest, GetJobPrototypeRequest, GetJobPrototypeResponse, GetJobRequest, GetJobStateCountsRequest, GetJobStateCountsResponse, GetTaskQueueStatsRequest, GetTaskQueueStatsResponse, ListJobTasksRequest, ListJobTasksResponse, QueryJobsRequest, QueryJobsResponse, RetryJobRequest, RetryJobResponse, SubmitJobRequest, VisualizeJobRequest + +if TYPE_CHECKING: + from collections.abc import AsyncGenerator, Iterable, Mapping + + from connectrpc.codec import Codec + from connectrpc.compression import Compression + from connectrpc.interceptor import Interceptor, InterceptorSync + from connectrpc.request import Headers, RequestContext class JobService(Protocol): - async def submit_job(self, request: workflows_dot_v1_dot_job__pb2.SubmitJobRequest, ctx: RequestContext) -> workflows_dot_v1_dot_core__pb2.Job: - raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") + """ + A service for interacting with jobs. + """ + async def submit_job(self, request: SubmitJobRequest, ctx: RequestContext[SubmitJobRequest, Job], /) -> Job: + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') - async def get_job(self, request: workflows_dot_v1_dot_job__pb2.GetJobRequest, ctx: RequestContext) -> workflows_dot_v1_dot_core__pb2.Job: - raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") + async def get_job(self, request: GetJobRequest, ctx: RequestContext[GetJobRequest, Job], /) -> Job: + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') - async def list_job_tasks(self, request: workflows_dot_v1_dot_job__pb2.ListJobTasksRequest, ctx: RequestContext) -> workflows_dot_v1_dot_job__pb2.ListJobTasksResponse: - raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") + async def list_job_tasks(self, request: ListJobTasksRequest, ctx: RequestContext[ListJobTasksRequest, ListJobTasksResponse], /) -> ListJobTasksResponse: + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') - async def retry_job(self, request: workflows_dot_v1_dot_job__pb2.RetryJobRequest, ctx: RequestContext) -> workflows_dot_v1_dot_job__pb2.RetryJobResponse: - raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") + async def retry_job(self, request: RetryJobRequest, ctx: RequestContext[RetryJobRequest, RetryJobResponse], /) -> RetryJobResponse: + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') - async def cancel_job(self, request: workflows_dot_v1_dot_job__pb2.CancelJobRequest, ctx: RequestContext) -> workflows_dot_v1_dot_job__pb2.CancelJobResponse: - raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") + async def cancel_job(self, request: CancelJobRequest, ctx: RequestContext[CancelJobRequest, CancelJobResponse], /) -> CancelJobResponse: + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') - async def visualize_job(self, request: workflows_dot_v1_dot_job__pb2.VisualizeJobRequest, ctx: RequestContext) -> workflows_dot_v1_dot_diagram__pb2.Diagram: - raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") + async def visualize_job(self, request: VisualizeJobRequest, ctx: RequestContext[VisualizeJobRequest, Diagram], /) -> Diagram: + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') - async def query_jobs(self, request: workflows_dot_v1_dot_job__pb2.QueryJobsRequest, ctx: RequestContext) -> workflows_dot_v1_dot_job__pb2.QueryJobsResponse: - raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") + async def query_jobs(self, request: QueryJobsRequest, ctx: RequestContext[QueryJobsRequest, QueryJobsResponse], /) -> QueryJobsResponse: + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') - async def get_job_prototype(self, request: workflows_dot_v1_dot_job__pb2.GetJobPrototypeRequest, ctx: RequestContext) -> workflows_dot_v1_dot_job__pb2.GetJobPrototypeResponse: - raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") + async def get_job_prototype(self, request: GetJobPrototypeRequest, ctx: RequestContext[GetJobPrototypeRequest, GetJobPrototypeResponse], /) -> GetJobPrototypeResponse: + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') - async def clone_job(self, request: workflows_dot_v1_dot_job__pb2.CloneJobRequest, ctx: RequestContext) -> workflows_dot_v1_dot_core__pb2.Job: - raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") + async def clone_job(self, request: CloneJobRequest, ctx: RequestContext[CloneJobRequest, Job], /) -> Job: + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') - async def get_job_state_counts(self, request: workflows_dot_v1_dot_job__pb2.GetJobStateCountsRequest, ctx: RequestContext) -> workflows_dot_v1_dot_job__pb2.GetJobStateCountsResponse: - raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") + async def get_job_state_counts(self, request: GetJobStateCountsRequest, ctx: RequestContext[GetJobStateCountsRequest, GetJobStateCountsResponse], /) -> GetJobStateCountsResponse: + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') - async def get_task_queue_stats(self, request: workflows_dot_v1_dot_job__pb2.GetTaskQueueStatsRequest, ctx: RequestContext) -> workflows_dot_v1_dot_job__pb2.GetTaskQueueStatsResponse: - raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") + async def get_task_queue_stats(self, request: GetTaskQueueStatsRequest, ctx: RequestContext[GetTaskQueueStatsRequest, GetTaskQueueStatsResponse], /) -> GetTaskQueueStatsResponse: + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') + @classmethod + def desc(cls) -> DescService: + """Returns the descriptor for this service.""" + return next(s for s in job_pb.desc().services if s.type_name == 'workflows.v1.JobService') class JobServiceASGIApplication(ConnectASGIApplication[JobService]): - def __init__(self, service: JobService | AsyncGenerator[JobService], *, interceptors: Iterable[Interceptor]=(), read_max_bytes: int | None = None, compressions: Iterable[Compression] | None = None, codecs: Iterable[Codec] | None = None) -> None: + def __init__( + self, + service: JobService | AsyncGenerator[JobService], + *, + interceptors: Iterable[Interceptor] = (), + read_max_bytes: int | None = DEFAULT_READ_MAX_BYTES, + compressions: Iterable[Compression] | None = None, + codecs: Iterable[Codec] | None = None, + ) -> None: super().__init__( service=service, endpoints=lambda svc: { @@ -63,8 +87,8 @@ def __init__(self, service: JobService | AsyncGenerator[JobService], *, intercep method=MethodInfo( name="SubmitJob", service_name="workflows.v1.JobService", - input=workflows_dot_v1_dot_job__pb2.SubmitJobRequest, - output=workflows_dot_v1_dot_core__pb2.Job, + input=SubmitJobRequest, + output=Job, idempotency_level=IdempotencyLevel.UNKNOWN, ), function=svc.submit_job, @@ -73,8 +97,8 @@ def __init__(self, service: JobService | AsyncGenerator[JobService], *, intercep method=MethodInfo( name="GetJob", service_name="workflows.v1.JobService", - input=workflows_dot_v1_dot_job__pb2.GetJobRequest, - output=workflows_dot_v1_dot_core__pb2.Job, + input=GetJobRequest, + output=Job, idempotency_level=IdempotencyLevel.UNKNOWN, ), function=svc.get_job, @@ -83,8 +107,8 @@ def __init__(self, service: JobService | AsyncGenerator[JobService], *, intercep method=MethodInfo( name="ListJobTasks", service_name="workflows.v1.JobService", - input=workflows_dot_v1_dot_job__pb2.ListJobTasksRequest, - output=workflows_dot_v1_dot_job__pb2.ListJobTasksResponse, + input=ListJobTasksRequest, + output=ListJobTasksResponse, idempotency_level=IdempotencyLevel.UNKNOWN, ), function=svc.list_job_tasks, @@ -93,8 +117,8 @@ def __init__(self, service: JobService | AsyncGenerator[JobService], *, intercep method=MethodInfo( name="RetryJob", service_name="workflows.v1.JobService", - input=workflows_dot_v1_dot_job__pb2.RetryJobRequest, - output=workflows_dot_v1_dot_job__pb2.RetryJobResponse, + input=RetryJobRequest, + output=RetryJobResponse, idempotency_level=IdempotencyLevel.UNKNOWN, ), function=svc.retry_job, @@ -103,8 +127,8 @@ def __init__(self, service: JobService | AsyncGenerator[JobService], *, intercep method=MethodInfo( name="CancelJob", service_name="workflows.v1.JobService", - input=workflows_dot_v1_dot_job__pb2.CancelJobRequest, - output=workflows_dot_v1_dot_job__pb2.CancelJobResponse, + input=CancelJobRequest, + output=CancelJobResponse, idempotency_level=IdempotencyLevel.UNKNOWN, ), function=svc.cancel_job, @@ -113,8 +137,8 @@ def __init__(self, service: JobService | AsyncGenerator[JobService], *, intercep method=MethodInfo( name="VisualizeJob", service_name="workflows.v1.JobService", - input=workflows_dot_v1_dot_job__pb2.VisualizeJobRequest, - output=workflows_dot_v1_dot_diagram__pb2.Diagram, + input=VisualizeJobRequest, + output=Diagram, idempotency_level=IdempotencyLevel.UNKNOWN, ), function=svc.visualize_job, @@ -123,8 +147,8 @@ def __init__(self, service: JobService | AsyncGenerator[JobService], *, intercep method=MethodInfo( name="QueryJobs", service_name="workflows.v1.JobService", - input=workflows_dot_v1_dot_job__pb2.QueryJobsRequest, - output=workflows_dot_v1_dot_job__pb2.QueryJobsResponse, + input=QueryJobsRequest, + output=QueryJobsResponse, idempotency_level=IdempotencyLevel.UNKNOWN, ), function=svc.query_jobs, @@ -133,8 +157,8 @@ def __init__(self, service: JobService | AsyncGenerator[JobService], *, intercep method=MethodInfo( name="GetJobPrototype", service_name="workflows.v1.JobService", - input=workflows_dot_v1_dot_job__pb2.GetJobPrototypeRequest, - output=workflows_dot_v1_dot_job__pb2.GetJobPrototypeResponse, + input=GetJobPrototypeRequest, + output=GetJobPrototypeResponse, idempotency_level=IdempotencyLevel.UNKNOWN, ), function=svc.get_job_prototype, @@ -143,8 +167,8 @@ def __init__(self, service: JobService | AsyncGenerator[JobService], *, intercep method=MethodInfo( name="CloneJob", service_name="workflows.v1.JobService", - input=workflows_dot_v1_dot_job__pb2.CloneJobRequest, - output=workflows_dot_v1_dot_core__pb2.Job, + input=CloneJobRequest, + output=Job, idempotency_level=IdempotencyLevel.UNKNOWN, ), function=svc.clone_job, @@ -153,8 +177,8 @@ def __init__(self, service: JobService | AsyncGenerator[JobService], *, intercep method=MethodInfo( name="GetJobStateCounts", service_name="workflows.v1.JobService", - input=workflows_dot_v1_dot_job__pb2.GetJobStateCountsRequest, - output=workflows_dot_v1_dot_job__pb2.GetJobStateCountsResponse, + input=GetJobStateCountsRequest, + output=GetJobStateCountsResponse, idempotency_level=IdempotencyLevel.UNKNOWN, ), function=svc.get_job_state_counts, @@ -163,8 +187,8 @@ def __init__(self, service: JobService | AsyncGenerator[JobService], *, intercep method=MethodInfo( name="GetTaskQueueStats", service_name="workflows.v1.JobService", - input=workflows_dot_v1_dot_job__pb2.GetTaskQueueStatsRequest, - output=workflows_dot_v1_dot_job__pb2.GetTaskQueueStatsResponse, + input=GetTaskQueueStatsRequest, + output=GetTaskQueueStatsResponse, idempotency_level=IdempotencyLevel.UNKNOWN, ), function=svc.get_task_queue_stats, @@ -183,20 +207,23 @@ def path(self) -> str: class JobServiceClient(ConnectClient): + """ + A service for interacting with jobs. + """ async def submit_job( self, - request: workflows_dot_v1_dot_job__pb2.SubmitJobRequest, + request: SubmitJobRequest, *, headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, - ) -> workflows_dot_v1_dot_core__pb2.Job: + ) -> Job: return await self.execute_unary( request=request, method=MethodInfo( name="SubmitJob", service_name="workflows.v1.JobService", - input=workflows_dot_v1_dot_job__pb2.SubmitJobRequest, - output=workflows_dot_v1_dot_core__pb2.Job, + input=SubmitJobRequest, + output=Job, idempotency_level=IdempotencyLevel.UNKNOWN, ), headers=headers, @@ -205,18 +232,18 @@ async def submit_job( async def get_job( self, - request: workflows_dot_v1_dot_job__pb2.GetJobRequest, + request: GetJobRequest, *, headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, - ) -> workflows_dot_v1_dot_core__pb2.Job: + ) -> Job: return await self.execute_unary( request=request, method=MethodInfo( name="GetJob", service_name="workflows.v1.JobService", - input=workflows_dot_v1_dot_job__pb2.GetJobRequest, - output=workflows_dot_v1_dot_core__pb2.Job, + input=GetJobRequest, + output=Job, idempotency_level=IdempotencyLevel.UNKNOWN, ), headers=headers, @@ -225,18 +252,18 @@ async def get_job( async def list_job_tasks( self, - request: workflows_dot_v1_dot_job__pb2.ListJobTasksRequest, + request: ListJobTasksRequest, *, headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, - ) -> workflows_dot_v1_dot_job__pb2.ListJobTasksResponse: + ) -> ListJobTasksResponse: return await self.execute_unary( request=request, method=MethodInfo( name="ListJobTasks", service_name="workflows.v1.JobService", - input=workflows_dot_v1_dot_job__pb2.ListJobTasksRequest, - output=workflows_dot_v1_dot_job__pb2.ListJobTasksResponse, + input=ListJobTasksRequest, + output=ListJobTasksResponse, idempotency_level=IdempotencyLevel.UNKNOWN, ), headers=headers, @@ -245,18 +272,18 @@ async def list_job_tasks( async def retry_job( self, - request: workflows_dot_v1_dot_job__pb2.RetryJobRequest, + request: RetryJobRequest, *, headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, - ) -> workflows_dot_v1_dot_job__pb2.RetryJobResponse: + ) -> RetryJobResponse: return await self.execute_unary( request=request, method=MethodInfo( name="RetryJob", service_name="workflows.v1.JobService", - input=workflows_dot_v1_dot_job__pb2.RetryJobRequest, - output=workflows_dot_v1_dot_job__pb2.RetryJobResponse, + input=RetryJobRequest, + output=RetryJobResponse, idempotency_level=IdempotencyLevel.UNKNOWN, ), headers=headers, @@ -265,18 +292,18 @@ async def retry_job( async def cancel_job( self, - request: workflows_dot_v1_dot_job__pb2.CancelJobRequest, + request: CancelJobRequest, *, headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, - ) -> workflows_dot_v1_dot_job__pb2.CancelJobResponse: + ) -> CancelJobResponse: return await self.execute_unary( request=request, method=MethodInfo( name="CancelJob", service_name="workflows.v1.JobService", - input=workflows_dot_v1_dot_job__pb2.CancelJobRequest, - output=workflows_dot_v1_dot_job__pb2.CancelJobResponse, + input=CancelJobRequest, + output=CancelJobResponse, idempotency_level=IdempotencyLevel.UNKNOWN, ), headers=headers, @@ -285,18 +312,18 @@ async def cancel_job( async def visualize_job( self, - request: workflows_dot_v1_dot_job__pb2.VisualizeJobRequest, + request: VisualizeJobRequest, *, headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, - ) -> workflows_dot_v1_dot_diagram__pb2.Diagram: + ) -> Diagram: return await self.execute_unary( request=request, method=MethodInfo( name="VisualizeJob", service_name="workflows.v1.JobService", - input=workflows_dot_v1_dot_job__pb2.VisualizeJobRequest, - output=workflows_dot_v1_dot_diagram__pb2.Diagram, + input=VisualizeJobRequest, + output=Diagram, idempotency_level=IdempotencyLevel.UNKNOWN, ), headers=headers, @@ -305,18 +332,18 @@ async def visualize_job( async def query_jobs( self, - request: workflows_dot_v1_dot_job__pb2.QueryJobsRequest, + request: QueryJobsRequest, *, headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, - ) -> workflows_dot_v1_dot_job__pb2.QueryJobsResponse: + ) -> QueryJobsResponse: return await self.execute_unary( request=request, method=MethodInfo( name="QueryJobs", service_name="workflows.v1.JobService", - input=workflows_dot_v1_dot_job__pb2.QueryJobsRequest, - output=workflows_dot_v1_dot_job__pb2.QueryJobsResponse, + input=QueryJobsRequest, + output=QueryJobsResponse, idempotency_level=IdempotencyLevel.UNKNOWN, ), headers=headers, @@ -325,18 +352,18 @@ async def query_jobs( async def get_job_prototype( self, - request: workflows_dot_v1_dot_job__pb2.GetJobPrototypeRequest, + request: GetJobPrototypeRequest, *, headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, - ) -> workflows_dot_v1_dot_job__pb2.GetJobPrototypeResponse: + ) -> GetJobPrototypeResponse: return await self.execute_unary( request=request, method=MethodInfo( name="GetJobPrototype", service_name="workflows.v1.JobService", - input=workflows_dot_v1_dot_job__pb2.GetJobPrototypeRequest, - output=workflows_dot_v1_dot_job__pb2.GetJobPrototypeResponse, + input=GetJobPrototypeRequest, + output=GetJobPrototypeResponse, idempotency_level=IdempotencyLevel.UNKNOWN, ), headers=headers, @@ -345,18 +372,18 @@ async def get_job_prototype( async def clone_job( self, - request: workflows_dot_v1_dot_job__pb2.CloneJobRequest, + request: CloneJobRequest, *, headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, - ) -> workflows_dot_v1_dot_core__pb2.Job: + ) -> Job: return await self.execute_unary( request=request, method=MethodInfo( name="CloneJob", service_name="workflows.v1.JobService", - input=workflows_dot_v1_dot_job__pb2.CloneJobRequest, - output=workflows_dot_v1_dot_core__pb2.Job, + input=CloneJobRequest, + output=Job, idempotency_level=IdempotencyLevel.UNKNOWN, ), headers=headers, @@ -365,18 +392,18 @@ async def clone_job( async def get_job_state_counts( self, - request: workflows_dot_v1_dot_job__pb2.GetJobStateCountsRequest, + request: GetJobStateCountsRequest, *, headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, - ) -> workflows_dot_v1_dot_job__pb2.GetJobStateCountsResponse: + ) -> GetJobStateCountsResponse: return await self.execute_unary( request=request, method=MethodInfo( name="GetJobStateCounts", service_name="workflows.v1.JobService", - input=workflows_dot_v1_dot_job__pb2.GetJobStateCountsRequest, - output=workflows_dot_v1_dot_job__pb2.GetJobStateCountsResponse, + input=GetJobStateCountsRequest, + output=GetJobStateCountsResponse, idempotency_level=IdempotencyLevel.UNKNOWN, ), headers=headers, @@ -385,63 +412,83 @@ async def get_job_state_counts( async def get_task_queue_stats( self, - request: workflows_dot_v1_dot_job__pb2.GetTaskQueueStatsRequest, + request: GetTaskQueueStatsRequest, *, headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, - ) -> workflows_dot_v1_dot_job__pb2.GetTaskQueueStatsResponse: + ) -> GetTaskQueueStatsResponse: return await self.execute_unary( request=request, method=MethodInfo( name="GetTaskQueueStats", service_name="workflows.v1.JobService", - input=workflows_dot_v1_dot_job__pb2.GetTaskQueueStatsRequest, - output=workflows_dot_v1_dot_job__pb2.GetTaskQueueStatsResponse, + input=GetTaskQueueStatsRequest, + output=GetTaskQueueStatsResponse, idempotency_level=IdempotencyLevel.UNKNOWN, ), headers=headers, timeout_ms=timeout_ms, ) +class JobServiceSync(Protocol): + """ + A service for interacting with jobs. + """ + def submit_job(self, request: SubmitJobRequest, ctx: RequestContext[SubmitJobRequest, Job], /) -> Job: + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') + + def get_job(self, request: GetJobRequest, ctx: RequestContext[GetJobRequest, Job], /) -> Job: + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') + def list_job_tasks(self, request: ListJobTasksRequest, ctx: RequestContext[ListJobTasksRequest, ListJobTasksResponse], /) -> ListJobTasksResponse: + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') + def retry_job(self, request: RetryJobRequest, ctx: RequestContext[RetryJobRequest, RetryJobResponse], /) -> RetryJobResponse: + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') + def cancel_job(self, request: CancelJobRequest, ctx: RequestContext[CancelJobRequest, CancelJobResponse], /) -> CancelJobResponse: + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') -class JobServiceSync(Protocol): - def submit_job(self, request: workflows_dot_v1_dot_job__pb2.SubmitJobRequest, ctx: RequestContext) -> workflows_dot_v1_dot_core__pb2.Job: - raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") - def get_job(self, request: workflows_dot_v1_dot_job__pb2.GetJobRequest, ctx: RequestContext) -> workflows_dot_v1_dot_core__pb2.Job: - raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") - def list_job_tasks(self, request: workflows_dot_v1_dot_job__pb2.ListJobTasksRequest, ctx: RequestContext) -> workflows_dot_v1_dot_job__pb2.ListJobTasksResponse: - raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") - def retry_job(self, request: workflows_dot_v1_dot_job__pb2.RetryJobRequest, ctx: RequestContext) -> workflows_dot_v1_dot_job__pb2.RetryJobResponse: - raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") - def cancel_job(self, request: workflows_dot_v1_dot_job__pb2.CancelJobRequest, ctx: RequestContext) -> workflows_dot_v1_dot_job__pb2.CancelJobResponse: - raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") - def visualize_job(self, request: workflows_dot_v1_dot_job__pb2.VisualizeJobRequest, ctx: RequestContext) -> workflows_dot_v1_dot_diagram__pb2.Diagram: - raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") - def query_jobs(self, request: workflows_dot_v1_dot_job__pb2.QueryJobsRequest, ctx: RequestContext) -> workflows_dot_v1_dot_job__pb2.QueryJobsResponse: - raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") - def get_job_prototype(self, request: workflows_dot_v1_dot_job__pb2.GetJobPrototypeRequest, ctx: RequestContext) -> workflows_dot_v1_dot_job__pb2.GetJobPrototypeResponse: - raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") - def clone_job(self, request: workflows_dot_v1_dot_job__pb2.CloneJobRequest, ctx: RequestContext) -> workflows_dot_v1_dot_core__pb2.Job: - raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") - def get_job_state_counts(self, request: workflows_dot_v1_dot_job__pb2.GetJobStateCountsRequest, ctx: RequestContext) -> workflows_dot_v1_dot_job__pb2.GetJobStateCountsResponse: - raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") - def get_task_queue_stats(self, request: workflows_dot_v1_dot_job__pb2.GetTaskQueueStatsRequest, ctx: RequestContext) -> workflows_dot_v1_dot_job__pb2.GetTaskQueueStatsResponse: - raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") + def visualize_job(self, request: VisualizeJobRequest, ctx: RequestContext[VisualizeJobRequest, Diagram], /) -> Diagram: + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') + + def query_jobs(self, request: QueryJobsRequest, ctx: RequestContext[QueryJobsRequest, QueryJobsResponse], /) -> QueryJobsResponse: + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') + + def get_job_prototype(self, request: GetJobPrototypeRequest, ctx: RequestContext[GetJobPrototypeRequest, GetJobPrototypeResponse], /) -> GetJobPrototypeResponse: + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') + + def clone_job(self, request: CloneJobRequest, ctx: RequestContext[CloneJobRequest, Job], /) -> Job: + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') + + def get_job_state_counts(self, request: GetJobStateCountsRequest, ctx: RequestContext[GetJobStateCountsRequest, GetJobStateCountsResponse], /) -> GetJobStateCountsResponse: + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') + def get_task_queue_stats(self, request: GetTaskQueueStatsRequest, ctx: RequestContext[GetTaskQueueStatsRequest, GetTaskQueueStatsResponse], /) -> GetTaskQueueStatsResponse: + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') + + @classmethod + def desc(cls) -> DescService: + """Returns the descriptor for this service.""" + return next(s for s in job_pb.desc().services if s.type_name == 'workflows.v1.JobService') class JobServiceWSGIApplication(ConnectWSGIApplication): - def __init__(self, service: JobServiceSync, interceptors: Iterable[InterceptorSync]=(), read_max_bytes: int | None = None, compressions: Iterable[Compression] | None = None, codecs: Iterable[Codec] | None = None) -> None: + def __init__( + self, + service: JobServiceSync, + interceptors: Iterable[InterceptorSync] = (), + read_max_bytes: int | None = DEFAULT_READ_MAX_BYTES, + compressions: Iterable[Compression] | None = None, + codecs: Iterable[Codec] | None = None, + ) -> None: super().__init__( endpoints={ "/workflows.v1.JobService/SubmitJob": EndpointSync.unary( method=MethodInfo( name="SubmitJob", service_name="workflows.v1.JobService", - input=workflows_dot_v1_dot_job__pb2.SubmitJobRequest, - output=workflows_dot_v1_dot_core__pb2.Job, + input=SubmitJobRequest, + output=Job, idempotency_level=IdempotencyLevel.UNKNOWN, ), function=service.submit_job, @@ -450,8 +497,8 @@ def __init__(self, service: JobServiceSync, interceptors: Iterable[InterceptorSy method=MethodInfo( name="GetJob", service_name="workflows.v1.JobService", - input=workflows_dot_v1_dot_job__pb2.GetJobRequest, - output=workflows_dot_v1_dot_core__pb2.Job, + input=GetJobRequest, + output=Job, idempotency_level=IdempotencyLevel.UNKNOWN, ), function=service.get_job, @@ -460,8 +507,8 @@ def __init__(self, service: JobServiceSync, interceptors: Iterable[InterceptorSy method=MethodInfo( name="ListJobTasks", service_name="workflows.v1.JobService", - input=workflows_dot_v1_dot_job__pb2.ListJobTasksRequest, - output=workflows_dot_v1_dot_job__pb2.ListJobTasksResponse, + input=ListJobTasksRequest, + output=ListJobTasksResponse, idempotency_level=IdempotencyLevel.UNKNOWN, ), function=service.list_job_tasks, @@ -470,8 +517,8 @@ def __init__(self, service: JobServiceSync, interceptors: Iterable[InterceptorSy method=MethodInfo( name="RetryJob", service_name="workflows.v1.JobService", - input=workflows_dot_v1_dot_job__pb2.RetryJobRequest, - output=workflows_dot_v1_dot_job__pb2.RetryJobResponse, + input=RetryJobRequest, + output=RetryJobResponse, idempotency_level=IdempotencyLevel.UNKNOWN, ), function=service.retry_job, @@ -480,8 +527,8 @@ def __init__(self, service: JobServiceSync, interceptors: Iterable[InterceptorSy method=MethodInfo( name="CancelJob", service_name="workflows.v1.JobService", - input=workflows_dot_v1_dot_job__pb2.CancelJobRequest, - output=workflows_dot_v1_dot_job__pb2.CancelJobResponse, + input=CancelJobRequest, + output=CancelJobResponse, idempotency_level=IdempotencyLevel.UNKNOWN, ), function=service.cancel_job, @@ -490,8 +537,8 @@ def __init__(self, service: JobServiceSync, interceptors: Iterable[InterceptorSy method=MethodInfo( name="VisualizeJob", service_name="workflows.v1.JobService", - input=workflows_dot_v1_dot_job__pb2.VisualizeJobRequest, - output=workflows_dot_v1_dot_diagram__pb2.Diagram, + input=VisualizeJobRequest, + output=Diagram, idempotency_level=IdempotencyLevel.UNKNOWN, ), function=service.visualize_job, @@ -500,8 +547,8 @@ def __init__(self, service: JobServiceSync, interceptors: Iterable[InterceptorSy method=MethodInfo( name="QueryJobs", service_name="workflows.v1.JobService", - input=workflows_dot_v1_dot_job__pb2.QueryJobsRequest, - output=workflows_dot_v1_dot_job__pb2.QueryJobsResponse, + input=QueryJobsRequest, + output=QueryJobsResponse, idempotency_level=IdempotencyLevel.UNKNOWN, ), function=service.query_jobs, @@ -510,8 +557,8 @@ def __init__(self, service: JobServiceSync, interceptors: Iterable[InterceptorSy method=MethodInfo( name="GetJobPrototype", service_name="workflows.v1.JobService", - input=workflows_dot_v1_dot_job__pb2.GetJobPrototypeRequest, - output=workflows_dot_v1_dot_job__pb2.GetJobPrototypeResponse, + input=GetJobPrototypeRequest, + output=GetJobPrototypeResponse, idempotency_level=IdempotencyLevel.UNKNOWN, ), function=service.get_job_prototype, @@ -520,8 +567,8 @@ def __init__(self, service: JobServiceSync, interceptors: Iterable[InterceptorSy method=MethodInfo( name="CloneJob", service_name="workflows.v1.JobService", - input=workflows_dot_v1_dot_job__pb2.CloneJobRequest, - output=workflows_dot_v1_dot_core__pb2.Job, + input=CloneJobRequest, + output=Job, idempotency_level=IdempotencyLevel.UNKNOWN, ), function=service.clone_job, @@ -530,8 +577,8 @@ def __init__(self, service: JobServiceSync, interceptors: Iterable[InterceptorSy method=MethodInfo( name="GetJobStateCounts", service_name="workflows.v1.JobService", - input=workflows_dot_v1_dot_job__pb2.GetJobStateCountsRequest, - output=workflows_dot_v1_dot_job__pb2.GetJobStateCountsResponse, + input=GetJobStateCountsRequest, + output=GetJobStateCountsResponse, idempotency_level=IdempotencyLevel.UNKNOWN, ), function=service.get_job_state_counts, @@ -540,8 +587,8 @@ def __init__(self, service: JobServiceSync, interceptors: Iterable[InterceptorSy method=MethodInfo( name="GetTaskQueueStats", service_name="workflows.v1.JobService", - input=workflows_dot_v1_dot_job__pb2.GetTaskQueueStatsRequest, - output=workflows_dot_v1_dot_job__pb2.GetTaskQueueStatsResponse, + input=GetTaskQueueStatsRequest, + output=GetTaskQueueStatsResponse, idempotency_level=IdempotencyLevel.UNKNOWN, ), function=service.get_task_queue_stats, @@ -560,220 +607,213 @@ def path(self) -> str: class JobServiceClientSync(ConnectClientSync): + """ + A service for interacting with jobs. + """ def submit_job( self, - request: workflows_dot_v1_dot_job__pb2.SubmitJobRequest, + request: SubmitJobRequest, *, headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, - ) -> workflows_dot_v1_dot_core__pb2.Job: + ) -> Job: return self.execute_unary( request=request, method=MethodInfo( name="SubmitJob", service_name="workflows.v1.JobService", - input=workflows_dot_v1_dot_job__pb2.SubmitJobRequest, - output=workflows_dot_v1_dot_core__pb2.Job, + input=SubmitJobRequest, + output=Job, idempotency_level=IdempotencyLevel.UNKNOWN, ), headers=headers, timeout_ms=timeout_ms, ) - def get_job( self, - request: workflows_dot_v1_dot_job__pb2.GetJobRequest, + request: GetJobRequest, *, headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, - ) -> workflows_dot_v1_dot_core__pb2.Job: + ) -> Job: return self.execute_unary( request=request, method=MethodInfo( name="GetJob", service_name="workflows.v1.JobService", - input=workflows_dot_v1_dot_job__pb2.GetJobRequest, - output=workflows_dot_v1_dot_core__pb2.Job, + input=GetJobRequest, + output=Job, idempotency_level=IdempotencyLevel.UNKNOWN, ), headers=headers, timeout_ms=timeout_ms, ) - def list_job_tasks( self, - request: workflows_dot_v1_dot_job__pb2.ListJobTasksRequest, + request: ListJobTasksRequest, *, headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, - ) -> workflows_dot_v1_dot_job__pb2.ListJobTasksResponse: + ) -> ListJobTasksResponse: return self.execute_unary( request=request, method=MethodInfo( name="ListJobTasks", service_name="workflows.v1.JobService", - input=workflows_dot_v1_dot_job__pb2.ListJobTasksRequest, - output=workflows_dot_v1_dot_job__pb2.ListJobTasksResponse, + input=ListJobTasksRequest, + output=ListJobTasksResponse, idempotency_level=IdempotencyLevel.UNKNOWN, ), headers=headers, timeout_ms=timeout_ms, ) - def retry_job( self, - request: workflows_dot_v1_dot_job__pb2.RetryJobRequest, + request: RetryJobRequest, *, headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, - ) -> workflows_dot_v1_dot_job__pb2.RetryJobResponse: + ) -> RetryJobResponse: return self.execute_unary( request=request, method=MethodInfo( name="RetryJob", service_name="workflows.v1.JobService", - input=workflows_dot_v1_dot_job__pb2.RetryJobRequest, - output=workflows_dot_v1_dot_job__pb2.RetryJobResponse, + input=RetryJobRequest, + output=RetryJobResponse, idempotency_level=IdempotencyLevel.UNKNOWN, ), headers=headers, timeout_ms=timeout_ms, ) - def cancel_job( self, - request: workflows_dot_v1_dot_job__pb2.CancelJobRequest, + request: CancelJobRequest, *, headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, - ) -> workflows_dot_v1_dot_job__pb2.CancelJobResponse: + ) -> CancelJobResponse: return self.execute_unary( request=request, method=MethodInfo( name="CancelJob", service_name="workflows.v1.JobService", - input=workflows_dot_v1_dot_job__pb2.CancelJobRequest, - output=workflows_dot_v1_dot_job__pb2.CancelJobResponse, + input=CancelJobRequest, + output=CancelJobResponse, idempotency_level=IdempotencyLevel.UNKNOWN, ), headers=headers, timeout_ms=timeout_ms, ) - def visualize_job( self, - request: workflows_dot_v1_dot_job__pb2.VisualizeJobRequest, + request: VisualizeJobRequest, *, headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, - ) -> workflows_dot_v1_dot_diagram__pb2.Diagram: + ) -> Diagram: return self.execute_unary( request=request, method=MethodInfo( name="VisualizeJob", service_name="workflows.v1.JobService", - input=workflows_dot_v1_dot_job__pb2.VisualizeJobRequest, - output=workflows_dot_v1_dot_diagram__pb2.Diagram, + input=VisualizeJobRequest, + output=Diagram, idempotency_level=IdempotencyLevel.UNKNOWN, ), headers=headers, timeout_ms=timeout_ms, ) - def query_jobs( self, - request: workflows_dot_v1_dot_job__pb2.QueryJobsRequest, + request: QueryJobsRequest, *, headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, - ) -> workflows_dot_v1_dot_job__pb2.QueryJobsResponse: + ) -> QueryJobsResponse: return self.execute_unary( request=request, method=MethodInfo( name="QueryJobs", service_name="workflows.v1.JobService", - input=workflows_dot_v1_dot_job__pb2.QueryJobsRequest, - output=workflows_dot_v1_dot_job__pb2.QueryJobsResponse, + input=QueryJobsRequest, + output=QueryJobsResponse, idempotency_level=IdempotencyLevel.UNKNOWN, ), headers=headers, timeout_ms=timeout_ms, ) - def get_job_prototype( self, - request: workflows_dot_v1_dot_job__pb2.GetJobPrototypeRequest, + request: GetJobPrototypeRequest, *, headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, - ) -> workflows_dot_v1_dot_job__pb2.GetJobPrototypeResponse: + ) -> GetJobPrototypeResponse: return self.execute_unary( request=request, method=MethodInfo( name="GetJobPrototype", service_name="workflows.v1.JobService", - input=workflows_dot_v1_dot_job__pb2.GetJobPrototypeRequest, - output=workflows_dot_v1_dot_job__pb2.GetJobPrototypeResponse, + input=GetJobPrototypeRequest, + output=GetJobPrototypeResponse, idempotency_level=IdempotencyLevel.UNKNOWN, ), headers=headers, timeout_ms=timeout_ms, ) - def clone_job( self, - request: workflows_dot_v1_dot_job__pb2.CloneJobRequest, + request: CloneJobRequest, *, headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, - ) -> workflows_dot_v1_dot_core__pb2.Job: + ) -> Job: return self.execute_unary( request=request, method=MethodInfo( name="CloneJob", service_name="workflows.v1.JobService", - input=workflows_dot_v1_dot_job__pb2.CloneJobRequest, - output=workflows_dot_v1_dot_core__pb2.Job, + input=CloneJobRequest, + output=Job, idempotency_level=IdempotencyLevel.UNKNOWN, ), headers=headers, timeout_ms=timeout_ms, ) - def get_job_state_counts( self, - request: workflows_dot_v1_dot_job__pb2.GetJobStateCountsRequest, + request: GetJobStateCountsRequest, *, headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, - ) -> workflows_dot_v1_dot_job__pb2.GetJobStateCountsResponse: + ) -> GetJobStateCountsResponse: return self.execute_unary( request=request, method=MethodInfo( name="GetJobStateCounts", service_name="workflows.v1.JobService", - input=workflows_dot_v1_dot_job__pb2.GetJobStateCountsRequest, - output=workflows_dot_v1_dot_job__pb2.GetJobStateCountsResponse, + input=GetJobStateCountsRequest, + output=GetJobStateCountsResponse, idempotency_level=IdempotencyLevel.UNKNOWN, ), headers=headers, timeout_ms=timeout_ms, ) - def get_task_queue_stats( self, - request: workflows_dot_v1_dot_job__pb2.GetTaskQueueStatsRequest, + request: GetTaskQueueStatsRequest, *, headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, - ) -> workflows_dot_v1_dot_job__pb2.GetTaskQueueStatsResponse: + ) -> GetTaskQueueStatsResponse: return self.execute_unary( request=request, method=MethodInfo( name="GetTaskQueueStats", service_name="workflows.v1.JobService", - input=workflows_dot_v1_dot_job__pb2.GetTaskQueueStatsRequest, - output=workflows_dot_v1_dot_job__pb2.GetTaskQueueStatsResponse, + input=GetTaskQueueStatsRequest, + output=GetTaskQueueStatsResponse, idempotency_level=IdempotencyLevel.UNKNOWN, ), headers=headers, diff --git a/tilebox-workflows/tilebox/workflows/v1/job_pb.py b/tilebox-workflows/tilebox/workflows/v1/job_pb.py new file mode 100644 index 0000000..9763564 --- /dev/null +++ b/tilebox-workflows/tilebox/workflows/v1/job_pb.py @@ -0,0 +1,1200 @@ +# Generated from workflows/v1/job.proto. DO NOT EDIT. +# Generated by protoc-gen-py v0.6.0 with parameter "init_files=false,map_imports=datasets/**:tilebox,map_imports=workflows/**:tilebox,map_imports=tilebox/**:_tilebox.proto,map_imports=opentelemetry/**:tilebox.workflows._otel". +# ruff: noqa: PGH004 +# ruff: noqa +# fmt: off + +from __future__ import annotations + +from typing import Literal, NoReturn, TYPE_CHECKING, TypeAlias + +from _tilebox.proto.tilebox.v1 import id_pb, query_pb +from protobuf import Enum, Message +from protobuf._codegen import file_desc +from protobuf.wkt.google.protobuf import duration_pb, timestamp_pb +from tilebox.workflows.v1 import core_pb, diagram_pb + +if TYPE_CHECKING: + from _tilebox.proto.tilebox.v1.id_pb import ID + from _tilebox.proto.tilebox.v1.query_pb import IDInterval, Pagination, SortDirection, TimeInterval + from protobuf import DescFile + from protobuf.wkt.google.protobuf.duration_pb import Duration + from protobuf.wkt.google.protobuf.timestamp_pb import Timestamp + from tilebox.workflows.v1.core_pb import Job, JobState, SingleTaskSubmission, TaskState, TaskSubmissions + from tilebox.workflows.v1.diagram_pb import RenderOptions + + +_SubmitJobRequestFields: TypeAlias = Literal["tasks", "job_name", "trace_parent", "automation_id", "legacy_tasks"] + +class SubmitJobRequest(Message[_SubmitJobRequestFields]): + """ + SubmitJobRequest submits and schedules a job for execution. The job can have multiple root tasks. + + ```proto + message workflows.v1.SubmitJobRequest + ``` + + Attributes: + tasks: + The root tasks for the job. + + ```proto + optional workflows.v1.TaskSubmissions tasks = 5; + ``` + job_name: + The name of the job. + + ```proto + string job_name = 2; + ``` + trace_parent: + Optional. Tracing information for the job. If omitted, the service generates a new trace parent. + This is used to propagate tracing information to the workers that execute the job. + + ```proto + string trace_parent = 3; + ``` + automation_id: + Optional. The ID of the automation that submits this job. + + ```proto + optional tilebox.v1.ID automation_id = 4; + ``` + legacy_tasks: + The root tasks for the job. This field is deprecated and will be removed in the future. Kept around for backwards + compatibility. + + ```proto + repeated workflows.v1.SingleTaskSubmission legacy_tasks = 1 [deprecated = true]; + ``` + """ + + __slots__ = ("tasks", "job_name", "trace_parent", "automation_id", "legacy_tasks") + + if TYPE_CHECKING: + + def __init__( + self, + *, + tasks: TaskSubmissions | None = None, + job_name: str = "", + trace_parent: str = "", + automation_id: ID | None = None, + legacy_tasks: list[SingleTaskSubmission] | None = None, + ) -> None: + pass + + tasks: TaskSubmissions | None + job_name: str + trace_parent: str + automation_id: ID | None + legacy_tasks: list[SingleTaskSubmission] + +_GetJobRequestFields: TypeAlias = Literal["job_id"] + +class GetJobRequest(Message[_GetJobRequestFields]): + """ + GetJobRequest requests details for a job. + + ```proto + message workflows.v1.GetJobRequest + ``` + + Attributes: + job_id: + The ID of the job to get details for. + + ```proto + optional tilebox.v1.ID job_id = 1; + ``` + """ + + __slots__ = ("job_id",) + + if TYPE_CHECKING: + + def __init__( + self, + *, + job_id: ID | None = None, + ) -> None: + pass + + job_id: ID | None + +_TaskSummaryFields: TypeAlias = Literal["id", "parent_id", "display", "state", "submitted_at", "started_at", "stopped_at", "input", "retry_count", "max_retries", "has_children", "cluster_slug", "optional", "workflow_reference"] + +class TaskSummary(Message[_TaskSummaryFields]): + """ + TaskSummary is a compact representation of a task within a job's task tree. + + ```proto + message workflows.v1.TaskSummary + ``` + + Attributes: + id: + The ID of the task. Tasks are ordered by this ID within a sibling collection. + + ```proto + optional tilebox.v1.ID id = 1; + ``` + parent_id: + The ID of the closest non-virtual ancestor task. It is absent for a root task. + + ```proto + optional tilebox.v1.ID parent_id = 2; + ``` + display: + A human-readable representation of the task. + + ```proto + string display = 3; + ``` + state: + The current state of the task. + + ```proto + workflows.v1.TaskState state = 4; + ``` + submitted_at: + The time the task was submitted. + + ```proto + optional google.protobuf.Timestamp submitted_at = 5; + ``` + started_at: + The time the task started running. + + ```proto + optional google.protobuf.Timestamp started_at = 6; + ``` + stopped_at: + The time the task stopped running. + + ```proto + optional google.protobuf.Timestamp stopped_at = 7; + ``` + input: + The serialized input parameters in the format expected by the task. + + ```proto + optional bytes input = 8; + ``` + retry_count: + The number of times the task has been retried. + + ```proto + int64 retry_count = 9; + ``` + max_retries: + The maximum number of times the task may be retried. + + ```proto + int64 max_retries = 10; + ``` + has_children: + Whether the task has children in the user-visible tree. + + ```proto + bool has_children = 11; + ``` + cluster_slug: + The slug of the cluster on which the task is scheduled to run. + + ```proto + string cluster_slug = 12; + ``` + optional: + Whether failure of the task is optional, either directly or through an optional ancestor. + + ```proto + bool optional = 13; + ``` + workflow_reference: + The remote reference of the task's workflow, owner-local or namespace-qualified as in Workflow.reference. + Absent for unscoped tasks or deleted workflows. Omission does not change the task's stored execution scope. + + ```proto + optional string workflow_reference = 14; + ``` + """ + + __slots__ = ("id", "parent_id", "display", "state", "submitted_at", "started_at", "stopped_at", "input", "retry_count", "max_retries", "has_children", "cluster_slug", "optional", "workflow_reference") + + if TYPE_CHECKING: + + def __init__( + self, + *, + id: ID | None = None, + parent_id: ID | None = None, + display: str = "", + state: TaskState | None = None, + submitted_at: Timestamp | None = None, + started_at: Timestamp | None = None, + stopped_at: Timestamp | None = None, + input: bytes | None = None, + retry_count: int = 0, + max_retries: int = 0, + has_children: bool = False, + cluster_slug: str = "", + optional: bool = False, + workflow_reference: str | None = None, + ) -> None: + pass + + id: ID | None + parent_id: ID | None + display: str + state: TaskState + submitted_at: Timestamp | None + started_at: Timestamp | None + stopped_at: Timestamp | None + input: bytes + retry_count: int + max_retries: int + has_children: bool + cluster_slug: str + optional: bool + workflow_reference: str + +_JobTaskChildrenPrefetchFields: TypeAlias = Literal["limit"] + +class JobTaskChildrenPrefetch(Message[_JobTaskChildrenPrefetchFields]): + """ + JobTaskChildrenPrefetch configures the child pages included with the requested task page. + + ```proto + message workflows.v1.JobTaskChildrenPrefetch + ``` + + Attributes: + limit: + The maximum number of user-visible children to return for each task in the requested page. + + ```proto + int64 limit = 1; + ``` + """ + + __slots__ = ("limit",) + + if TYPE_CHECKING: + + def __init__( + self, + *, + limit: int = 0, + ) -> None: + pass + + limit: int + +_ListJobTasksRequestFields: TypeAlias = Literal["job_id", "parent_task_id", "page", "prefetch_children"] + +class ListJobTasksRequest(Message[_ListJobTasksRequestFields]): + """ + ListJobTasksRequest requests one page from a job's task tree. + + ```proto + message workflows.v1.ListJobTasksRequest + ``` + + Attributes: + job_id: + The ID of the job whose tasks to list. + + ```proto + optional tilebox.v1.ID job_id = 1; + ``` + parent_task_id: + The parent whose user-visible children to list. If absent, root tasks are listed. + + ```proto + optional tilebox.v1.ID parent_task_id = 2; + ``` + page: + The pagination parameters for the requested sibling collection. + + ```proto + optional tilebox.v1.Pagination page = 3; + ``` + prefetch_children: + Optional configuration for prefetching one user-visible child page per returned task. + + ```proto + optional workflows.v1.JobTaskChildrenPrefetch prefetch_children = 4; + ``` + """ + + __slots__ = ("job_id", "parent_task_id", "page", "prefetch_children") + + if TYPE_CHECKING: + + def __init__( + self, + *, + job_id: ID | None = None, + parent_task_id: ID | None = None, + page: Pagination | None = None, + prefetch_children: JobTaskChildrenPrefetch | None = None, + ) -> None: + pass + + job_id: ID | None + parent_task_id: ID | None + page: Pagination | None + prefetch_children: JobTaskChildrenPrefetch | None + +_JobTaskPageFields: TypeAlias = Literal["parent_task_id", "tasks", "next_page"] + +class JobTaskPage(Message[_JobTaskPageFields]): + """ + JobTaskPage is one page of a job's root tasks or one parent's user-visible children. + + ```proto + message workflows.v1.JobTaskPage + ``` + + Attributes: + parent_task_id: + The user-visible parent of this sibling collection. It is absent for the root collection. + + ```proto + optional tilebox.v1.ID parent_task_id = 1; + ``` + tasks: + The tasks in this page, ordered by ascending task ID. + + ```proto + repeated workflows.v1.TaskSummary tasks = 2; + ``` + next_page: + The pagination parameters for the next page of this sibling collection. + + ```proto + optional tilebox.v1.Pagination next_page = 3; + ``` + """ + + __slots__ = ("parent_task_id", "tasks", "next_page") + + if TYPE_CHECKING: + + def __init__( + self, + *, + parent_task_id: ID | None = None, + tasks: list[TaskSummary] | None = None, + next_page: Pagination | None = None, + ) -> None: + pass + + parent_task_id: ID | None + tasks: list[TaskSummary] + next_page: Pagination | None + +_ListJobTasksResponseFields: TypeAlias = Literal["page", "prefetched_child_pages"] + +class ListJobTasksResponse(Message[_ListJobTasksResponseFields]): + """ + ListJobTasksResponse contains the requested task page and any prefetched child pages. + + ```proto + message workflows.v1.ListJobTasksResponse + ``` + + Attributes: + page: + The requested page. + + ```proto + optional workflows.v1.JobTaskPage page = 1; + ``` + prefetched_child_pages: + The first user-visible child page for each task in page that has children. + + ```proto + repeated workflows.v1.JobTaskPage prefetched_child_pages = 2; + ``` + """ + + __slots__ = ("page", "prefetched_child_pages") + + if TYPE_CHECKING: + + def __init__( + self, + *, + page: JobTaskPage | None = None, + prefetched_child_pages: list[JobTaskPage] | None = None, + ) -> None: + pass + + page: JobTaskPage | None + prefetched_child_pages: list[JobTaskPage] + +_RetryJobRequestFields: TypeAlias = Literal["job_id"] + +class RetryJobRequest(Message[_RetryJobRequestFields]): + """ + RetryJobRequest requests a retry of a job that has failed. + + ```proto + message workflows.v1.RetryJobRequest + ``` + + Attributes: + job_id: + The job to retry. + + ```proto + optional tilebox.v1.ID job_id = 1; + ``` + """ + + __slots__ = ("job_id",) + + if TYPE_CHECKING: + + def __init__( + self, + *, + job_id: ID | None = None, + ) -> None: + pass + + job_id: ID | None + +_RetryJobResponseFields: TypeAlias = Literal["num_tasks_rescheduled"] + +class RetryJobResponse(Message[_RetryJobResponseFields]): + """ + RetryJobResponse is the response to a RetryJobRequest. + + ```proto + message workflows.v1.RetryJobResponse + ``` + + Attributes: + num_tasks_rescheduled: + The number of tasks that were rescheduled. + + ```proto + int64 num_tasks_rescheduled = 1; + ``` + """ + + __slots__ = ("num_tasks_rescheduled",) + + if TYPE_CHECKING: + + def __init__( + self, + *, + num_tasks_rescheduled: int = 0, + ) -> None: + pass + + num_tasks_rescheduled: int + +_CancelJobRequestFields: TypeAlias = Literal["job_id"] + +class CancelJobRequest(Message[_CancelJobRequestFields]): + """ + CancelJobRequest requests a cancel of a job. + + ```proto + message workflows.v1.CancelJobRequest + ``` + + Attributes: + job_id: + The job to cancel. + + ```proto + optional tilebox.v1.ID job_id = 1; + ``` + """ + + __slots__ = ("job_id",) + + if TYPE_CHECKING: + + def __init__( + self, + *, + job_id: ID | None = None, + ) -> None: + pass + + job_id: ID | None + +_CancelJobResponseFields: TypeAlias = NoReturn + +class CancelJobResponse(Message[_CancelJobResponseFields]): + """ + CancelJobResponse is the response to a CancelJobRequest. + + no fields at the moment + + ```proto + message workflows.v1.CancelJobResponse + ``` + """ + + __slots__ = () + + if TYPE_CHECKING: + + def __init__( + self, + ) -> None: + pass + +_VisualizeJobRequestFields: TypeAlias = Literal["job_id", "render_options", "theme", "include_job_name"] + +class VisualizeJobRequest(Message[_VisualizeJobRequestFields]): + """ + VisualizeJobRequest requests a visualization of a job. + + ```proto + message workflows.v1.VisualizeJobRequest + ``` + + Attributes: + job_id: + The job to visualize. + + ```proto + optional tilebox.v1.ID job_id = 1; + ``` + render_options: + The options for rendering the diagram + + ```proto + optional workflows.v1.RenderOptions render_options = 2; + ``` + theme: + The theme to render the workflow diagram in + + ```proto + workflows.v1.WorkflowDiagramTheme theme = 3; + ``` + include_job_name: + whether to include the job name as title in the diagram + + ```proto + bool include_job_name = 4; + ``` + """ + + __slots__ = ("job_id", "render_options", "theme", "include_job_name") + + if TYPE_CHECKING: + + def __init__( + self, + *, + job_id: ID | None = None, + render_options: RenderOptions | None = None, + theme: WorkflowDiagramTheme | None = None, + include_job_name: bool = False, + ) -> None: + pass + + job_id: ID | None + render_options: RenderOptions | None + theme: WorkflowDiagramTheme + include_job_name: bool + +_QueryFiltersFields: TypeAlias = Literal["time_interval", "id_interval", "automation_ids", "states", "name", "task_states", "cluster_slugs", "workflow_references"] + +class QueryFilters(Message[_QueryFiltersFields]): + """ + QueryFilters contains the filters to apply to a query. + + ```proto + message workflows.v1.QueryFilters + ``` + + Attributes: + time_interval: + Filters by job submission time. If absent or neither bound is set, submission time is not restricted. If only + start_time is set, it is used as the lower bound. If only end_time is set, it is used as the upper bound. If both + are set, jobs between the two bounds are returned. start_exclusive and end_inclusive control whether each bound is + included. + + ```proto + optional tilebox.v1.TimeInterval time_interval = 1; + ``` + id_interval: + Filters by job ID. If absent or neither bound is set, job ID is not restricted. If only start_id is set, it is used + as the lower bound. If only end_id is set, it is used as the upper bound. If both are set, jobs between the two + bounds are returned. start_exclusive and end_inclusive control whether each bound is included. + + ```proto + optional tilebox.v1.IDInterval id_interval = 2; + ``` + automation_ids: + Filter jobs by automations. + + ```proto + repeated tilebox.v1.ID automation_ids = 3; + ``` + states: + Filter jobs by states. + + ```proto + repeated workflows.v1.JobState states = 4 [packed = true]; + ``` + name: + Filter jobs by name. + + ```proto + string name = 5; + ``` + task_states: + Filter jobs by their tasks (whether they have tasks in a certain state). + + ```proto + repeated workflows.v1.TaskState task_states = 6 [packed = true]; + ``` + cluster_slugs: + Filter jobs by their clusters (whether they have tasks on certain clusters). + + ```proto + repeated string cluster_slugs = 7; + ``` + workflow_references: + Filter jobs having at least one task scoped to any of these workflow references. + Empty means no workflow filter. Unscoped tasks never match. + + ```proto + repeated string workflow_references = 8; + ``` + """ + + __slots__ = ("time_interval", "id_interval", "automation_ids", "states", "name", "task_states", "cluster_slugs", "workflow_references") + + if TYPE_CHECKING: + + def __init__( + self, + *, + time_interval: TimeInterval | None = None, + id_interval: IDInterval | None = None, + automation_ids: list[ID] | None = None, + states: list[JobState] | None = None, + name: str = "", + task_states: list[TaskState] | None = None, + cluster_slugs: list[str] | None = None, + workflow_references: list[str] | None = None, + ) -> None: + pass + + time_interval: TimeInterval | None + id_interval: IDInterval | None + automation_ids: list[ID] + states: list[JobState] + name: str + task_states: list[TaskState] + cluster_slugs: list[str] + workflow_references: list[str] + +_QueryJobsRequestFields: TypeAlias = Literal["filters", "page", "sort_direction"] + +class QueryJobsRequest(Message[_QueryJobsRequestFields]): + """ + QueryJobsRequest requests a list of jobs. + + ```proto + message workflows.v1.QueryJobsRequest + ``` + + Attributes: + filters: + Filters to apply to the query. + + ```proto + optional workflows.v1.QueryFilters filters = 1; + ``` + page: + The pagination parameters for this request. + + ```proto + optional tilebox.v1.Pagination page = 2; + ``` + sort_direction: + The sort order for jobs (by submission date). Defaults to descending order (newest jobs first). + + ```proto + tilebox.v1.SortDirection sort_direction = 3; + ``` + """ + + __slots__ = ("filters", "page", "sort_direction") + + if TYPE_CHECKING: + + def __init__( + self, + *, + filters: QueryFilters | None = None, + page: Pagination | None = None, + sort_direction: SortDirection | None = None, + ) -> None: + pass + + filters: QueryFilters | None + page: Pagination | None + sort_direction: SortDirection + +_QueryJobsResponseFields: TypeAlias = Literal["jobs", "next_page"] + +class QueryJobsResponse(Message[_QueryJobsResponseFields]): + """ + A list of jobs. + + ```proto + message workflows.v1.QueryJobsResponse + ``` + + Attributes: + jobs: + The jobs. + + ```proto + repeated workflows.v1.Job jobs = 1; + ``` + next_page: + The pagination parameters for the next page. + + ```proto + optional tilebox.v1.Pagination next_page = 3; + ``` + """ + + __slots__ = ("jobs", "next_page") + + if TYPE_CHECKING: + + def __init__( + self, + *, + jobs: list[Job] | None = None, + next_page: Pagination | None = None, + ) -> None: + pass + + jobs: list[Job] + next_page: Pagination | None + +_GetJobPrototypeRequestFields: TypeAlias = Literal["job_id"] + +class GetJobPrototypeRequest(Message[_GetJobPrototypeRequestFields]): + """ + GetJobPrototypeRequest requests a clone prototype of a job. + + ```proto + message workflows.v1.GetJobPrototypeRequest + ``` + + Attributes: + job_id: + The ID of the job to get a clone prototype for. + + ```proto + optional tilebox.v1.ID job_id = 1; + ``` + """ + + __slots__ = ("job_id",) + + if TYPE_CHECKING: + + def __init__( + self, + *, + job_id: ID | None = None, + ) -> None: + pass + + job_id: ID | None + +_GetJobPrototypeResponseFields: TypeAlias = Literal["root_tasks", "job_name"] + +class GetJobPrototypeResponse(Message[_GetJobPrototypeResponseFields]): + """ + A clone prototype of a job. + + ```proto + message workflows.v1.GetJobPrototypeResponse + ``` + + Attributes: + root_tasks: + The root tasks of the job. + + ```proto + repeated workflows.v1.SingleTaskSubmission root_tasks = 1; + ``` + job_name: + The name of the job. + + ```proto + string job_name = 2; + ``` + """ + + __slots__ = ("root_tasks", "job_name") + + if TYPE_CHECKING: + + def __init__( + self, + *, + root_tasks: list[SingleTaskSubmission] | None = None, + job_name: str = "", + ) -> None: + pass + + root_tasks: list[SingleTaskSubmission] + job_name: str + +_CloneJobRequestFields: TypeAlias = Literal["job_id", "root_tasks_overrides", "job_name"] + +class CloneJobRequest(Message[_CloneJobRequestFields]): + """ + CloneJobRequest requests a clone of a job. + + ```proto + message workflows.v1.CloneJobRequest + ``` + + Attributes: + job_id: + The ID of the job to clone. + + ```proto + optional tilebox.v1.ID job_id = 1; + ``` + root_tasks_overrides: + The updated root tasks of the job. + + ```proto + repeated workflows.v1.SingleTaskSubmission root_tasks_overrides = 2; + ``` + job_name: + The name of the job. + + ```proto + string job_name = 3; + ``` + """ + + __slots__ = ("job_id", "root_tasks_overrides", "job_name") + + if TYPE_CHECKING: + + def __init__( + self, + *, + job_id: ID | None = None, + root_tasks_overrides: list[SingleTaskSubmission] | None = None, + job_name: str = "", + ) -> None: + pass + + job_id: ID | None + root_tasks_overrides: list[SingleTaskSubmission] + job_name: str + +_GetJobStateCountsRequestFields: TypeAlias = Literal["time_interval"] + +class GetJobStateCountsRequest(Message[_GetJobStateCountsRequestFields]): + """ + GetJobStateCountsRequest requests counts of jobs by their state, optionally filtered by submission time. + + ```proto + message workflows.v1.GetJobStateCountsRequest + ``` + + Attributes: + time_interval: + Filters by job submission time. If absent or neither bound is set, submission time is not restricted. If only + start_time is set, it is used as the lower bound. If only end_time is set, it is used as the upper bound. If both + are set, jobs between the two bounds are counted. start_exclusive and end_inclusive control whether each bound is + included. + + ```proto + optional tilebox.v1.TimeInterval time_interval = 1; + ``` + """ + + __slots__ = ("time_interval",) + + if TYPE_CHECKING: + + def __init__( + self, + *, + time_interval: TimeInterval | None = None, + ) -> None: + pass + + time_interval: TimeInterval | None + +_GetJobStateCountsResponseFields: TypeAlias = Literal["state_counts", "total_jobs"] + +class GetJobStateCountsResponse(Message[_GetJobStateCountsResponseFields]): + """ + GetJobStateCountsResponse is the response to a GetJobStateCountsRequest, containing counts of jobs by their state. + + ```proto + message workflows.v1.GetJobStateCountsResponse + ``` + + Attributes: + state_counts: + ```proto + repeated workflows.v1.JobStateCount state_counts = 1; + ``` + total_jobs: + ```proto + int64 total_jobs = 2; + ``` + """ + + __slots__ = ("state_counts", "total_jobs") + + if TYPE_CHECKING: + + def __init__( + self, + *, + state_counts: list[JobStateCount] | None = None, + total_jobs: int = 0, + ) -> None: + pass + + state_counts: list[JobStateCount] + total_jobs: int + +_JobStateCountFields: TypeAlias = Literal["state", "count"] + +class JobStateCount(Message[_JobStateCountFields]): + """ + JobStateCount is a message that represents the count of jobs for a specific state. + + ```proto + message workflows.v1.JobStateCount + ``` + + Attributes: + state: + ```proto + workflows.v1.JobState state = 1; + ``` + count: + ```proto + uint64 count = 2; + ``` + """ + + __slots__ = ("state", "count") + + if TYPE_CHECKING: + + def __init__( + self, + *, + state: JobState | None = None, + count: int = 0, + ) -> None: + pass + + state: JobState + count: int + +_GetTaskQueueStatsRequestFields: TypeAlias = Literal["job_time_interval", "job_max_age"] + +class GetTaskQueueStatsRequest(Message[_GetTaskQueueStatsRequestFields]): + """ + GetTaskQueueStatsRequest requests statistics about jobs in the task queue, filtered by submission time. + + ```proto + message workflows.v1.GetTaskQueueStatsRequest + ``` + + Attributes: + job_time_interval: + Filters by job submission time. If absent or neither bound is set, submission time is not restricted. If only + start_time is set, it is used as the lower bound. If only end_time is set, it is used as the upper bound. If both + are set, jobs between the two bounds are considered. start_exclusive and end_inclusive control whether each bound + is included. + + ```proto + optional tilebox.v1.TimeInterval job_time_interval = 1; + ``` + job_max_age: + Filters to jobs submitted within this duration before the current time. + + ```proto + optional google.protobuf.Duration job_max_age = 2; + ``` + """ + + __slots__ = ("job_time_interval", "job_max_age") + + if TYPE_CHECKING: + + def __init__( + self, + *, + job_time_interval: TimeInterval | None = None, + job_max_age: Duration | None = None, + ) -> None: + pass + + job_time_interval: TimeInterval | None + job_max_age: Duration | None + +_GetTaskQueueStatsResponseFields: TypeAlias = Literal["waiting_jobs", "running_jobs", "queued_tasks", "oldest_waiting_job"] + +class GetTaskQueueStatsResponse(Message[_GetTaskQueueStatsResponseFields]): + """ + GetTaskQueueStatsResponse represents statistics about jobs and tasks in the task queue. + + ```proto + message workflows.v1.GetTaskQueueStatsResponse + ``` + + Attributes: + waiting_jobs: + Jobs in SUBMITTED or STARTED. + + ```proto + uint64 waiting_jobs = 1; + ``` + running_jobs: + Jobs in RUNNING. + + ```proto + uint64 running_jobs = 2; + ``` + queued_tasks: + QUEUED tasks across all jobs in SUBMITTED, STARTED, or RUNNING state. + + ```proto + uint64 queued_tasks = 3; + ``` + oldest_waiting_job: + The oldest job in the requested interval in SUBMITTED or STARTED state. + Will have the id, name, and submitted_at fields set. If there are no waiting jobs, this field will be absent. + + ```proto + optional workflows.v1.Job oldest_waiting_job = 4; + ``` + """ + + __slots__ = ("waiting_jobs", "running_jobs", "queued_tasks", "oldest_waiting_job") + + if TYPE_CHECKING: + + def __init__( + self, + *, + waiting_jobs: int = 0, + running_jobs: int = 0, + queued_tasks: int = 0, + oldest_waiting_job: Job | None = None, + ) -> None: + pass + + waiting_jobs: int + running_jobs: int + queued_tasks: int + oldest_waiting_job: Job | None + +class WorkflowDiagramTheme(Enum): + """ + The theme in which to render a workflow diagram + + ```proto + enum workflows.v1.WorkflowDiagramTheme + ``` + + Attributes: + UNSPECIFIED: + ```proto + WORKFLOW_DIAGRAM_THEME_UNSPECIFIED = 0 + ``` + LIGHT: + ```proto + WORKFLOW_DIAGRAM_THEME_LIGHT = 1 + ``` + DARK: + ```proto + WORKFLOW_DIAGRAM_THEME_DARK = 2 + ``` + CONSOLE_LIGHT: + ```proto + WORKFLOW_DIAGRAM_THEME_CONSOLE_LIGHT = 3 + ``` + CONSOLE_DARK: + ```proto + WORKFLOW_DIAGRAM_THEME_CONSOLE_DARK = 4 + ``` + """ + + UNSPECIFIED = 0 + LIGHT = 1 + DARK = 2 + CONSOLE_LIGHT = 3 + CONSOLE_DARK = 4 + + +_DESC = file_desc( + b'\n\x16workflows/v1/job.proto\x12\x0cworkflows.v1\x1a\x1egoogle/protobuf/duration.proto\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\x13tilebox/v1/id.proto\x1a\x16tilebox/v1/query.proto\x1a\x17workflows/v1/core.proto\x1a\x1aworkflows/v1/diagram.proto"\x85\x02\n\x10SubmitJobRequest\x123\n\x05tasks\x18\x05 \x01(\x0b2\x1d.workflows.v1.TaskSubmissionsR\x05tasks\x12\x19\n\x08job_name\x18\x02 \x01(\tR\x07jobName\x12!\n\x0ctrace_parent\x18\x03 \x01(\tR\x0btraceParent\x123\n\rautomation_id\x18\x04 \x01(\x0b2\x0e.tilebox.v1.IDR\x0cautomationId\x12I\n\x0clegacy_tasks\x18\x01 \x03(\x0b2".workflows.v1.SingleTaskSubmissionR\x0blegacyTasksB\x02\x18\x01"6\n\rGetJobRequest\x12%\n\x06job_id\x18\x01 \x01(\x0b2\x0e.tilebox.v1.IDR\x05jobId"\xd6\x04\n\x0bTaskSummary\x12\x1e\n\x02id\x18\x01 \x01(\x0b2\x0e.tilebox.v1.IDR\x02id\x122\n\tparent_id\x18\x02 \x01(\x0b2\x0e.tilebox.v1.IDR\x08parentIdB\x05\xaa\x01\x02\x08\x01\x12\x18\n\x07display\x18\x03 \x01(\tR\x07display\x12-\n\x05state\x18\x04 \x01(\x0e2\x17.workflows.v1.TaskStateR\x05state\x12=\n\x0csubmitted_at\x18\x05 \x01(\x0b2\x1a.google.protobuf.TimestampR\x0bsubmittedAt\x129\n\nstarted_at\x18\x06 \x01(\x0b2\x1a.google.protobuf.TimestampR\tstartedAt\x129\n\nstopped_at\x18\x07 \x01(\x0b2\x1a.google.protobuf.TimestampR\tstoppedAt\x12\x1b\n\x05input\x18\x08 \x01(\x0cR\x05inputB\x05\xaa\x01\x02\x08\x01\x12\x1f\n\x0bretry_count\x18\t \x01(\x03R\nretryCount\x12\x1f\n\x0bmax_retries\x18\n \x01(\x03R\nmaxRetries\x12!\n\x0chas_children\x18\x0b \x01(\x08R\x0bhasChildren\x12!\n\x0ccluster_slug\x18\x0c \x01(\tR\x0bclusterSlug\x12\x1a\n\x08optional\x18\r \x01(\x08R\x08optional\x124\n\x12workflow_reference\x18\x0e \x01(\tR\x11workflowReferenceB\x05\xaa\x01\x02\x08\x01"/\n\x17JobTaskChildrenPrefetch\x12\x14\n\x05limit\x18\x01 \x01(\x03R\x05limit"\x87\x02\n\x13ListJobTasksRequest\x12%\n\x06job_id\x18\x01 \x01(\x0b2\x0e.tilebox.v1.IDR\x05jobId\x12;\n\x0eparent_task_id\x18\x02 \x01(\x0b2\x0e.tilebox.v1.IDR\x0cparentTaskIdB\x05\xaa\x01\x02\x08\x01\x121\n\x04page\x18\x03 \x01(\x0b2\x16.tilebox.v1.PaginationR\x04pageB\x05\xaa\x01\x02\x08\x01\x12Y\n\x11prefetch_children\x18\x04 \x01(\x0b2%.workflows.v1.JobTaskChildrenPrefetchR\x10prefetchChildrenB\x05\xaa\x01\x02\x08\x01"\xb7\x01\n\x0bJobTaskPage\x12;\n\x0eparent_task_id\x18\x01 \x01(\x0b2\x0e.tilebox.v1.IDR\x0cparentTaskIdB\x05\xaa\x01\x02\x08\x01\x12/\n\x05tasks\x18\x02 \x03(\x0b2\x19.workflows.v1.TaskSummaryR\x05tasks\x12:\n\tnext_page\x18\x03 \x01(\x0b2\x16.tilebox.v1.PaginationR\x08nextPageB\x05\xaa\x01\x02\x08\x01"\x96\x01\n\x14ListJobTasksResponse\x12-\n\x04page\x18\x01 \x01(\x0b2\x19.workflows.v1.JobTaskPageR\x04page\x12O\n\x16prefetched_child_pages\x18\x02 \x03(\x0b2\x19.workflows.v1.JobTaskPageR\x14prefetchedChildPages"8\n\x0fRetryJobRequest\x12%\n\x06job_id\x18\x01 \x01(\x0b2\x0e.tilebox.v1.IDR\x05jobId"F\n\x10RetryJobResponse\x122\n\x15num_tasks_rescheduled\x18\x01 \x01(\x03R\x13numTasksRescheduled"9\n\x10CancelJobRequest\x12%\n\x06job_id\x18\x01 \x01(\x0b2\x0e.tilebox.v1.IDR\x05jobId"\x13\n\x11CancelJobResponse"\xe4\x01\n\x13VisualizeJobRequest\x12%\n\x06job_id\x18\x01 \x01(\x0b2\x0e.tilebox.v1.IDR\x05jobId\x12B\n\x0erender_options\x18\x02 \x01(\x0b2\x1b.workflows.v1.RenderOptionsR\rrenderOptions\x128\n\x05theme\x18\x03 \x01(\x0e2".workflows.v1.WorkflowDiagramThemeR\x05theme\x12(\n\x10include_job_name\x18\x04 \x01(\x08R\x0eincludeJobName"\x91\x03\n\x0cQueryFilters\x12=\n\rtime_interval\x18\x01 \x01(\x0b2\x18.tilebox.v1.TimeIntervalR\x0ctimeInterval\x127\n\x0bid_interval\x18\x02 \x01(\x0b2\x16.tilebox.v1.IDIntervalR\nidInterval\x125\n\x0eautomation_ids\x18\x03 \x03(\x0b2\x0e.tilebox.v1.IDR\rautomationIds\x12.\n\x06states\x18\x04 \x03(\x0e2\x16.workflows.v1.JobStateR\x06states\x12\x12\n\x04name\x18\x05 \x01(\tR\x04name\x128\n\x0btask_states\x18\x06 \x03(\x0e2\x17.workflows.v1.TaskStateR\ntaskStates\x12#\n\rcluster_slugs\x18\x07 \x03(\tR\x0cclusterSlugs\x12/\n\x13workflow_references\x18\x08 \x03(\tR\x12workflowReferences"\xbd\x01\n\x10QueryJobsRequest\x124\n\x07filters\x18\x01 \x01(\x0b2\x1a.workflows.v1.QueryFiltersR\x07filters\x121\n\x04page\x18\x02 \x01(\x0b2\x16.tilebox.v1.PaginationR\x04pageB\x05\xaa\x01\x02\x08\x01\x12@\n\x0esort_direction\x18\x03 \x01(\x0e2\x19.tilebox.v1.SortDirectionR\rsortDirection"v\n\x11QueryJobsResponse\x12%\n\x04jobs\x18\x01 \x03(\x0b2\x11.workflows.v1.JobR\x04jobs\x12:\n\tnext_page\x18\x03 \x01(\x0b2\x16.tilebox.v1.PaginationR\x08nextPageB\x05\xaa\x01\x02\x08\x01"?\n\x16GetJobPrototypeRequest\x12%\n\x06job_id\x18\x01 \x01(\x0b2\x0e.tilebox.v1.IDR\x05jobId"w\n\x17GetJobPrototypeResponse\x12A\n\nroot_tasks\x18\x01 \x03(\x0b2".workflows.v1.SingleTaskSubmissionR\trootTasks\x12\x19\n\x08job_name\x18\x02 \x01(\tR\x07jobName"\xa9\x01\n\x0fCloneJobRequest\x12%\n\x06job_id\x18\x01 \x01(\x0b2\x0e.tilebox.v1.IDR\x05jobId\x12T\n\x14root_tasks_overrides\x18\x02 \x03(\x0b2".workflows.v1.SingleTaskSubmissionR\x12rootTasksOverrides\x12\x19\n\x08job_name\x18\x03 \x01(\tR\x07jobName"Y\n\x18GetJobStateCountsRequest\x12=\n\rtime_interval\x18\x01 \x01(\x0b2\x18.tilebox.v1.TimeIntervalR\x0ctimeInterval"z\n\x19GetJobStateCountsResponse\x12>\n\x0cstate_counts\x18\x01 \x03(\x0b2\x1b.workflows.v1.JobStateCountR\x0bstateCounts\x12\x1d\n\ntotal_jobs\x18\x02 \x01(\x03R\ttotalJobs"S\n\rJobStateCount\x12,\n\x05state\x18\x01 \x01(\x0e2\x16.workflows.v1.JobStateR\x05state\x12\x14\n\x05count\x18\x02 \x01(\x04R\x05count"\x9b\x01\n\x18GetTaskQueueStatsRequest\x12D\n\x11job_time_interval\x18\x01 \x01(\x0b2\x18.tilebox.v1.TimeIntervalR\x0fjobTimeInterval\x129\n\x0bjob_max_age\x18\x02 \x01(\x0b2\x19.google.protobuf.DurationR\tjobMaxAge"\xc5\x01\n\x19GetTaskQueueStatsResponse\x12!\n\x0cwaiting_jobs\x18\x01 \x01(\x04R\x0bwaitingJobs\x12!\n\x0crunning_jobs\x18\x02 \x01(\x04R\x0brunningJobs\x12!\n\x0cqueued_tasks\x18\x03 \x01(\x04R\x0bqueuedTasks\x12?\n\x12oldest_waiting_job\x18\x04 \x01(\x0b2\x11.workflows.v1.JobR\x10oldestWaitingJob*\xd4\x01\n\x14WorkflowDiagramTheme\x12&\n"WORKFLOW_DIAGRAM_THEME_UNSPECIFIED\x10\x00\x12 \n\x1cWORKFLOW_DIAGRAM_THEME_LIGHT\x10\x01\x12\x1f\n\x1bWORKFLOW_DIAGRAM_THEME_DARK\x10\x02\x12(\n$WORKFLOW_DIAGRAM_THEME_CONSOLE_LIGHT\x10\x03\x12\'\n#WORKFLOW_DIAGRAM_THEME_CONSOLE_DARK\x10\x042\xf8\x06\n\nJobService\x12>\n\tSubmitJob\x12\x1e.workflows.v1.SubmitJobRequest\x1a\x11.workflows.v1.Job\x128\n\x06GetJob\x12\x1b.workflows.v1.GetJobRequest\x1a\x11.workflows.v1.Job\x12U\n\x0cListJobTasks\x12!.workflows.v1.ListJobTasksRequest\x1a".workflows.v1.ListJobTasksResponse\x12I\n\x08RetryJob\x12\x1d.workflows.v1.RetryJobRequest\x1a\x1e.workflows.v1.RetryJobResponse\x12L\n\tCancelJob\x12\x1e.workflows.v1.CancelJobRequest\x1a\x1f.workflows.v1.CancelJobResponse\x12H\n\x0cVisualizeJob\x12!.workflows.v1.VisualizeJobRequest\x1a\x15.workflows.v1.Diagram\x12L\n\tQueryJobs\x12\x1e.workflows.v1.QueryJobsRequest\x1a\x1f.workflows.v1.QueryJobsResponse\x12^\n\x0fGetJobPrototype\x12$.workflows.v1.GetJobPrototypeRequest\x1a%.workflows.v1.GetJobPrototypeResponse\x12<\n\x08CloneJob\x12\x1d.workflows.v1.CloneJobRequest\x1a\x11.workflows.v1.Job\x12d\n\x11GetJobStateCounts\x12&.workflows.v1.GetJobStateCountsRequest\x1a\'.workflows.v1.GetJobStateCountsResponse\x12d\n\x11GetTaskQueueStats\x12&.workflows.v1.GetTaskQueueStatsRequest\x1a\'.workflows.v1.GetTaskQueueStatsResponseB\x05\x92\x03\x02\x08\x02b\x08editionsp\xe8\x07', + [ + duration_pb.desc(), + timestamp_pb.desc(), + id_pb.desc(), + query_pb.desc(), + core_pb.desc(), + diagram_pb.desc(), + ], + { + "SubmitJobRequest": SubmitJobRequest, + "GetJobRequest": GetJobRequest, + "TaskSummary": TaskSummary, + "JobTaskChildrenPrefetch": JobTaskChildrenPrefetch, + "ListJobTasksRequest": ListJobTasksRequest, + "JobTaskPage": JobTaskPage, + "ListJobTasksResponse": ListJobTasksResponse, + "RetryJobRequest": RetryJobRequest, + "RetryJobResponse": RetryJobResponse, + "CancelJobRequest": CancelJobRequest, + "CancelJobResponse": CancelJobResponse, + "VisualizeJobRequest": VisualizeJobRequest, + "QueryFilters": QueryFilters, + "QueryJobsRequest": QueryJobsRequest, + "QueryJobsResponse": QueryJobsResponse, + "GetJobPrototypeRequest": GetJobPrototypeRequest, + "GetJobPrototypeResponse": GetJobPrototypeResponse, + "CloneJobRequest": CloneJobRequest, + "GetJobStateCountsRequest": GetJobStateCountsRequest, + "GetJobStateCountsResponse": GetJobStateCountsResponse, + "JobStateCount": JobStateCount, + "GetTaskQueueStatsRequest": GetTaskQueueStatsRequest, + "GetTaskQueueStatsResponse": GetTaskQueueStatsResponse, + "WorkflowDiagramTheme": WorkflowDiagramTheme, + }, +) + + +def desc() -> DescFile: + """Returns the descriptor for the file `workflows/v1/job.proto`.""" + return _DESC diff --git a/tilebox-workflows/tilebox/workflows/workflows/v1/storage_location_connect.py b/tilebox-workflows/tilebox/workflows/v1/storage_location_connect.py similarity index 55% rename from tilebox-workflows/tilebox/workflows/workflows/v1/storage_location_connect.py rename to tilebox-workflows/tilebox/workflows/v1/storage_location_connect.py index 71defb2..da187bd 100644 --- a/tilebox-workflows/tilebox/workflows/workflows/v1/storage_location_connect.py +++ b/tilebox-workflows/tilebox/workflows/v1/storage_location_connect.py @@ -1,61 +1,119 @@ -# -*- coding: utf-8 -*- -# Generated by https://github.com/connectrpc/connect-python. DO NOT EDIT! -# source: workflows/v1/storage_location.proto +# Generated from workflows/v1/storage_location.proto. DO NOT EDIT. +# Generated by protoc-gen-connectrpc-py v0.12.1 with parameter "map_imports=datasets/**:tilebox,map_imports=workflows/**:tilebox,map_imports=tilebox/**:_tilebox.proto,map_imports=opentelemetry/**:tilebox.workflows._otel". +# ruff: noqa: PGH004 +# ruff: noqa +# fmt: off -from collections.abc import AsyncGenerator, AsyncIterator, Iterable, Iterator, Mapping -from typing import Protocol +from __future__ import annotations +from typing import Protocol, TYPE_CHECKING + +from _tilebox.proto.tilebox.v1.id_pb import ID from connectrpc.client import ConnectClient, ConnectClientSync from connectrpc.code import Code -from connectrpc.codec import Codec -from connectrpc.compression import Compression from connectrpc.errors import ConnectError -from connectrpc.interceptor import Interceptor, InterceptorSync from connectrpc.method import IdempotencyLevel, MethodInfo -from connectrpc.request import Headers, RequestContext -from connectrpc.server import ConnectASGIApplication, ConnectWSGIApplication, Endpoint, EndpointSync -import google.protobuf.empty_pb2 as google_dot_protobuf_dot_empty__pb2 -import tilebox.datasets.tilebox.v1.id_pb2 as tilebox_dot_v1_dot_id__pb2 -import tilebox.workflows.workflows.v1.storage_location_pb2 as workflows_dot_v1_dot_storage__location__pb2 - - -class StorageLocationService(Protocol): - async def list_storage_locations(self, request: google_dot_protobuf_dot_empty__pb2.Empty, ctx: RequestContext) -> workflows_dot_v1_dot_storage__location__pb2.StorageLocations: - raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") - - async def get_storage_location(self, request: tilebox_dot_v1_dot_id__pb2.ID, ctx: RequestContext) -> workflows_dot_v1_dot_storage__location__pb2.StorageLocation: - raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") - - async def create_storage_location(self, request: workflows_dot_v1_dot_storage__location__pb2.CreateStorageLocationRequest, ctx: RequestContext) -> workflows_dot_v1_dot_storage__location__pb2.StorageLocation: - raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") - - async def update_storage_location(self, request: workflows_dot_v1_dot_storage__location__pb2.UpdateStorageLocationRequest, ctx: RequestContext) -> workflows_dot_v1_dot_storage__location__pb2.StorageLocation: - raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") - - async def delete_storage_location(self, request: tilebox_dot_v1_dot_id__pb2.ID, ctx: RequestContext) -> google_dot_protobuf_dot_empty__pb2.Empty: - raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") +from connectrpc.server import ConnectASGIApplication, ConnectWSGIApplication, DEFAULT_READ_MAX_BYTES, Endpoint, EndpointSync +from protobuf import DescService +from protobuf.wkt.google.protobuf.empty_pb import Empty +from tilebox.workflows.v1 import storage_location_pb +from tilebox.workflows.v1.storage_location_pb import CreateStorageLocationRequest, CreateStorageSubscriptionRequest, ListStorageSubscriptionEventsRequest, ListStorageSubscriptionsRequest, NotifyObjectCreatedRequest, StorageLocation, StorageLocations, StorageSubscription, StorageSubscriptionEvents, StorageSubscriptions, UpdateStorageLocationRequest - async def create_storage_subscription(self, request: workflows_dot_v1_dot_storage__location__pb2.CreateStorageSubscriptionRequest, ctx: RequestContext) -> workflows_dot_v1_dot_storage__location__pb2.StorageSubscription: - raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") +if TYPE_CHECKING: + from collections.abc import AsyncGenerator, Iterable, Mapping - async def list_storage_subscriptions(self, request: workflows_dot_v1_dot_storage__location__pb2.ListStorageSubscriptionsRequest, ctx: RequestContext) -> workflows_dot_v1_dot_storage__location__pb2.StorageSubscriptions: - raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") + from connectrpc.codec import Codec + from connectrpc.compression import Compression + from connectrpc.interceptor import Interceptor, InterceptorSync + from connectrpc.request import Headers, RequestContext - async def get_storage_subscription(self, request: tilebox_dot_v1_dot_id__pb2.ID, ctx: RequestContext) -> workflows_dot_v1_dot_storage__location__pb2.StorageSubscription: - raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") - - async def delete_storage_subscription(self, request: tilebox_dot_v1_dot_id__pb2.ID, ctx: RequestContext) -> google_dot_protobuf_dot_empty__pb2.Empty: - raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") - - async def list_storage_subscription_events(self, request: workflows_dot_v1_dot_storage__location__pb2.ListStorageSubscriptionEventsRequest, ctx: RequestContext) -> workflows_dot_v1_dot_storage__location__pb2.StorageSubscriptionEvents: - raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") - - async def notify_object_created(self, request: workflows_dot_v1_dot_storage__location__pb2.NotifyObjectCreatedRequest, ctx: RequestContext) -> google_dot_protobuf_dot_empty__pb2.Empty: - raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") +class StorageLocationService(Protocol): + """ + StorageLocationService manages storage locations, notification subscriptions, and received events. + """ + async def list_storage_locations(self, request: Empty, ctx: RequestContext[Empty, StorageLocations], /) -> StorageLocations: + """ + ListStorageLocations lists storage locations available for automation triggers. + """ + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') + + async def get_storage_location(self, request: ID, ctx: RequestContext[ID, StorageLocation], /) -> StorageLocation: + """ + GetStorageLocation gets a storage location by its ID. + """ + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') + + async def create_storage_location(self, request: CreateStorageLocationRequest, ctx: RequestContext[CreateStorageLocationRequest, StorageLocation], /) -> StorageLocation: + """ + CreateStorageLocation registers an existing bucket, container, or directory for automation triggers. + """ + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') + + async def update_storage_location(self, request: UpdateStorageLocationRequest, ctx: RequestContext[UpdateStorageLocationRequest, StorageLocation], /) -> StorageLocation: + """ + UpdateStorageLocation changes the display name of a storage location. + """ + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') + + async def delete_storage_location(self, request: ID, ctx: RequestContext[ID, Empty], /) -> Empty: + """ + DeleteStorageLocation removes the location and its subscriptions from Tilebox without deleting cloud resources. + """ + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') + + async def create_storage_subscription(self, request: CreateStorageSubscriptionRequest, ctx: RequestContext[CreateStorageSubscriptionRequest, StorageSubscription], /) -> StorageSubscription: + """ + CreateStorageSubscription registers notification delivery and returns the provider setup values. + """ + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') + + async def list_storage_subscriptions(self, request: ListStorageSubscriptionsRequest, ctx: RequestContext[ListStorageSubscriptionsRequest, StorageSubscriptions], /) -> StorageSubscriptions: + """ + ListStorageSubscriptions lists notification subscriptions for a storage location. + """ + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') + + async def get_storage_subscription(self, request: ID, ctx: RequestContext[ID, StorageSubscription], /) -> StorageSubscription: + """ + GetStorageSubscription gets a subscription by its ID. + """ + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') + + async def delete_storage_subscription(self, request: ID, ctx: RequestContext[ID, Empty], /) -> Empty: + """ + DeleteStorageSubscription stops accepting notifications at its endpoint without deleting cloud resources. + """ + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') + + async def list_storage_subscription_events(self, request: ListStorageSubscriptionEventsRequest, ctx: RequestContext[ListStorageSubscriptionEventsRequest, StorageSubscriptionEvents], /) -> StorageSubscriptionEvents: + """ + ListStorageSubscriptionEvents lists received object notifications and the jobs they triggered. + """ + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') + + async def notify_object_created(self, request: NotifyObjectCreatedRequest, ctx: RequestContext[NotifyObjectCreatedRequest, Empty], /) -> Empty: + """ + NotifyObjectCreated delivers a storage event of a local storage location. Typically called by the Tilebox CLI + during the `tilebox storage-location watch` command. The subscription must be of type TILEBOX_CLI. + """ + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') + + @classmethod + def desc(cls) -> DescService: + """Returns the descriptor for this service.""" + return next(s for s in storage_location_pb.desc().services if s.type_name == 'workflows.v1.StorageLocationService') class StorageLocationServiceASGIApplication(ConnectASGIApplication[StorageLocationService]): - def __init__(self, service: StorageLocationService | AsyncGenerator[StorageLocationService], *, interceptors: Iterable[Interceptor]=(), read_max_bytes: int | None = None, compressions: Iterable[Compression] | None = None, codecs: Iterable[Codec] | None = None) -> None: + def __init__( + self, + service: StorageLocationService | AsyncGenerator[StorageLocationService], + *, + interceptors: Iterable[Interceptor] = (), + read_max_bytes: int | None = DEFAULT_READ_MAX_BYTES, + compressions: Iterable[Compression] | None = None, + codecs: Iterable[Codec] | None = None, + ) -> None: super().__init__( service=service, endpoints=lambda svc: { @@ -63,8 +121,8 @@ def __init__(self, service: StorageLocationService | AsyncGenerator[StorageLocat method=MethodInfo( name="ListStorageLocations", service_name="workflows.v1.StorageLocationService", - input=google_dot_protobuf_dot_empty__pb2.Empty, - output=workflows_dot_v1_dot_storage__location__pb2.StorageLocations, + input=Empty, + output=StorageLocations, idempotency_level=IdempotencyLevel.UNKNOWN, ), function=svc.list_storage_locations, @@ -73,8 +131,8 @@ def __init__(self, service: StorageLocationService | AsyncGenerator[StorageLocat method=MethodInfo( name="GetStorageLocation", service_name="workflows.v1.StorageLocationService", - input=tilebox_dot_v1_dot_id__pb2.ID, - output=workflows_dot_v1_dot_storage__location__pb2.StorageLocation, + input=ID, + output=StorageLocation, idempotency_level=IdempotencyLevel.UNKNOWN, ), function=svc.get_storage_location, @@ -83,8 +141,8 @@ def __init__(self, service: StorageLocationService | AsyncGenerator[StorageLocat method=MethodInfo( name="CreateStorageLocation", service_name="workflows.v1.StorageLocationService", - input=workflows_dot_v1_dot_storage__location__pb2.CreateStorageLocationRequest, - output=workflows_dot_v1_dot_storage__location__pb2.StorageLocation, + input=CreateStorageLocationRequest, + output=StorageLocation, idempotency_level=IdempotencyLevel.UNKNOWN, ), function=svc.create_storage_location, @@ -93,8 +151,8 @@ def __init__(self, service: StorageLocationService | AsyncGenerator[StorageLocat method=MethodInfo( name="UpdateStorageLocation", service_name="workflows.v1.StorageLocationService", - input=workflows_dot_v1_dot_storage__location__pb2.UpdateStorageLocationRequest, - output=workflows_dot_v1_dot_storage__location__pb2.StorageLocation, + input=UpdateStorageLocationRequest, + output=StorageLocation, idempotency_level=IdempotencyLevel.UNKNOWN, ), function=svc.update_storage_location, @@ -103,8 +161,8 @@ def __init__(self, service: StorageLocationService | AsyncGenerator[StorageLocat method=MethodInfo( name="DeleteStorageLocation", service_name="workflows.v1.StorageLocationService", - input=tilebox_dot_v1_dot_id__pb2.ID, - output=google_dot_protobuf_dot_empty__pb2.Empty, + input=ID, + output=Empty, idempotency_level=IdempotencyLevel.UNKNOWN, ), function=svc.delete_storage_location, @@ -113,8 +171,8 @@ def __init__(self, service: StorageLocationService | AsyncGenerator[StorageLocat method=MethodInfo( name="CreateStorageSubscription", service_name="workflows.v1.StorageLocationService", - input=workflows_dot_v1_dot_storage__location__pb2.CreateStorageSubscriptionRequest, - output=workflows_dot_v1_dot_storage__location__pb2.StorageSubscription, + input=CreateStorageSubscriptionRequest, + output=StorageSubscription, idempotency_level=IdempotencyLevel.UNKNOWN, ), function=svc.create_storage_subscription, @@ -123,8 +181,8 @@ def __init__(self, service: StorageLocationService | AsyncGenerator[StorageLocat method=MethodInfo( name="ListStorageSubscriptions", service_name="workflows.v1.StorageLocationService", - input=workflows_dot_v1_dot_storage__location__pb2.ListStorageSubscriptionsRequest, - output=workflows_dot_v1_dot_storage__location__pb2.StorageSubscriptions, + input=ListStorageSubscriptionsRequest, + output=StorageSubscriptions, idempotency_level=IdempotencyLevel.UNKNOWN, ), function=svc.list_storage_subscriptions, @@ -133,8 +191,8 @@ def __init__(self, service: StorageLocationService | AsyncGenerator[StorageLocat method=MethodInfo( name="GetStorageSubscription", service_name="workflows.v1.StorageLocationService", - input=tilebox_dot_v1_dot_id__pb2.ID, - output=workflows_dot_v1_dot_storage__location__pb2.StorageSubscription, + input=ID, + output=StorageSubscription, idempotency_level=IdempotencyLevel.UNKNOWN, ), function=svc.get_storage_subscription, @@ -143,8 +201,8 @@ def __init__(self, service: StorageLocationService | AsyncGenerator[StorageLocat method=MethodInfo( name="DeleteStorageSubscription", service_name="workflows.v1.StorageLocationService", - input=tilebox_dot_v1_dot_id__pb2.ID, - output=google_dot_protobuf_dot_empty__pb2.Empty, + input=ID, + output=Empty, idempotency_level=IdempotencyLevel.UNKNOWN, ), function=svc.delete_storage_subscription, @@ -153,8 +211,8 @@ def __init__(self, service: StorageLocationService | AsyncGenerator[StorageLocat method=MethodInfo( name="ListStorageSubscriptionEvents", service_name="workflows.v1.StorageLocationService", - input=workflows_dot_v1_dot_storage__location__pb2.ListStorageSubscriptionEventsRequest, - output=workflows_dot_v1_dot_storage__location__pb2.StorageSubscriptionEvents, + input=ListStorageSubscriptionEventsRequest, + output=StorageSubscriptionEvents, idempotency_level=IdempotencyLevel.UNKNOWN, ), function=svc.list_storage_subscription_events, @@ -163,8 +221,8 @@ def __init__(self, service: StorageLocationService | AsyncGenerator[StorageLocat method=MethodInfo( name="NotifyObjectCreated", service_name="workflows.v1.StorageLocationService", - input=workflows_dot_v1_dot_storage__location__pb2.NotifyObjectCreatedRequest, - output=google_dot_protobuf_dot_empty__pb2.Empty, + input=NotifyObjectCreatedRequest, + output=Empty, idempotency_level=IdempotencyLevel.UNKNOWN, ), function=svc.notify_object_created, @@ -183,20 +241,26 @@ def path(self) -> str: class StorageLocationServiceClient(ConnectClient): + """ + StorageLocationService manages storage locations, notification subscriptions, and received events. + """ async def list_storage_locations( self, - request: google_dot_protobuf_dot_empty__pb2.Empty, + request: Empty, *, headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, - ) -> workflows_dot_v1_dot_storage__location__pb2.StorageLocations: + ) -> StorageLocations: + """ + ListStorageLocations lists storage locations available for automation triggers. + """ return await self.execute_unary( request=request, method=MethodInfo( name="ListStorageLocations", service_name="workflows.v1.StorageLocationService", - input=google_dot_protobuf_dot_empty__pb2.Empty, - output=workflows_dot_v1_dot_storage__location__pb2.StorageLocations, + input=Empty, + output=StorageLocations, idempotency_level=IdempotencyLevel.UNKNOWN, ), headers=headers, @@ -205,18 +269,21 @@ async def list_storage_locations( async def get_storage_location( self, - request: tilebox_dot_v1_dot_id__pb2.ID, + request: ID, *, headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, - ) -> workflows_dot_v1_dot_storage__location__pb2.StorageLocation: + ) -> StorageLocation: + """ + GetStorageLocation gets a storage location by its ID. + """ return await self.execute_unary( request=request, method=MethodInfo( name="GetStorageLocation", service_name="workflows.v1.StorageLocationService", - input=tilebox_dot_v1_dot_id__pb2.ID, - output=workflows_dot_v1_dot_storage__location__pb2.StorageLocation, + input=ID, + output=StorageLocation, idempotency_level=IdempotencyLevel.UNKNOWN, ), headers=headers, @@ -225,18 +292,21 @@ async def get_storage_location( async def create_storage_location( self, - request: workflows_dot_v1_dot_storage__location__pb2.CreateStorageLocationRequest, + request: CreateStorageLocationRequest, *, headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, - ) -> workflows_dot_v1_dot_storage__location__pb2.StorageLocation: + ) -> StorageLocation: + """ + CreateStorageLocation registers an existing bucket, container, or directory for automation triggers. + """ return await self.execute_unary( request=request, method=MethodInfo( name="CreateStorageLocation", service_name="workflows.v1.StorageLocationService", - input=workflows_dot_v1_dot_storage__location__pb2.CreateStorageLocationRequest, - output=workflows_dot_v1_dot_storage__location__pb2.StorageLocation, + input=CreateStorageLocationRequest, + output=StorageLocation, idempotency_level=IdempotencyLevel.UNKNOWN, ), headers=headers, @@ -245,18 +315,21 @@ async def create_storage_location( async def update_storage_location( self, - request: workflows_dot_v1_dot_storage__location__pb2.UpdateStorageLocationRequest, + request: UpdateStorageLocationRequest, *, headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, - ) -> workflows_dot_v1_dot_storage__location__pb2.StorageLocation: + ) -> StorageLocation: + """ + UpdateStorageLocation changes the display name of a storage location. + """ return await self.execute_unary( request=request, method=MethodInfo( name="UpdateStorageLocation", service_name="workflows.v1.StorageLocationService", - input=workflows_dot_v1_dot_storage__location__pb2.UpdateStorageLocationRequest, - output=workflows_dot_v1_dot_storage__location__pb2.StorageLocation, + input=UpdateStorageLocationRequest, + output=StorageLocation, idempotency_level=IdempotencyLevel.UNKNOWN, ), headers=headers, @@ -265,18 +338,21 @@ async def update_storage_location( async def delete_storage_location( self, - request: tilebox_dot_v1_dot_id__pb2.ID, + request: ID, *, headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, - ) -> google_dot_protobuf_dot_empty__pb2.Empty: + ) -> Empty: + """ + DeleteStorageLocation removes the location and its subscriptions from Tilebox without deleting cloud resources. + """ return await self.execute_unary( request=request, method=MethodInfo( name="DeleteStorageLocation", service_name="workflows.v1.StorageLocationService", - input=tilebox_dot_v1_dot_id__pb2.ID, - output=google_dot_protobuf_dot_empty__pb2.Empty, + input=ID, + output=Empty, idempotency_level=IdempotencyLevel.UNKNOWN, ), headers=headers, @@ -285,18 +361,21 @@ async def delete_storage_location( async def create_storage_subscription( self, - request: workflows_dot_v1_dot_storage__location__pb2.CreateStorageSubscriptionRequest, + request: CreateStorageSubscriptionRequest, *, headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, - ) -> workflows_dot_v1_dot_storage__location__pb2.StorageSubscription: + ) -> StorageSubscription: + """ + CreateStorageSubscription registers notification delivery and returns the provider setup values. + """ return await self.execute_unary( request=request, method=MethodInfo( name="CreateStorageSubscription", service_name="workflows.v1.StorageLocationService", - input=workflows_dot_v1_dot_storage__location__pb2.CreateStorageSubscriptionRequest, - output=workflows_dot_v1_dot_storage__location__pb2.StorageSubscription, + input=CreateStorageSubscriptionRequest, + output=StorageSubscription, idempotency_level=IdempotencyLevel.UNKNOWN, ), headers=headers, @@ -305,18 +384,21 @@ async def create_storage_subscription( async def list_storage_subscriptions( self, - request: workflows_dot_v1_dot_storage__location__pb2.ListStorageSubscriptionsRequest, + request: ListStorageSubscriptionsRequest, *, headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, - ) -> workflows_dot_v1_dot_storage__location__pb2.StorageSubscriptions: + ) -> StorageSubscriptions: + """ + ListStorageSubscriptions lists notification subscriptions for a storage location. + """ return await self.execute_unary( request=request, method=MethodInfo( name="ListStorageSubscriptions", service_name="workflows.v1.StorageLocationService", - input=workflows_dot_v1_dot_storage__location__pb2.ListStorageSubscriptionsRequest, - output=workflows_dot_v1_dot_storage__location__pb2.StorageSubscriptions, + input=ListStorageSubscriptionsRequest, + output=StorageSubscriptions, idempotency_level=IdempotencyLevel.UNKNOWN, ), headers=headers, @@ -325,18 +407,21 @@ async def list_storage_subscriptions( async def get_storage_subscription( self, - request: tilebox_dot_v1_dot_id__pb2.ID, + request: ID, *, headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, - ) -> workflows_dot_v1_dot_storage__location__pb2.StorageSubscription: + ) -> StorageSubscription: + """ + GetStorageSubscription gets a subscription by its ID. + """ return await self.execute_unary( request=request, method=MethodInfo( name="GetStorageSubscription", service_name="workflows.v1.StorageLocationService", - input=tilebox_dot_v1_dot_id__pb2.ID, - output=workflows_dot_v1_dot_storage__location__pb2.StorageSubscription, + input=ID, + output=StorageSubscription, idempotency_level=IdempotencyLevel.UNKNOWN, ), headers=headers, @@ -345,18 +430,21 @@ async def get_storage_subscription( async def delete_storage_subscription( self, - request: tilebox_dot_v1_dot_id__pb2.ID, + request: ID, *, headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, - ) -> google_dot_protobuf_dot_empty__pb2.Empty: + ) -> Empty: + """ + DeleteStorageSubscription stops accepting notifications at its endpoint without deleting cloud resources. + """ return await self.execute_unary( request=request, method=MethodInfo( name="DeleteStorageSubscription", service_name="workflows.v1.StorageLocationService", - input=tilebox_dot_v1_dot_id__pb2.ID, - output=google_dot_protobuf_dot_empty__pb2.Empty, + input=ID, + output=Empty, idempotency_level=IdempotencyLevel.UNKNOWN, ), headers=headers, @@ -365,18 +453,21 @@ async def delete_storage_subscription( async def list_storage_subscription_events( self, - request: workflows_dot_v1_dot_storage__location__pb2.ListStorageSubscriptionEventsRequest, + request: ListStorageSubscriptionEventsRequest, *, headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, - ) -> workflows_dot_v1_dot_storage__location__pb2.StorageSubscriptionEvents: + ) -> StorageSubscriptionEvents: + """ + ListStorageSubscriptionEvents lists received object notifications and the jobs they triggered. + """ return await self.execute_unary( request=request, method=MethodInfo( name="ListStorageSubscriptionEvents", service_name="workflows.v1.StorageLocationService", - input=workflows_dot_v1_dot_storage__location__pb2.ListStorageSubscriptionEventsRequest, - output=workflows_dot_v1_dot_storage__location__pb2.StorageSubscriptionEvents, + input=ListStorageSubscriptionEventsRequest, + output=StorageSubscriptionEvents, idempotency_level=IdempotencyLevel.UNKNOWN, ), headers=headers, @@ -385,63 +476,121 @@ async def list_storage_subscription_events( async def notify_object_created( self, - request: workflows_dot_v1_dot_storage__location__pb2.NotifyObjectCreatedRequest, + request: NotifyObjectCreatedRequest, *, headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, - ) -> google_dot_protobuf_dot_empty__pb2.Empty: + ) -> Empty: + """ + NotifyObjectCreated delivers a storage event of a local storage location. Typically called by the Tilebox CLI + during the `tilebox storage-location watch` command. The subscription must be of type TILEBOX_CLI. + """ return await self.execute_unary( request=request, method=MethodInfo( name="NotifyObjectCreated", service_name="workflows.v1.StorageLocationService", - input=workflows_dot_v1_dot_storage__location__pb2.NotifyObjectCreatedRequest, - output=google_dot_protobuf_dot_empty__pb2.Empty, + input=NotifyObjectCreatedRequest, + output=Empty, idempotency_level=IdempotencyLevel.UNKNOWN, ), headers=headers, timeout_ms=timeout_ms, ) - - - - class StorageLocationServiceSync(Protocol): - def list_storage_locations(self, request: google_dot_protobuf_dot_empty__pb2.Empty, ctx: RequestContext) -> workflows_dot_v1_dot_storage__location__pb2.StorageLocations: - raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") - def get_storage_location(self, request: tilebox_dot_v1_dot_id__pb2.ID, ctx: RequestContext) -> workflows_dot_v1_dot_storage__location__pb2.StorageLocation: - raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") - def create_storage_location(self, request: workflows_dot_v1_dot_storage__location__pb2.CreateStorageLocationRequest, ctx: RequestContext) -> workflows_dot_v1_dot_storage__location__pb2.StorageLocation: - raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") - def update_storage_location(self, request: workflows_dot_v1_dot_storage__location__pb2.UpdateStorageLocationRequest, ctx: RequestContext) -> workflows_dot_v1_dot_storage__location__pb2.StorageLocation: - raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") - def delete_storage_location(self, request: tilebox_dot_v1_dot_id__pb2.ID, ctx: RequestContext) -> google_dot_protobuf_dot_empty__pb2.Empty: - raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") - def create_storage_subscription(self, request: workflows_dot_v1_dot_storage__location__pb2.CreateStorageSubscriptionRequest, ctx: RequestContext) -> workflows_dot_v1_dot_storage__location__pb2.StorageSubscription: - raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") - def list_storage_subscriptions(self, request: workflows_dot_v1_dot_storage__location__pb2.ListStorageSubscriptionsRequest, ctx: RequestContext) -> workflows_dot_v1_dot_storage__location__pb2.StorageSubscriptions: - raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") - def get_storage_subscription(self, request: tilebox_dot_v1_dot_id__pb2.ID, ctx: RequestContext) -> workflows_dot_v1_dot_storage__location__pb2.StorageSubscription: - raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") - def delete_storage_subscription(self, request: tilebox_dot_v1_dot_id__pb2.ID, ctx: RequestContext) -> google_dot_protobuf_dot_empty__pb2.Empty: - raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") - def list_storage_subscription_events(self, request: workflows_dot_v1_dot_storage__location__pb2.ListStorageSubscriptionEventsRequest, ctx: RequestContext) -> workflows_dot_v1_dot_storage__location__pb2.StorageSubscriptionEvents: - raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") - def notify_object_created(self, request: workflows_dot_v1_dot_storage__location__pb2.NotifyObjectCreatedRequest, ctx: RequestContext) -> google_dot_protobuf_dot_empty__pb2.Empty: - raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") - + """ + StorageLocationService manages storage locations, notification subscriptions, and received events. + """ + def list_storage_locations(self, request: Empty, ctx: RequestContext[Empty, StorageLocations], /) -> StorageLocations: + """ + ListStorageLocations lists storage locations available for automation triggers. + """ + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') + + def get_storage_location(self, request: ID, ctx: RequestContext[ID, StorageLocation], /) -> StorageLocation: + """ + GetStorageLocation gets a storage location by its ID. + """ + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') + + def create_storage_location(self, request: CreateStorageLocationRequest, ctx: RequestContext[CreateStorageLocationRequest, StorageLocation], /) -> StorageLocation: + """ + CreateStorageLocation registers an existing bucket, container, or directory for automation triggers. + """ + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') + + def update_storage_location(self, request: UpdateStorageLocationRequest, ctx: RequestContext[UpdateStorageLocationRequest, StorageLocation], /) -> StorageLocation: + """ + UpdateStorageLocation changes the display name of a storage location. + """ + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') + + def delete_storage_location(self, request: ID, ctx: RequestContext[ID, Empty], /) -> Empty: + """ + DeleteStorageLocation removes the location and its subscriptions from Tilebox without deleting cloud resources. + """ + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') + + def create_storage_subscription(self, request: CreateStorageSubscriptionRequest, ctx: RequestContext[CreateStorageSubscriptionRequest, StorageSubscription], /) -> StorageSubscription: + """ + CreateStorageSubscription registers notification delivery and returns the provider setup values. + """ + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') + + def list_storage_subscriptions(self, request: ListStorageSubscriptionsRequest, ctx: RequestContext[ListStorageSubscriptionsRequest, StorageSubscriptions], /) -> StorageSubscriptions: + """ + ListStorageSubscriptions lists notification subscriptions for a storage location. + """ + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') + + def get_storage_subscription(self, request: ID, ctx: RequestContext[ID, StorageSubscription], /) -> StorageSubscription: + """ + GetStorageSubscription gets a subscription by its ID. + """ + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') + + def delete_storage_subscription(self, request: ID, ctx: RequestContext[ID, Empty], /) -> Empty: + """ + DeleteStorageSubscription stops accepting notifications at its endpoint without deleting cloud resources. + """ + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') + + def list_storage_subscription_events(self, request: ListStorageSubscriptionEventsRequest, ctx: RequestContext[ListStorageSubscriptionEventsRequest, StorageSubscriptionEvents], /) -> StorageSubscriptionEvents: + """ + ListStorageSubscriptionEvents lists received object notifications and the jobs they triggered. + """ + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') + + def notify_object_created(self, request: NotifyObjectCreatedRequest, ctx: RequestContext[NotifyObjectCreatedRequest, Empty], /) -> Empty: + """ + NotifyObjectCreated delivers a storage event of a local storage location. Typically called by the Tilebox CLI + during the `tilebox storage-location watch` command. The subscription must be of type TILEBOX_CLI. + """ + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') + + @classmethod + def desc(cls) -> DescService: + """Returns the descriptor for this service.""" + return next(s for s in storage_location_pb.desc().services if s.type_name == 'workflows.v1.StorageLocationService') class StorageLocationServiceWSGIApplication(ConnectWSGIApplication): - def __init__(self, service: StorageLocationServiceSync, interceptors: Iterable[InterceptorSync]=(), read_max_bytes: int | None = None, compressions: Iterable[Compression] | None = None, codecs: Iterable[Codec] | None = None) -> None: + def __init__( + self, + service: StorageLocationServiceSync, + interceptors: Iterable[InterceptorSync] = (), + read_max_bytes: int | None = DEFAULT_READ_MAX_BYTES, + compressions: Iterable[Compression] | None = None, + codecs: Iterable[Codec] | None = None, + ) -> None: super().__init__( endpoints={ "/workflows.v1.StorageLocationService/ListStorageLocations": EndpointSync.unary( method=MethodInfo( name="ListStorageLocations", service_name="workflows.v1.StorageLocationService", - input=google_dot_protobuf_dot_empty__pb2.Empty, - output=workflows_dot_v1_dot_storage__location__pb2.StorageLocations, + input=Empty, + output=StorageLocations, idempotency_level=IdempotencyLevel.UNKNOWN, ), function=service.list_storage_locations, @@ -450,8 +599,8 @@ def __init__(self, service: StorageLocationServiceSync, interceptors: Iterable[I method=MethodInfo( name="GetStorageLocation", service_name="workflows.v1.StorageLocationService", - input=tilebox_dot_v1_dot_id__pb2.ID, - output=workflows_dot_v1_dot_storage__location__pb2.StorageLocation, + input=ID, + output=StorageLocation, idempotency_level=IdempotencyLevel.UNKNOWN, ), function=service.get_storage_location, @@ -460,8 +609,8 @@ def __init__(self, service: StorageLocationServiceSync, interceptors: Iterable[I method=MethodInfo( name="CreateStorageLocation", service_name="workflows.v1.StorageLocationService", - input=workflows_dot_v1_dot_storage__location__pb2.CreateStorageLocationRequest, - output=workflows_dot_v1_dot_storage__location__pb2.StorageLocation, + input=CreateStorageLocationRequest, + output=StorageLocation, idempotency_level=IdempotencyLevel.UNKNOWN, ), function=service.create_storage_location, @@ -470,8 +619,8 @@ def __init__(self, service: StorageLocationServiceSync, interceptors: Iterable[I method=MethodInfo( name="UpdateStorageLocation", service_name="workflows.v1.StorageLocationService", - input=workflows_dot_v1_dot_storage__location__pb2.UpdateStorageLocationRequest, - output=workflows_dot_v1_dot_storage__location__pb2.StorageLocation, + input=UpdateStorageLocationRequest, + output=StorageLocation, idempotency_level=IdempotencyLevel.UNKNOWN, ), function=service.update_storage_location, @@ -480,8 +629,8 @@ def __init__(self, service: StorageLocationServiceSync, interceptors: Iterable[I method=MethodInfo( name="DeleteStorageLocation", service_name="workflows.v1.StorageLocationService", - input=tilebox_dot_v1_dot_id__pb2.ID, - output=google_dot_protobuf_dot_empty__pb2.Empty, + input=ID, + output=Empty, idempotency_level=IdempotencyLevel.UNKNOWN, ), function=service.delete_storage_location, @@ -490,8 +639,8 @@ def __init__(self, service: StorageLocationServiceSync, interceptors: Iterable[I method=MethodInfo( name="CreateStorageSubscription", service_name="workflows.v1.StorageLocationService", - input=workflows_dot_v1_dot_storage__location__pb2.CreateStorageSubscriptionRequest, - output=workflows_dot_v1_dot_storage__location__pb2.StorageSubscription, + input=CreateStorageSubscriptionRequest, + output=StorageSubscription, idempotency_level=IdempotencyLevel.UNKNOWN, ), function=service.create_storage_subscription, @@ -500,8 +649,8 @@ def __init__(self, service: StorageLocationServiceSync, interceptors: Iterable[I method=MethodInfo( name="ListStorageSubscriptions", service_name="workflows.v1.StorageLocationService", - input=workflows_dot_v1_dot_storage__location__pb2.ListStorageSubscriptionsRequest, - output=workflows_dot_v1_dot_storage__location__pb2.StorageSubscriptions, + input=ListStorageSubscriptionsRequest, + output=StorageSubscriptions, idempotency_level=IdempotencyLevel.UNKNOWN, ), function=service.list_storage_subscriptions, @@ -510,8 +659,8 @@ def __init__(self, service: StorageLocationServiceSync, interceptors: Iterable[I method=MethodInfo( name="GetStorageSubscription", service_name="workflows.v1.StorageLocationService", - input=tilebox_dot_v1_dot_id__pb2.ID, - output=workflows_dot_v1_dot_storage__location__pb2.StorageSubscription, + input=ID, + output=StorageSubscription, idempotency_level=IdempotencyLevel.UNKNOWN, ), function=service.get_storage_subscription, @@ -520,8 +669,8 @@ def __init__(self, service: StorageLocationServiceSync, interceptors: Iterable[I method=MethodInfo( name="DeleteStorageSubscription", service_name="workflows.v1.StorageLocationService", - input=tilebox_dot_v1_dot_id__pb2.ID, - output=google_dot_protobuf_dot_empty__pb2.Empty, + input=ID, + output=Empty, idempotency_level=IdempotencyLevel.UNKNOWN, ), function=service.delete_storage_subscription, @@ -530,8 +679,8 @@ def __init__(self, service: StorageLocationServiceSync, interceptors: Iterable[I method=MethodInfo( name="ListStorageSubscriptionEvents", service_name="workflows.v1.StorageLocationService", - input=workflows_dot_v1_dot_storage__location__pb2.ListStorageSubscriptionEventsRequest, - output=workflows_dot_v1_dot_storage__location__pb2.StorageSubscriptionEvents, + input=ListStorageSubscriptionEventsRequest, + output=StorageSubscriptionEvents, idempotency_level=IdempotencyLevel.UNKNOWN, ), function=service.list_storage_subscription_events, @@ -540,8 +689,8 @@ def __init__(self, service: StorageLocationServiceSync, interceptors: Iterable[I method=MethodInfo( name="NotifyObjectCreated", service_name="workflows.v1.StorageLocationService", - input=workflows_dot_v1_dot_storage__location__pb2.NotifyObjectCreatedRequest, - output=google_dot_protobuf_dot_empty__pb2.Empty, + input=NotifyObjectCreatedRequest, + output=Empty, idempotency_level=IdempotencyLevel.UNKNOWN, ), function=service.notify_object_created, @@ -560,220 +709,247 @@ def path(self) -> str: class StorageLocationServiceClientSync(ConnectClientSync): + """ + StorageLocationService manages storage locations, notification subscriptions, and received events. + """ def list_storage_locations( self, - request: google_dot_protobuf_dot_empty__pb2.Empty, + request: Empty, *, headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, - ) -> workflows_dot_v1_dot_storage__location__pb2.StorageLocations: + ) -> StorageLocations: + """ + ListStorageLocations lists storage locations available for automation triggers. + """ return self.execute_unary( request=request, method=MethodInfo( name="ListStorageLocations", service_name="workflows.v1.StorageLocationService", - input=google_dot_protobuf_dot_empty__pb2.Empty, - output=workflows_dot_v1_dot_storage__location__pb2.StorageLocations, + input=Empty, + output=StorageLocations, idempotency_level=IdempotencyLevel.UNKNOWN, ), headers=headers, timeout_ms=timeout_ms, ) - def get_storage_location( self, - request: tilebox_dot_v1_dot_id__pb2.ID, + request: ID, *, headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, - ) -> workflows_dot_v1_dot_storage__location__pb2.StorageLocation: + ) -> StorageLocation: + """ + GetStorageLocation gets a storage location by its ID. + """ return self.execute_unary( request=request, method=MethodInfo( name="GetStorageLocation", service_name="workflows.v1.StorageLocationService", - input=tilebox_dot_v1_dot_id__pb2.ID, - output=workflows_dot_v1_dot_storage__location__pb2.StorageLocation, + input=ID, + output=StorageLocation, idempotency_level=IdempotencyLevel.UNKNOWN, ), headers=headers, timeout_ms=timeout_ms, ) - def create_storage_location( self, - request: workflows_dot_v1_dot_storage__location__pb2.CreateStorageLocationRequest, + request: CreateStorageLocationRequest, *, headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, - ) -> workflows_dot_v1_dot_storage__location__pb2.StorageLocation: + ) -> StorageLocation: + """ + CreateStorageLocation registers an existing bucket, container, or directory for automation triggers. + """ return self.execute_unary( request=request, method=MethodInfo( name="CreateStorageLocation", service_name="workflows.v1.StorageLocationService", - input=workflows_dot_v1_dot_storage__location__pb2.CreateStorageLocationRequest, - output=workflows_dot_v1_dot_storage__location__pb2.StorageLocation, + input=CreateStorageLocationRequest, + output=StorageLocation, idempotency_level=IdempotencyLevel.UNKNOWN, ), headers=headers, timeout_ms=timeout_ms, ) - def update_storage_location( self, - request: workflows_dot_v1_dot_storage__location__pb2.UpdateStorageLocationRequest, + request: UpdateStorageLocationRequest, *, headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, - ) -> workflows_dot_v1_dot_storage__location__pb2.StorageLocation: + ) -> StorageLocation: + """ + UpdateStorageLocation changes the display name of a storage location. + """ return self.execute_unary( request=request, method=MethodInfo( name="UpdateStorageLocation", service_name="workflows.v1.StorageLocationService", - input=workflows_dot_v1_dot_storage__location__pb2.UpdateStorageLocationRequest, - output=workflows_dot_v1_dot_storage__location__pb2.StorageLocation, + input=UpdateStorageLocationRequest, + output=StorageLocation, idempotency_level=IdempotencyLevel.UNKNOWN, ), headers=headers, timeout_ms=timeout_ms, ) - def delete_storage_location( self, - request: tilebox_dot_v1_dot_id__pb2.ID, + request: ID, *, headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, - ) -> google_dot_protobuf_dot_empty__pb2.Empty: + ) -> Empty: + """ + DeleteStorageLocation removes the location and its subscriptions from Tilebox without deleting cloud resources. + """ return self.execute_unary( request=request, method=MethodInfo( name="DeleteStorageLocation", service_name="workflows.v1.StorageLocationService", - input=tilebox_dot_v1_dot_id__pb2.ID, - output=google_dot_protobuf_dot_empty__pb2.Empty, + input=ID, + output=Empty, idempotency_level=IdempotencyLevel.UNKNOWN, ), headers=headers, timeout_ms=timeout_ms, ) - def create_storage_subscription( self, - request: workflows_dot_v1_dot_storage__location__pb2.CreateStorageSubscriptionRequest, + request: CreateStorageSubscriptionRequest, *, headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, - ) -> workflows_dot_v1_dot_storage__location__pb2.StorageSubscription: + ) -> StorageSubscription: + """ + CreateStorageSubscription registers notification delivery and returns the provider setup values. + """ return self.execute_unary( request=request, method=MethodInfo( name="CreateStorageSubscription", service_name="workflows.v1.StorageLocationService", - input=workflows_dot_v1_dot_storage__location__pb2.CreateStorageSubscriptionRequest, - output=workflows_dot_v1_dot_storage__location__pb2.StorageSubscription, + input=CreateStorageSubscriptionRequest, + output=StorageSubscription, idempotency_level=IdempotencyLevel.UNKNOWN, ), headers=headers, timeout_ms=timeout_ms, ) - def list_storage_subscriptions( self, - request: workflows_dot_v1_dot_storage__location__pb2.ListStorageSubscriptionsRequest, + request: ListStorageSubscriptionsRequest, *, headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, - ) -> workflows_dot_v1_dot_storage__location__pb2.StorageSubscriptions: + ) -> StorageSubscriptions: + """ + ListStorageSubscriptions lists notification subscriptions for a storage location. + """ return self.execute_unary( request=request, method=MethodInfo( name="ListStorageSubscriptions", service_name="workflows.v1.StorageLocationService", - input=workflows_dot_v1_dot_storage__location__pb2.ListStorageSubscriptionsRequest, - output=workflows_dot_v1_dot_storage__location__pb2.StorageSubscriptions, + input=ListStorageSubscriptionsRequest, + output=StorageSubscriptions, idempotency_level=IdempotencyLevel.UNKNOWN, ), headers=headers, timeout_ms=timeout_ms, ) - def get_storage_subscription( self, - request: tilebox_dot_v1_dot_id__pb2.ID, + request: ID, *, headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, - ) -> workflows_dot_v1_dot_storage__location__pb2.StorageSubscription: + ) -> StorageSubscription: + """ + GetStorageSubscription gets a subscription by its ID. + """ return self.execute_unary( request=request, method=MethodInfo( name="GetStorageSubscription", service_name="workflows.v1.StorageLocationService", - input=tilebox_dot_v1_dot_id__pb2.ID, - output=workflows_dot_v1_dot_storage__location__pb2.StorageSubscription, + input=ID, + output=StorageSubscription, idempotency_level=IdempotencyLevel.UNKNOWN, ), headers=headers, timeout_ms=timeout_ms, ) - def delete_storage_subscription( self, - request: tilebox_dot_v1_dot_id__pb2.ID, + request: ID, *, headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, - ) -> google_dot_protobuf_dot_empty__pb2.Empty: + ) -> Empty: + """ + DeleteStorageSubscription stops accepting notifications at its endpoint without deleting cloud resources. + """ return self.execute_unary( request=request, method=MethodInfo( name="DeleteStorageSubscription", service_name="workflows.v1.StorageLocationService", - input=tilebox_dot_v1_dot_id__pb2.ID, - output=google_dot_protobuf_dot_empty__pb2.Empty, + input=ID, + output=Empty, idempotency_level=IdempotencyLevel.UNKNOWN, ), headers=headers, timeout_ms=timeout_ms, ) - def list_storage_subscription_events( self, - request: workflows_dot_v1_dot_storage__location__pb2.ListStorageSubscriptionEventsRequest, + request: ListStorageSubscriptionEventsRequest, *, headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, - ) -> workflows_dot_v1_dot_storage__location__pb2.StorageSubscriptionEvents: + ) -> StorageSubscriptionEvents: + """ + ListStorageSubscriptionEvents lists received object notifications and the jobs they triggered. + """ return self.execute_unary( request=request, method=MethodInfo( name="ListStorageSubscriptionEvents", service_name="workflows.v1.StorageLocationService", - input=workflows_dot_v1_dot_storage__location__pb2.ListStorageSubscriptionEventsRequest, - output=workflows_dot_v1_dot_storage__location__pb2.StorageSubscriptionEvents, + input=ListStorageSubscriptionEventsRequest, + output=StorageSubscriptionEvents, idempotency_level=IdempotencyLevel.UNKNOWN, ), headers=headers, timeout_ms=timeout_ms, ) - def notify_object_created( self, - request: workflows_dot_v1_dot_storage__location__pb2.NotifyObjectCreatedRequest, + request: NotifyObjectCreatedRequest, *, headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, - ) -> google_dot_protobuf_dot_empty__pb2.Empty: + ) -> Empty: + """ + NotifyObjectCreated delivers a storage event of a local storage location. Typically called by the Tilebox CLI + during the `tilebox storage-location watch` command. The subscription must be of type TILEBOX_CLI. + """ return self.execute_unary( request=request, method=MethodInfo( name="NotifyObjectCreated", service_name="workflows.v1.StorageLocationService", - input=workflows_dot_v1_dot_storage__location__pb2.NotifyObjectCreatedRequest, - output=google_dot_protobuf_dot_empty__pb2.Empty, + input=NotifyObjectCreatedRequest, + output=Empty, idempotency_level=IdempotencyLevel.UNKNOWN, ), headers=headers, diff --git a/tilebox-workflows/tilebox/workflows/v1/storage_location_pb.py b/tilebox-workflows/tilebox/workflows/v1/storage_location_pb.py new file mode 100644 index 0000000..6479278 --- /dev/null +++ b/tilebox-workflows/tilebox/workflows/v1/storage_location_pb.py @@ -0,0 +1,1215 @@ +# Generated from workflows/v1/storage_location.proto. DO NOT EDIT. +# Generated by protoc-gen-py v0.6.0 with parameter "init_files=false,map_imports=datasets/**:tilebox,map_imports=workflows/**:tilebox,map_imports=tilebox/**:_tilebox.proto,map_imports=opentelemetry/**:tilebox.workflows._otel". +# ruff: noqa: PGH004 +# ruff: noqa +# fmt: off + +from __future__ import annotations + +from typing import Literal, TYPE_CHECKING, TypeAlias + +from _tilebox.proto.tilebox.v1 import id_pb, query_pb +from protobuf import Enum, Message +from protobuf._codegen import file_desc +from protobuf.wkt.google.protobuf import empty_pb, timestamp_pb + +if TYPE_CHECKING: + from _tilebox.proto.tilebox.v1.id_pb import ID + from _tilebox.proto.tilebox.v1.query_pb import Pagination + from protobuf import DescFile + from protobuf.wkt.google.protobuf.timestamp_pb import Timestamp + + +_StorageLocationFields: TypeAlias = Literal["id", "location", "type", "name", "reference"] + +class StorageLocation(Message[_StorageLocationFields]): + """ + StorageLocation identifies a bucket, container, or local directory whose objects can trigger automations. + + ```proto + message workflows.v1.StorageLocation + ``` + + Attributes: + id: + Unique identifier of the storage location. + + ```proto + optional tilebox.v1.ID id = 1; + ``` + location: + Use reference for the provider coordinates. + + ```proto + string location = 2 [deprecated = true]; + ``` + type: + Use reference.type for the storage type. + + ```proto + workflows.v1.StorageType type = 3 [deprecated = true]; + ``` + name: + Display name of the storage location. + + ```proto + string name = 4; + ``` + reference: + Immutable provider coordinates of the storage location. + + ```proto + optional workflows.v1.StorageLocationReference reference = 5; + ``` + """ + + __slots__ = ("id", "location", "type", "name", "reference") + + if TYPE_CHECKING: + + def __init__( + self, + *, + id: ID | None = None, + location: str = "", + type: StorageType | None = None, + name: str = "", + reference: StorageLocationReference | None = None, + ) -> None: + pass + + id: ID | None + location: str + type: StorageType + name: str + reference: StorageLocationReference | None + +_StorageLocationReferenceFields: TypeAlias = Literal["aws_s3_bucket", "gcs_bucket", "azure_blob", "local", "type"] + +class StorageLocationReference(Message[_StorageLocationReferenceFields]): + """ + StorageLocationReference identifies an existing storage resource. Set the field corresponding to type. + + ```proto + message workflows.v1.StorageLocationReference + ``` + + Attributes: + aws_s3_bucket: + ```proto + optional workflows.v1.AWSS3BucketReference aws_s3_bucket = 1; + ``` + gcs_bucket: + ```proto + optional workflows.v1.GCSBucketReference gcs_bucket = 2; + ``` + azure_blob: + ```proto + optional workflows.v1.AzureBlobReference azure_blob = 3; + ``` + local: + ```proto + optional workflows.v1.LocalReference local = 4; + ``` + type: + ```proto + workflows.v1.StorageType type = 5; + ``` + """ + + __slots__ = ("aws_s3_bucket", "gcs_bucket", "azure_blob", "local", "type") + + if TYPE_CHECKING: + + def __init__( + self, + *, + aws_s3_bucket: AWSS3BucketReference | None = None, + gcs_bucket: GCSBucketReference | None = None, + azure_blob: AzureBlobReference | None = None, + local: LocalReference | None = None, + type: StorageType | None = None, + ) -> None: + pass + + aws_s3_bucket: AWSS3BucketReference | None + gcs_bucket: GCSBucketReference | None + azure_blob: AzureBlobReference | None + local: LocalReference | None + type: StorageType + +_AWSS3BucketReferenceFields: TypeAlias = Literal["bucket", "region"] + +class AWSS3BucketReference(Message[_AWSS3BucketReferenceFields]): + """ + AWSS3BucketReference identifies an Amazon Web Services S3 bucket and its region. + + ```proto + message workflows.v1.AWSS3BucketReference + ``` + + Attributes: + bucket: + ```proto + string bucket = 1; + ``` + region: + ```proto + string region = 2; + ``` + """ + + __slots__ = ("bucket", "region") + + if TYPE_CHECKING: + + def __init__( + self, + *, + bucket: str = "", + region: str = "", + ) -> None: + pass + + bucket: str + region: str + +_GCSBucketReferenceFields: TypeAlias = Literal["bucket", "project_id", "location"] + +class GCSBucketReference(Message[_GCSBucketReferenceFields]): + """ + GCSBucketReference identifies a Google Cloud Storage bucket and its project. + + ```proto + message workflows.v1.GCSBucketReference + ``` + + Attributes: + bucket: + ```proto + string bucket = 1; + ``` + project_id: + ID of the Google Cloud project that owns the bucket. + + ```proto + string project_id = 2; + ``` + location: + Optional bucket location, such as europe-west2, EUR4, or EU. May be a region, dual-region, or multi-region. + + ```proto + string location = 3; + ``` + """ + + __slots__ = ("bucket", "project_id", "location") + + if TYPE_CHECKING: + + def __init__( + self, + *, + bucket: str = "", + project_id: str = "", + location: str = "", + ) -> None: + pass + + bucket: str + project_id: str + location: str + +_AzureBlobReferenceFields: TypeAlias = Literal["storage_account_resource_id", "container", "region"] + +class AzureBlobReference(Message[_AzureBlobReferenceFields]): + """ + AzureBlobReference identifies a container in an Azure storage account. + + ```proto + message workflows.v1.AzureBlobReference + ``` + + Attributes: + storage_account_resource_id: + Full ARM resource ID of the storage account, not its URL or a connection string. + + ```proto + string storage_account_resource_id = 1; + ``` + container: + ```proto + string container = 2; + ``` + region: + Optional primary region of the storage account, such as westeurope. Containers use their account's region. + + ```proto + string region = 3; + ``` + """ + + __slots__ = ("storage_account_resource_id", "container", "region") + + if TYPE_CHECKING: + + def __init__( + self, + *, + storage_account_resource_id: str = "", + container: str = "", + region: str = "", + ) -> None: + pass + + storage_account_resource_id: str + container: str + region: str + +_LocalReferenceFields: TypeAlias = Literal["path"] + +class LocalReference(Message[_LocalReferenceFields]): + """ + LocalReference identifies a local directory monitored by the Tilebox CLI. + + ```proto + message workflows.v1.LocalReference + ``` + + Attributes: + path: + ```proto + string path = 1; + ``` + """ + + __slots__ = ("path",) + + if TYPE_CHECKING: + + def __init__( + self, + *, + path: str = "", + ) -> None: + pass + + path: str + +_TriggeredJobFields: TypeAlias = Literal["job_id", "automation_id", "glob_pattern"] + +class TriggeredJob(Message[_TriggeredJobFields]): + """ + TriggeredJob identifies a job submitted by an automation for a matching object path. + + ```proto + message workflows.v1.TriggeredJob + ``` + + Attributes: + job_id: + ```proto + optional tilebox.v1.ID job_id = 1; + ``` + automation_id: + ```proto + optional tilebox.v1.ID automation_id = 2; + ``` + glob_pattern: + ```proto + string glob_pattern = 3; + ``` + """ + + __slots__ = ("job_id", "automation_id", "glob_pattern") + + if TYPE_CHECKING: + + def __init__( + self, + *, + job_id: ID | None = None, + automation_id: ID | None = None, + glob_pattern: str = "", + ) -> None: + pass + + job_id: ID | None + automation_id: ID | None + glob_pattern: str + +_TriggeredJobsFields: TypeAlias = Literal["triggered_jobs"] + +class TriggeredJobs(Message[_TriggeredJobsFields]): + """ + TriggeredJobs contains the jobs submitted for an event. + + ```proto + message workflows.v1.TriggeredJobs + ``` + + Attributes: + triggered_jobs: + ```proto + repeated workflows.v1.TriggeredJob triggered_jobs = 1; + ``` + """ + + __slots__ = ("triggered_jobs",) + + if TYPE_CHECKING: + + def __init__( + self, + *, + triggered_jobs: list[TriggeredJob] | None = None, + ) -> None: + pass + + triggered_jobs: list[TriggeredJob] + +_CreateStorageLocationRequestFields: TypeAlias = Literal["name", "reference"] + +class CreateStorageLocationRequest(Message[_CreateStorageLocationRequestFields]): + """ + CreateStorageLocationRequest registers an existing bucket, container, or directory as a storage location. + + ```proto + message workflows.v1.CreateStorageLocationRequest + ``` + + Attributes: + name: + ```proto + string name = 1; + ``` + reference: + ```proto + optional workflows.v1.StorageLocationReference reference = 2; + ``` + """ + + __slots__ = ("name", "reference") + + if TYPE_CHECKING: + + def __init__( + self, + *, + name: str = "", + reference: StorageLocationReference | None = None, + ) -> None: + pass + + name: str + reference: StorageLocationReference | None + +_UpdateStorageLocationRequestFields: TypeAlias = Literal["storage_location_id", "name"] + +class UpdateStorageLocationRequest(Message[_UpdateStorageLocationRequestFields]): + """ + UpdateStorageLocationRequest changes the display name of a storage location. + + ```proto + message workflows.v1.UpdateStorageLocationRequest + ``` + + Attributes: + storage_location_id: + ```proto + optional tilebox.v1.ID storage_location_id = 1; + ``` + name: + ```proto + string name = 2; + ``` + """ + + __slots__ = ("storage_location_id", "name") + + if TYPE_CHECKING: + + def __init__( + self, + *, + storage_location_id: ID | None = None, + name: str = "", + ) -> None: + pass + + storage_location_id: ID | None + name: str + +_AWSSNSStorageSubscriptionFields: TypeAlias = Literal["topic_arn", "message_format"] + +class AWSSNSStorageSubscription(Message[_AWSSNSStorageSubscriptionFields]): + """ + AWSSNSStorageSubscription identifies the SNS topic whose signed notifications are accepted. + Configure an HTTPS subscription with raw message delivery disabled. Tilebox verifies and confirms signed SNS requests. + + ```proto + message workflows.v1.AWSSNSStorageSubscription + ``` + + Attributes: + topic_arn: + ```proto + string topic_arn = 1; + ``` + message_format: + Payload format: s3, earthsearch_stac, or usgs_landsat_c2. If omitted, s3 is assumed. + + ```proto + string message_format = 2; + ``` + """ + + __slots__ = ("topic_arn", "message_format") + + if TYPE_CHECKING: + + def __init__( + self, + *, + topic_arn: str = "", + message_format: str = "", + ) -> None: + pass + + topic_arn: str + message_format: str + +_GooglePubSubStorageSubscriptionFields: TypeAlias = Literal["subscription", "service_account_email", "audience"] + +class GooglePubSubStorageSubscription(Message[_GooglePubSubStorageSubscriptionFields]): + """ + GooglePubSubStorageSubscription identifies the delivery subscription and authenticated push identity. + Use authenticated push with wrapped JSON and the returned audience. + + ```proto + message workflows.v1.GooglePubSubStorageSubscription + ``` + + Attributes: + subscription: + Full resource name: projects/{project}/subscriptions/{subscription}. + + ```proto + string subscription = 1; + ``` + service_account_email: + Expected Google-authenticated service account email. No service account key is accepted. + + ```proto + string service_account_email = 2; + ``` + audience: + OIDC audience to configure for authenticated push, equal to the subscription's endpoint. + + ```proto + string audience = 3; + ``` + """ + + __slots__ = ("subscription", "service_account_email", "audience") + + if TYPE_CHECKING: + + def __init__( + self, + *, + subscription: str = "", + service_account_email: str = "", + audience: str = "", + ) -> None: + pass + + subscription: str + service_account_email: str + audience: str + +_AzureEventGridStorageSubscriptionFields: TypeAlias = Literal["webhook_secret_header", "webhook_secret", "webhook_secret_hash"] + +class AzureEventGridStorageSubscription(Message[_AzureEventGridStorageSubscriptionFields]): + """ + AzureEventGridStorageSubscription configures Event Grid delivery with a secret header. + Subscribe to Microsoft.Storage.BlobCreated with the location's container subject prefix. + Use Event Grid schema, not CloudEvents schema. + + ```proto + message workflows.v1.AzureEventGridStorageSubscription + ``` + + Attributes: + webhook_secret_header: + Delivery header to configure in Event Grid: X-Tilebox-Webhook-Secret. + + ```proto + string webhook_secret_header = 1; + ``` + webhook_secret: + Secret delivery header value, returned only when the subscription is created. + Copy it into Event Grid's secret delivery header configuration; it cannot be retrieved later. + + ```proto + string webhook_secret = 2; + ``` + webhook_secret_hash: + Server-managed verifier; never returned by the API. + + ```proto + bytes webhook_secret_hash = 3; + ``` + """ + + __slots__ = ("webhook_secret_header", "webhook_secret", "webhook_secret_hash") + + if TYPE_CHECKING: + + def __init__( + self, + *, + webhook_secret_header: str = "", + webhook_secret: str = "", + webhook_secret_hash: bytes = b"", + ) -> None: + pass + + webhook_secret_header: str + webhook_secret: str + webhook_secret_hash: bytes + +_StorageSubscriptionFields: TypeAlias = Literal["id", "storage_location_id", "type", "endpoint", "created_at", "aws_sns", "google_pubsub", "azure_event_grid"] + +class StorageSubscription(Message[_StorageSubscriptionFields]): + """ + StorageSubscription receives object notifications for a storage location. + To change delivery configuration, create a replacement subscription and delete the old one. + + ```proto + message workflows.v1.StorageSubscription + ``` + + Attributes: + id: + ```proto + optional tilebox.v1.ID id = 1; + ``` + storage_location_id: + ```proto + optional tilebox.v1.ID storage_location_id = 2; + ``` + type: + ```proto + workflows.v1.StorageSubscriptionType type = 3; + ``` + endpoint: + Notification delivery URL: HTTPS for the hosted API, HTTP for standalone on-orbit. + With a Unix-socket listener, on-orbit returns a relative path to use over that socket. + + ```proto + string endpoint = 4; + ``` + created_at: + ```proto + optional google.protobuf.Timestamp created_at = 6; + ``` + aws_sns: + ```proto + optional workflows.v1.AWSSNSStorageSubscription aws_sns = 7; + ``` + google_pubsub: + ```proto + optional workflows.v1.GooglePubSubStorageSubscription google_pubsub = 8; + ``` + azure_event_grid: + ```proto + optional workflows.v1.AzureEventGridStorageSubscription azure_event_grid = 9; + ``` + """ + + __slots__ = ("id", "storage_location_id", "type", "endpoint", "created_at", "aws_sns", "google_pubsub", "azure_event_grid") + + if TYPE_CHECKING: + + def __init__( + self, + *, + id: ID | None = None, + storage_location_id: ID | None = None, + type: StorageSubscriptionType | None = None, + endpoint: str = "", + created_at: Timestamp | None = None, + aws_sns: AWSSNSStorageSubscription | None = None, + google_pubsub: GooglePubSubStorageSubscription | None = None, + azure_event_grid: AzureEventGridStorageSubscription | None = None, + ) -> None: + pass + + id: ID | None + storage_location_id: ID | None + type: StorageSubscriptionType + endpoint: str + created_at: Timestamp | None + aws_sns: AWSSNSStorageSubscription | None + google_pubsub: GooglePubSubStorageSubscription | None + azure_event_grid: AzureEventGridStorageSubscription | None + +_CreateStorageSubscriptionRequestFields: TypeAlias = Literal["storage_location_id", "type", "aws_sns", "google_pubsub", "azure_event_grid"] + +class CreateStorageSubscriptionRequest(Message[_CreateStorageSubscriptionRequestFields]): + """ + CreateStorageSubscriptionRequest registers notification delivery for a storage location. It enables the + Tilebox webhook endpoint to receive object notifications from the provider. Infrastructure on the provider side + must then be configured to send notifications to the returned endpoint. + + ```proto + message workflows.v1.CreateStorageSubscriptionRequest + ``` + + Attributes: + storage_location_id: + ```proto + optional tilebox.v1.ID storage_location_id = 1; + ``` + type: + ```proto + workflows.v1.StorageSubscriptionType type = 2; + ``` + aws_sns: + ```proto + optional workflows.v1.AWSSNSStorageSubscription aws_sns = 3; + ``` + google_pubsub: + ```proto + optional workflows.v1.GooglePubSubStorageSubscription google_pubsub = 4; + ``` + azure_event_grid: + ```proto + optional workflows.v1.AzureEventGridStorageSubscription azure_event_grid = 5; + ``` + """ + + __slots__ = ("storage_location_id", "type", "aws_sns", "google_pubsub", "azure_event_grid") + + if TYPE_CHECKING: + + def __init__( + self, + *, + storage_location_id: ID | None = None, + type: StorageSubscriptionType | None = None, + aws_sns: AWSSNSStorageSubscription | None = None, + google_pubsub: GooglePubSubStorageSubscription | None = None, + azure_event_grid: AzureEventGridStorageSubscription | None = None, + ) -> None: + pass + + storage_location_id: ID | None + type: StorageSubscriptionType + aws_sns: AWSSNSStorageSubscription | None + google_pubsub: GooglePubSubStorageSubscription | None + azure_event_grid: AzureEventGridStorageSubscription | None + +_ListStorageSubscriptionsRequestFields: TypeAlias = Literal["storage_location_id"] + +class ListStorageSubscriptionsRequest(Message[_ListStorageSubscriptionsRequestFields]): + """ + ListStorageSubscriptionsRequest lists subscriptions for a storage location. + + ```proto + message workflows.v1.ListStorageSubscriptionsRequest + ``` + + Attributes: + storage_location_id: + ```proto + optional tilebox.v1.ID storage_location_id = 1; + ``` + """ + + __slots__ = ("storage_location_id",) + + if TYPE_CHECKING: + + def __init__( + self, + *, + storage_location_id: ID | None = None, + ) -> None: + pass + + storage_location_id: ID | None + +_NotifyObjectCreatedRequestFields: TypeAlias = Literal["storage_subscription_id", "object_path", "event_time"] + +class NotifyObjectCreatedRequest(Message[_NotifyObjectCreatedRequestFields]): + """ + NotifyObjectCreatedRequest sends a file-ready event to a TILEBOX_CLI subscription. + + ```proto + message workflows.v1.NotifyObjectCreatedRequest + ``` + + Attributes: + storage_subscription_id: + Subscription through which to deliver the event. It must have type TILEBOX_CLI. + + ```proto + optional tilebox.v1.ID storage_subscription_id = 1; + ``` + object_path: + Path relative to the location root, using slash separators without dot or parent components. Must not include + a leading slash. For example, "subdir/file.txt" or "file.txt". + + ```proto + string object_path = 2; + ``` + event_time: + Time the object was observed, absent when unavailable. + + ```proto + optional google.protobuf.Timestamp event_time = 3; + ``` + """ + + __slots__ = ("storage_subscription_id", "object_path", "event_time") + + if TYPE_CHECKING: + + def __init__( + self, + *, + storage_subscription_id: ID | None = None, + object_path: str = "", + event_time: Timestamp | None = None, + ) -> None: + pass + + storage_subscription_id: ID | None + object_path: str + event_time: Timestamp | None + +_StorageSubscriptionsFields: TypeAlias = Literal["subscriptions"] + +class StorageSubscriptions(Message[_StorageSubscriptionsFields]): + """ + StorageSubscriptions contains notification subscriptions for a storage location. + + ```proto + message workflows.v1.StorageSubscriptions + ``` + + Attributes: + subscriptions: + ```proto + repeated workflows.v1.StorageSubscription subscriptions = 1; + ``` + """ + + __slots__ = ("subscriptions",) + + if TYPE_CHECKING: + + def __init__( + self, + *, + subscriptions: list[StorageSubscription] | None = None, + ) -> None: + pass + + subscriptions: list[StorageSubscription] + +_StorageSubscriptionEventFields: TypeAlias = Literal["id", "object_key", "type", "event_time", "received_at", "triggered_jobs", "storage_subscription_id"] + +class StorageSubscriptionEvent(Message[_StorageSubscriptionEventFields]): + """ + StorageSubscriptionEvent is an object notification received for a storage location. + + ```proto + message workflows.v1.StorageSubscriptionEvent + ``` + + Attributes: + id: + Unique identifier of the received event. + + ```proto + optional tilebox.v1.ID id = 1; + ``` + object_key: + Object key within the bucket or container, or path relative to the local directory root. + + ```proto + string object_key = 2; + ``` + type: + ```proto + workflows.v1.StorageEventType type = 3; + ``` + event_time: + Provider event time, absent when unavailable. + + ```proto + optional google.protobuf.Timestamp event_time = 4; + ``` + received_at: + Time Tilebox received the notification. + + ```proto + optional google.protobuf.Timestamp received_at = 5; + ``` + triggered_jobs: + Jobs triggered by this event. + + ```proto + repeated workflows.v1.TriggeredJob triggered_jobs = 6; + ``` + storage_subscription_id: + Subscription through which the event was received. + + ```proto + optional tilebox.v1.ID storage_subscription_id = 7; + ``` + """ + + __slots__ = ("id", "object_key", "type", "event_time", "received_at", "triggered_jobs", "storage_subscription_id") + + if TYPE_CHECKING: + + def __init__( + self, + *, + id: ID | None = None, + object_key: str = "", + type: StorageEventType | None = None, + event_time: Timestamp | None = None, + received_at: Timestamp | None = None, + triggered_jobs: list[TriggeredJob] | None = None, + storage_subscription_id: ID | None = None, + ) -> None: + pass + + id: ID | None + object_key: str + type: StorageEventType + event_time: Timestamp | None + received_at: Timestamp | None + triggered_jobs: list[TriggeredJob] + storage_subscription_id: ID | None + +_ListStorageSubscriptionEventsRequestFields: TypeAlias = Literal["storage_location_id", "page"] + +class ListStorageSubscriptionEventsRequest(Message[_ListStorageSubscriptionEventsRequestFields]): + """ + ListStorageSubscriptionEventsRequest requests events received for a storage location. + + ```proto + message workflows.v1.ListStorageSubscriptionEventsRequest + ``` + + Attributes: + storage_location_id: + ```proto + optional tilebox.v1.ID storage_location_id = 1; + ``` + page: + Pagination in descending receipt order; the default and maximum limit is 100. + + ```proto + optional tilebox.v1.Pagination page = 2; + ``` + """ + + __slots__ = ("storage_location_id", "page") + + if TYPE_CHECKING: + + def __init__( + self, + *, + storage_location_id: ID | None = None, + page: Pagination | None = None, + ) -> None: + pass + + storage_location_id: ID | None + page: Pagination | None + +_StorageSubscriptionEventsFields: TypeAlias = Literal["events", "next_page"] + +class StorageSubscriptionEvents(Message[_StorageSubscriptionEventsFields]): + """ + StorageSubscriptionEvents contains one page of received events, newest first. + + ```proto + message workflows.v1.StorageSubscriptionEvents + ``` + + Attributes: + events: + ```proto + repeated workflows.v1.StorageSubscriptionEvent events = 1; + ``` + next_page: + Pagination parameters for the next page, absent when there are no more events. + + ```proto + optional tilebox.v1.Pagination next_page = 2; + ``` + """ + + __slots__ = ("events", "next_page") + + if TYPE_CHECKING: + + def __init__( + self, + *, + events: list[StorageSubscriptionEvent] | None = None, + next_page: Pagination | None = None, + ) -> None: + pass + + events: list[StorageSubscriptionEvent] + next_page: Pagination | None + +_StorageLocationsFields: TypeAlias = Literal["locations"] + +class StorageLocations(Message[_StorageLocationsFields]): + """ + StorageLocations contains storage locations available for automation triggers. + + ```proto + message workflows.v1.StorageLocations + ``` + + Attributes: + locations: + ```proto + repeated workflows.v1.StorageLocation locations = 1; + ``` + """ + + __slots__ = ("locations",) + + if TYPE_CHECKING: + + def __init__( + self, + *, + locations: list[StorageLocation] | None = None, + ) -> None: + pass + + locations: list[StorageLocation] + +_TriggeredStorageEventFields: TypeAlias = Literal["storage_location_id", "type", "location"] + +class TriggeredStorageEvent(Message[_TriggeredStorageEventFields]): + """ + TriggeredStorageEvent contains the details of the concrete event that triggered a storage event trigger. + + ```proto + message workflows.v1.TriggeredStorageEvent + ``` + + Attributes: + storage_location_id: + The storage location that triggered the task + + ```proto + optional tilebox.v1.ID storage_location_id = 1; + ``` + type: + The type of the storage event, e.g. created + + ```proto + workflows.v1.StorageEventType type = 2; + ``` + location: + The object key/path that triggered the task, not the storage location's provider coordinates. + + ```proto + string location = 3; + ``` + """ + + __slots__ = ("storage_location_id", "type", "location") + + if TYPE_CHECKING: + + def __init__( + self, + *, + storage_location_id: ID | None = None, + type: StorageEventType | None = None, + location: str = "", + ) -> None: + pass + + storage_location_id: ID | None + type: StorageEventType + location: str + +class StorageType(Enum): + """ + StorageType identifies the storage provider and resource kind. + + ```proto + enum workflows.v1.StorageType + ``` + + Attributes: + UNSPECIFIED: + ```proto + STORAGE_TYPE_UNSPECIFIED = 0 + ``` + GCS: + Google Cloud Storage bucket + + ```proto + STORAGE_TYPE_GCS = 1 + ``` + AWS_S3: + Amazon Web Services S3 bucket + + ```proto + STORAGE_TYPE_AWS_S3 = 2 + ``` + LOCAL: + Local directory + + ```proto + STORAGE_TYPE_LOCAL = 3 + ``` + AZURE_BLOB: + Azure Blob Storage container + + ```proto + STORAGE_TYPE_AZURE_BLOB = 4 + ``` + """ + + UNSPECIFIED = 0 + GCS = 1 + AWS_S3 = 2 + LOCAL = 3 + AZURE_BLOB = 4 + +class StorageSubscriptionType(Enum): + """ + StorageSubscriptionType identifies the notification delivery provider. + + ```proto + enum workflows.v1.StorageSubscriptionType + ``` + + Attributes: + UNSPECIFIED: + ```proto + STORAGE_SUBSCRIPTION_TYPE_UNSPECIFIED = 0 + ``` + AWS_SNS: + Amazon SNS delivering object notifications from an AWS S3 bucket, or custom SNS message formats configured via message_format. + + ```proto + STORAGE_SUBSCRIPTION_TYPE_AWS_SNS = 1 + ``` + GOOGLE_PUBSUB: + Google Cloud Pub/Sub delivering notifications from a GCS bucket. + + ```proto + STORAGE_SUBSCRIPTION_TYPE_GOOGLE_PUBSUB = 2 + ``` + AZURE_EVENT_GRID: + Azure Event Grid delivering notifications from an Azure Blob Storage container. + + ```proto + STORAGE_SUBSCRIPTION_TYPE_AZURE_EVENT_GRID = 3 + ``` + TILEBOX_CLI: + Storage notifications sent by the Tilebox CLI. Typically for local storage locations. No delivery configuration is required. + Use StorageLocationService.NotifyObjectCreated, or POST JSON containing object_path and optional RFC3339 event_time + to /v1/storage-notifications/tilebox-cli/. + object_path is relative to the directory root and uses slash separators, without dot or parent components. + Authenticate with Authorization: Bearer ; the organization must manage the storage location. + The standalone on-orbit runtime does not require authentication. + Successful HTTP deliveries return 204 after jobs and history are committed. Retries can create duplicate jobs. + + ```proto + STORAGE_SUBSCRIPTION_TYPE_TILEBOX_CLI = 4 + ``` + """ + + UNSPECIFIED = 0 + AWS_SNS = 1 + GOOGLE_PUBSUB = 2 + AZURE_EVENT_GRID = 3 + TILEBOX_CLI = 4 + +class StorageEventType(Enum): + """ + StorageEventType specifies the type of event that triggered the task. + + ```proto + enum workflows.v1.StorageEventType + ``` + + Attributes: + UNSPECIFIED: + ```proto + STORAGE_EVENT_TYPE_UNSPECIFIED = 0 + ``` + CREATED: + ```proto + STORAGE_EVENT_TYPE_CREATED = 1 + ``` + """ + + UNSPECIFIED = 0 + CREATED = 1 + + +_DESC = file_desc( + b'\n#workflows/v1/storage_location.proto\x12\x0cworkflows.v1\x1a\x1bgoogle/protobuf/empty.proto\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\x13tilebox/v1/id.proto\x1a\x16tilebox/v1/query.proto"\xde\x01\n\x0fStorageLocation\x12\x1e\n\x02id\x18\x01 \x01(\x0b2\x0e.tilebox.v1.IDR\x02id\x12\x1e\n\x08location\x18\x02 \x01(\tR\x08locationB\x02\x18\x01\x121\n\x04type\x18\x03 \x01(\x0e2\x19.workflows.v1.StorageTypeR\x04typeB\x02\x18\x01\x12\x12\n\x04name\x18\x04 \x01(\tR\x04name\x12D\n\treference\x18\x05 \x01(\x0b2&.workflows.v1.StorageLocationReferenceR\treference"\xc7\x02\n\x18StorageLocationReference\x12F\n\raws_s3_bucket\x18\x01 \x01(\x0b2".workflows.v1.AWSS3BucketReferenceR\x0bawsS3Bucket\x12?\n\ngcs_bucket\x18\x02 \x01(\x0b2 .workflows.v1.GCSBucketReferenceR\tgcsBucket\x12?\n\nazure_blob\x18\x03 \x01(\x0b2 .workflows.v1.AzureBlobReferenceR\tazureBlob\x122\n\x05local\x18\x04 \x01(\x0b2\x1c.workflows.v1.LocalReferenceR\x05local\x12-\n\x04type\x18\x05 \x01(\x0e2\x19.workflows.v1.StorageTypeR\x04type"F\n\x14AWSS3BucketReference\x12\x16\n\x06bucket\x18\x01 \x01(\tR\x06bucket\x12\x16\n\x06region\x18\x02 \x01(\tR\x06region"g\n\x12GCSBucketReference\x12\x16\n\x06bucket\x18\x01 \x01(\tR\x06bucket\x12\x1d\n\nproject_id\x18\x02 \x01(\tR\tprojectId\x12\x1a\n\x08location\x18\x03 \x01(\tR\x08location"\x89\x01\n\x12AzureBlobReference\x12=\n\x1bstorage_account_resource_id\x18\x01 \x01(\tR\x18storageAccountResourceId\x12\x1c\n\tcontainer\x18\x02 \x01(\tR\tcontainer\x12\x16\n\x06region\x18\x03 \x01(\tR\x06region"$\n\x0eLocalReference\x12\x12\n\x04path\x18\x01 \x01(\tR\x04path"\x8d\x01\n\x0cTriggeredJob\x12%\n\x06job_id\x18\x01 \x01(\x0b2\x0e.tilebox.v1.IDR\x05jobId\x123\n\rautomation_id\x18\x02 \x01(\x0b2\x0e.tilebox.v1.IDR\x0cautomationId\x12!\n\x0cglob_pattern\x18\x03 \x01(\tR\x0bglobPattern"R\n\rTriggeredJobs\x12A\n\x0etriggered_jobs\x18\x01 \x03(\x0b2\x1a.workflows.v1.TriggeredJobR\rtriggeredJobs"x\n\x1cCreateStorageLocationRequest\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\x12D\n\treference\x18\x02 \x01(\x0b2&.workflows.v1.StorageLocationReferenceR\treference"r\n\x1cUpdateStorageLocationRequest\x12>\n\x13storage_location_id\x18\x01 \x01(\x0b2\x0e.tilebox.v1.IDR\x11storageLocationId\x12\x12\n\x04name\x18\x02 \x01(\tR\x04name"_\n\x19AWSSNSStorageSubscription\x12\x1b\n\ttopic_arn\x18\x01 \x01(\tR\x08topicArn\x12%\n\x0emessage_format\x18\x02 \x01(\tR\rmessageFormat"\x95\x01\n\x1fGooglePubSubStorageSubscription\x12"\n\x0csubscription\x18\x01 \x01(\tR\x0csubscription\x122\n\x15service_account_email\x18\x02 \x01(\tR\x13serviceAccountEmail\x12\x1a\n\x08audience\x18\x03 \x01(\tR\x08audience"\xae\x01\n!AzureEventGridStorageSubscription\x122\n\x15webhook_secret_header\x18\x01 \x01(\tR\x13webhookSecretHeader\x12%\n\x0ewebhook_secret\x18\x02 \x01(\tR\rwebhookSecret\x12.\n\x13webhook_secret_hash\x18\x03 \x01(\x0cR\x11webhookSecretHash"\xf8\x03\n\x13StorageSubscription\x12\x1e\n\x02id\x18\x01 \x01(\x0b2\x0e.tilebox.v1.IDR\x02id\x12>\n\x13storage_location_id\x18\x02 \x01(\x0b2\x0e.tilebox.v1.IDR\x11storageLocationId\x129\n\x04type\x18\x03 \x01(\x0e2%.workflows.v1.StorageSubscriptionTypeR\x04type\x12\x1a\n\x08endpoint\x18\x04 \x01(\tR\x08endpoint\x129\n\ncreated_at\x18\x06 \x01(\x0b2\x1a.google.protobuf.TimestampR\tcreatedAt\x12@\n\x07aws_sns\x18\x07 \x01(\x0b2\'.workflows.v1.AWSSNSStorageSubscriptionR\x06awsSns\x12R\n\rgoogle_pubsub\x18\x08 \x01(\x0b2-.workflows.v1.GooglePubSubStorageSubscriptionR\x0cgooglePubsub\x12Y\n\x10azure_event_grid\x18\t \x01(\x0b2/.workflows.v1.AzureEventGridStorageSubscriptionR\x0eazureEventGrid"\x8e\x03\n CreateStorageSubscriptionRequest\x12>\n\x13storage_location_id\x18\x01 \x01(\x0b2\x0e.tilebox.v1.IDR\x11storageLocationId\x129\n\x04type\x18\x02 \x01(\x0e2%.workflows.v1.StorageSubscriptionTypeR\x04type\x12@\n\x07aws_sns\x18\x03 \x01(\x0b2\'.workflows.v1.AWSSNSStorageSubscriptionR\x06awsSns\x12R\n\rgoogle_pubsub\x18\x04 \x01(\x0b2-.workflows.v1.GooglePubSubStorageSubscriptionR\x0cgooglePubsub\x12Y\n\x10azure_event_grid\x18\x05 \x01(\x0b2/.workflows.v1.AzureEventGridStorageSubscriptionR\x0eazureEventGrid"a\n\x1fListStorageSubscriptionsRequest\x12>\n\x13storage_location_id\x18\x01 \x01(\x0b2\x0e.tilebox.v1.IDR\x11storageLocationId"\xc0\x01\n\x1aNotifyObjectCreatedRequest\x12F\n\x17storage_subscription_id\x18\x01 \x01(\x0b2\x0e.tilebox.v1.IDR\x15storageSubscriptionId\x12\x1f\n\x0bobject_path\x18\x02 \x01(\tR\nobjectPath\x129\n\nevent_time\x18\x03 \x01(\x0b2\x1a.google.protobuf.TimestampR\teventTime"_\n\x14StorageSubscriptions\x12G\n\rsubscriptions\x18\x01 \x03(\x0b2!.workflows.v1.StorageSubscriptionR\rsubscriptions"\x90\x03\n\x18StorageSubscriptionEvent\x12\x1e\n\x02id\x18\x01 \x01(\x0b2\x0e.tilebox.v1.IDR\x02id\x12\x1d\n\nobject_key\x18\x02 \x01(\tR\tobjectKey\x122\n\x04type\x18\x03 \x01(\x0e2\x1e.workflows.v1.StorageEventTypeR\x04type\x129\n\nevent_time\x18\x04 \x01(\x0b2\x1a.google.protobuf.TimestampR\teventTime\x12;\n\x0breceived_at\x18\x05 \x01(\x0b2\x1a.google.protobuf.TimestampR\nreceivedAt\x12A\n\x0etriggered_jobs\x18\x06 \x03(\x0b2\x1a.workflows.v1.TriggeredJobR\rtriggeredJobs\x12F\n\x17storage_subscription_id\x18\x07 \x01(\x0b2\x0e.tilebox.v1.IDR\x15storageSubscriptionId"\x99\x01\n$ListStorageSubscriptionEventsRequest\x12>\n\x13storage_location_id\x18\x01 \x01(\x0b2\x0e.tilebox.v1.IDR\x11storageLocationId\x121\n\x04page\x18\x02 \x01(\x0b2\x16.tilebox.v1.PaginationR\x04pageB\x05\xaa\x01\x02\x08\x01"\x97\x01\n\x19StorageSubscriptionEvents\x12>\n\x06events\x18\x01 \x03(\x0b2&.workflows.v1.StorageSubscriptionEventR\x06events\x12:\n\tnext_page\x18\x02 \x01(\x0b2\x16.tilebox.v1.PaginationR\x08nextPageB\x05\xaa\x01\x02\x08\x01"O\n\x10StorageLocations\x12;\n\tlocations\x18\x01 \x03(\x0b2\x1d.workflows.v1.StorageLocationR\tlocations"\xa7\x01\n\x15TriggeredStorageEvent\x12>\n\x13storage_location_id\x18\x01 \x01(\x0b2\x0e.tilebox.v1.IDR\x11storageLocationId\x122\n\x04type\x18\x02 \x01(\x0e2\x1e.workflows.v1.StorageEventTypeR\x04type\x12\x1a\n\x08location\x18\x03 \x01(\tR\x08location*\x8f\x01\n\x0bStorageType\x12\x1c\n\x18STORAGE_TYPE_UNSPECIFIED\x10\x00\x12\x14\n\x10STORAGE_TYPE_GCS\x10\x01\x12\x17\n\x13STORAGE_TYPE_AWS_S3\x10\x02\x12\x16\n\x12STORAGE_TYPE_LOCAL\x10\x03\x12\x1b\n\x17STORAGE_TYPE_AZURE_BLOB\x10\x04*\xf3\x01\n\x17StorageSubscriptionType\x12)\n%STORAGE_SUBSCRIPTION_TYPE_UNSPECIFIED\x10\x00\x12%\n!STORAGE_SUBSCRIPTION_TYPE_AWS_SNS\x10\x01\x12+\n\'STORAGE_SUBSCRIPTION_TYPE_GOOGLE_PUBSUB\x10\x02\x12.\n*STORAGE_SUBSCRIPTION_TYPE_AZURE_EVENT_GRID\x10\x03\x12)\n%STORAGE_SUBSCRIPTION_TYPE_TILEBOX_CLI\x10\x04*V\n\x10StorageEventType\x12"\n\x1eSTORAGE_EVENT_TYPE_UNSPECIFIED\x10\x00\x12\x1e\n\x1aSTORAGE_EVENT_TYPE_CREATED\x10\x012\xfe\x07\n\x16StorageLocationService\x12N\n\x14ListStorageLocations\x12\x16.google.protobuf.Empty\x1a\x1e.workflows.v1.StorageLocations\x12C\n\x12GetStorageLocation\x12\x0e.tilebox.v1.ID\x1a\x1d.workflows.v1.StorageLocation\x12b\n\x15CreateStorageLocation\x12*.workflows.v1.CreateStorageLocationRequest\x1a\x1d.workflows.v1.StorageLocation\x12b\n\x15UpdateStorageLocation\x12*.workflows.v1.UpdateStorageLocationRequest\x1a\x1d.workflows.v1.StorageLocation\x12?\n\x15DeleteStorageLocation\x12\x0e.tilebox.v1.ID\x1a\x16.google.protobuf.Empty\x12n\n\x19CreateStorageSubscription\x12..workflows.v1.CreateStorageSubscriptionRequest\x1a!.workflows.v1.StorageSubscription\x12m\n\x18ListStorageSubscriptions\x12-.workflows.v1.ListStorageSubscriptionsRequest\x1a".workflows.v1.StorageSubscriptions\x12K\n\x16GetStorageSubscription\x12\x0e.tilebox.v1.ID\x1a!.workflows.v1.StorageSubscription\x12C\n\x19DeleteStorageSubscription\x12\x0e.tilebox.v1.ID\x1a\x16.google.protobuf.Empty\x12|\n\x1dListStorageSubscriptionEvents\x122.workflows.v1.ListStorageSubscriptionEventsRequest\x1a\'.workflows.v1.StorageSubscriptionEvents\x12W\n\x13NotifyObjectCreated\x12(.workflows.v1.NotifyObjectCreatedRequest\x1a\x16.google.protobuf.EmptyB\x05\x92\x03\x02\x08\x02b\x08editionsp\xe8\x07', + [ + empty_pb.desc(), + timestamp_pb.desc(), + id_pb.desc(), + query_pb.desc(), + ], + { + "StorageLocation": StorageLocation, + "StorageLocationReference": StorageLocationReference, + "AWSS3BucketReference": AWSS3BucketReference, + "GCSBucketReference": GCSBucketReference, + "AzureBlobReference": AzureBlobReference, + "LocalReference": LocalReference, + "TriggeredJob": TriggeredJob, + "TriggeredJobs": TriggeredJobs, + "CreateStorageLocationRequest": CreateStorageLocationRequest, + "UpdateStorageLocationRequest": UpdateStorageLocationRequest, + "AWSSNSStorageSubscription": AWSSNSStorageSubscription, + "GooglePubSubStorageSubscription": GooglePubSubStorageSubscription, + "AzureEventGridStorageSubscription": AzureEventGridStorageSubscription, + "StorageSubscription": StorageSubscription, + "CreateStorageSubscriptionRequest": CreateStorageSubscriptionRequest, + "ListStorageSubscriptionsRequest": ListStorageSubscriptionsRequest, + "NotifyObjectCreatedRequest": NotifyObjectCreatedRequest, + "StorageSubscriptions": StorageSubscriptions, + "StorageSubscriptionEvent": StorageSubscriptionEvent, + "ListStorageSubscriptionEventsRequest": ListStorageSubscriptionEventsRequest, + "StorageSubscriptionEvents": StorageSubscriptionEvents, + "StorageLocations": StorageLocations, + "TriggeredStorageEvent": TriggeredStorageEvent, + "StorageType": StorageType, + "StorageSubscriptionType": StorageSubscriptionType, + "StorageEventType": StorageEventType, + }, +) + + +def desc() -> DescFile: + """Returns the descriptor for the file `workflows/v1/storage_location.proto`.""" + return _DESC diff --git a/tilebox-workflows/tilebox/workflows/v1/task_connect.py b/tilebox-workflows/tilebox/workflows/v1/task_connect.py new file mode 100644 index 0000000..50b373f --- /dev/null +++ b/tilebox-workflows/tilebox/workflows/v1/task_connect.py @@ -0,0 +1,380 @@ +# Generated from workflows/v1/task.proto. DO NOT EDIT. +# Generated by protoc-gen-connectrpc-py v0.12.1 with parameter "map_imports=datasets/**:tilebox,map_imports=workflows/**:tilebox,map_imports=tilebox/**:_tilebox.proto,map_imports=opentelemetry/**:tilebox.workflows._otel". +# ruff: noqa: PGH004 +# ruff: noqa +# fmt: off + +from __future__ import annotations + +from typing import Protocol, TYPE_CHECKING + +from connectrpc.client import ConnectClient, ConnectClientSync +from connectrpc.code import Code +from connectrpc.errors import ConnectError +from connectrpc.method import IdempotencyLevel, MethodInfo +from connectrpc.server import ConnectASGIApplication, ConnectWSGIApplication, DEFAULT_READ_MAX_BYTES, Endpoint, EndpointSync +from protobuf import DescService +from tilebox.workflows.v1 import task_pb +from tilebox.workflows.v1.core_pb import TaskLease +from tilebox.workflows.v1.task_pb import NextTaskRequest, NextTaskResponse, TaskFailedRequest, TaskLeaseRequest, TaskStateResponse + +if TYPE_CHECKING: + from collections.abc import AsyncGenerator, Iterable, Mapping + + from connectrpc.codec import Codec + from connectrpc.compression import Compression + from connectrpc.interceptor import Interceptor, InterceptorSync + from connectrpc.request import Headers, RequestContext + + +class TaskService(Protocol): + """ + A service for task runners to communicate with the workflows service. + """ + async def next_task(self, request: NextTaskRequest, ctx: RequestContext[NextTaskRequest, NextTaskResponse], /) -> NextTaskResponse: + """ + NextTask marks a task as computed and asks for the next task to run. + If no task marked as computed is sent, it is assumed that the task runner just started up or was idling so + the task server will send a task to run using a work-stealing algorithm. + If a task marked as computed is sent, the task server will send a next task to run using a depth first execution + algorithm, and only fall back to work-stealing if otherwise no tasks are available. + If the next_task_to_run field of the request is not set, a next task will never be returned, but a task + can still be marked as computed this way. + """ + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') + + async def task_failed(self, request: TaskFailedRequest, ctx: RequestContext[TaskFailedRequest, TaskStateResponse], /) -> TaskStateResponse: + """ + TaskFailed tells the task server that we have failed to compute a task. + The task server will then mark the task as queued or failed, depending on the retry policy, + and possibly cancel the job. + If a task runner wants to continue executing tasks, it should afterwards fetch a new one using GetTaskToRun. + """ + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') + + async def extend_task_lease(self, request: TaskLeaseRequest, ctx: RequestContext[TaskLeaseRequest, TaskLease], /) -> TaskLease: + """ + ExtendTaskLease is called by the task runner to extend the lease on a task. + On success, the response will contain the new lease expiration time. + If the task does not need to be extended, the response will be empty. + """ + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') + + @classmethod + def desc(cls) -> DescService: + """Returns the descriptor for this service.""" + return next(s for s in task_pb.desc().services if s.type_name == 'workflows.v1.TaskService') + +class TaskServiceASGIApplication(ConnectASGIApplication[TaskService]): + def __init__( + self, + service: TaskService | AsyncGenerator[TaskService], + *, + interceptors: Iterable[Interceptor] = (), + read_max_bytes: int | None = DEFAULT_READ_MAX_BYTES, + compressions: Iterable[Compression] | None = None, + codecs: Iterable[Codec] | None = None, + ) -> None: + super().__init__( + service=service, + endpoints=lambda svc: { + "/workflows.v1.TaskService/NextTask": Endpoint.unary( + method=MethodInfo( + name="NextTask", + service_name="workflows.v1.TaskService", + input=NextTaskRequest, + output=NextTaskResponse, + idempotency_level=IdempotencyLevel.UNKNOWN, + ), + function=svc.next_task, + ), + "/workflows.v1.TaskService/TaskFailed": Endpoint.unary( + method=MethodInfo( + name="TaskFailed", + service_name="workflows.v1.TaskService", + input=TaskFailedRequest, + output=TaskStateResponse, + idempotency_level=IdempotencyLevel.UNKNOWN, + ), + function=svc.task_failed, + ), + "/workflows.v1.TaskService/ExtendTaskLease": Endpoint.unary( + method=MethodInfo( + name="ExtendTaskLease", + service_name="workflows.v1.TaskService", + input=TaskLeaseRequest, + output=TaskLease, + idempotency_level=IdempotencyLevel.UNKNOWN, + ), + function=svc.extend_task_lease, + ), + }, + interceptors=interceptors, + read_max_bytes=read_max_bytes, + compressions=compressions, + codecs=codecs, + ) + + @property + def path(self) -> str: + """Returns the URL path to mount the application to when serving multiple applications.""" + return "/workflows.v1.TaskService" + + +class TaskServiceClient(ConnectClient): + """ + A service for task runners to communicate with the workflows service. + """ + async def next_task( + self, + request: NextTaskRequest, + *, + headers: Headers | Mapping[str, str] | None = None, + timeout_ms: int | None = None, + ) -> NextTaskResponse: + """ + NextTask marks a task as computed and asks for the next task to run. + If no task marked as computed is sent, it is assumed that the task runner just started up or was idling so + the task server will send a task to run using a work-stealing algorithm. + If a task marked as computed is sent, the task server will send a next task to run using a depth first execution + algorithm, and only fall back to work-stealing if otherwise no tasks are available. + If the next_task_to_run field of the request is not set, a next task will never be returned, but a task + can still be marked as computed this way. + """ + return await self.execute_unary( + request=request, + method=MethodInfo( + name="NextTask", + service_name="workflows.v1.TaskService", + input=NextTaskRequest, + output=NextTaskResponse, + idempotency_level=IdempotencyLevel.UNKNOWN, + ), + headers=headers, + timeout_ms=timeout_ms, + ) + + async def task_failed( + self, + request: TaskFailedRequest, + *, + headers: Headers | Mapping[str, str] | None = None, + timeout_ms: int | None = None, + ) -> TaskStateResponse: + """ + TaskFailed tells the task server that we have failed to compute a task. + The task server will then mark the task as queued or failed, depending on the retry policy, + and possibly cancel the job. + If a task runner wants to continue executing tasks, it should afterwards fetch a new one using GetTaskToRun. + """ + return await self.execute_unary( + request=request, + method=MethodInfo( + name="TaskFailed", + service_name="workflows.v1.TaskService", + input=TaskFailedRequest, + output=TaskStateResponse, + idempotency_level=IdempotencyLevel.UNKNOWN, + ), + headers=headers, + timeout_ms=timeout_ms, + ) + + async def extend_task_lease( + self, + request: TaskLeaseRequest, + *, + headers: Headers | Mapping[str, str] | None = None, + timeout_ms: int | None = None, + ) -> TaskLease: + """ + ExtendTaskLease is called by the task runner to extend the lease on a task. + On success, the response will contain the new lease expiration time. + If the task does not need to be extended, the response will be empty. + """ + return await self.execute_unary( + request=request, + method=MethodInfo( + name="ExtendTaskLease", + service_name="workflows.v1.TaskService", + input=TaskLeaseRequest, + output=TaskLease, + idempotency_level=IdempotencyLevel.UNKNOWN, + ), + headers=headers, + timeout_ms=timeout_ms, + ) + +class TaskServiceSync(Protocol): + """ + A service for task runners to communicate with the workflows service. + """ + def next_task(self, request: NextTaskRequest, ctx: RequestContext[NextTaskRequest, NextTaskResponse], /) -> NextTaskResponse: + """ + NextTask marks a task as computed and asks for the next task to run. + If no task marked as computed is sent, it is assumed that the task runner just started up or was idling so + the task server will send a task to run using a work-stealing algorithm. + If a task marked as computed is sent, the task server will send a next task to run using a depth first execution + algorithm, and only fall back to work-stealing if otherwise no tasks are available. + If the next_task_to_run field of the request is not set, a next task will never be returned, but a task + can still be marked as computed this way. + """ + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') + + def task_failed(self, request: TaskFailedRequest, ctx: RequestContext[TaskFailedRequest, TaskStateResponse], /) -> TaskStateResponse: + """ + TaskFailed tells the task server that we have failed to compute a task. + The task server will then mark the task as queued or failed, depending on the retry policy, + and possibly cancel the job. + If a task runner wants to continue executing tasks, it should afterwards fetch a new one using GetTaskToRun. + """ + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') + + def extend_task_lease(self, request: TaskLeaseRequest, ctx: RequestContext[TaskLeaseRequest, TaskLease], /) -> TaskLease: + """ + ExtendTaskLease is called by the task runner to extend the lease on a task. + On success, the response will contain the new lease expiration time. + If the task does not need to be extended, the response will be empty. + """ + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') + + @classmethod + def desc(cls) -> DescService: + """Returns the descriptor for this service.""" + return next(s for s in task_pb.desc().services if s.type_name == 'workflows.v1.TaskService') + +class TaskServiceWSGIApplication(ConnectWSGIApplication): + def __init__( + self, + service: TaskServiceSync, + interceptors: Iterable[InterceptorSync] = (), + read_max_bytes: int | None = DEFAULT_READ_MAX_BYTES, + compressions: Iterable[Compression] | None = None, + codecs: Iterable[Codec] | None = None, + ) -> None: + super().__init__( + endpoints={ + "/workflows.v1.TaskService/NextTask": EndpointSync.unary( + method=MethodInfo( + name="NextTask", + service_name="workflows.v1.TaskService", + input=NextTaskRequest, + output=NextTaskResponse, + idempotency_level=IdempotencyLevel.UNKNOWN, + ), + function=service.next_task, + ), + "/workflows.v1.TaskService/TaskFailed": EndpointSync.unary( + method=MethodInfo( + name="TaskFailed", + service_name="workflows.v1.TaskService", + input=TaskFailedRequest, + output=TaskStateResponse, + idempotency_level=IdempotencyLevel.UNKNOWN, + ), + function=service.task_failed, + ), + "/workflows.v1.TaskService/ExtendTaskLease": EndpointSync.unary( + method=MethodInfo( + name="ExtendTaskLease", + service_name="workflows.v1.TaskService", + input=TaskLeaseRequest, + output=TaskLease, + idempotency_level=IdempotencyLevel.UNKNOWN, + ), + function=service.extend_task_lease, + ), + }, + interceptors=interceptors, + read_max_bytes=read_max_bytes, + compressions=compressions, + codecs=codecs, + ) + + @property + def path(self) -> str: + """Returns the URL path to mount the application to when serving multiple applications.""" + return "/workflows.v1.TaskService" + + +class TaskServiceClientSync(ConnectClientSync): + """ + A service for task runners to communicate with the workflows service. + """ + def next_task( + self, + request: NextTaskRequest, + *, + headers: Headers | Mapping[str, str] | None = None, + timeout_ms: int | None = None, + ) -> NextTaskResponse: + """ + NextTask marks a task as computed and asks for the next task to run. + If no task marked as computed is sent, it is assumed that the task runner just started up or was idling so + the task server will send a task to run using a work-stealing algorithm. + If a task marked as computed is sent, the task server will send a next task to run using a depth first execution + algorithm, and only fall back to work-stealing if otherwise no tasks are available. + If the next_task_to_run field of the request is not set, a next task will never be returned, but a task + can still be marked as computed this way. + """ + return self.execute_unary( + request=request, + method=MethodInfo( + name="NextTask", + service_name="workflows.v1.TaskService", + input=NextTaskRequest, + output=NextTaskResponse, + idempotency_level=IdempotencyLevel.UNKNOWN, + ), + headers=headers, + timeout_ms=timeout_ms, + ) + def task_failed( + self, + request: TaskFailedRequest, + *, + headers: Headers | Mapping[str, str] | None = None, + timeout_ms: int | None = None, + ) -> TaskStateResponse: + """ + TaskFailed tells the task server that we have failed to compute a task. + The task server will then mark the task as queued or failed, depending on the retry policy, + and possibly cancel the job. + If a task runner wants to continue executing tasks, it should afterwards fetch a new one using GetTaskToRun. + """ + return self.execute_unary( + request=request, + method=MethodInfo( + name="TaskFailed", + service_name="workflows.v1.TaskService", + input=TaskFailedRequest, + output=TaskStateResponse, + idempotency_level=IdempotencyLevel.UNKNOWN, + ), + headers=headers, + timeout_ms=timeout_ms, + ) + def extend_task_lease( + self, + request: TaskLeaseRequest, + *, + headers: Headers | Mapping[str, str] | None = None, + timeout_ms: int | None = None, + ) -> TaskLease: + """ + ExtendTaskLease is called by the task runner to extend the lease on a task. + On success, the response will contain the new lease expiration time. + If the task does not need to be extended, the response will be empty. + """ + return self.execute_unary( + request=request, + method=MethodInfo( + name="ExtendTaskLease", + service_name="workflows.v1.TaskService", + input=TaskLeaseRequest, + output=TaskLease, + idempotency_level=IdempotencyLevel.UNKNOWN, + ), + headers=headers, + timeout_ms=timeout_ms, + ) diff --git a/tilebox-workflows/tilebox/workflows/v1/task_pb.py b/tilebox-workflows/tilebox/workflows/v1/task_pb.py new file mode 100644 index 0000000..1f9571e --- /dev/null +++ b/tilebox-workflows/tilebox/workflows/v1/task_pb.py @@ -0,0 +1,399 @@ +# Generated from workflows/v1/task.proto. DO NOT EDIT. +# Generated by protoc-gen-py v0.6.0 with parameter "init_files=false,map_imports=datasets/**:tilebox,map_imports=workflows/**:tilebox,map_imports=tilebox/**:_tilebox.proto,map_imports=opentelemetry/**:tilebox.workflows._otel". +# ruff: noqa: PGH004 +# ruff: noqa +# fmt: off + +from __future__ import annotations + +from typing import Literal, TYPE_CHECKING, TypeAlias + +from _tilebox.proto.tilebox.v1 import id_pb +from protobuf import Message +from protobuf._codegen import file_desc +from protobuf.wkt.google.protobuf import duration_pb +from tilebox.workflows.v1 import core_pb + +if TYPE_CHECKING: + from _tilebox.proto.tilebox.v1.id_pb import ID + from protobuf import DescFile + from protobuf.wkt.google.protobuf.duration_pb import Duration + from tilebox.workflows.v1.core_pb import Progress, SingleTaskSubmission, Task, TaskIdentifier, TaskState, TaskSubmissions + + +_NextTaskRequestFields: TypeAlias = Literal["computed_task", "next_task_to_run"] + +class NextTaskRequest(Message[_NextTaskRequestFields]): + """ + NextTaskRequest is the request for requesting the next task to run and marking a task as computed. + + ```proto + message workflows.v1.NextTaskRequest + ``` + + Attributes: + computed_task: + The task that has been computed. If not set, the next task will + + ```proto + optional workflows.v1.ComputedTask computed_task = 1; + ``` + next_task_to_run: + The capabilities of the task runner, and therefore the potential tasks that can be run by that task runner. + + ```proto + optional workflows.v1.NextTaskToRun next_task_to_run = 2; + ``` + """ + + __slots__ = ("computed_task", "next_task_to_run") + + if TYPE_CHECKING: + + def __init__( + self, + *, + computed_task: ComputedTask | None = None, + next_task_to_run: NextTaskToRun | None = None, + ) -> None: + pass + + computed_task: ComputedTask | None + next_task_to_run: NextTaskToRun | None + +_NextTaskToRunFields: TypeAlias = Literal["cluster_slug", "identifiers"] + +class NextTaskToRun(Message[_NextTaskToRunFields]): + """ + NextTaskToRun is a message specifying the capabilities of the task runner, and therefore the potential + tasks that can be run by that task runner. + + ```proto + message workflows.v1.NextTaskToRun + ``` + + Attributes: + cluster_slug: + The cluster that this task runner is running on. + + ```proto + string cluster_slug = 1; + ``` + identifiers: + The concrete task addresses this runner can execute. Scoped capabilities include workflow_id; + omission advertises only an unscoped address. During the migration/introduction phase of scoped tasks, + runners advertise legacy unscoped aliases as separate entries. + + ```proto + repeated workflows.v1.TaskIdentifier identifiers = 2; + ``` + """ + + __slots__ = ("cluster_slug", "identifiers") + + if TYPE_CHECKING: + + def __init__( + self, + *, + cluster_slug: str = "", + identifiers: list[TaskIdentifier] | None = None, + ) -> None: + pass + + cluster_slug: str + identifiers: list[TaskIdentifier] + +_ComputedTaskFields: TypeAlias = Literal["id", "display", "sub_tasks", "progress_updates", "legacy_sub_tasks"] + +class ComputedTask(Message[_ComputedTaskFields]): + """ + ComputedTask is a message specifying a task that has been computed by the task runner. + + ```proto + message workflows.v1.ComputedTask + ``` + + Attributes: + id: + The id of the task that has been computed. + + ```proto + optional tilebox.v1.ID id = 1; + ``` + display: + A display name for the task that has been computed for visualization purposes. + If not set, the display message specified upon task submission will be kept. + + ```proto + string display = 2; + ``` + sub_tasks: + A list of sub-tasks that the just computed task spawned. + Workflow scope must be explicitly set in the sub-task identifiers, omission means explicitly unscoped. + Cross-workflow child submissions are also allowed. + + ```proto + optional workflows.v1.TaskSubmissions sub_tasks = 5; + ``` + progress_updates: + A list of progress updates that the computed task wants to report. + + ```proto + repeated workflows.v1.Progress progress_updates = 4; + ``` + legacy_sub_tasks: + A list of sub-tasks that the just computed task spawned. This field is deprecated and will be removed in the + future. Kept around for backwards compatibility. Workflow scope follows the same rules as sub_tasks. + + ```proto + repeated workflows.v1.SingleTaskSubmission legacy_sub_tasks = 3 [deprecated = true]; + ``` + """ + + __slots__ = ("id", "display", "sub_tasks", "progress_updates", "legacy_sub_tasks") + + if TYPE_CHECKING: + + def __init__( + self, + *, + id: ID | None = None, + display: str = "", + sub_tasks: TaskSubmissions | None = None, + progress_updates: list[Progress] | None = None, + legacy_sub_tasks: list[SingleTaskSubmission] | None = None, + ) -> None: + pass + + id: ID | None + display: str + sub_tasks: TaskSubmissions | None + progress_updates: list[Progress] + legacy_sub_tasks: list[SingleTaskSubmission] + +_IdlingResponseFields: TypeAlias = Literal["suggested_idling_duration"] + +class IdlingResponse(Message[_IdlingResponseFields]): + """ + IdlingResponse is set in the response of a NextTaskRequest if no task was returned, indicating that the requesting + task runner should idle. + + ```proto + message workflows.v1.IdlingResponse + ``` + + Attributes: + suggested_idling_duration: + The suggested duration the task runner should idle for before sending another request for a task. + + ```proto + optional google.protobuf.Duration suggested_idling_duration = 1; + ``` + """ + + __slots__ = ("suggested_idling_duration",) + + if TYPE_CHECKING: + + def __init__( + self, + *, + suggested_idling_duration: Duration | None = None, + ) -> None: + pass + + suggested_idling_duration: Duration | None + +_NextTaskResponseFields: TypeAlias = Literal["next_task", "idling"] + +class NextTaskResponse(Message[_NextTaskResponseFields]): + """ + NextTaskResponse is the response to the NextTask request. + If the NextTask request contained a request for a task, the response will either contain a next_task, or an idling + response, but not both. + If the NextTask request did not contain a request for a next task (indicating the the runner is about to shut down), + the response will be empty. + + ```proto + message workflows.v1.NextTaskResponse + ``` + + Attributes: + next_task: + ```proto + optional workflows.v1.Task next_task = 1; + ``` + idling: + ```proto + optional workflows.v1.IdlingResponse idling = 2; + ``` + """ + + __slots__ = ("next_task", "idling") + + if TYPE_CHECKING: + + def __init__( + self, + *, + next_task: Task | None = None, + idling: IdlingResponse | None = None, + ) -> None: + pass + + next_task: Task | None + idling: IdlingResponse | None + +_TaskFailedRequestFields: TypeAlias = Literal["task_id", "display", "was_workflow_error", "progress_updates"] + +class TaskFailedRequest(Message[_TaskFailedRequestFields]): + """ + TaskFailedRequest is the request for marking a task as failed. + + ```proto + message workflows.v1.TaskFailedRequest + ``` + + Attributes: + task_id: + The id of the task that has failed. + + ```proto + optional tilebox.v1.ID task_id = 1; + ``` + display: + A display name for the task that has failed for visualization purposes. + + ```proto + string display = 2; + ``` + was_workflow_error: + Whether the failure was caused by the workflow itself, as opposed to a failure in the task runner + (e.g. a requested runner shutdown). If the task failure was not a workflow error, it won't be marked as failed, + but will instead be QUEUED again. Since the task didn't actually fail, this does not count as a retry. + + ```proto + bool was_workflow_error = 3; + ``` + progress_updates: + A list of progress updates that the failed task wants to report. Even if a task fails, it could + still have made some progress before failing, and we want to reflect that progress. + Since progress updates are idempotent, retrying the task and getting the same progress update again + is not an issue. + + ```proto + repeated workflows.v1.Progress progress_updates = 4; + ``` + """ + + __slots__ = ("task_id", "display", "was_workflow_error", "progress_updates") + + if TYPE_CHECKING: + + def __init__( + self, + *, + task_id: ID | None = None, + display: str = "", + was_workflow_error: bool = False, + progress_updates: list[Progress] | None = None, + ) -> None: + pass + + task_id: ID | None + display: str + was_workflow_error: bool + progress_updates: list[Progress] + +_TaskStateResponseFields: TypeAlias = Literal["state"] + +class TaskStateResponse(Message[_TaskStateResponseFields]): + """ + TaskStateResponse is the response to the TaskFailed request, + indicating the current state of the task marked as failed. + + ```proto + message workflows.v1.TaskStateResponse + ``` + + Attributes: + state: + ```proto + workflows.v1.TaskState state = 1; + ``` + """ + + __slots__ = ("state",) + + if TYPE_CHECKING: + + def __init__( + self, + *, + state: TaskState | None = None, + ) -> None: + pass + + state: TaskState + +_TaskLeaseRequestFields: TypeAlias = Literal["task_id", "requested_lease"] + +class TaskLeaseRequest(Message[_TaskLeaseRequestFields]): + """ + TaskLease is a message specifying the new lease expiration time of a task. + + ```proto + message workflows.v1.TaskLeaseRequest + ``` + + Attributes: + task_id: + ```proto + optional tilebox.v1.ID task_id = 1; + ``` + requested_lease: + ```proto + optional google.protobuf.Duration requested_lease = 2; + ``` + """ + + __slots__ = ("task_id", "requested_lease") + + if TYPE_CHECKING: + + def __init__( + self, + *, + task_id: ID | None = None, + requested_lease: Duration | None = None, + ) -> None: + pass + + task_id: ID | None + requested_lease: Duration | None + + +_DESC = file_desc( + b'\n\x17workflows/v1/task.proto\x12\x0cworkflows.v1\x1a\x1egoogle/protobuf/duration.proto\x1a\x13tilebox/v1/id.proto\x1a\x17workflows/v1/core.proto"\xa6\x01\n\x0fNextTaskRequest\x12F\n\rcomputed_task\x18\x01 \x01(\x0b2\x1a.workflows.v1.ComputedTaskR\x0ccomputedTaskB\x05\xaa\x01\x02\x08\x01\x12K\n\x10next_task_to_run\x18\x02 \x01(\x0b2\x1b.workflows.v1.NextTaskToRunR\rnextTaskToRunB\x05\xaa\x01\x02\x08\x01"r\n\rNextTaskToRun\x12!\n\x0ccluster_slug\x18\x01 \x01(\tR\x0bclusterSlug\x12>\n\x0bidentifiers\x18\x02 \x03(\x0b2\x1c.workflows.v1.TaskIdentifierR\x0bidentifiers"\x99\x02\n\x0cComputedTask\x12\x1e\n\x02id\x18\x01 \x01(\x0b2\x0e.tilebox.v1.IDR\x02id\x12\x18\n\x07display\x18\x02 \x01(\tR\x07display\x12:\n\tsub_tasks\x18\x05 \x01(\x0b2\x1d.workflows.v1.TaskSubmissionsR\x08subTasks\x12A\n\x10progress_updates\x18\x04 \x03(\x0b2\x16.workflows.v1.ProgressR\x0fprogressUpdates\x12P\n\x10legacy_sub_tasks\x18\x03 \x03(\x0b2".workflows.v1.SingleTaskSubmissionR\x0elegacySubTasksB\x02\x18\x01"g\n\x0eIdlingResponse\x12U\n\x19suggested_idling_duration\x18\x01 \x01(\x0b2\x19.google.protobuf.DurationR\x17suggestedIdlingDuration"y\n\x10NextTaskResponse\x12/\n\tnext_task\x18\x01 \x01(\x0b2\x12.workflows.v1.TaskR\x08nextTask\x124\n\x06idling\x18\x02 \x01(\x0b2\x1c.workflows.v1.IdlingResponseR\x06idling"\xc7\x01\n\x11TaskFailedRequest\x12\'\n\x07task_id\x18\x01 \x01(\x0b2\x0e.tilebox.v1.IDR\x06taskId\x12\x18\n\x07display\x18\x02 \x01(\tR\x07display\x12,\n\x12was_workflow_error\x18\x03 \x01(\x08R\x10wasWorkflowError\x12A\n\x10progress_updates\x18\x04 \x03(\x0b2\x16.workflows.v1.ProgressR\x0fprogressUpdates"B\n\x11TaskStateResponse\x12-\n\x05state\x18\x01 \x01(\x0e2\x17.workflows.v1.TaskStateR\x05state"\x7f\n\x10TaskLeaseRequest\x12\'\n\x07task_id\x18\x01 \x01(\x0b2\x0e.tilebox.v1.IDR\x06taskId\x12B\n\x0frequested_lease\x18\x02 \x01(\x0b2\x19.google.protobuf.DurationR\x0erequestedLease2\xf4\x01\n\x0bTaskService\x12I\n\x08NextTask\x12\x1d.workflows.v1.NextTaskRequest\x1a\x1e.workflows.v1.NextTaskResponse\x12N\n\nTaskFailed\x12\x1f.workflows.v1.TaskFailedRequest\x1a\x1f.workflows.v1.TaskStateResponse\x12J\n\x0fExtendTaskLease\x12\x1e.workflows.v1.TaskLeaseRequest\x1a\x17.workflows.v1.TaskLeaseB\x05\x92\x03\x02\x08\x02b\x08editionsp\xe8\x07', + [ + duration_pb.desc(), + id_pb.desc(), + core_pb.desc(), + ], + { + "NextTaskRequest": NextTaskRequest, + "NextTaskToRun": NextTaskToRun, + "ComputedTask": ComputedTask, + "IdlingResponse": IdlingResponse, + "NextTaskResponse": NextTaskResponse, + "TaskFailedRequest": TaskFailedRequest, + "TaskStateResponse": TaskStateResponse, + "TaskLeaseRequest": TaskLeaseRequest, + }, +) + + +def desc() -> DescFile: + """Returns the descriptor for the file `workflows/v1/task.proto`.""" + return _DESC diff --git a/tilebox-workflows/tilebox/workflows/v1/telemetry_connect.py b/tilebox-workflows/tilebox/workflows/v1/telemetry_connect.py new file mode 100644 index 0000000..42254a8 --- /dev/null +++ b/tilebox-workflows/tilebox/workflows/v1/telemetry_connect.py @@ -0,0 +1,433 @@ +# Generated from workflows/v1/telemetry.proto. DO NOT EDIT. +# Generated by protoc-gen-connectrpc-py v0.12.1 with parameter "map_imports=datasets/**:tilebox,map_imports=workflows/**:tilebox,map_imports=tilebox/**:_tilebox.proto,map_imports=opentelemetry/**:tilebox.workflows._otel". +# ruff: noqa: PGH004 +# ruff: noqa +# fmt: off + +from __future__ import annotations + +from typing import Protocol, TYPE_CHECKING + +from connectrpc.client import ConnectClient, ConnectClientSync +from connectrpc.code import Code +from connectrpc.errors import ConnectError +from connectrpc.method import IdempotencyLevel, MethodInfo +from connectrpc.server import ConnectASGIApplication, ConnectWSGIApplication, DEFAULT_READ_MAX_BYTES, Endpoint, EndpointSync +from protobuf import DescService +from tilebox.workflows.v1 import telemetry_pb +from tilebox.workflows.v1.telemetry_pb import GetJobLogMessageCountsRequest, GetLogMessageCountsRequest, GetLogMessageCountsResponse, PaginatedLogsData, PaginatedSpansData, QueryJobLogsRequest, QueryJobSpansRequest, QueryLogsInIntervalRequest + +if TYPE_CHECKING: + from collections.abc import AsyncGenerator, Iterable, Mapping + + from connectrpc.codec import Codec + from connectrpc.compression import Compression + from connectrpc.interceptor import Interceptor, InterceptorSync + from connectrpc.request import Headers, RequestContext + + +class TelemetryQueryService(Protocol): + """ + TelemetryQueryService is the service definition for querying telemetry data about workflows such as logs, traces + and metrics. + """ + async def query_job_logs(self, request: QueryJobLogsRequest, ctx: RequestContext[QueryJobLogsRequest, PaginatedLogsData], /) -> PaginatedLogsData: + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') + + async def query_logs_in_interval(self, request: QueryLogsInIntervalRequest, ctx: RequestContext[QueryLogsInIntervalRequest, PaginatedLogsData], /) -> PaginatedLogsData: + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') + + async def get_log_message_counts(self, request: GetLogMessageCountsRequest, ctx: RequestContext[GetLogMessageCountsRequest, GetLogMessageCountsResponse], /) -> GetLogMessageCountsResponse: + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') + + async def get_job_log_message_counts(self, request: GetJobLogMessageCountsRequest, ctx: RequestContext[GetJobLogMessageCountsRequest, GetLogMessageCountsResponse], /) -> GetLogMessageCountsResponse: + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') + + async def query_job_spans(self, request: QueryJobSpansRequest, ctx: RequestContext[QueryJobSpansRequest, PaginatedSpansData], /) -> PaginatedSpansData: + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') + + @classmethod + def desc(cls) -> DescService: + """Returns the descriptor for this service.""" + return next(s for s in telemetry_pb.desc().services if s.type_name == 'workflows.v1.TelemetryQueryService') + +class TelemetryQueryServiceASGIApplication(ConnectASGIApplication[TelemetryQueryService]): + def __init__( + self, + service: TelemetryQueryService | AsyncGenerator[TelemetryQueryService], + *, + interceptors: Iterable[Interceptor] = (), + read_max_bytes: int | None = DEFAULT_READ_MAX_BYTES, + compressions: Iterable[Compression] | None = None, + codecs: Iterable[Codec] | None = None, + ) -> None: + super().__init__( + service=service, + endpoints=lambda svc: { + "/workflows.v1.TelemetryQueryService/QueryJobLogs": Endpoint.unary( + method=MethodInfo( + name="QueryJobLogs", + service_name="workflows.v1.TelemetryQueryService", + input=QueryJobLogsRequest, + output=PaginatedLogsData, + idempotency_level=IdempotencyLevel.UNKNOWN, + ), + function=svc.query_job_logs, + ), + "/workflows.v1.TelemetryQueryService/QueryLogsInInterval": Endpoint.unary( + method=MethodInfo( + name="QueryLogsInInterval", + service_name="workflows.v1.TelemetryQueryService", + input=QueryLogsInIntervalRequest, + output=PaginatedLogsData, + idempotency_level=IdempotencyLevel.UNKNOWN, + ), + function=svc.query_logs_in_interval, + ), + "/workflows.v1.TelemetryQueryService/GetLogMessageCounts": Endpoint.unary( + method=MethodInfo( + name="GetLogMessageCounts", + service_name="workflows.v1.TelemetryQueryService", + input=GetLogMessageCountsRequest, + output=GetLogMessageCountsResponse, + idempotency_level=IdempotencyLevel.UNKNOWN, + ), + function=svc.get_log_message_counts, + ), + "/workflows.v1.TelemetryQueryService/GetJobLogMessageCounts": Endpoint.unary( + method=MethodInfo( + name="GetJobLogMessageCounts", + service_name="workflows.v1.TelemetryQueryService", + input=GetJobLogMessageCountsRequest, + output=GetLogMessageCountsResponse, + idempotency_level=IdempotencyLevel.UNKNOWN, + ), + function=svc.get_job_log_message_counts, + ), + "/workflows.v1.TelemetryQueryService/QueryJobSpans": Endpoint.unary( + method=MethodInfo( + name="QueryJobSpans", + service_name="workflows.v1.TelemetryQueryService", + input=QueryJobSpansRequest, + output=PaginatedSpansData, + idempotency_level=IdempotencyLevel.UNKNOWN, + ), + function=svc.query_job_spans, + ), + }, + interceptors=interceptors, + read_max_bytes=read_max_bytes, + compressions=compressions, + codecs=codecs, + ) + + @property + def path(self) -> str: + """Returns the URL path to mount the application to when serving multiple applications.""" + return "/workflows.v1.TelemetryQueryService" + + +class TelemetryQueryServiceClient(ConnectClient): + """ + TelemetryQueryService is the service definition for querying telemetry data about workflows such as logs, traces + and metrics. + """ + async def query_job_logs( + self, + request: QueryJobLogsRequest, + *, + headers: Headers | Mapping[str, str] | None = None, + timeout_ms: int | None = None, + ) -> PaginatedLogsData: + return await self.execute_unary( + request=request, + method=MethodInfo( + name="QueryJobLogs", + service_name="workflows.v1.TelemetryQueryService", + input=QueryJobLogsRequest, + output=PaginatedLogsData, + idempotency_level=IdempotencyLevel.UNKNOWN, + ), + headers=headers, + timeout_ms=timeout_ms, + ) + + async def query_logs_in_interval( + self, + request: QueryLogsInIntervalRequest, + *, + headers: Headers | Mapping[str, str] | None = None, + timeout_ms: int | None = None, + ) -> PaginatedLogsData: + return await self.execute_unary( + request=request, + method=MethodInfo( + name="QueryLogsInInterval", + service_name="workflows.v1.TelemetryQueryService", + input=QueryLogsInIntervalRequest, + output=PaginatedLogsData, + idempotency_level=IdempotencyLevel.UNKNOWN, + ), + headers=headers, + timeout_ms=timeout_ms, + ) + + async def get_log_message_counts( + self, + request: GetLogMessageCountsRequest, + *, + headers: Headers | Mapping[str, str] | None = None, + timeout_ms: int | None = None, + ) -> GetLogMessageCountsResponse: + return await self.execute_unary( + request=request, + method=MethodInfo( + name="GetLogMessageCounts", + service_name="workflows.v1.TelemetryQueryService", + input=GetLogMessageCountsRequest, + output=GetLogMessageCountsResponse, + idempotency_level=IdempotencyLevel.UNKNOWN, + ), + headers=headers, + timeout_ms=timeout_ms, + ) + + async def get_job_log_message_counts( + self, + request: GetJobLogMessageCountsRequest, + *, + headers: Headers | Mapping[str, str] | None = None, + timeout_ms: int | None = None, + ) -> GetLogMessageCountsResponse: + return await self.execute_unary( + request=request, + method=MethodInfo( + name="GetJobLogMessageCounts", + service_name="workflows.v1.TelemetryQueryService", + input=GetJobLogMessageCountsRequest, + output=GetLogMessageCountsResponse, + idempotency_level=IdempotencyLevel.UNKNOWN, + ), + headers=headers, + timeout_ms=timeout_ms, + ) + + async def query_job_spans( + self, + request: QueryJobSpansRequest, + *, + headers: Headers | Mapping[str, str] | None = None, + timeout_ms: int | None = None, + ) -> PaginatedSpansData: + return await self.execute_unary( + request=request, + method=MethodInfo( + name="QueryJobSpans", + service_name="workflows.v1.TelemetryQueryService", + input=QueryJobSpansRequest, + output=PaginatedSpansData, + idempotency_level=IdempotencyLevel.UNKNOWN, + ), + headers=headers, + timeout_ms=timeout_ms, + ) + +class TelemetryQueryServiceSync(Protocol): + """ + TelemetryQueryService is the service definition for querying telemetry data about workflows such as logs, traces + and metrics. + """ + def query_job_logs(self, request: QueryJobLogsRequest, ctx: RequestContext[QueryJobLogsRequest, PaginatedLogsData], /) -> PaginatedLogsData: + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') + + def query_logs_in_interval(self, request: QueryLogsInIntervalRequest, ctx: RequestContext[QueryLogsInIntervalRequest, PaginatedLogsData], /) -> PaginatedLogsData: + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') + + def get_log_message_counts(self, request: GetLogMessageCountsRequest, ctx: RequestContext[GetLogMessageCountsRequest, GetLogMessageCountsResponse], /) -> GetLogMessageCountsResponse: + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') + + def get_job_log_message_counts(self, request: GetJobLogMessageCountsRequest, ctx: RequestContext[GetJobLogMessageCountsRequest, GetLogMessageCountsResponse], /) -> GetLogMessageCountsResponse: + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') + + def query_job_spans(self, request: QueryJobSpansRequest, ctx: RequestContext[QueryJobSpansRequest, PaginatedSpansData], /) -> PaginatedSpansData: + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') + + @classmethod + def desc(cls) -> DescService: + """Returns the descriptor for this service.""" + return next(s for s in telemetry_pb.desc().services if s.type_name == 'workflows.v1.TelemetryQueryService') + +class TelemetryQueryServiceWSGIApplication(ConnectWSGIApplication): + def __init__( + self, + service: TelemetryQueryServiceSync, + interceptors: Iterable[InterceptorSync] = (), + read_max_bytes: int | None = DEFAULT_READ_MAX_BYTES, + compressions: Iterable[Compression] | None = None, + codecs: Iterable[Codec] | None = None, + ) -> None: + super().__init__( + endpoints={ + "/workflows.v1.TelemetryQueryService/QueryJobLogs": EndpointSync.unary( + method=MethodInfo( + name="QueryJobLogs", + service_name="workflows.v1.TelemetryQueryService", + input=QueryJobLogsRequest, + output=PaginatedLogsData, + idempotency_level=IdempotencyLevel.UNKNOWN, + ), + function=service.query_job_logs, + ), + "/workflows.v1.TelemetryQueryService/QueryLogsInInterval": EndpointSync.unary( + method=MethodInfo( + name="QueryLogsInInterval", + service_name="workflows.v1.TelemetryQueryService", + input=QueryLogsInIntervalRequest, + output=PaginatedLogsData, + idempotency_level=IdempotencyLevel.UNKNOWN, + ), + function=service.query_logs_in_interval, + ), + "/workflows.v1.TelemetryQueryService/GetLogMessageCounts": EndpointSync.unary( + method=MethodInfo( + name="GetLogMessageCounts", + service_name="workflows.v1.TelemetryQueryService", + input=GetLogMessageCountsRequest, + output=GetLogMessageCountsResponse, + idempotency_level=IdempotencyLevel.UNKNOWN, + ), + function=service.get_log_message_counts, + ), + "/workflows.v1.TelemetryQueryService/GetJobLogMessageCounts": EndpointSync.unary( + method=MethodInfo( + name="GetJobLogMessageCounts", + service_name="workflows.v1.TelemetryQueryService", + input=GetJobLogMessageCountsRequest, + output=GetLogMessageCountsResponse, + idempotency_level=IdempotencyLevel.UNKNOWN, + ), + function=service.get_job_log_message_counts, + ), + "/workflows.v1.TelemetryQueryService/QueryJobSpans": EndpointSync.unary( + method=MethodInfo( + name="QueryJobSpans", + service_name="workflows.v1.TelemetryQueryService", + input=QueryJobSpansRequest, + output=PaginatedSpansData, + idempotency_level=IdempotencyLevel.UNKNOWN, + ), + function=service.query_job_spans, + ), + }, + interceptors=interceptors, + read_max_bytes=read_max_bytes, + compressions=compressions, + codecs=codecs, + ) + + @property + def path(self) -> str: + """Returns the URL path to mount the application to when serving multiple applications.""" + return "/workflows.v1.TelemetryQueryService" + + +class TelemetryQueryServiceClientSync(ConnectClientSync): + """ + TelemetryQueryService is the service definition for querying telemetry data about workflows such as logs, traces + and metrics. + """ + def query_job_logs( + self, + request: QueryJobLogsRequest, + *, + headers: Headers | Mapping[str, str] | None = None, + timeout_ms: int | None = None, + ) -> PaginatedLogsData: + return self.execute_unary( + request=request, + method=MethodInfo( + name="QueryJobLogs", + service_name="workflows.v1.TelemetryQueryService", + input=QueryJobLogsRequest, + output=PaginatedLogsData, + idempotency_level=IdempotencyLevel.UNKNOWN, + ), + headers=headers, + timeout_ms=timeout_ms, + ) + def query_logs_in_interval( + self, + request: QueryLogsInIntervalRequest, + *, + headers: Headers | Mapping[str, str] | None = None, + timeout_ms: int | None = None, + ) -> PaginatedLogsData: + return self.execute_unary( + request=request, + method=MethodInfo( + name="QueryLogsInInterval", + service_name="workflows.v1.TelemetryQueryService", + input=QueryLogsInIntervalRequest, + output=PaginatedLogsData, + idempotency_level=IdempotencyLevel.UNKNOWN, + ), + headers=headers, + timeout_ms=timeout_ms, + ) + def get_log_message_counts( + self, + request: GetLogMessageCountsRequest, + *, + headers: Headers | Mapping[str, str] | None = None, + timeout_ms: int | None = None, + ) -> GetLogMessageCountsResponse: + return self.execute_unary( + request=request, + method=MethodInfo( + name="GetLogMessageCounts", + service_name="workflows.v1.TelemetryQueryService", + input=GetLogMessageCountsRequest, + output=GetLogMessageCountsResponse, + idempotency_level=IdempotencyLevel.UNKNOWN, + ), + headers=headers, + timeout_ms=timeout_ms, + ) + def get_job_log_message_counts( + self, + request: GetJobLogMessageCountsRequest, + *, + headers: Headers | Mapping[str, str] | None = None, + timeout_ms: int | None = None, + ) -> GetLogMessageCountsResponse: + return self.execute_unary( + request=request, + method=MethodInfo( + name="GetJobLogMessageCounts", + service_name="workflows.v1.TelemetryQueryService", + input=GetJobLogMessageCountsRequest, + output=GetLogMessageCountsResponse, + idempotency_level=IdempotencyLevel.UNKNOWN, + ), + headers=headers, + timeout_ms=timeout_ms, + ) + def query_job_spans( + self, + request: QueryJobSpansRequest, + *, + headers: Headers | Mapping[str, str] | None = None, + timeout_ms: int | None = None, + ) -> PaginatedSpansData: + return self.execute_unary( + request=request, + method=MethodInfo( + name="QueryJobSpans", + service_name="workflows.v1.TelemetryQueryService", + input=QueryJobSpansRequest, + output=PaginatedSpansData, + idempotency_level=IdempotencyLevel.UNKNOWN, + ), + headers=headers, + timeout_ms=timeout_ms, + ) diff --git a/tilebox-workflows/tilebox/workflows/v1/telemetry_pb.py b/tilebox-workflows/tilebox/workflows/v1/telemetry_pb.py new file mode 100644 index 0000000..c684ad6 --- /dev/null +++ b/tilebox-workflows/tilebox/workflows/v1/telemetry_pb.py @@ -0,0 +1,534 @@ +# Generated from workflows/v1/telemetry.proto. DO NOT EDIT. +# Generated by protoc-gen-py v0.6.0 with parameter "init_files=false,map_imports=datasets/**:tilebox,map_imports=workflows/**:tilebox,map_imports=tilebox/**:_tilebox.proto,map_imports=opentelemetry/**:tilebox.workflows._otel". +# ruff: noqa: PGH004 +# ruff: noqa +# fmt: off + +from __future__ import annotations + +from typing import Literal, TYPE_CHECKING, TypeAlias + +from _tilebox.proto.tilebox.v1 import id_pb, query_pb +from protobuf import Enum, Message +from protobuf._codegen import file_desc +from tilebox.workflows._otel.opentelemetry.proto.logs.v1 import logs_pb +from tilebox.workflows._otel.opentelemetry.proto.trace.v1 import trace_pb + +if TYPE_CHECKING: + from _tilebox.proto.tilebox.v1.id_pb import ID + from _tilebox.proto.tilebox.v1.query_pb import Pagination, SortDirection, TimeInterval + from protobuf import DescFile + from tilebox.workflows._otel.opentelemetry.proto.logs.v1.logs_pb import ResourceLogs + from tilebox.workflows._otel.opentelemetry.proto.trace.v1.trace_pb import ResourceSpans + + +_LogQueryFiltersFields: TypeAlias = Literal["severity_levels"] + +class LogQueryFilters(Message[_LogQueryFiltersFields]): + """ + LogQueryFilters define additional query filters for log records, supported by both the QueryJobLogs and QueryLogsInInterval endpoints. + These filters can be used to narrow down the log entries returned by the query. + + ```proto + message workflows.v1.LogQueryFilters + ``` + + Attributes: + severity_levels: + Filter log entries by severity levels. If not specified, all severity levels will be returned. + + ```proto + repeated workflows.v1.LogSeverityGroup severity_levels = 1 [packed = true]; + ``` + """ + + __slots__ = ("severity_levels",) + + if TYPE_CHECKING: + + def __init__( + self, + *, + severity_levels: list[LogSeverityGroup] | None = None, + ) -> None: + pass + + severity_levels: list[LogSeverityGroup] + +_QueryJobLogsRequestFields: TypeAlias = Literal["job_id", "page", "sort_direction", "task_id", "filters"] + +class QueryJobLogsRequest(Message[_QueryJobLogsRequestFields]): + """ + QueryJobLogsRequest is the request message for querying logs of a specific job, in ascending or descending order, + with pagination support. + + ```proto + message workflows.v1.QueryJobLogsRequest + ``` + + Attributes: + job_id: + The ID of the job to query logs for. + + ```proto + optional tilebox.v1.ID job_id = 1; + ``` + page: + The pagination parameters for this request. + + ```proto + optional tilebox.v1.Pagination page = 2; + ``` + sort_direction: + The direction in which to sort log entries. If not specified, defaults to ascending order (oldest entries first). + + ```proto + optional tilebox.v1.SortDirection sort_direction = 3; + ``` + task_id: + The ID of the task to query logs for. If not specified, logs for the full job are returned. + + ```proto + optional tilebox.v1.ID task_id = 4; + ``` + filters: + Additional filters to apply to the log query. + + ```proto + optional workflows.v1.LogQueryFilters filters = 5; + ``` + """ + + __slots__ = ("job_id", "page", "sort_direction", "task_id", "filters") + + if TYPE_CHECKING: + + def __init__( + self, + *, + job_id: ID | None = None, + page: Pagination | None = None, + sort_direction: SortDirection | None = None, + task_id: ID | None = None, + filters: LogQueryFilters | None = None, + ) -> None: + pass + + job_id: ID | None + page: Pagination | None + sort_direction: SortDirection + task_id: ID | None + filters: LogQueryFilters | None + +_QueryLogsInIntervalRequestFields: TypeAlias = Literal["time_interval", "page", "sort_direction", "filters"] + +class QueryLogsInIntervalRequest(Message[_QueryLogsInIntervalRequestFields]): + """ + QueryLogsInIntervalRequest is the request message for querying logs in a specific time interval. + It can be used to query all log messages across multiple jobs, and can be filtered by other parameters in the future. + + ```proto + message workflows.v1.QueryLogsInIntervalRequest + ``` + + Attributes: + time_interval: + The time interval to query logs for. + + ```proto + optional tilebox.v1.TimeInterval time_interval = 1; + ``` + page: + The pagination parameters for this request. + + ```proto + optional tilebox.v1.Pagination page = 2; + ``` + sort_direction: + The direction in which to sort log entries. If not specified, defaults to descending order (newest entries first). + + ```proto + optional tilebox.v1.SortDirection sort_direction = 3; + ``` + filters: + Additional filters to apply to the log query. + + ```proto + optional workflows.v1.LogQueryFilters filters = 4; + ``` + """ + + __slots__ = ("time_interval", "page", "sort_direction", "filters") + + if TYPE_CHECKING: + + def __init__( + self, + *, + time_interval: TimeInterval | None = None, + page: Pagination | None = None, + sort_direction: SortDirection | None = None, + filters: LogQueryFilters | None = None, + ) -> None: + pass + + time_interval: TimeInterval | None + page: Pagination | None + sort_direction: SortDirection + filters: LogQueryFilters | None + +_PaginatedLogsDataFields: TypeAlias = Literal["resource_logs", "next_page"] + +class PaginatedLogsData(Message[_PaginatedLogsDataFields]): + """ + PaginatedLogsData is the response message for paginated log queries. It's a message compatible with LogsData, + but with an additional field for pagination parameters for the next page. + + ```proto + message workflows.v1.PaginatedLogsData + ``` + + Attributes: + resource_logs: + An array of ResourceLogs. + + ```proto + repeated opentelemetry.proto.logs.v1.ResourceLogs resource_logs = 1; + ``` + next_page: + The pagination parameters for the next page. + + ```proto + optional tilebox.v1.Pagination next_page = 2; + ``` + """ + + __slots__ = ("resource_logs", "next_page") + + if TYPE_CHECKING: + + def __init__( + self, + *, + resource_logs: list[ResourceLogs] | None = None, + next_page: Pagination | None = None, + ) -> None: + pass + + resource_logs: list[ResourceLogs] + next_page: Pagination | None + +_GetLogMessageCountsRequestFields: TypeAlias = Literal["time_interval"] + +class GetLogMessageCountsRequest(Message[_GetLogMessageCountsRequestFields]): + """ + GetLogMessageCountsRequest is the request message for retrieving counts of log messages per severity level within a specified time interval. + + ```proto + message workflows.v1.GetLogMessageCountsRequest + ``` + + Attributes: + time_interval: + The time interval to fetch log message counts for. + + ```proto + optional tilebox.v1.TimeInterval time_interval = 1; + ``` + """ + + __slots__ = ("time_interval",) + + if TYPE_CHECKING: + + def __init__( + self, + *, + time_interval: TimeInterval | None = None, + ) -> None: + pass + + time_interval: TimeInterval | None + +_GetJobLogMessageCountsRequestFields: TypeAlias = Literal["job_id", "task_id"] + +class GetJobLogMessageCountsRequest(Message[_GetJobLogMessageCountsRequestFields]): + """ + GetJobLogMessageCountsRequest is the request message for retrieving counts of log messages per severity level for a job. + + ```proto + message workflows.v1.GetJobLogMessageCountsRequest + ``` + + Attributes: + job_id: + The ID of the job to count logs for. + + ```proto + optional tilebox.v1.ID job_id = 1; + ``` + task_id: + The ID of the task to count logs for. If not specified, logs for the full job are counted. + + ```proto + optional tilebox.v1.ID task_id = 2; + ``` + """ + + __slots__ = ("job_id", "task_id") + + if TYPE_CHECKING: + + def __init__( + self, + *, + job_id: ID | None = None, + task_id: ID | None = None, + ) -> None: + pass + + job_id: ID | None + task_id: ID | None + +_GetLogMessageCountsResponseFields: TypeAlias = Literal["counts", "total_messages"] + +class GetLogMessageCountsResponse(Message[_GetLogMessageCountsResponseFields]): + """ + GetLogMessageCountsResponse contains log message counts per severity level and a total across all severities. + + ```proto + message workflows.v1.GetLogMessageCountsResponse + ``` + + Attributes: + counts: + ```proto + repeated workflows.v1.LogSeverityCount counts = 1; + ``` + total_messages: + ```proto + optional int64 total_messages = 2; + ``` + """ + + __slots__ = ("counts", "total_messages") + + if TYPE_CHECKING: + + def __init__( + self, + *, + counts: list[LogSeverityCount] | None = None, + total_messages: int | None = None, + ) -> None: + pass + + counts: list[LogSeverityCount] + total_messages: int + +_LogSeverityCountFields: TypeAlias = Literal["severity", "count"] + +class LogSeverityCount(Message[_LogSeverityCountFields]): + """ + LogSeverityCount is a message that represents the count of log messages for a specific severity level. + + ```proto + message workflows.v1.LogSeverityCount + ``` + + Attributes: + severity: + ```proto + optional workflows.v1.LogSeverityGroup severity = 1; + ``` + count: + ```proto + optional int64 count = 2; + ``` + """ + + __slots__ = ("severity", "count") + + if TYPE_CHECKING: + + def __init__( + self, + *, + severity: LogSeverityGroup | None = None, + count: int | None = None, + ) -> None: + pass + + severity: LogSeverityGroup + count: int + +_QueryJobSpansRequestFields: TypeAlias = Literal["job_id", "page", "sort_direction", "task_id"] + +class QueryJobSpansRequest(Message[_QueryJobSpansRequestFields]): + """ + QueryJobLogsRequest is the request message for querying logs of a specific job, in ascending or descending order, + with pagination support. + + ```proto + message workflows.v1.QueryJobSpansRequest + ``` + + Attributes: + job_id: + The ID of the job to query logs for. + + ```proto + optional tilebox.v1.ID job_id = 1; + ``` + page: + The pagination parameters for this request. + + ```proto + optional tilebox.v1.Pagination page = 2; + ``` + sort_direction: + The direction in which to sort log entries. If not specified, defaults to ascending order (oldest entries first). + + ```proto + optional tilebox.v1.SortDirection sort_direction = 3; + ``` + task_id: + The ID of the task to query spans for. If not specified, spans for the full job are returned. + + ```proto + optional tilebox.v1.ID task_id = 4; + ``` + """ + + __slots__ = ("job_id", "page", "sort_direction", "task_id") + + if TYPE_CHECKING: + + def __init__( + self, + *, + job_id: ID | None = None, + page: Pagination | None = None, + sort_direction: SortDirection | None = None, + task_id: ID | None = None, + ) -> None: + pass + + job_id: ID | None + page: Pagination | None + sort_direction: SortDirection + task_id: ID | None + +_PaginatedSpansDataFields: TypeAlias = Literal["resource_spans", "next_page"] + +class PaginatedSpansData(Message[_PaginatedSpansDataFields]): + """ + PaginatedSpansData is the response message for paginated log queries. It's a message compatible with LogsData, + but with an additional field for pagination parameters for the next page. + + ```proto + message workflows.v1.PaginatedSpansData + ``` + + Attributes: + resource_spans: + An array of ResourceLogs. + + ```proto + repeated opentelemetry.proto.trace.v1.ResourceSpans resource_spans = 1; + ``` + next_page: + The pagination parameters for the next page. + + ```proto + optional tilebox.v1.Pagination next_page = 2; + ``` + """ + + __slots__ = ("resource_spans", "next_page") + + if TYPE_CHECKING: + + def __init__( + self, + *, + resource_spans: list[ResourceSpans] | None = None, + next_page: Pagination | None = None, + ) -> None: + pass + + resource_spans: list[ResourceSpans] + next_page: Pagination | None + +class LogSeverityGroup(Enum): + """ + LogSeverityGroup is an enum that groups log severity levels into broader categories for easier filtering and analysis. + + ```proto + enum workflows.v1.LogSeverityGroup + ``` + + Attributes: + UNSPECIFIED: + ```proto + LOG_SEVERITY_GROUP_UNSPECIFIED = 0 + ``` + TRACE: + ```proto + LOG_SEVERITY_GROUP_TRACE = 1 + ``` + DEBUG: + ```proto + LOG_SEVERITY_GROUP_DEBUG = 2 + ``` + INFO: + ```proto + LOG_SEVERITY_GROUP_INFO = 3 + ``` + WARNING: + ```proto + LOG_SEVERITY_GROUP_WARNING = 4 + ``` + ERROR: + Includes OpenTelemetry ERROR and FATAL severities. + + ```proto + LOG_SEVERITY_GROUP_ERROR = 5 + ``` + """ + + UNSPECIFIED = 0 + TRACE = 1 + DEBUG = 2 + INFO = 3 + WARNING = 4 + ERROR = 5 + + +_DESC = file_desc( + b'\n\x1cworkflows/v1/telemetry.proto\x12\x0cworkflows.v1\x1a&opentelemetry/proto/logs/v1/logs.proto\x1a(opentelemetry/proto/trace/v1/trace.proto\x1a\x13tilebox/v1/id.proto\x1a\x16tilebox/v1/query.proto"Z\n\x0fLogQueryFilters\x12G\n\x0fseverity_levels\x18\x01 \x03(\x0e2\x1e.workflows.v1.LogSeverityGroupR\x0eseverityLevels"\x9a\x02\n\x13QueryJobLogsRequest\x12%\n\x06job_id\x18\x01 \x01(\x0b2\x0e.tilebox.v1.IDR\x05jobId\x121\n\x04page\x18\x02 \x01(\x0b2\x16.tilebox.v1.PaginationR\x04pageB\x05\xaa\x01\x02\x08\x01\x12@\n\x0esort_direction\x18\x03 \x01(\x0e2\x19.tilebox.v1.SortDirectionR\rsortDirection\x12.\n\x07task_id\x18\x04 \x01(\x0b2\x0e.tilebox.v1.IDR\x06taskIdB\x05\xaa\x01\x02\x08\x01\x127\n\x07filters\x18\x05 \x01(\x0b2\x1d.workflows.v1.LogQueryFiltersR\x07filters"\x89\x02\n\x1aQueryLogsInIntervalRequest\x12=\n\rtime_interval\x18\x01 \x01(\x0b2\x18.tilebox.v1.TimeIntervalR\x0ctimeInterval\x121\n\x04page\x18\x02 \x01(\x0b2\x16.tilebox.v1.PaginationR\x04pageB\x05\xaa\x01\x02\x08\x01\x12@\n\x0esort_direction\x18\x03 \x01(\x0e2\x19.tilebox.v1.SortDirectionR\rsortDirection\x127\n\x07filters\x18\x04 \x01(\x0b2\x1d.workflows.v1.LogQueryFiltersR\x07filters"\x9f\x01\n\x11PaginatedLogsData\x12N\n\rresource_logs\x18\x01 \x03(\x0b2).opentelemetry.proto.logs.v1.ResourceLogsR\x0cresourceLogs\x12:\n\tnext_page\x18\x02 \x01(\x0b2\x16.tilebox.v1.PaginationR\x08nextPageB\x05\xaa\x01\x02\x08\x01"[\n\x1aGetLogMessageCountsRequest\x12=\n\rtime_interval\x18\x01 \x01(\x0b2\x18.tilebox.v1.TimeIntervalR\x0ctimeInterval"v\n\x1dGetJobLogMessageCountsRequest\x12%\n\x06job_id\x18\x01 \x01(\x0b2\x0e.tilebox.v1.IDR\x05jobId\x12.\n\x07task_id\x18\x02 \x01(\x0b2\x0e.tilebox.v1.IDR\x06taskIdB\x05\xaa\x01\x02\x08\x01"|\n\x1bGetLogMessageCountsResponse\x126\n\x06counts\x18\x01 \x03(\x0b2\x1e.workflows.v1.LogSeverityCountR\x06counts\x12%\n\x0etotal_messages\x18\x02 \x01(\x03R\rtotalMessages"d\n\x10LogSeverityCount\x12:\n\x08severity\x18\x01 \x01(\x0e2\x1e.workflows.v1.LogSeverityGroupR\x08severity\x12\x14\n\x05count\x18\x02 \x01(\x03R\x05count"\xe2\x01\n\x14QueryJobSpansRequest\x12%\n\x06job_id\x18\x01 \x01(\x0b2\x0e.tilebox.v1.IDR\x05jobId\x121\n\x04page\x18\x02 \x01(\x0b2\x16.tilebox.v1.PaginationR\x04pageB\x05\xaa\x01\x02\x08\x01\x12@\n\x0esort_direction\x18\x03 \x01(\x0e2\x19.tilebox.v1.SortDirectionR\rsortDirection\x12.\n\x07task_id\x18\x04 \x01(\x0b2\x0e.tilebox.v1.IDR\x06taskIdB\x05\xaa\x01\x02\x08\x01"\xa4\x01\n\x12PaginatedSpansData\x12R\n\x0eresource_spans\x18\x01 \x03(\x0b2+.opentelemetry.proto.trace.v1.ResourceSpansR\rresourceSpans\x12:\n\tnext_page\x18\x02 \x01(\x0b2\x16.tilebox.v1.PaginationR\x08nextPageB\x05\xaa\x01\x02\x08\x01*\xcd\x01\n\x10LogSeverityGroup\x12"\n\x1eLOG_SEVERITY_GROUP_UNSPECIFIED\x10\x00\x12\x1c\n\x18LOG_SEVERITY_GROUP_TRACE\x10\x01\x12\x1c\n\x18LOG_SEVERITY_GROUP_DEBUG\x10\x02\x12\x1b\n\x17LOG_SEVERITY_GROUP_INFO\x10\x03\x12\x1e\n\x1aLOG_SEVERITY_GROUP_WARNING\x10\x04\x12\x1c\n\x18LOG_SEVERITY_GROUP_ERROR\x10\x052\x82\x04\n\x15TelemetryQueryService\x12R\n\x0cQueryJobLogs\x12!.workflows.v1.QueryJobLogsRequest\x1a\x1f.workflows.v1.PaginatedLogsData\x12`\n\x13QueryLogsInInterval\x12(.workflows.v1.QueryLogsInIntervalRequest\x1a\x1f.workflows.v1.PaginatedLogsData\x12j\n\x13GetLogMessageCounts\x12(.workflows.v1.GetLogMessageCountsRequest\x1a).workflows.v1.GetLogMessageCountsResponse\x12p\n\x16GetJobLogMessageCounts\x12+.workflows.v1.GetJobLogMessageCountsRequest\x1a).workflows.v1.GetLogMessageCountsResponse\x12U\n\rQueryJobSpans\x12".workflows.v1.QueryJobSpansRequest\x1a .workflows.v1.PaginatedSpansDatab\x08editionsp\xe8\x07', + [ + logs_pb.desc(), + trace_pb.desc(), + id_pb.desc(), + query_pb.desc(), + ], + { + "LogQueryFilters": LogQueryFilters, + "QueryJobLogsRequest": QueryJobLogsRequest, + "QueryLogsInIntervalRequest": QueryLogsInIntervalRequest, + "PaginatedLogsData": PaginatedLogsData, + "GetLogMessageCountsRequest": GetLogMessageCountsRequest, + "GetJobLogMessageCountsRequest": GetJobLogMessageCountsRequest, + "GetLogMessageCountsResponse": GetLogMessageCountsResponse, + "LogSeverityCount": LogSeverityCount, + "QueryJobSpansRequest": QueryJobSpansRequest, + "PaginatedSpansData": PaginatedSpansData, + "LogSeverityGroup": LogSeverityGroup, + }, +) + + +def desc() -> DescFile: + """Returns the descriptor for the file `workflows/v1/telemetry.proto`.""" + return _DESC diff --git a/tilebox-workflows/tilebox/workflows/v1/worker_connect.py b/tilebox-workflows/tilebox/workflows/v1/worker_connect.py new file mode 100644 index 0000000..76b32dd --- /dev/null +++ b/tilebox-workflows/tilebox/workflows/v1/worker_connect.py @@ -0,0 +1,584 @@ +# Generated from workflows/v1/worker.proto. DO NOT EDIT. +# Generated by protoc-gen-connectrpc-py v0.12.1 with parameter "map_imports=datasets/**:tilebox,map_imports=workflows/**:tilebox,map_imports=tilebox/**:_tilebox.proto,map_imports=opentelemetry/**:tilebox.workflows._otel". +# ruff: noqa: PGH004 +# ruff: noqa +# fmt: off + +from __future__ import annotations + +from typing import Protocol, TYPE_CHECKING + +from connectrpc.client import ConnectClient, ConnectClientSync +from connectrpc.code import Code +from connectrpc.errors import ConnectError +from connectrpc.method import IdempotencyLevel, MethodInfo +from connectrpc.server import ConnectASGIApplication, ConnectWSGIApplication, DEFAULT_READ_MAX_BYTES, Endpoint, EndpointSync +from protobuf import DescService +from protobuf.wkt.google.protobuf.empty_pb import Empty +from tilebox.workflows._otel.opentelemetry.proto.logs.v1.logs_pb import LogRecord +from tilebox.workflows.v1 import worker_pb +from tilebox.workflows.v1.core_pb import Task, UnboundTaskIdentifiers +from tilebox.workflows.v1.worker_pb import ExecuteTaskResponse, InitializeRunnerRequest, InitializeRunnerResponse + +if TYPE_CHECKING: + from collections.abc import AsyncGenerator, AsyncIterator, Iterable, Iterator, Mapping + + from connectrpc.codec import Codec + from connectrpc.compression import Compression + from connectrpc.interceptor import Interceptor, InterceptorSync + from connectrpc.request import Headers, RequestContext + + +class WorkerService(Protocol): + """ + WorkerService defines the RPC interface between a runner and a a worker runtime that can execute workflow tasks. + """ + async def list_registered_tasks(self, request: Empty, ctx: RequestContext[Empty, UnboundTaskIdentifiers], /) -> UnboundTaskIdentifiers: + """ + ListRegisteredTasks is called by the runner to discover the task identifiers that a worker runtime can execute. + These are local task definitions without workflow IDs. The managed runner binds them to its deployed workflow + and adds any enabled legacy aliases when advertising capabilities to the task service. + """ + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') + + async def initialize_worker(self, request: InitializeRunnerRequest, ctx: RequestContext[InitializeRunnerRequest, InitializeRunnerResponse], /) -> InitializeRunnerResponse: + """ + InitializeWorker is called by the runner to initialize a worker runtime with some known metadata about the + runner. This allows us to add those metadata to any logs and traces emitted by the worker. + """ + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') + + async def execute_task(self, request: Task, ctx: RequestContext[Task, ExecuteTaskResponse], /) -> ExecuteTaskResponse: + """ + ExecuteTask is called by the runner to execute a task on the worker runtime. The worker runtime will execute the + task, and then respond with the result of the task execution, which can either be a computed task, or a failed + task. If the worker runtime crashes or becomes unreachable during task execution, the runner will treat the task + as failed with an unknown error. + The runner preserves the executing task's scope. Workers dispatch by local name/version and resolve child scopes + before returning them, defaulting to the executing task's scope rather than the release's workflow ID. + During migration, the managed runner fills missing child workflow IDs from the executing task's scope for legacy + workers, preserving explicit IDs. An explicit unscoped child of a scoped task cannot be expressed through this + adapter; it must be removed before supporting that override. The task service does not perform inheritance. + """ + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') + + def watch_logs(self, request: Empty, ctx: RequestContext[Empty, LogRecord], /) -> AsyncIterator[LogRecord]: + """ + WatchLogs streams structured runtime logs to the local runner, independently of API log exports and raw + stdout/stderr. It is available before workflow import completes and before InitializeWorker is called. + The runner sends one request; the worker sends buffered startup records followed by live records in enqueue + order. The stream stays open until shutdown, cancellation, or a connection failure, even while no tasks run. + Only one subscriber is allowed per runtime; concurrent subscriptions fail with ALREADY_EXISTS. + Delivery is best-effort with bounded buffering: slow or disconnected subscribers must not block tasks. + Buffer overflow is reported as a warning record when delivery resumes. There is no acknowledgement or replay + of delivered records. Canceling this stream does not shut down the worker or disable its API log exports. + Messages use the log body, severity, trace/span IDs, and attributes from the OpenTelemetry log model; + exception details use exception.type, exception.message, and exception.stacktrace attributes. + buf:lint:ignore RPC_NO_SERVER_STREAMING + """ + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') + + async def shutdown_worker(self, request: Empty, ctx: RequestContext[Empty, Empty], /) -> Empty: + """ + Gracefully shuts down the worker runtime. After receiving this request, the worker runtime will + finish task cleanup, flush API logs, and drain buffered WatchLogs records within a bounded deadline. + The log stream ends before the worker stops its RPC server; an open subscription must not prevent shutdown. + """ + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') + + @classmethod + def desc(cls) -> DescService: + """Returns the descriptor for this service.""" + return next(s for s in worker_pb.desc().services if s.type_name == 'workflows.v1.WorkerService') + +class WorkerServiceASGIApplication(ConnectASGIApplication[WorkerService]): + def __init__( + self, + service: WorkerService | AsyncGenerator[WorkerService], + *, + interceptors: Iterable[Interceptor] = (), + read_max_bytes: int | None = DEFAULT_READ_MAX_BYTES, + compressions: Iterable[Compression] | None = None, + codecs: Iterable[Codec] | None = None, + ) -> None: + super().__init__( + service=service, + endpoints=lambda svc: { + "/workflows.v1.WorkerService/ListRegisteredTasks": Endpoint.unary( + method=MethodInfo( + name="ListRegisteredTasks", + service_name="workflows.v1.WorkerService", + input=Empty, + output=UnboundTaskIdentifiers, + idempotency_level=IdempotencyLevel.UNKNOWN, + ), + function=svc.list_registered_tasks, + ), + "/workflows.v1.WorkerService/InitializeWorker": Endpoint.unary( + method=MethodInfo( + name="InitializeWorker", + service_name="workflows.v1.WorkerService", + input=InitializeRunnerRequest, + output=InitializeRunnerResponse, + idempotency_level=IdempotencyLevel.UNKNOWN, + ), + function=svc.initialize_worker, + ), + "/workflows.v1.WorkerService/ExecuteTask": Endpoint.unary( + method=MethodInfo( + name="ExecuteTask", + service_name="workflows.v1.WorkerService", + input=Task, + output=ExecuteTaskResponse, + idempotency_level=IdempotencyLevel.UNKNOWN, + ), + function=svc.execute_task, + ), + "/workflows.v1.WorkerService/WatchLogs": Endpoint.server_stream( + method=MethodInfo( + name="WatchLogs", + service_name="workflows.v1.WorkerService", + input=Empty, + output=LogRecord, + idempotency_level=IdempotencyLevel.UNKNOWN, + ), + function=svc.watch_logs, + ), + "/workflows.v1.WorkerService/ShutdownWorker": Endpoint.unary( + method=MethodInfo( + name="ShutdownWorker", + service_name="workflows.v1.WorkerService", + input=Empty, + output=Empty, + idempotency_level=IdempotencyLevel.UNKNOWN, + ), + function=svc.shutdown_worker, + ), + }, + interceptors=interceptors, + read_max_bytes=read_max_bytes, + compressions=compressions, + codecs=codecs, + ) + + @property + def path(self) -> str: + """Returns the URL path to mount the application to when serving multiple applications.""" + return "/workflows.v1.WorkerService" + + +class WorkerServiceClient(ConnectClient): + """ + WorkerService defines the RPC interface between a runner and a a worker runtime that can execute workflow tasks. + """ + async def list_registered_tasks( + self, + request: Empty, + *, + headers: Headers | Mapping[str, str] | None = None, + timeout_ms: int | None = None, + ) -> UnboundTaskIdentifiers: + """ + ListRegisteredTasks is called by the runner to discover the task identifiers that a worker runtime can execute. + These are local task definitions without workflow IDs. The managed runner binds them to its deployed workflow + and adds any enabled legacy aliases when advertising capabilities to the task service. + """ + return await self.execute_unary( + request=request, + method=MethodInfo( + name="ListRegisteredTasks", + service_name="workflows.v1.WorkerService", + input=Empty, + output=UnboundTaskIdentifiers, + idempotency_level=IdempotencyLevel.UNKNOWN, + ), + headers=headers, + timeout_ms=timeout_ms, + ) + + async def initialize_worker( + self, + request: InitializeRunnerRequest, + *, + headers: Headers | Mapping[str, str] | None = None, + timeout_ms: int | None = None, + ) -> InitializeRunnerResponse: + """ + InitializeWorker is called by the runner to initialize a worker runtime with some known metadata about the + runner. This allows us to add those metadata to any logs and traces emitted by the worker. + """ + return await self.execute_unary( + request=request, + method=MethodInfo( + name="InitializeWorker", + service_name="workflows.v1.WorkerService", + input=InitializeRunnerRequest, + output=InitializeRunnerResponse, + idempotency_level=IdempotencyLevel.UNKNOWN, + ), + headers=headers, + timeout_ms=timeout_ms, + ) + + async def execute_task( + self, + request: Task, + *, + headers: Headers | Mapping[str, str] | None = None, + timeout_ms: int | None = None, + ) -> ExecuteTaskResponse: + """ + ExecuteTask is called by the runner to execute a task on the worker runtime. The worker runtime will execute the + task, and then respond with the result of the task execution, which can either be a computed task, or a failed + task. If the worker runtime crashes or becomes unreachable during task execution, the runner will treat the task + as failed with an unknown error. + The runner preserves the executing task's scope. Workers dispatch by local name/version and resolve child scopes + before returning them, defaulting to the executing task's scope rather than the release's workflow ID. + During migration, the managed runner fills missing child workflow IDs from the executing task's scope for legacy + workers, preserving explicit IDs. An explicit unscoped child of a scoped task cannot be expressed through this + adapter; it must be removed before supporting that override. The task service does not perform inheritance. + """ + return await self.execute_unary( + request=request, + method=MethodInfo( + name="ExecuteTask", + service_name="workflows.v1.WorkerService", + input=Task, + output=ExecuteTaskResponse, + idempotency_level=IdempotencyLevel.UNKNOWN, + ), + headers=headers, + timeout_ms=timeout_ms, + ) + + def watch_logs( + self, + request: Empty, + *, + headers: Headers | Mapping[str, str] | None = None, + timeout_ms: int | None = None, + ) -> AsyncIterator[LogRecord]: + """ + WatchLogs streams structured runtime logs to the local runner, independently of API log exports and raw + stdout/stderr. It is available before workflow import completes and before InitializeWorker is called. + The runner sends one request; the worker sends buffered startup records followed by live records in enqueue + order. The stream stays open until shutdown, cancellation, or a connection failure, even while no tasks run. + Only one subscriber is allowed per runtime; concurrent subscriptions fail with ALREADY_EXISTS. + Delivery is best-effort with bounded buffering: slow or disconnected subscribers must not block tasks. + Buffer overflow is reported as a warning record when delivery resumes. There is no acknowledgement or replay + of delivered records. Canceling this stream does not shut down the worker or disable its API log exports. + Messages use the log body, severity, trace/span IDs, and attributes from the OpenTelemetry log model; + exception details use exception.type, exception.message, and exception.stacktrace attributes. + buf:lint:ignore RPC_NO_SERVER_STREAMING + """ + return self.execute_server_stream( + request=request, + method=MethodInfo( + name="WatchLogs", + service_name="workflows.v1.WorkerService", + input=Empty, + output=LogRecord, + idempotency_level=IdempotencyLevel.UNKNOWN, + ), + headers=headers, + timeout_ms=timeout_ms, + ) + + async def shutdown_worker( + self, + request: Empty, + *, + headers: Headers | Mapping[str, str] | None = None, + timeout_ms: int | None = None, + ) -> Empty: + """ + Gracefully shuts down the worker runtime. After receiving this request, the worker runtime will + finish task cleanup, flush API logs, and drain buffered WatchLogs records within a bounded deadline. + The log stream ends before the worker stops its RPC server; an open subscription must not prevent shutdown. + """ + return await self.execute_unary( + request=request, + method=MethodInfo( + name="ShutdownWorker", + service_name="workflows.v1.WorkerService", + input=Empty, + output=Empty, + idempotency_level=IdempotencyLevel.UNKNOWN, + ), + headers=headers, + timeout_ms=timeout_ms, + ) + +class WorkerServiceSync(Protocol): + """ + WorkerService defines the RPC interface between a runner and a a worker runtime that can execute workflow tasks. + """ + def list_registered_tasks(self, request: Empty, ctx: RequestContext[Empty, UnboundTaskIdentifiers], /) -> UnboundTaskIdentifiers: + """ + ListRegisteredTasks is called by the runner to discover the task identifiers that a worker runtime can execute. + These are local task definitions without workflow IDs. The managed runner binds them to its deployed workflow + and adds any enabled legacy aliases when advertising capabilities to the task service. + """ + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') + + def initialize_worker(self, request: InitializeRunnerRequest, ctx: RequestContext[InitializeRunnerRequest, InitializeRunnerResponse], /) -> InitializeRunnerResponse: + """ + InitializeWorker is called by the runner to initialize a worker runtime with some known metadata about the + runner. This allows us to add those metadata to any logs and traces emitted by the worker. + """ + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') + + def execute_task(self, request: Task, ctx: RequestContext[Task, ExecuteTaskResponse], /) -> ExecuteTaskResponse: + """ + ExecuteTask is called by the runner to execute a task on the worker runtime. The worker runtime will execute the + task, and then respond with the result of the task execution, which can either be a computed task, or a failed + task. If the worker runtime crashes or becomes unreachable during task execution, the runner will treat the task + as failed with an unknown error. + The runner preserves the executing task's scope. Workers dispatch by local name/version and resolve child scopes + before returning them, defaulting to the executing task's scope rather than the release's workflow ID. + During migration, the managed runner fills missing child workflow IDs from the executing task's scope for legacy + workers, preserving explicit IDs. An explicit unscoped child of a scoped task cannot be expressed through this + adapter; it must be removed before supporting that override. The task service does not perform inheritance. + """ + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') + + def watch_logs(self, request: Empty, ctx: RequestContext[Empty, LogRecord], /) -> Iterator[LogRecord]: + """ + WatchLogs streams structured runtime logs to the local runner, independently of API log exports and raw + stdout/stderr. It is available before workflow import completes and before InitializeWorker is called. + The runner sends one request; the worker sends buffered startup records followed by live records in enqueue + order. The stream stays open until shutdown, cancellation, or a connection failure, even while no tasks run. + Only one subscriber is allowed per runtime; concurrent subscriptions fail with ALREADY_EXISTS. + Delivery is best-effort with bounded buffering: slow or disconnected subscribers must not block tasks. + Buffer overflow is reported as a warning record when delivery resumes. There is no acknowledgement or replay + of delivered records. Canceling this stream does not shut down the worker or disable its API log exports. + Messages use the log body, severity, trace/span IDs, and attributes from the OpenTelemetry log model; + exception details use exception.type, exception.message, and exception.stacktrace attributes. + buf:lint:ignore RPC_NO_SERVER_STREAMING + """ + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') + + def shutdown_worker(self, request: Empty, ctx: RequestContext[Empty, Empty], /) -> Empty: + """ + Gracefully shuts down the worker runtime. After receiving this request, the worker runtime will + finish task cleanup, flush API logs, and drain buffered WatchLogs records within a bounded deadline. + The log stream ends before the worker stops its RPC server; an open subscription must not prevent shutdown. + """ + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') + + @classmethod + def desc(cls) -> DescService: + """Returns the descriptor for this service.""" + return next(s for s in worker_pb.desc().services if s.type_name == 'workflows.v1.WorkerService') + +class WorkerServiceWSGIApplication(ConnectWSGIApplication): + def __init__( + self, + service: WorkerServiceSync, + interceptors: Iterable[InterceptorSync] = (), + read_max_bytes: int | None = DEFAULT_READ_MAX_BYTES, + compressions: Iterable[Compression] | None = None, + codecs: Iterable[Codec] | None = None, + ) -> None: + super().__init__( + endpoints={ + "/workflows.v1.WorkerService/ListRegisteredTasks": EndpointSync.unary( + method=MethodInfo( + name="ListRegisteredTasks", + service_name="workflows.v1.WorkerService", + input=Empty, + output=UnboundTaskIdentifiers, + idempotency_level=IdempotencyLevel.UNKNOWN, + ), + function=service.list_registered_tasks, + ), + "/workflows.v1.WorkerService/InitializeWorker": EndpointSync.unary( + method=MethodInfo( + name="InitializeWorker", + service_name="workflows.v1.WorkerService", + input=InitializeRunnerRequest, + output=InitializeRunnerResponse, + idempotency_level=IdempotencyLevel.UNKNOWN, + ), + function=service.initialize_worker, + ), + "/workflows.v1.WorkerService/ExecuteTask": EndpointSync.unary( + method=MethodInfo( + name="ExecuteTask", + service_name="workflows.v1.WorkerService", + input=Task, + output=ExecuteTaskResponse, + idempotency_level=IdempotencyLevel.UNKNOWN, + ), + function=service.execute_task, + ), + "/workflows.v1.WorkerService/WatchLogs": EndpointSync.server_stream( + method=MethodInfo( + name="WatchLogs", + service_name="workflows.v1.WorkerService", + input=Empty, + output=LogRecord, + idempotency_level=IdempotencyLevel.UNKNOWN, + ), + function=service.watch_logs, + ), + "/workflows.v1.WorkerService/ShutdownWorker": EndpointSync.unary( + method=MethodInfo( + name="ShutdownWorker", + service_name="workflows.v1.WorkerService", + input=Empty, + output=Empty, + idempotency_level=IdempotencyLevel.UNKNOWN, + ), + function=service.shutdown_worker, + ), + }, + interceptors=interceptors, + read_max_bytes=read_max_bytes, + compressions=compressions, + codecs=codecs, + ) + + @property + def path(self) -> str: + """Returns the URL path to mount the application to when serving multiple applications.""" + return "/workflows.v1.WorkerService" + + +class WorkerServiceClientSync(ConnectClientSync): + """ + WorkerService defines the RPC interface between a runner and a a worker runtime that can execute workflow tasks. + """ + def list_registered_tasks( + self, + request: Empty, + *, + headers: Headers | Mapping[str, str] | None = None, + timeout_ms: int | None = None, + ) -> UnboundTaskIdentifiers: + """ + ListRegisteredTasks is called by the runner to discover the task identifiers that a worker runtime can execute. + These are local task definitions without workflow IDs. The managed runner binds them to its deployed workflow + and adds any enabled legacy aliases when advertising capabilities to the task service. + """ + return self.execute_unary( + request=request, + method=MethodInfo( + name="ListRegisteredTasks", + service_name="workflows.v1.WorkerService", + input=Empty, + output=UnboundTaskIdentifiers, + idempotency_level=IdempotencyLevel.UNKNOWN, + ), + headers=headers, + timeout_ms=timeout_ms, + ) + def initialize_worker( + self, + request: InitializeRunnerRequest, + *, + headers: Headers | Mapping[str, str] | None = None, + timeout_ms: int | None = None, + ) -> InitializeRunnerResponse: + """ + InitializeWorker is called by the runner to initialize a worker runtime with some known metadata about the + runner. This allows us to add those metadata to any logs and traces emitted by the worker. + """ + return self.execute_unary( + request=request, + method=MethodInfo( + name="InitializeWorker", + service_name="workflows.v1.WorkerService", + input=InitializeRunnerRequest, + output=InitializeRunnerResponse, + idempotency_level=IdempotencyLevel.UNKNOWN, + ), + headers=headers, + timeout_ms=timeout_ms, + ) + def execute_task( + self, + request: Task, + *, + headers: Headers | Mapping[str, str] | None = None, + timeout_ms: int | None = None, + ) -> ExecuteTaskResponse: + """ + ExecuteTask is called by the runner to execute a task on the worker runtime. The worker runtime will execute the + task, and then respond with the result of the task execution, which can either be a computed task, or a failed + task. If the worker runtime crashes or becomes unreachable during task execution, the runner will treat the task + as failed with an unknown error. + The runner preserves the executing task's scope. Workers dispatch by local name/version and resolve child scopes + before returning them, defaulting to the executing task's scope rather than the release's workflow ID. + During migration, the managed runner fills missing child workflow IDs from the executing task's scope for legacy + workers, preserving explicit IDs. An explicit unscoped child of a scoped task cannot be expressed through this + adapter; it must be removed before supporting that override. The task service does not perform inheritance. + """ + return self.execute_unary( + request=request, + method=MethodInfo( + name="ExecuteTask", + service_name="workflows.v1.WorkerService", + input=Task, + output=ExecuteTaskResponse, + idempotency_level=IdempotencyLevel.UNKNOWN, + ), + headers=headers, + timeout_ms=timeout_ms, + ) + def watch_logs( + self, + request: Empty, + *, + headers: Headers | Mapping[str, str] | None = None, + timeout_ms: int | None = None, + ) -> Iterator[LogRecord]: + """ + WatchLogs streams structured runtime logs to the local runner, independently of API log exports and raw + stdout/stderr. It is available before workflow import completes and before InitializeWorker is called. + The runner sends one request; the worker sends buffered startup records followed by live records in enqueue + order. The stream stays open until shutdown, cancellation, or a connection failure, even while no tasks run. + Only one subscriber is allowed per runtime; concurrent subscriptions fail with ALREADY_EXISTS. + Delivery is best-effort with bounded buffering: slow or disconnected subscribers must not block tasks. + Buffer overflow is reported as a warning record when delivery resumes. There is no acknowledgement or replay + of delivered records. Canceling this stream does not shut down the worker or disable its API log exports. + Messages use the log body, severity, trace/span IDs, and attributes from the OpenTelemetry log model; + exception details use exception.type, exception.message, and exception.stacktrace attributes. + buf:lint:ignore RPC_NO_SERVER_STREAMING + """ + return self.execute_server_stream( + request=request, + method=MethodInfo( + name="WatchLogs", + service_name="workflows.v1.WorkerService", + input=Empty, + output=LogRecord, + idempotency_level=IdempotencyLevel.UNKNOWN, + ), + headers=headers, + timeout_ms=timeout_ms, + ) + def shutdown_worker( + self, + request: Empty, + *, + headers: Headers | Mapping[str, str] | None = None, + timeout_ms: int | None = None, + ) -> Empty: + """ + Gracefully shuts down the worker runtime. After receiving this request, the worker runtime will + finish task cleanup, flush API logs, and drain buffered WatchLogs records within a bounded deadline. + The log stream ends before the worker stops its RPC server; an open subscription must not prevent shutdown. + """ + return self.execute_unary( + request=request, + method=MethodInfo( + name="ShutdownWorker", + service_name="workflows.v1.WorkerService", + input=Empty, + output=Empty, + idempotency_level=IdempotencyLevel.UNKNOWN, + ), + headers=headers, + timeout_ms=timeout_ms, + ) diff --git a/tilebox-workflows/tilebox/workflows/v1/worker_pb.py b/tilebox-workflows/tilebox/workflows/v1/worker_pb.py new file mode 100644 index 0000000..18c2ff1 --- /dev/null +++ b/tilebox-workflows/tilebox/workflows/v1/worker_pb.py @@ -0,0 +1,218 @@ +# Generated from workflows/v1/worker.proto. DO NOT EDIT. +# Generated by protoc-gen-py v0.6.0 with parameter "init_files=false,map_imports=datasets/**:tilebox,map_imports=workflows/**:tilebox,map_imports=tilebox/**:_tilebox.proto,map_imports=opentelemetry/**:tilebox.workflows._otel". +# ruff: noqa: PGH004 +# ruff: noqa +# fmt: off + +from __future__ import annotations + +from typing import Literal, NoReturn, TYPE_CHECKING, TypeAlias + +from _tilebox.proto.tilebox.v1 import id_pb +from protobuf import Message +from protobuf._codegen import file_desc +from protobuf.wkt.google.protobuf import empty_pb +from tilebox.workflows._otel.opentelemetry.proto.logs.v1 import logs_pb +from tilebox.workflows.v1 import core_pb, task_pb, workflows_pb + +if TYPE_CHECKING: + from _tilebox.proto.tilebox.v1.id_pb import ID + from protobuf import DescFile + from tilebox.workflows.v1.task_pb import ComputedTask, TaskFailedRequest + from tilebox.workflows.v1.workflows_pb import Cluster, Workflow + + +_InitializeRunnerRequestFields: TypeAlias = Literal["runner_id", "trace_parent", "cluster", "workflow", "api_connection"] + +class InitializeRunnerRequest(Message[_InitializeRunnerRequestFields]): + """ + InitializeRunnerRequest is sent by the runner to a worker runtime to discover its capabilities. + + ```proto + message workflows.v1.InitializeRunnerRequest + ``` + + Attributes: + runner_id: + The id of the runner that this worker runtime is connected to. + + ```proto + optional tilebox.v1.ID runner_id = 1; + ``` + trace_parent: + The trace_parent that should be used for any traces emitted by this worker runtime (outside of task execution, + which has its own trace_parent propagation from jobs). This allows us to correlate any logs and traces emitted + by the worker runtime with the runner that it's connected to. + + ```proto + optional string trace_parent = 2; + ``` + cluster: + The cluster that the worker runtime is running on. + + ```proto + optional workflows.v1.Cluster cluster = 3; + ``` + workflow: + The workflow (and the release) that this worker was created from. + + ```proto + optional workflows.v1.Workflow workflow = 4; + ``` + api_connection: + The storage locations that the worker runtime can access, for triggered automations. + + ```proto + optional workflows.v1.TileboxAPIConnection api_connection = 5; + ``` + """ + + __slots__ = ("runner_id", "trace_parent", "cluster", "workflow", "api_connection") + + if TYPE_CHECKING: + + def __init__( + self, + *, + runner_id: ID | None = None, + trace_parent: str | None = None, + cluster: Cluster | None = None, + workflow: Workflow | None = None, + api_connection: TileboxAPIConnection | None = None, + ) -> None: + pass + + runner_id: ID | None + trace_parent: str + cluster: Cluster | None + workflow: Workflow | None + api_connection: TileboxAPIConnection | None + +_TileboxAPIConnectionFields: TypeAlias = Literal["url", "token"] + +class TileboxAPIConnection(Message[_TileboxAPIConnectionFields]): + """ + TileboxAPIConnection is a message containing the information needed for a worker runtime to connect to the Tilebox + API, to e.g. export observability data to (logs and traces). It's the credentials that the tilebox-cli locally + uses to connect to the Tilebox API, and is passed down to the worker runtimes so that they can also connect to the + Tilebox API in the same way. These credentials must only be sent over a trusted local runtime connection, + using a Unix socket or an authenticated loopback TCP connection, never an exposed network listener. + + ```proto + message workflows.v1.TileboxAPIConnection + ``` + + Attributes: + url: + The API url to which the worker runtime can connect, to e.g. export observability data to (logs and traces). + + ```proto + optional string url = 1; + ``` + token: + The token to use for authenticating to the API. + + ```proto + optional string token = 2; + ``` + """ + + __slots__ = ("url", "token") + + if TYPE_CHECKING: + + def __init__( + self, + *, + url: str | None = None, + token: str | None = None, + ) -> None: + pass + + url: str + token: str + +_InitializeRunnerResponseFields: TypeAlias = NoReturn + +class InitializeRunnerResponse(Message[_InitializeRunnerResponseFields]): + """ + HandshakeResponse is the response to a handshake request, containing the task identifiers that the worker can execute. + + ```proto + message workflows.v1.InitializeRunnerResponse + ``` + """ + + __slots__ = () + + if TYPE_CHECKING: + + def __init__( + self, + ) -> None: + pass + +_ExecuteTaskResponseFields: TypeAlias = Literal["computed_task", "failed_task"] + +class ExecuteTaskResponse(Message[_ExecuteTaskResponseFields]): + """ + ExecuteTaskResponse is the response of a worker runtime to a request to execute a task, after the task has + been executed. + + ```proto + message workflows.v1.ExecuteTaskResponse + ``` + + Attributes: + computed_task: + If the task execution succeeded, this field will be set with the computed task result. + + ```proto + optional workflows.v1.ComputedTask computed_task = 1; + ``` + failed_task: + If the task execution failed, this field will be set with details about the failure. + + ```proto + optional workflows.v1.TaskFailedRequest failed_task = 2; + ``` + """ + + __slots__ = ("computed_task", "failed_task") + + if TYPE_CHECKING: + + def __init__( + self, + *, + computed_task: ComputedTask | None = None, + failed_task: TaskFailedRequest | None = None, + ) -> None: + pass + + computed_task: ComputedTask | None + failed_task: TaskFailedRequest | None + + +_DESC = file_desc( + b'\n\x19workflows/v1/worker.proto\x12\x0cworkflows.v1\x1a\x1bgoogle/protobuf/empty.proto\x1a&opentelemetry/proto/logs/v1/logs.proto\x1a\x13tilebox/v1/id.proto\x1a\x17workflows/v1/core.proto\x1a\x17workflows/v1/task.proto\x1a\x1cworkflows/v1/workflows.proto"\x99\x02\n\x17InitializeRunnerRequest\x12+\n\trunner_id\x18\x01 \x01(\x0b2\x0e.tilebox.v1.IDR\x08runnerId\x12!\n\x0ctrace_parent\x18\x02 \x01(\tR\x0btraceParent\x12/\n\x07cluster\x18\x03 \x01(\x0b2\x15.workflows.v1.ClusterR\x07cluster\x122\n\x08workflow\x18\x04 \x01(\x0b2\x16.workflows.v1.WorkflowR\x08workflow\x12I\n\x0eapi_connection\x18\x05 \x01(\x0b2".workflows.v1.TileboxAPIConnectionR\rapiConnection">\n\x14TileboxAPIConnection\x12\x10\n\x03url\x18\x01 \x01(\tR\x03url\x12\x14\n\x05token\x18\x02 \x01(\tR\x05token"\x1a\n\x18InitializeRunnerResponse"\x98\x01\n\x13ExecuteTaskResponse\x12?\n\rcomputed_task\x18\x01 \x01(\x0b2\x1a.workflows.v1.ComputedTaskR\x0ccomputedTask\x12@\n\x0bfailed_task\x18\x02 \x01(\x0b2\x1f.workflows.v1.TaskFailedRequestR\nfailedTask2\x9e\x03\n\rWorkerService\x12S\n\x13ListRegisteredTasks\x12\x16.google.protobuf.Empty\x1a$.workflows.v1.UnboundTaskIdentifiers\x12a\n\x10InitializeWorker\x12%.workflows.v1.InitializeRunnerRequest\x1a&.workflows.v1.InitializeRunnerResponse\x12D\n\x0bExecuteTask\x12\x12.workflows.v1.Task\x1a!.workflows.v1.ExecuteTaskResponse\x12M\n\tWatchLogs\x12\x16.google.protobuf.Empty\x1a&.opentelemetry.proto.logs.v1.LogRecord0\x01\x12@\n\x0eShutdownWorker\x12\x16.google.protobuf.Empty\x1a\x16.google.protobuf.Emptyb\x08editionsp\xe8\x07', + [ + empty_pb.desc(), + logs_pb.desc(), + id_pb.desc(), + core_pb.desc(), + task_pb.desc(), + workflows_pb.desc(), + ], + { + "InitializeRunnerRequest": InitializeRunnerRequest, + "TileboxAPIConnection": TileboxAPIConnection, + "InitializeRunnerResponse": InitializeRunnerResponse, + "ExecuteTaskResponse": ExecuteTaskResponse, + }, +) + + +def desc() -> DescFile: + """Returns the descriptor for the file `workflows/v1/worker.proto`.""" + return _DESC diff --git a/tilebox-workflows/tilebox/workflows/workflows/v1/workflows_connect.py b/tilebox-workflows/tilebox/workflows/v1/workflows_connect.py similarity index 55% rename from tilebox-workflows/tilebox/workflows/workflows/v1/workflows_connect.py rename to tilebox-workflows/tilebox/workflows/v1/workflows_connect.py index 5262cda..2e97749 100644 --- a/tilebox-workflows/tilebox/workflows/workflows/v1/workflows_connect.py +++ b/tilebox-workflows/tilebox/workflows/v1/workflows_connect.py @@ -1,71 +1,95 @@ -# -*- coding: utf-8 -*- -# Generated by https://github.com/connectrpc/connect-python. DO NOT EDIT! -# source: workflows/v1/workflows.proto +# Generated from workflows/v1/workflows.proto. DO NOT EDIT. +# Generated by protoc-gen-connectrpc-py v0.12.1 with parameter "map_imports=datasets/**:tilebox,map_imports=workflows/**:tilebox,map_imports=tilebox/**:_tilebox.proto,map_imports=opentelemetry/**:tilebox.workflows._otel". +# ruff: noqa: PGH004 +# ruff: noqa +# fmt: off -from collections.abc import AsyncGenerator, AsyncIterator, Iterable, Iterator, Mapping -from typing import Protocol +from __future__ import annotations + +from typing import Protocol, TYPE_CHECKING from connectrpc.client import ConnectClient, ConnectClientSync from connectrpc.code import Code -from connectrpc.codec import Codec -from connectrpc.compression import Compression from connectrpc.errors import ConnectError -from connectrpc.interceptor import Interceptor, InterceptorSync from connectrpc.method import IdempotencyLevel, MethodInfo -from connectrpc.request import Headers, RequestContext -from connectrpc.server import ConnectASGIApplication, ConnectWSGIApplication, Endpoint, EndpointSync -import tilebox.workflows.workflows.v1.workflows_pb2 as workflows_dot_v1_dot_workflows__pb2 +from connectrpc.server import ConnectASGIApplication, ConnectWSGIApplication, DEFAULT_READ_MAX_BYTES, Endpoint, EndpointSync +from protobuf import DescService +from tilebox.workflows.v1 import workflows_pb +from tilebox.workflows.v1.workflows_pb import Cluster, CreateClusterRequest, CreateWorkflowRequest, DeleteClusterRequest, DeleteClusterResponse, DeleteWorkflowRequest, DeleteWorkflowResponse, DeployWorkflowReleaseRequest, DeployWorkflowReleaseResponse, GetClusterRequest, GetWorkflowRequest, ListClustersRequest, ListClustersResponse, ListPublicWorkflowsRequest, ListPublicWorkflowsResponse, ListWorkflowsRequest, ListWorkflowsResponse, PublishWorkflowReleaseRequest, UndeployWorkflowReleaseRequest, UndeployWorkflowReleaseResponse, UnpublishWorkflowReleaseRequest, UnpublishWorkflowReleaseResponse, UpdateClusterRequest, UpdateWorkflowRequest, Workflow, WorkflowRelease + +if TYPE_CHECKING: + from collections.abc import AsyncGenerator, Iterable, Mapping + + from connectrpc.codec import Codec + from connectrpc.compression import Compression + from connectrpc.interceptor import Interceptor, InterceptorSync + from connectrpc.request import Headers, RequestContext class WorkflowsService(Protocol): - async def create_cluster(self, request: workflows_dot_v1_dot_workflows__pb2.CreateClusterRequest, ctx: RequestContext) -> workflows_dot_v1_dot_workflows__pb2.Cluster: - raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") + """ + A service for managing workflows. + """ + async def create_cluster(self, request: CreateClusterRequest, ctx: RequestContext[CreateClusterRequest, Cluster], /) -> Cluster: + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') - async def get_cluster(self, request: workflows_dot_v1_dot_workflows__pb2.GetClusterRequest, ctx: RequestContext) -> workflows_dot_v1_dot_workflows__pb2.Cluster: - raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") + async def get_cluster(self, request: GetClusterRequest, ctx: RequestContext[GetClusterRequest, Cluster], /) -> Cluster: + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') - async def update_cluster(self, request: workflows_dot_v1_dot_workflows__pb2.UpdateClusterRequest, ctx: RequestContext) -> workflows_dot_v1_dot_workflows__pb2.Cluster: - raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") + async def update_cluster(self, request: UpdateClusterRequest, ctx: RequestContext[UpdateClusterRequest, Cluster], /) -> Cluster: + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') - async def delete_cluster(self, request: workflows_dot_v1_dot_workflows__pb2.DeleteClusterRequest, ctx: RequestContext) -> workflows_dot_v1_dot_workflows__pb2.DeleteClusterResponse: - raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") + async def delete_cluster(self, request: DeleteClusterRequest, ctx: RequestContext[DeleteClusterRequest, DeleteClusterResponse], /) -> DeleteClusterResponse: + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') - async def list_clusters(self, request: workflows_dot_v1_dot_workflows__pb2.ListClustersRequest, ctx: RequestContext) -> workflows_dot_v1_dot_workflows__pb2.ListClustersResponse: - raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") + async def list_clusters(self, request: ListClustersRequest, ctx: RequestContext[ListClustersRequest, ListClustersResponse], /) -> ListClustersResponse: + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') - async def create_workflow(self, request: workflows_dot_v1_dot_workflows__pb2.CreateWorkflowRequest, ctx: RequestContext) -> workflows_dot_v1_dot_workflows__pb2.Workflow: - raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") + async def create_workflow(self, request: CreateWorkflowRequest, ctx: RequestContext[CreateWorkflowRequest, Workflow], /) -> Workflow: + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') - async def list_workflows(self, request: workflows_dot_v1_dot_workflows__pb2.ListWorkflowsRequest, ctx: RequestContext) -> workflows_dot_v1_dot_workflows__pb2.ListWorkflowsResponse: - raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") + async def list_workflows(self, request: ListWorkflowsRequest, ctx: RequestContext[ListWorkflowsRequest, ListWorkflowsResponse], /) -> ListWorkflowsResponse: + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') - async def list_public_workflows(self, request: workflows_dot_v1_dot_workflows__pb2.ListPublicWorkflowsRequest, ctx: RequestContext) -> workflows_dot_v1_dot_workflows__pb2.ListPublicWorkflowsResponse: - raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") + async def list_public_workflows(self, request: ListPublicWorkflowsRequest, ctx: RequestContext[ListPublicWorkflowsRequest, ListPublicWorkflowsResponse], /) -> ListPublicWorkflowsResponse: + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') - async def get_workflow(self, request: workflows_dot_v1_dot_workflows__pb2.GetWorkflowRequest, ctx: RequestContext) -> workflows_dot_v1_dot_workflows__pb2.Workflow: - raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") + async def get_workflow(self, request: GetWorkflowRequest, ctx: RequestContext[GetWorkflowRequest, Workflow], /) -> Workflow: + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') - async def update_workflow(self, request: workflows_dot_v1_dot_workflows__pb2.UpdateWorkflowRequest, ctx: RequestContext) -> workflows_dot_v1_dot_workflows__pb2.Workflow: - raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") + async def update_workflow(self, request: UpdateWorkflowRequest, ctx: RequestContext[UpdateWorkflowRequest, Workflow], /) -> Workflow: + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') - async def delete_workflow(self, request: workflows_dot_v1_dot_workflows__pb2.DeleteWorkflowRequest, ctx: RequestContext) -> workflows_dot_v1_dot_workflows__pb2.DeleteWorkflowResponse: - raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") + async def delete_workflow(self, request: DeleteWorkflowRequest, ctx: RequestContext[DeleteWorkflowRequest, DeleteWorkflowResponse], /) -> DeleteWorkflowResponse: + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') - async def publish_workflow_release(self, request: workflows_dot_v1_dot_workflows__pb2.PublishWorkflowReleaseRequest, ctx: RequestContext) -> workflows_dot_v1_dot_workflows__pb2.WorkflowRelease: - raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") + async def publish_workflow_release(self, request: PublishWorkflowReleaseRequest, ctx: RequestContext[PublishWorkflowReleaseRequest, WorkflowRelease], /) -> WorkflowRelease: + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') - async def unpublish_workflow_release(self, request: workflows_dot_v1_dot_workflows__pb2.UnpublishWorkflowReleaseRequest, ctx: RequestContext) -> workflows_dot_v1_dot_workflows__pb2.UnpublishWorkflowReleaseResponse: - raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") + async def unpublish_workflow_release(self, request: UnpublishWorkflowReleaseRequest, ctx: RequestContext[UnpublishWorkflowReleaseRequest, UnpublishWorkflowReleaseResponse], /) -> UnpublishWorkflowReleaseResponse: + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') - async def deploy_workflow_release(self, request: workflows_dot_v1_dot_workflows__pb2.DeployWorkflowReleaseRequest, ctx: RequestContext) -> workflows_dot_v1_dot_workflows__pb2.DeployWorkflowReleaseResponse: - raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") + async def deploy_workflow_release(self, request: DeployWorkflowReleaseRequest, ctx: RequestContext[DeployWorkflowReleaseRequest, DeployWorkflowReleaseResponse], /) -> DeployWorkflowReleaseResponse: + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') - async def undeploy_workflow_release(self, request: workflows_dot_v1_dot_workflows__pb2.UndeployWorkflowReleaseRequest, ctx: RequestContext) -> workflows_dot_v1_dot_workflows__pb2.UndeployWorkflowReleaseResponse: - raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") + async def undeploy_workflow_release(self, request: UndeployWorkflowReleaseRequest, ctx: RequestContext[UndeployWorkflowReleaseRequest, UndeployWorkflowReleaseResponse], /) -> UndeployWorkflowReleaseResponse: + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') + @classmethod + def desc(cls) -> DescService: + """Returns the descriptor for this service.""" + return next(s for s in workflows_pb.desc().services if s.type_name == 'workflows.v1.WorkflowsService') class WorkflowsServiceASGIApplication(ConnectASGIApplication[WorkflowsService]): - def __init__(self, service: WorkflowsService | AsyncGenerator[WorkflowsService], *, interceptors: Iterable[Interceptor]=(), read_max_bytes: int | None = None, compressions: Iterable[Compression] | None = None, codecs: Iterable[Codec] | None = None) -> None: + def __init__( + self, + service: WorkflowsService | AsyncGenerator[WorkflowsService], + *, + interceptors: Iterable[Interceptor] = (), + read_max_bytes: int | None = DEFAULT_READ_MAX_BYTES, + compressions: Iterable[Compression] | None = None, + codecs: Iterable[Codec] | None = None, + ) -> None: super().__init__( service=service, endpoints=lambda svc: { @@ -73,8 +97,8 @@ def __init__(self, service: WorkflowsService | AsyncGenerator[WorkflowsService], method=MethodInfo( name="CreateCluster", service_name="workflows.v1.WorkflowsService", - input=workflows_dot_v1_dot_workflows__pb2.CreateClusterRequest, - output=workflows_dot_v1_dot_workflows__pb2.Cluster, + input=CreateClusterRequest, + output=Cluster, idempotency_level=IdempotencyLevel.UNKNOWN, ), function=svc.create_cluster, @@ -83,8 +107,8 @@ def __init__(self, service: WorkflowsService | AsyncGenerator[WorkflowsService], method=MethodInfo( name="GetCluster", service_name="workflows.v1.WorkflowsService", - input=workflows_dot_v1_dot_workflows__pb2.GetClusterRequest, - output=workflows_dot_v1_dot_workflows__pb2.Cluster, + input=GetClusterRequest, + output=Cluster, idempotency_level=IdempotencyLevel.UNKNOWN, ), function=svc.get_cluster, @@ -93,8 +117,8 @@ def __init__(self, service: WorkflowsService | AsyncGenerator[WorkflowsService], method=MethodInfo( name="UpdateCluster", service_name="workflows.v1.WorkflowsService", - input=workflows_dot_v1_dot_workflows__pb2.UpdateClusterRequest, - output=workflows_dot_v1_dot_workflows__pb2.Cluster, + input=UpdateClusterRequest, + output=Cluster, idempotency_level=IdempotencyLevel.UNKNOWN, ), function=svc.update_cluster, @@ -103,8 +127,8 @@ def __init__(self, service: WorkflowsService | AsyncGenerator[WorkflowsService], method=MethodInfo( name="DeleteCluster", service_name="workflows.v1.WorkflowsService", - input=workflows_dot_v1_dot_workflows__pb2.DeleteClusterRequest, - output=workflows_dot_v1_dot_workflows__pb2.DeleteClusterResponse, + input=DeleteClusterRequest, + output=DeleteClusterResponse, idempotency_level=IdempotencyLevel.UNKNOWN, ), function=svc.delete_cluster, @@ -113,8 +137,8 @@ def __init__(self, service: WorkflowsService | AsyncGenerator[WorkflowsService], method=MethodInfo( name="ListClusters", service_name="workflows.v1.WorkflowsService", - input=workflows_dot_v1_dot_workflows__pb2.ListClustersRequest, - output=workflows_dot_v1_dot_workflows__pb2.ListClustersResponse, + input=ListClustersRequest, + output=ListClustersResponse, idempotency_level=IdempotencyLevel.UNKNOWN, ), function=svc.list_clusters, @@ -123,8 +147,8 @@ def __init__(self, service: WorkflowsService | AsyncGenerator[WorkflowsService], method=MethodInfo( name="CreateWorkflow", service_name="workflows.v1.WorkflowsService", - input=workflows_dot_v1_dot_workflows__pb2.CreateWorkflowRequest, - output=workflows_dot_v1_dot_workflows__pb2.Workflow, + input=CreateWorkflowRequest, + output=Workflow, idempotency_level=IdempotencyLevel.UNKNOWN, ), function=svc.create_workflow, @@ -133,8 +157,8 @@ def __init__(self, service: WorkflowsService | AsyncGenerator[WorkflowsService], method=MethodInfo( name="ListWorkflows", service_name="workflows.v1.WorkflowsService", - input=workflows_dot_v1_dot_workflows__pb2.ListWorkflowsRequest, - output=workflows_dot_v1_dot_workflows__pb2.ListWorkflowsResponse, + input=ListWorkflowsRequest, + output=ListWorkflowsResponse, idempotency_level=IdempotencyLevel.UNKNOWN, ), function=svc.list_workflows, @@ -143,8 +167,8 @@ def __init__(self, service: WorkflowsService | AsyncGenerator[WorkflowsService], method=MethodInfo( name="ListPublicWorkflows", service_name="workflows.v1.WorkflowsService", - input=workflows_dot_v1_dot_workflows__pb2.ListPublicWorkflowsRequest, - output=workflows_dot_v1_dot_workflows__pb2.ListPublicWorkflowsResponse, + input=ListPublicWorkflowsRequest, + output=ListPublicWorkflowsResponse, idempotency_level=IdempotencyLevel.UNKNOWN, ), function=svc.list_public_workflows, @@ -153,8 +177,8 @@ def __init__(self, service: WorkflowsService | AsyncGenerator[WorkflowsService], method=MethodInfo( name="GetWorkflow", service_name="workflows.v1.WorkflowsService", - input=workflows_dot_v1_dot_workflows__pb2.GetWorkflowRequest, - output=workflows_dot_v1_dot_workflows__pb2.Workflow, + input=GetWorkflowRequest, + output=Workflow, idempotency_level=IdempotencyLevel.UNKNOWN, ), function=svc.get_workflow, @@ -163,8 +187,8 @@ def __init__(self, service: WorkflowsService | AsyncGenerator[WorkflowsService], method=MethodInfo( name="UpdateWorkflow", service_name="workflows.v1.WorkflowsService", - input=workflows_dot_v1_dot_workflows__pb2.UpdateWorkflowRequest, - output=workflows_dot_v1_dot_workflows__pb2.Workflow, + input=UpdateWorkflowRequest, + output=Workflow, idempotency_level=IdempotencyLevel.UNKNOWN, ), function=svc.update_workflow, @@ -173,8 +197,8 @@ def __init__(self, service: WorkflowsService | AsyncGenerator[WorkflowsService], method=MethodInfo( name="DeleteWorkflow", service_name="workflows.v1.WorkflowsService", - input=workflows_dot_v1_dot_workflows__pb2.DeleteWorkflowRequest, - output=workflows_dot_v1_dot_workflows__pb2.DeleteWorkflowResponse, + input=DeleteWorkflowRequest, + output=DeleteWorkflowResponse, idempotency_level=IdempotencyLevel.UNKNOWN, ), function=svc.delete_workflow, @@ -183,8 +207,8 @@ def __init__(self, service: WorkflowsService | AsyncGenerator[WorkflowsService], method=MethodInfo( name="PublishWorkflowRelease", service_name="workflows.v1.WorkflowsService", - input=workflows_dot_v1_dot_workflows__pb2.PublishWorkflowReleaseRequest, - output=workflows_dot_v1_dot_workflows__pb2.WorkflowRelease, + input=PublishWorkflowReleaseRequest, + output=WorkflowRelease, idempotency_level=IdempotencyLevel.UNKNOWN, ), function=svc.publish_workflow_release, @@ -193,8 +217,8 @@ def __init__(self, service: WorkflowsService | AsyncGenerator[WorkflowsService], method=MethodInfo( name="UnpublishWorkflowRelease", service_name="workflows.v1.WorkflowsService", - input=workflows_dot_v1_dot_workflows__pb2.UnpublishWorkflowReleaseRequest, - output=workflows_dot_v1_dot_workflows__pb2.UnpublishWorkflowReleaseResponse, + input=UnpublishWorkflowReleaseRequest, + output=UnpublishWorkflowReleaseResponse, idempotency_level=IdempotencyLevel.UNKNOWN, ), function=svc.unpublish_workflow_release, @@ -203,8 +227,8 @@ def __init__(self, service: WorkflowsService | AsyncGenerator[WorkflowsService], method=MethodInfo( name="DeployWorkflowRelease", service_name="workflows.v1.WorkflowsService", - input=workflows_dot_v1_dot_workflows__pb2.DeployWorkflowReleaseRequest, - output=workflows_dot_v1_dot_workflows__pb2.DeployWorkflowReleaseResponse, + input=DeployWorkflowReleaseRequest, + output=DeployWorkflowReleaseResponse, idempotency_level=IdempotencyLevel.UNKNOWN, ), function=svc.deploy_workflow_release, @@ -213,8 +237,8 @@ def __init__(self, service: WorkflowsService | AsyncGenerator[WorkflowsService], method=MethodInfo( name="UndeployWorkflowRelease", service_name="workflows.v1.WorkflowsService", - input=workflows_dot_v1_dot_workflows__pb2.UndeployWorkflowReleaseRequest, - output=workflows_dot_v1_dot_workflows__pb2.UndeployWorkflowReleaseResponse, + input=UndeployWorkflowReleaseRequest, + output=UndeployWorkflowReleaseResponse, idempotency_level=IdempotencyLevel.UNKNOWN, ), function=svc.undeploy_workflow_release, @@ -233,20 +257,23 @@ def path(self) -> str: class WorkflowsServiceClient(ConnectClient): + """ + A service for managing workflows. + """ async def create_cluster( self, - request: workflows_dot_v1_dot_workflows__pb2.CreateClusterRequest, + request: CreateClusterRequest, *, headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, - ) -> workflows_dot_v1_dot_workflows__pb2.Cluster: + ) -> Cluster: return await self.execute_unary( request=request, method=MethodInfo( name="CreateCluster", service_name="workflows.v1.WorkflowsService", - input=workflows_dot_v1_dot_workflows__pb2.CreateClusterRequest, - output=workflows_dot_v1_dot_workflows__pb2.Cluster, + input=CreateClusterRequest, + output=Cluster, idempotency_level=IdempotencyLevel.UNKNOWN, ), headers=headers, @@ -255,18 +282,18 @@ async def create_cluster( async def get_cluster( self, - request: workflows_dot_v1_dot_workflows__pb2.GetClusterRequest, + request: GetClusterRequest, *, headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, - ) -> workflows_dot_v1_dot_workflows__pb2.Cluster: + ) -> Cluster: return await self.execute_unary( request=request, method=MethodInfo( name="GetCluster", service_name="workflows.v1.WorkflowsService", - input=workflows_dot_v1_dot_workflows__pb2.GetClusterRequest, - output=workflows_dot_v1_dot_workflows__pb2.Cluster, + input=GetClusterRequest, + output=Cluster, idempotency_level=IdempotencyLevel.UNKNOWN, ), headers=headers, @@ -275,18 +302,18 @@ async def get_cluster( async def update_cluster( self, - request: workflows_dot_v1_dot_workflows__pb2.UpdateClusterRequest, + request: UpdateClusterRequest, *, headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, - ) -> workflows_dot_v1_dot_workflows__pb2.Cluster: + ) -> Cluster: return await self.execute_unary( request=request, method=MethodInfo( name="UpdateCluster", service_name="workflows.v1.WorkflowsService", - input=workflows_dot_v1_dot_workflows__pb2.UpdateClusterRequest, - output=workflows_dot_v1_dot_workflows__pb2.Cluster, + input=UpdateClusterRequest, + output=Cluster, idempotency_level=IdempotencyLevel.UNKNOWN, ), headers=headers, @@ -295,18 +322,18 @@ async def update_cluster( async def delete_cluster( self, - request: workflows_dot_v1_dot_workflows__pb2.DeleteClusterRequest, + request: DeleteClusterRequest, *, headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, - ) -> workflows_dot_v1_dot_workflows__pb2.DeleteClusterResponse: + ) -> DeleteClusterResponse: return await self.execute_unary( request=request, method=MethodInfo( name="DeleteCluster", service_name="workflows.v1.WorkflowsService", - input=workflows_dot_v1_dot_workflows__pb2.DeleteClusterRequest, - output=workflows_dot_v1_dot_workflows__pb2.DeleteClusterResponse, + input=DeleteClusterRequest, + output=DeleteClusterResponse, idempotency_level=IdempotencyLevel.UNKNOWN, ), headers=headers, @@ -315,18 +342,18 @@ async def delete_cluster( async def list_clusters( self, - request: workflows_dot_v1_dot_workflows__pb2.ListClustersRequest, + request: ListClustersRequest, *, headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, - ) -> workflows_dot_v1_dot_workflows__pb2.ListClustersResponse: + ) -> ListClustersResponse: return await self.execute_unary( request=request, method=MethodInfo( name="ListClusters", service_name="workflows.v1.WorkflowsService", - input=workflows_dot_v1_dot_workflows__pb2.ListClustersRequest, - output=workflows_dot_v1_dot_workflows__pb2.ListClustersResponse, + input=ListClustersRequest, + output=ListClustersResponse, idempotency_level=IdempotencyLevel.UNKNOWN, ), headers=headers, @@ -335,18 +362,18 @@ async def list_clusters( async def create_workflow( self, - request: workflows_dot_v1_dot_workflows__pb2.CreateWorkflowRequest, + request: CreateWorkflowRequest, *, headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, - ) -> workflows_dot_v1_dot_workflows__pb2.Workflow: + ) -> Workflow: return await self.execute_unary( request=request, method=MethodInfo( name="CreateWorkflow", service_name="workflows.v1.WorkflowsService", - input=workflows_dot_v1_dot_workflows__pb2.CreateWorkflowRequest, - output=workflows_dot_v1_dot_workflows__pb2.Workflow, + input=CreateWorkflowRequest, + output=Workflow, idempotency_level=IdempotencyLevel.UNKNOWN, ), headers=headers, @@ -355,18 +382,18 @@ async def create_workflow( async def list_workflows( self, - request: workflows_dot_v1_dot_workflows__pb2.ListWorkflowsRequest, + request: ListWorkflowsRequest, *, headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, - ) -> workflows_dot_v1_dot_workflows__pb2.ListWorkflowsResponse: + ) -> ListWorkflowsResponse: return await self.execute_unary( request=request, method=MethodInfo( name="ListWorkflows", service_name="workflows.v1.WorkflowsService", - input=workflows_dot_v1_dot_workflows__pb2.ListWorkflowsRequest, - output=workflows_dot_v1_dot_workflows__pb2.ListWorkflowsResponse, + input=ListWorkflowsRequest, + output=ListWorkflowsResponse, idempotency_level=IdempotencyLevel.UNKNOWN, ), headers=headers, @@ -375,18 +402,18 @@ async def list_workflows( async def list_public_workflows( self, - request: workflows_dot_v1_dot_workflows__pb2.ListPublicWorkflowsRequest, + request: ListPublicWorkflowsRequest, *, headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, - ) -> workflows_dot_v1_dot_workflows__pb2.ListPublicWorkflowsResponse: + ) -> ListPublicWorkflowsResponse: return await self.execute_unary( request=request, method=MethodInfo( name="ListPublicWorkflows", service_name="workflows.v1.WorkflowsService", - input=workflows_dot_v1_dot_workflows__pb2.ListPublicWorkflowsRequest, - output=workflows_dot_v1_dot_workflows__pb2.ListPublicWorkflowsResponse, + input=ListPublicWorkflowsRequest, + output=ListPublicWorkflowsResponse, idempotency_level=IdempotencyLevel.UNKNOWN, ), headers=headers, @@ -395,18 +422,18 @@ async def list_public_workflows( async def get_workflow( self, - request: workflows_dot_v1_dot_workflows__pb2.GetWorkflowRequest, + request: GetWorkflowRequest, *, headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, - ) -> workflows_dot_v1_dot_workflows__pb2.Workflow: + ) -> Workflow: return await self.execute_unary( request=request, method=MethodInfo( name="GetWorkflow", service_name="workflows.v1.WorkflowsService", - input=workflows_dot_v1_dot_workflows__pb2.GetWorkflowRequest, - output=workflows_dot_v1_dot_workflows__pb2.Workflow, + input=GetWorkflowRequest, + output=Workflow, idempotency_level=IdempotencyLevel.UNKNOWN, ), headers=headers, @@ -415,18 +442,18 @@ async def get_workflow( async def update_workflow( self, - request: workflows_dot_v1_dot_workflows__pb2.UpdateWorkflowRequest, + request: UpdateWorkflowRequest, *, headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, - ) -> workflows_dot_v1_dot_workflows__pb2.Workflow: + ) -> Workflow: return await self.execute_unary( request=request, method=MethodInfo( name="UpdateWorkflow", service_name="workflows.v1.WorkflowsService", - input=workflows_dot_v1_dot_workflows__pb2.UpdateWorkflowRequest, - output=workflows_dot_v1_dot_workflows__pb2.Workflow, + input=UpdateWorkflowRequest, + output=Workflow, idempotency_level=IdempotencyLevel.UNKNOWN, ), headers=headers, @@ -435,18 +462,18 @@ async def update_workflow( async def delete_workflow( self, - request: workflows_dot_v1_dot_workflows__pb2.DeleteWorkflowRequest, + request: DeleteWorkflowRequest, *, headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, - ) -> workflows_dot_v1_dot_workflows__pb2.DeleteWorkflowResponse: + ) -> DeleteWorkflowResponse: return await self.execute_unary( request=request, method=MethodInfo( name="DeleteWorkflow", service_name="workflows.v1.WorkflowsService", - input=workflows_dot_v1_dot_workflows__pb2.DeleteWorkflowRequest, - output=workflows_dot_v1_dot_workflows__pb2.DeleteWorkflowResponse, + input=DeleteWorkflowRequest, + output=DeleteWorkflowResponse, idempotency_level=IdempotencyLevel.UNKNOWN, ), headers=headers, @@ -455,18 +482,18 @@ async def delete_workflow( async def publish_workflow_release( self, - request: workflows_dot_v1_dot_workflows__pb2.PublishWorkflowReleaseRequest, + request: PublishWorkflowReleaseRequest, *, headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, - ) -> workflows_dot_v1_dot_workflows__pb2.WorkflowRelease: + ) -> WorkflowRelease: return await self.execute_unary( request=request, method=MethodInfo( name="PublishWorkflowRelease", service_name="workflows.v1.WorkflowsService", - input=workflows_dot_v1_dot_workflows__pb2.PublishWorkflowReleaseRequest, - output=workflows_dot_v1_dot_workflows__pb2.WorkflowRelease, + input=PublishWorkflowReleaseRequest, + output=WorkflowRelease, idempotency_level=IdempotencyLevel.UNKNOWN, ), headers=headers, @@ -475,18 +502,18 @@ async def publish_workflow_release( async def unpublish_workflow_release( self, - request: workflows_dot_v1_dot_workflows__pb2.UnpublishWorkflowReleaseRequest, + request: UnpublishWorkflowReleaseRequest, *, headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, - ) -> workflows_dot_v1_dot_workflows__pb2.UnpublishWorkflowReleaseResponse: + ) -> UnpublishWorkflowReleaseResponse: return await self.execute_unary( request=request, method=MethodInfo( name="UnpublishWorkflowRelease", service_name="workflows.v1.WorkflowsService", - input=workflows_dot_v1_dot_workflows__pb2.UnpublishWorkflowReleaseRequest, - output=workflows_dot_v1_dot_workflows__pb2.UnpublishWorkflowReleaseResponse, + input=UnpublishWorkflowReleaseRequest, + output=UnpublishWorkflowReleaseResponse, idempotency_level=IdempotencyLevel.UNKNOWN, ), headers=headers, @@ -495,18 +522,18 @@ async def unpublish_workflow_release( async def deploy_workflow_release( self, - request: workflows_dot_v1_dot_workflows__pb2.DeployWorkflowReleaseRequest, + request: DeployWorkflowReleaseRequest, *, headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, - ) -> workflows_dot_v1_dot_workflows__pb2.DeployWorkflowReleaseResponse: + ) -> DeployWorkflowReleaseResponse: return await self.execute_unary( request=request, method=MethodInfo( name="DeployWorkflowRelease", service_name="workflows.v1.WorkflowsService", - input=workflows_dot_v1_dot_workflows__pb2.DeployWorkflowReleaseRequest, - output=workflows_dot_v1_dot_workflows__pb2.DeployWorkflowReleaseResponse, + input=DeployWorkflowReleaseRequest, + output=DeployWorkflowReleaseResponse, idempotency_level=IdempotencyLevel.UNKNOWN, ), headers=headers, @@ -515,71 +542,95 @@ async def deploy_workflow_release( async def undeploy_workflow_release( self, - request: workflows_dot_v1_dot_workflows__pb2.UndeployWorkflowReleaseRequest, + request: UndeployWorkflowReleaseRequest, *, headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, - ) -> workflows_dot_v1_dot_workflows__pb2.UndeployWorkflowReleaseResponse: + ) -> UndeployWorkflowReleaseResponse: return await self.execute_unary( request=request, method=MethodInfo( name="UndeployWorkflowRelease", service_name="workflows.v1.WorkflowsService", - input=workflows_dot_v1_dot_workflows__pb2.UndeployWorkflowReleaseRequest, - output=workflows_dot_v1_dot_workflows__pb2.UndeployWorkflowReleaseResponse, + input=UndeployWorkflowReleaseRequest, + output=UndeployWorkflowReleaseResponse, idempotency_level=IdempotencyLevel.UNKNOWN, ), headers=headers, timeout_ms=timeout_ms, ) +class WorkflowsServiceSync(Protocol): + """ + A service for managing workflows. + """ + def create_cluster(self, request: CreateClusterRequest, ctx: RequestContext[CreateClusterRequest, Cluster], /) -> Cluster: + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') + + def get_cluster(self, request: GetClusterRequest, ctx: RequestContext[GetClusterRequest, Cluster], /) -> Cluster: + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') + def update_cluster(self, request: UpdateClusterRequest, ctx: RequestContext[UpdateClusterRequest, Cluster], /) -> Cluster: + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') + def delete_cluster(self, request: DeleteClusterRequest, ctx: RequestContext[DeleteClusterRequest, DeleteClusterResponse], /) -> DeleteClusterResponse: + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') + def list_clusters(self, request: ListClustersRequest, ctx: RequestContext[ListClustersRequest, ListClustersResponse], /) -> ListClustersResponse: + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') -class WorkflowsServiceSync(Protocol): - def create_cluster(self, request: workflows_dot_v1_dot_workflows__pb2.CreateClusterRequest, ctx: RequestContext) -> workflows_dot_v1_dot_workflows__pb2.Cluster: - raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") - def get_cluster(self, request: workflows_dot_v1_dot_workflows__pb2.GetClusterRequest, ctx: RequestContext) -> workflows_dot_v1_dot_workflows__pb2.Cluster: - raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") - def update_cluster(self, request: workflows_dot_v1_dot_workflows__pb2.UpdateClusterRequest, ctx: RequestContext) -> workflows_dot_v1_dot_workflows__pb2.Cluster: - raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") - def delete_cluster(self, request: workflows_dot_v1_dot_workflows__pb2.DeleteClusterRequest, ctx: RequestContext) -> workflows_dot_v1_dot_workflows__pb2.DeleteClusterResponse: - raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") - def list_clusters(self, request: workflows_dot_v1_dot_workflows__pb2.ListClustersRequest, ctx: RequestContext) -> workflows_dot_v1_dot_workflows__pb2.ListClustersResponse: - raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") - def create_workflow(self, request: workflows_dot_v1_dot_workflows__pb2.CreateWorkflowRequest, ctx: RequestContext) -> workflows_dot_v1_dot_workflows__pb2.Workflow: - raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") - def list_workflows(self, request: workflows_dot_v1_dot_workflows__pb2.ListWorkflowsRequest, ctx: RequestContext) -> workflows_dot_v1_dot_workflows__pb2.ListWorkflowsResponse: - raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") - def list_public_workflows(self, request: workflows_dot_v1_dot_workflows__pb2.ListPublicWorkflowsRequest, ctx: RequestContext) -> workflows_dot_v1_dot_workflows__pb2.ListPublicWorkflowsResponse: - raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") - def get_workflow(self, request: workflows_dot_v1_dot_workflows__pb2.GetWorkflowRequest, ctx: RequestContext) -> workflows_dot_v1_dot_workflows__pb2.Workflow: - raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") - def update_workflow(self, request: workflows_dot_v1_dot_workflows__pb2.UpdateWorkflowRequest, ctx: RequestContext) -> workflows_dot_v1_dot_workflows__pb2.Workflow: - raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") - def delete_workflow(self, request: workflows_dot_v1_dot_workflows__pb2.DeleteWorkflowRequest, ctx: RequestContext) -> workflows_dot_v1_dot_workflows__pb2.DeleteWorkflowResponse: - raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") - def publish_workflow_release(self, request: workflows_dot_v1_dot_workflows__pb2.PublishWorkflowReleaseRequest, ctx: RequestContext) -> workflows_dot_v1_dot_workflows__pb2.WorkflowRelease: - raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") - def unpublish_workflow_release(self, request: workflows_dot_v1_dot_workflows__pb2.UnpublishWorkflowReleaseRequest, ctx: RequestContext) -> workflows_dot_v1_dot_workflows__pb2.UnpublishWorkflowReleaseResponse: - raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") - def deploy_workflow_release(self, request: workflows_dot_v1_dot_workflows__pb2.DeployWorkflowReleaseRequest, ctx: RequestContext) -> workflows_dot_v1_dot_workflows__pb2.DeployWorkflowReleaseResponse: - raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") - def undeploy_workflow_release(self, request: workflows_dot_v1_dot_workflows__pb2.UndeployWorkflowReleaseRequest, ctx: RequestContext) -> workflows_dot_v1_dot_workflows__pb2.UndeployWorkflowReleaseResponse: - raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") + def create_workflow(self, request: CreateWorkflowRequest, ctx: RequestContext[CreateWorkflowRequest, Workflow], /) -> Workflow: + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') + + def list_workflows(self, request: ListWorkflowsRequest, ctx: RequestContext[ListWorkflowsRequest, ListWorkflowsResponse], /) -> ListWorkflowsResponse: + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') + + def list_public_workflows(self, request: ListPublicWorkflowsRequest, ctx: RequestContext[ListPublicWorkflowsRequest, ListPublicWorkflowsResponse], /) -> ListPublicWorkflowsResponse: + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') + + def get_workflow(self, request: GetWorkflowRequest, ctx: RequestContext[GetWorkflowRequest, Workflow], /) -> Workflow: + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') + + def update_workflow(self, request: UpdateWorkflowRequest, ctx: RequestContext[UpdateWorkflowRequest, Workflow], /) -> Workflow: + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') + + def delete_workflow(self, request: DeleteWorkflowRequest, ctx: RequestContext[DeleteWorkflowRequest, DeleteWorkflowResponse], /) -> DeleteWorkflowResponse: + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') + + def publish_workflow_release(self, request: PublishWorkflowReleaseRequest, ctx: RequestContext[PublishWorkflowReleaseRequest, WorkflowRelease], /) -> WorkflowRelease: + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') + + def unpublish_workflow_release(self, request: UnpublishWorkflowReleaseRequest, ctx: RequestContext[UnpublishWorkflowReleaseRequest, UnpublishWorkflowReleaseResponse], /) -> UnpublishWorkflowReleaseResponse: + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') + + def deploy_workflow_release(self, request: DeployWorkflowReleaseRequest, ctx: RequestContext[DeployWorkflowReleaseRequest, DeployWorkflowReleaseResponse], /) -> DeployWorkflowReleaseResponse: + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') + def undeploy_workflow_release(self, request: UndeployWorkflowReleaseRequest, ctx: RequestContext[UndeployWorkflowReleaseRequest, UndeployWorkflowReleaseResponse], /) -> UndeployWorkflowReleaseResponse: + raise ConnectError(Code.UNIMPLEMENTED, 'Not implemented') + + @classmethod + def desc(cls) -> DescService: + """Returns the descriptor for this service.""" + return next(s for s in workflows_pb.desc().services if s.type_name == 'workflows.v1.WorkflowsService') class WorkflowsServiceWSGIApplication(ConnectWSGIApplication): - def __init__(self, service: WorkflowsServiceSync, interceptors: Iterable[InterceptorSync]=(), read_max_bytes: int | None = None, compressions: Iterable[Compression] | None = None, codecs: Iterable[Codec] | None = None) -> None: + def __init__( + self, + service: WorkflowsServiceSync, + interceptors: Iterable[InterceptorSync] = (), + read_max_bytes: int | None = DEFAULT_READ_MAX_BYTES, + compressions: Iterable[Compression] | None = None, + codecs: Iterable[Codec] | None = None, + ) -> None: super().__init__( endpoints={ "/workflows.v1.WorkflowsService/CreateCluster": EndpointSync.unary( method=MethodInfo( name="CreateCluster", service_name="workflows.v1.WorkflowsService", - input=workflows_dot_v1_dot_workflows__pb2.CreateClusterRequest, - output=workflows_dot_v1_dot_workflows__pb2.Cluster, + input=CreateClusterRequest, + output=Cluster, idempotency_level=IdempotencyLevel.UNKNOWN, ), function=service.create_cluster, @@ -588,8 +639,8 @@ def __init__(self, service: WorkflowsServiceSync, interceptors: Iterable[Interce method=MethodInfo( name="GetCluster", service_name="workflows.v1.WorkflowsService", - input=workflows_dot_v1_dot_workflows__pb2.GetClusterRequest, - output=workflows_dot_v1_dot_workflows__pb2.Cluster, + input=GetClusterRequest, + output=Cluster, idempotency_level=IdempotencyLevel.UNKNOWN, ), function=service.get_cluster, @@ -598,8 +649,8 @@ def __init__(self, service: WorkflowsServiceSync, interceptors: Iterable[Interce method=MethodInfo( name="UpdateCluster", service_name="workflows.v1.WorkflowsService", - input=workflows_dot_v1_dot_workflows__pb2.UpdateClusterRequest, - output=workflows_dot_v1_dot_workflows__pb2.Cluster, + input=UpdateClusterRequest, + output=Cluster, idempotency_level=IdempotencyLevel.UNKNOWN, ), function=service.update_cluster, @@ -608,8 +659,8 @@ def __init__(self, service: WorkflowsServiceSync, interceptors: Iterable[Interce method=MethodInfo( name="DeleteCluster", service_name="workflows.v1.WorkflowsService", - input=workflows_dot_v1_dot_workflows__pb2.DeleteClusterRequest, - output=workflows_dot_v1_dot_workflows__pb2.DeleteClusterResponse, + input=DeleteClusterRequest, + output=DeleteClusterResponse, idempotency_level=IdempotencyLevel.UNKNOWN, ), function=service.delete_cluster, @@ -618,8 +669,8 @@ def __init__(self, service: WorkflowsServiceSync, interceptors: Iterable[Interce method=MethodInfo( name="ListClusters", service_name="workflows.v1.WorkflowsService", - input=workflows_dot_v1_dot_workflows__pb2.ListClustersRequest, - output=workflows_dot_v1_dot_workflows__pb2.ListClustersResponse, + input=ListClustersRequest, + output=ListClustersResponse, idempotency_level=IdempotencyLevel.UNKNOWN, ), function=service.list_clusters, @@ -628,8 +679,8 @@ def __init__(self, service: WorkflowsServiceSync, interceptors: Iterable[Interce method=MethodInfo( name="CreateWorkflow", service_name="workflows.v1.WorkflowsService", - input=workflows_dot_v1_dot_workflows__pb2.CreateWorkflowRequest, - output=workflows_dot_v1_dot_workflows__pb2.Workflow, + input=CreateWorkflowRequest, + output=Workflow, idempotency_level=IdempotencyLevel.UNKNOWN, ), function=service.create_workflow, @@ -638,8 +689,8 @@ def __init__(self, service: WorkflowsServiceSync, interceptors: Iterable[Interce method=MethodInfo( name="ListWorkflows", service_name="workflows.v1.WorkflowsService", - input=workflows_dot_v1_dot_workflows__pb2.ListWorkflowsRequest, - output=workflows_dot_v1_dot_workflows__pb2.ListWorkflowsResponse, + input=ListWorkflowsRequest, + output=ListWorkflowsResponse, idempotency_level=IdempotencyLevel.UNKNOWN, ), function=service.list_workflows, @@ -648,8 +699,8 @@ def __init__(self, service: WorkflowsServiceSync, interceptors: Iterable[Interce method=MethodInfo( name="ListPublicWorkflows", service_name="workflows.v1.WorkflowsService", - input=workflows_dot_v1_dot_workflows__pb2.ListPublicWorkflowsRequest, - output=workflows_dot_v1_dot_workflows__pb2.ListPublicWorkflowsResponse, + input=ListPublicWorkflowsRequest, + output=ListPublicWorkflowsResponse, idempotency_level=IdempotencyLevel.UNKNOWN, ), function=service.list_public_workflows, @@ -658,8 +709,8 @@ def __init__(self, service: WorkflowsServiceSync, interceptors: Iterable[Interce method=MethodInfo( name="GetWorkflow", service_name="workflows.v1.WorkflowsService", - input=workflows_dot_v1_dot_workflows__pb2.GetWorkflowRequest, - output=workflows_dot_v1_dot_workflows__pb2.Workflow, + input=GetWorkflowRequest, + output=Workflow, idempotency_level=IdempotencyLevel.UNKNOWN, ), function=service.get_workflow, @@ -668,8 +719,8 @@ def __init__(self, service: WorkflowsServiceSync, interceptors: Iterable[Interce method=MethodInfo( name="UpdateWorkflow", service_name="workflows.v1.WorkflowsService", - input=workflows_dot_v1_dot_workflows__pb2.UpdateWorkflowRequest, - output=workflows_dot_v1_dot_workflows__pb2.Workflow, + input=UpdateWorkflowRequest, + output=Workflow, idempotency_level=IdempotencyLevel.UNKNOWN, ), function=service.update_workflow, @@ -678,8 +729,8 @@ def __init__(self, service: WorkflowsServiceSync, interceptors: Iterable[Interce method=MethodInfo( name="DeleteWorkflow", service_name="workflows.v1.WorkflowsService", - input=workflows_dot_v1_dot_workflows__pb2.DeleteWorkflowRequest, - output=workflows_dot_v1_dot_workflows__pb2.DeleteWorkflowResponse, + input=DeleteWorkflowRequest, + output=DeleteWorkflowResponse, idempotency_level=IdempotencyLevel.UNKNOWN, ), function=service.delete_workflow, @@ -688,8 +739,8 @@ def __init__(self, service: WorkflowsServiceSync, interceptors: Iterable[Interce method=MethodInfo( name="PublishWorkflowRelease", service_name="workflows.v1.WorkflowsService", - input=workflows_dot_v1_dot_workflows__pb2.PublishWorkflowReleaseRequest, - output=workflows_dot_v1_dot_workflows__pb2.WorkflowRelease, + input=PublishWorkflowReleaseRequest, + output=WorkflowRelease, idempotency_level=IdempotencyLevel.UNKNOWN, ), function=service.publish_workflow_release, @@ -698,8 +749,8 @@ def __init__(self, service: WorkflowsServiceSync, interceptors: Iterable[Interce method=MethodInfo( name="UnpublishWorkflowRelease", service_name="workflows.v1.WorkflowsService", - input=workflows_dot_v1_dot_workflows__pb2.UnpublishWorkflowReleaseRequest, - output=workflows_dot_v1_dot_workflows__pb2.UnpublishWorkflowReleaseResponse, + input=UnpublishWorkflowReleaseRequest, + output=UnpublishWorkflowReleaseResponse, idempotency_level=IdempotencyLevel.UNKNOWN, ), function=service.unpublish_workflow_release, @@ -708,8 +759,8 @@ def __init__(self, service: WorkflowsServiceSync, interceptors: Iterable[Interce method=MethodInfo( name="DeployWorkflowRelease", service_name="workflows.v1.WorkflowsService", - input=workflows_dot_v1_dot_workflows__pb2.DeployWorkflowReleaseRequest, - output=workflows_dot_v1_dot_workflows__pb2.DeployWorkflowReleaseResponse, + input=DeployWorkflowReleaseRequest, + output=DeployWorkflowReleaseResponse, idempotency_level=IdempotencyLevel.UNKNOWN, ), function=service.deploy_workflow_release, @@ -718,8 +769,8 @@ def __init__(self, service: WorkflowsServiceSync, interceptors: Iterable[Interce method=MethodInfo( name="UndeployWorkflowRelease", service_name="workflows.v1.WorkflowsService", - input=workflows_dot_v1_dot_workflows__pb2.UndeployWorkflowReleaseRequest, - output=workflows_dot_v1_dot_workflows__pb2.UndeployWorkflowReleaseResponse, + input=UndeployWorkflowReleaseRequest, + output=UndeployWorkflowReleaseResponse, idempotency_level=IdempotencyLevel.UNKNOWN, ), function=service.undeploy_workflow_release, @@ -738,300 +789,289 @@ def path(self) -> str: class WorkflowsServiceClientSync(ConnectClientSync): + """ + A service for managing workflows. + """ def create_cluster( self, - request: workflows_dot_v1_dot_workflows__pb2.CreateClusterRequest, + request: CreateClusterRequest, *, headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, - ) -> workflows_dot_v1_dot_workflows__pb2.Cluster: + ) -> Cluster: return self.execute_unary( request=request, method=MethodInfo( name="CreateCluster", service_name="workflows.v1.WorkflowsService", - input=workflows_dot_v1_dot_workflows__pb2.CreateClusterRequest, - output=workflows_dot_v1_dot_workflows__pb2.Cluster, + input=CreateClusterRequest, + output=Cluster, idempotency_level=IdempotencyLevel.UNKNOWN, ), headers=headers, timeout_ms=timeout_ms, ) - def get_cluster( self, - request: workflows_dot_v1_dot_workflows__pb2.GetClusterRequest, + request: GetClusterRequest, *, headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, - ) -> workflows_dot_v1_dot_workflows__pb2.Cluster: + ) -> Cluster: return self.execute_unary( request=request, method=MethodInfo( name="GetCluster", service_name="workflows.v1.WorkflowsService", - input=workflows_dot_v1_dot_workflows__pb2.GetClusterRequest, - output=workflows_dot_v1_dot_workflows__pb2.Cluster, + input=GetClusterRequest, + output=Cluster, idempotency_level=IdempotencyLevel.UNKNOWN, ), headers=headers, timeout_ms=timeout_ms, ) - def update_cluster( self, - request: workflows_dot_v1_dot_workflows__pb2.UpdateClusterRequest, + request: UpdateClusterRequest, *, headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, - ) -> workflows_dot_v1_dot_workflows__pb2.Cluster: + ) -> Cluster: return self.execute_unary( request=request, method=MethodInfo( name="UpdateCluster", service_name="workflows.v1.WorkflowsService", - input=workflows_dot_v1_dot_workflows__pb2.UpdateClusterRequest, - output=workflows_dot_v1_dot_workflows__pb2.Cluster, + input=UpdateClusterRequest, + output=Cluster, idempotency_level=IdempotencyLevel.UNKNOWN, ), headers=headers, timeout_ms=timeout_ms, ) - def delete_cluster( self, - request: workflows_dot_v1_dot_workflows__pb2.DeleteClusterRequest, + request: DeleteClusterRequest, *, headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, - ) -> workflows_dot_v1_dot_workflows__pb2.DeleteClusterResponse: + ) -> DeleteClusterResponse: return self.execute_unary( request=request, method=MethodInfo( name="DeleteCluster", service_name="workflows.v1.WorkflowsService", - input=workflows_dot_v1_dot_workflows__pb2.DeleteClusterRequest, - output=workflows_dot_v1_dot_workflows__pb2.DeleteClusterResponse, + input=DeleteClusterRequest, + output=DeleteClusterResponse, idempotency_level=IdempotencyLevel.UNKNOWN, ), headers=headers, timeout_ms=timeout_ms, ) - def list_clusters( self, - request: workflows_dot_v1_dot_workflows__pb2.ListClustersRequest, + request: ListClustersRequest, *, headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, - ) -> workflows_dot_v1_dot_workflows__pb2.ListClustersResponse: + ) -> ListClustersResponse: return self.execute_unary( request=request, method=MethodInfo( name="ListClusters", service_name="workflows.v1.WorkflowsService", - input=workflows_dot_v1_dot_workflows__pb2.ListClustersRequest, - output=workflows_dot_v1_dot_workflows__pb2.ListClustersResponse, + input=ListClustersRequest, + output=ListClustersResponse, idempotency_level=IdempotencyLevel.UNKNOWN, ), headers=headers, timeout_ms=timeout_ms, ) - def create_workflow( self, - request: workflows_dot_v1_dot_workflows__pb2.CreateWorkflowRequest, + request: CreateWorkflowRequest, *, headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, - ) -> workflows_dot_v1_dot_workflows__pb2.Workflow: + ) -> Workflow: return self.execute_unary( request=request, method=MethodInfo( name="CreateWorkflow", service_name="workflows.v1.WorkflowsService", - input=workflows_dot_v1_dot_workflows__pb2.CreateWorkflowRequest, - output=workflows_dot_v1_dot_workflows__pb2.Workflow, + input=CreateWorkflowRequest, + output=Workflow, idempotency_level=IdempotencyLevel.UNKNOWN, ), headers=headers, timeout_ms=timeout_ms, ) - def list_workflows( self, - request: workflows_dot_v1_dot_workflows__pb2.ListWorkflowsRequest, + request: ListWorkflowsRequest, *, headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, - ) -> workflows_dot_v1_dot_workflows__pb2.ListWorkflowsResponse: + ) -> ListWorkflowsResponse: return self.execute_unary( request=request, method=MethodInfo( name="ListWorkflows", service_name="workflows.v1.WorkflowsService", - input=workflows_dot_v1_dot_workflows__pb2.ListWorkflowsRequest, - output=workflows_dot_v1_dot_workflows__pb2.ListWorkflowsResponse, + input=ListWorkflowsRequest, + output=ListWorkflowsResponse, idempotency_level=IdempotencyLevel.UNKNOWN, ), headers=headers, timeout_ms=timeout_ms, ) - def list_public_workflows( self, - request: workflows_dot_v1_dot_workflows__pb2.ListPublicWorkflowsRequest, + request: ListPublicWorkflowsRequest, *, headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, - ) -> workflows_dot_v1_dot_workflows__pb2.ListPublicWorkflowsResponse: + ) -> ListPublicWorkflowsResponse: return self.execute_unary( request=request, method=MethodInfo( name="ListPublicWorkflows", service_name="workflows.v1.WorkflowsService", - input=workflows_dot_v1_dot_workflows__pb2.ListPublicWorkflowsRequest, - output=workflows_dot_v1_dot_workflows__pb2.ListPublicWorkflowsResponse, + input=ListPublicWorkflowsRequest, + output=ListPublicWorkflowsResponse, idempotency_level=IdempotencyLevel.UNKNOWN, ), headers=headers, timeout_ms=timeout_ms, ) - def get_workflow( self, - request: workflows_dot_v1_dot_workflows__pb2.GetWorkflowRequest, + request: GetWorkflowRequest, *, headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, - ) -> workflows_dot_v1_dot_workflows__pb2.Workflow: + ) -> Workflow: return self.execute_unary( request=request, method=MethodInfo( name="GetWorkflow", service_name="workflows.v1.WorkflowsService", - input=workflows_dot_v1_dot_workflows__pb2.GetWorkflowRequest, - output=workflows_dot_v1_dot_workflows__pb2.Workflow, + input=GetWorkflowRequest, + output=Workflow, idempotency_level=IdempotencyLevel.UNKNOWN, ), headers=headers, timeout_ms=timeout_ms, ) - def update_workflow( self, - request: workflows_dot_v1_dot_workflows__pb2.UpdateWorkflowRequest, + request: UpdateWorkflowRequest, *, headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, - ) -> workflows_dot_v1_dot_workflows__pb2.Workflow: + ) -> Workflow: return self.execute_unary( request=request, method=MethodInfo( name="UpdateWorkflow", service_name="workflows.v1.WorkflowsService", - input=workflows_dot_v1_dot_workflows__pb2.UpdateWorkflowRequest, - output=workflows_dot_v1_dot_workflows__pb2.Workflow, + input=UpdateWorkflowRequest, + output=Workflow, idempotency_level=IdempotencyLevel.UNKNOWN, ), headers=headers, timeout_ms=timeout_ms, ) - def delete_workflow( self, - request: workflows_dot_v1_dot_workflows__pb2.DeleteWorkflowRequest, + request: DeleteWorkflowRequest, *, headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, - ) -> workflows_dot_v1_dot_workflows__pb2.DeleteWorkflowResponse: + ) -> DeleteWorkflowResponse: return self.execute_unary( request=request, method=MethodInfo( name="DeleteWorkflow", service_name="workflows.v1.WorkflowsService", - input=workflows_dot_v1_dot_workflows__pb2.DeleteWorkflowRequest, - output=workflows_dot_v1_dot_workflows__pb2.DeleteWorkflowResponse, + input=DeleteWorkflowRequest, + output=DeleteWorkflowResponse, idempotency_level=IdempotencyLevel.UNKNOWN, ), headers=headers, timeout_ms=timeout_ms, ) - def publish_workflow_release( self, - request: workflows_dot_v1_dot_workflows__pb2.PublishWorkflowReleaseRequest, + request: PublishWorkflowReleaseRequest, *, headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, - ) -> workflows_dot_v1_dot_workflows__pb2.WorkflowRelease: + ) -> WorkflowRelease: return self.execute_unary( request=request, method=MethodInfo( name="PublishWorkflowRelease", service_name="workflows.v1.WorkflowsService", - input=workflows_dot_v1_dot_workflows__pb2.PublishWorkflowReleaseRequest, - output=workflows_dot_v1_dot_workflows__pb2.WorkflowRelease, + input=PublishWorkflowReleaseRequest, + output=WorkflowRelease, idempotency_level=IdempotencyLevel.UNKNOWN, ), headers=headers, timeout_ms=timeout_ms, ) - def unpublish_workflow_release( self, - request: workflows_dot_v1_dot_workflows__pb2.UnpublishWorkflowReleaseRequest, + request: UnpublishWorkflowReleaseRequest, *, headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, - ) -> workflows_dot_v1_dot_workflows__pb2.UnpublishWorkflowReleaseResponse: + ) -> UnpublishWorkflowReleaseResponse: return self.execute_unary( request=request, method=MethodInfo( name="UnpublishWorkflowRelease", service_name="workflows.v1.WorkflowsService", - input=workflows_dot_v1_dot_workflows__pb2.UnpublishWorkflowReleaseRequest, - output=workflows_dot_v1_dot_workflows__pb2.UnpublishWorkflowReleaseResponse, + input=UnpublishWorkflowReleaseRequest, + output=UnpublishWorkflowReleaseResponse, idempotency_level=IdempotencyLevel.UNKNOWN, ), headers=headers, timeout_ms=timeout_ms, ) - def deploy_workflow_release( self, - request: workflows_dot_v1_dot_workflows__pb2.DeployWorkflowReleaseRequest, + request: DeployWorkflowReleaseRequest, *, headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, - ) -> workflows_dot_v1_dot_workflows__pb2.DeployWorkflowReleaseResponse: + ) -> DeployWorkflowReleaseResponse: return self.execute_unary( request=request, method=MethodInfo( name="DeployWorkflowRelease", service_name="workflows.v1.WorkflowsService", - input=workflows_dot_v1_dot_workflows__pb2.DeployWorkflowReleaseRequest, - output=workflows_dot_v1_dot_workflows__pb2.DeployWorkflowReleaseResponse, + input=DeployWorkflowReleaseRequest, + output=DeployWorkflowReleaseResponse, idempotency_level=IdempotencyLevel.UNKNOWN, ), headers=headers, timeout_ms=timeout_ms, ) - def undeploy_workflow_release( self, - request: workflows_dot_v1_dot_workflows__pb2.UndeployWorkflowReleaseRequest, + request: UndeployWorkflowReleaseRequest, *, headers: Headers | Mapping[str, str] | None = None, timeout_ms: int | None = None, - ) -> workflows_dot_v1_dot_workflows__pb2.UndeployWorkflowReleaseResponse: + ) -> UndeployWorkflowReleaseResponse: return self.execute_unary( request=request, method=MethodInfo( name="UndeployWorkflowRelease", service_name="workflows.v1.WorkflowsService", - input=workflows_dot_v1_dot_workflows__pb2.UndeployWorkflowReleaseRequest, - output=workflows_dot_v1_dot_workflows__pb2.UndeployWorkflowReleaseResponse, + input=UndeployWorkflowReleaseRequest, + output=UndeployWorkflowReleaseResponse, idempotency_level=IdempotencyLevel.UNKNOWN, ), headers=headers, diff --git a/tilebox-workflows/tilebox/workflows/v1/workflows_pb.py b/tilebox-workflows/tilebox/workflows/v1/workflows_pb.py new file mode 100644 index 0000000..40b44f3 --- /dev/null +++ b/tilebox-workflows/tilebox/workflows/v1/workflows_pb.py @@ -0,0 +1,1415 @@ +# Generated from workflows/v1/workflows.proto. DO NOT EDIT. +# Generated by protoc-gen-py v0.6.0 with parameter "init_files=false,map_imports=datasets/**:tilebox,map_imports=workflows/**:tilebox,map_imports=tilebox/**:_tilebox.proto,map_imports=opentelemetry/**:tilebox.workflows._otel". +# ruff: noqa: PGH004 +# ruff: noqa +# fmt: off + +from __future__ import annotations + +from typing import Literal, NoReturn, TYPE_CHECKING, TypeAlias + +from _tilebox.proto.tilebox.v1 import id_pb, query_pb +from protobuf import Message +from protobuf._codegen import file_desc +from protobuf.wkt.google.protobuf import timestamp_pb +from tilebox.workflows.v1 import core_pb + +if TYPE_CHECKING: + from _tilebox.proto.tilebox.v1.id_pb import ID + from _tilebox.proto.tilebox.v1.query_pb import Pagination + from protobuf import DescFile + from protobuf.wkt.google.protobuf.timestamp_pb import Timestamp + from tilebox.workflows.v1.core_pb import UnboundTaskIdentifier + + +_ClusterFields: TypeAlias = Literal["slug", "display_name", "description", "deletable", "deployed_releases"] + +class Cluster(Message[_ClusterFields]): + """ + A cluster is a grouping of tasks that are related. + + ```proto + message workflows.v1.Cluster + ``` + + Attributes: + slug: + The unique slug of the cluster within the namespace. + + ```proto + string slug = 2; + ``` + display_name: + The display name of the cluster. + + ```proto + string display_name = 3; + ``` + description: + A description for the cluster. + + ```proto + string description = 6; + ``` + deletable: + Where the cluster is deletable + + ```proto + bool deletable = 4; + ``` + deployed_releases: + The workflows that are currently deployed to this cluster. Each workflow will only contain one release, + which is the currently deployed release for that workflow on this cluster. + + ```proto + repeated workflows.v1.Workflow deployed_releases = 5; + ``` + """ + + __slots__ = ("slug", "display_name", "description", "deletable", "deployed_releases") + + if TYPE_CHECKING: + + def __init__( + self, + *, + slug: str = "", + display_name: str = "", + description: str = "", + deletable: bool = False, + deployed_releases: list[Workflow] | None = None, + ) -> None: + pass + + slug: str + display_name: str + description: str + deletable: bool + deployed_releases: list[Workflow] + +_CreateClusterRequestFields: TypeAlias = Literal["name", "description", "slug"] + +class CreateClusterRequest(Message[_CreateClusterRequestFields]): + """ + CreateClusterRequest creates a new cluster. + + ```proto + message workflows.v1.CreateClusterRequest + ``` + + Attributes: + name: + The name of the cluster. + + ```proto + string name = 1; + ``` + description: + A description for the cluster. + + ```proto + string description = 2; + ``` + slug: + An optional cluster slug. If not set, a slug derived from the name will automatically be generated. + + ```proto + string slug = 3; + ``` + """ + + __slots__ = ("name", "description", "slug") + + if TYPE_CHECKING: + + def __init__( + self, + *, + name: str = "", + description: str = "", + slug: str = "", + ) -> None: + pass + + name: str + description: str + slug: str + +_GetClusterRequestFields: TypeAlias = Literal["cluster_slug"] + +class GetClusterRequest(Message[_GetClusterRequestFields]): + """ + GetClusterRequest requests details for a cluster. + + ```proto + message workflows.v1.GetClusterRequest + ``` + + Attributes: + cluster_slug: + The slug of the cluster to get details for. Empty string requests the default cluster. + + ```proto + string cluster_slug = 1; + ``` + """ + + __slots__ = ("cluster_slug",) + + if TYPE_CHECKING: + + def __init__( + self, + *, + cluster_slug: str = "", + ) -> None: + pass + + cluster_slug: str + +_UpdateClusterRequestFields: TypeAlias = Literal["cluster_slug", "name", "description"] + +class UpdateClusterRequest(Message[_UpdateClusterRequestFields]): + """ + UpdateClusterRequest updates an existing cluster, by updating all fields that are set to their new values. + If a field is not set, it will not be updated. + + ```proto + message workflows.v1.UpdateClusterRequest + ``` + + Attributes: + cluster_slug: + ```proto + string cluster_slug = 1; + ``` + name: + ```proto + optional string name = 2; + ``` + description: + ```proto + optional string description = 3; + ``` + """ + + __slots__ = ("cluster_slug", "name", "description") + + if TYPE_CHECKING: + + def __init__( + self, + *, + cluster_slug: str = "", + name: str | None = None, + description: str | None = None, + ) -> None: + pass + + cluster_slug: str + name: str + description: str + +_DeleteClusterRequestFields: TypeAlias = Literal["cluster_slug"] + +class DeleteClusterRequest(Message[_DeleteClusterRequestFields]): + """ + DeleteClusterRequest deletes an existing cluster. + + ```proto + message workflows.v1.DeleteClusterRequest + ``` + + Attributes: + cluster_slug: + The slug of the cluster to delete. + + ```proto + string cluster_slug = 1; + ``` + """ + + __slots__ = ("cluster_slug",) + + if TYPE_CHECKING: + + def __init__( + self, + *, + cluster_slug: str = "", + ) -> None: + pass + + cluster_slug: str + +_DeleteClusterResponseFields: TypeAlias = NoReturn + +class DeleteClusterResponse(Message[_DeleteClusterResponseFields]): + """ + DeleteClusterResponse is the response to DeleteClusterRequest. + + ```proto + message workflows.v1.DeleteClusterResponse + ``` + """ + + __slots__ = () + + if TYPE_CHECKING: + + def __init__( + self, + ) -> None: + pass + +_ListClustersRequestFields: TypeAlias = NoReturn + +class ListClustersRequest(Message[_ListClustersRequestFields]): + """ + ListClustersRequest lists all clusters. + + ```proto + message workflows.v1.ListClustersRequest + ``` + """ + + __slots__ = () + + if TYPE_CHECKING: + + def __init__( + self, + ) -> None: + pass + +_ListClustersResponseFields: TypeAlias = Literal["clusters"] + +class ListClustersResponse(Message[_ListClustersResponseFields]): + """ + ListClustersResponse is the response to ListClustersRequest. + + ```proto + message workflows.v1.ListClustersResponse + ``` + + Attributes: + clusters: + The clusters. + + ```proto + repeated workflows.v1.Cluster clusters = 1; + ``` + """ + + __slots__ = ("clusters",) + + if TYPE_CHECKING: + + def __init__( + self, + *, + clusters: list[Cluster] | None = None, + ) -> None: + pass + + clusters: list[Cluster] + +_GetWorkflowRequestFields: TypeAlias = Literal["workflow_reference"] + +class GetWorkflowRequest(Message[_GetWorkflowRequestFields]): + """ + GetWorkflowRequest requests details for a workflow. + + ```proto + message workflows.v1.GetWorkflowRequest + ``` + + Attributes: + workflow_reference: + The unique workflow reference, potentially suffixed, and potentially namespace-qualified remote reference, which + acts as the unique external identifier for a workflow. + e.g. `my-worklow`, `my-workflow-AGimUihHGGahtT`, or `other-namespace:my-workflow`. + + ```proto + string workflow_reference = 1; + ``` + """ + + __slots__ = ("workflow_reference",) + + if TYPE_CHECKING: + + def __init__( + self, + *, + workflow_reference: str = "", + ) -> None: + pass + + workflow_reference: str + +_UpdateWorkflowRequestFields: TypeAlias = Literal["workflow_reference", "name", "summary", "description"] + +class UpdateWorkflowRequest(Message[_UpdateWorkflowRequestFields]): + """ + UpdateWorkflowRequest updates an existing workflow, by updating all fields that are set to their new values. + If a field is not set, it will not be updated. + + ```proto + message workflows.v1.UpdateWorkflowRequest + ``` + + Attributes: + workflow_reference: + The unique workflow reference, potentially suffixed, and potentially namespace-qualified remote reference, which + acts as the unique external identifier for a workflow. + e.g. `my-worklow`, `my-workflow-AGimUihHGGahtT`, or `other-namespace:my-workflow`. + + ```proto + string workflow_reference = 1; + ``` + name: + ```proto + optional string name = 2; + ``` + summary: + Short text summary. Omit to leave unchanged, or set to empty to clear. + + ```proto + optional string summary = 4; + ``` + description: + ```proto + optional string description = 3; + ``` + """ + + __slots__ = ("workflow_reference", "name", "summary", "description") + + if TYPE_CHECKING: + + def __init__( + self, + *, + workflow_reference: str = "", + name: str | None = None, + summary: str | None = None, + description: str | None = None, + ) -> None: + pass + + workflow_reference: str + name: str + summary: str + description: str + +_DeleteWorkflowRequestFields: TypeAlias = Literal["workflow_reference"] + +class DeleteWorkflowRequest(Message[_DeleteWorkflowRequestFields]): + """ + DeleteWorkflowRequest deletes an existing workflow. + + ```proto + message workflows.v1.DeleteWorkflowRequest + ``` + + Attributes: + workflow_reference: + The unique workflow reference, potentially suffixed, and potentially namespace-qualified remote reference, which + acts as the unique external identifier for a workflow. + e.g. `my-worklow`, `my-workflow-AGimUihHGGahtT`, or `other-namespace:my-workflow`. + + ```proto + string workflow_reference = 1; + ``` + """ + + __slots__ = ("workflow_reference",) + + if TYPE_CHECKING: + + def __init__( + self, + *, + workflow_reference: str = "", + ) -> None: + pass + + workflow_reference: str + +_DeleteWorkflowResponseFields: TypeAlias = NoReturn + +class DeleteWorkflowResponse(Message[_DeleteWorkflowResponseFields]): + """ + DeleteWorkflowResponse is the response to DeleteWorkflowRequest. + + ```proto + message workflows.v1.DeleteWorkflowResponse + ``` + """ + + __slots__ = () + + if TYPE_CHECKING: + + def __init__( + self, + ) -> None: + pass + +_PublishWorkflowReleaseRequestFields: TypeAlias = Literal["workflow_reference", "artifact_id", "content"] + +class PublishWorkflowReleaseRequest(Message[_PublishWorkflowReleaseRequestFields]): + """ + PublishWorkflowReleaseRequest publishes a new workflow release artifact, and associates it with a workflow. + + ```proto + message workflows.v1.PublishWorkflowReleaseRequest + ``` + + Attributes: + workflow_reference: + The unique workflow reference, potentially suffixed, and potentially namespace-qualified remote reference, which + acts as the unique external identifier for a workflow. + e.g. `my-worklow`, `my-workflow-AGimUihHGGahtT`, or `other-namespace:my-workflow`. + + ```proto + string workflow_reference = 1; + ``` + artifact_id: + ```proto + optional tilebox.v1.ID artifact_id = 2; + ``` + content: + ```proto + optional workflows.v1.ReleaseContent content = 3; + ``` + """ + + __slots__ = ("workflow_reference", "artifact_id", "content") + + if TYPE_CHECKING: + + def __init__( + self, + *, + workflow_reference: str = "", + artifact_id: ID | None = None, + content: ReleaseContent | None = None, + ) -> None: + pass + + workflow_reference: str + artifact_id: ID | None + content: ReleaseContent | None + +_UnpublishWorkflowReleaseRequestFields: TypeAlias = Literal["workflow_reference", "release_id"] + +class UnpublishWorkflowReleaseRequest(Message[_UnpublishWorkflowReleaseRequestFields]): + """ + UnpublishWorkflowReleaseRequest unpublishes a specific workflow release. + + ```proto + message workflows.v1.UnpublishWorkflowReleaseRequest + ``` + + Attributes: + workflow_reference: + The unique workflow reference, potentially suffixed, and potentially namespace-qualified remote reference, which + acts as the unique external identifier for a workflow. + e.g. `my-worklow`, `my-workflow-AGimUihHGGahtT`, or `other-namespace:my-workflow`. + + ```proto + string workflow_reference = 1; + ``` + release_id: + The ID of the release to unpublish. + + ```proto + optional tilebox.v1.ID release_id = 2; + ``` + """ + + __slots__ = ("workflow_reference", "release_id") + + if TYPE_CHECKING: + + def __init__( + self, + *, + workflow_reference: str = "", + release_id: ID | None = None, + ) -> None: + pass + + workflow_reference: str + release_id: ID | None + +_UnpublishWorkflowReleaseResponseFields: TypeAlias = NoReturn + +class UnpublishWorkflowReleaseResponse(Message[_UnpublishWorkflowReleaseResponseFields]): + """ + UnpublishWorkflowReleaseResponse is the response to UnpublishWorkflowReleaseRequest. + + ```proto + message workflows.v1.UnpublishWorkflowReleaseResponse + ``` + """ + + __slots__ = () + + if TYPE_CHECKING: + + def __init__( + self, + ) -> None: + pass + +_ListWorkflowsRequestFields: TypeAlias = Literal["page"] + +class ListWorkflowsRequest(Message[_ListWorkflowsRequestFields]): + """ + ListWorkflowsRequest lists all workflows owned by the authenticated organization. + + ```proto + message workflows.v1.ListWorkflowsRequest + ``` + + Attributes: + page: + Pagination in descending creation order; the default and maximum limit is 100. + + ```proto + optional tilebox.v1.Pagination page = 1; + ``` + """ + + __slots__ = ("page",) + + if TYPE_CHECKING: + + def __init__( + self, + *, + page: Pagination | None = None, + ) -> None: + pass + + page: Pagination | None + +_ListWorkflowsResponseFields: TypeAlias = Literal["workflows", "next_page"] + +class ListWorkflowsResponse(Message[_ListWorkflowsResponseFields]): + """ + ListWorkflowsResponse is the response to ListWorkflowsRequest. + + ```proto + message workflows.v1.ListWorkflowsResponse + ``` + + Attributes: + workflows: + ```proto + repeated workflows.v1.Workflow workflows = 1; + ``` + next_page: + Pagination parameters for the next page, absent when there are no more workflows. + + ```proto + optional tilebox.v1.Pagination next_page = 2; + ``` + """ + + __slots__ = ("workflows", "next_page") + + if TYPE_CHECKING: + + def __init__( + self, + *, + workflows: list[Workflow] | None = None, + next_page: Pagination | None = None, + ) -> None: + pass + + workflows: list[Workflow] + next_page: Pagination | None + +_ListPublicWorkflowsRequestFields: TypeAlias = Literal["page"] + +class ListPublicWorkflowsRequest(Message[_ListPublicWorkflowsRequestFields]): + """ + ListPublicWorkflowsRequest lists workflow summaries shared with every organization in named namespaces. + Releases are available through GetWorkflow; private sharing grants are not included in this listing. + + ```proto + message workflows.v1.ListPublicWorkflowsRequest + ``` + + Attributes: + page: + Pagination in descending creation order; the default and maximum limit is 100. + + ```proto + optional tilebox.v1.Pagination page = 1; + ``` + """ + + __slots__ = ("page",) + + if TYPE_CHECKING: + + def __init__( + self, + *, + page: Pagination | None = None, + ) -> None: + pass + + page: Pagination | None + +_ListPublicWorkflowsResponseFields: TypeAlias = Literal["workflows", "next_page"] + +class ListPublicWorkflowsResponse(Message[_ListPublicWorkflowsResponseFields]): + """ + ListPublicWorkflowsResponse is the response to ListPublicWorkflowsRequest. + + ```proto + message workflows.v1.ListPublicWorkflowsResponse + ``` + + Attributes: + workflows: + ```proto + repeated workflows.v1.Workflow workflows = 1; + ``` + next_page: + Pagination parameters for the next page, absent when there are no more workflows. + + ```proto + optional tilebox.v1.Pagination next_page = 2; + ``` + """ + + __slots__ = ("workflows", "next_page") + + if TYPE_CHECKING: + + def __init__( + self, + *, + workflows: list[Workflow] | None = None, + next_page: Pagination | None = None, + ) -> None: + pass + + workflows: list[Workflow] + next_page: Pagination | None + +_CreateWorkflowRequestFields: TypeAlias = Literal["name", "project_slug", "summary", "description", "legacy_task_aliases"] + +class CreateWorkflowRequest(Message[_CreateWorkflowRequestFields]): + """ + CreateWorkflowRequest creates a new workflow. The request can include a workflow slug, which will be used as the + preferred remote reference for the workflow if it's not in use yet. + + ```proto + message workflows.v1.CreateWorkflowRequest + ``` + + Attributes: + name: + ```proto + string name = 1; + ``` + project_slug: + Portable project slug. Empty derives it from the name (slugify). + Used as the preferred remote reference; collisions add a suffix to the reference only. + + ```proto + string project_slug = 4; + ``` + summary: + short text summary of the workflow to create + + ```proto + string summary = 3; + ``` + description: + optional longer description of the workflow to create, in markdown format + + ```proto + optional string description = 2; + ``` + legacy_task_aliases: + Whether managed runners must also advertise unscoped task aliases for this workflow. + Omission defaults to true for older clients. Set false explicitly to create a scoped-only workflow. + Scoped task addresses are available regardless of this value. Needed during the migration to scoped task addresses, + and this flag will be retired once all deployed runners support scoped tasks. + + ```proto + optional bool legacy_task_aliases = 5; + ``` + """ + + __slots__ = ("name", "project_slug", "summary", "description", "legacy_task_aliases") + + if TYPE_CHECKING: + + def __init__( + self, + *, + name: str = "", + project_slug: str = "", + summary: str = "", + description: str | None = None, + legacy_task_aliases: bool | None = None, + ) -> None: + pass + + name: str + project_slug: str + summary: str + description: str + legacy_task_aliases: bool + +_WorkflowFields: TypeAlias = Literal["id", "reference", "project_slug", "name", "summary", "description", "releases", "presentation", "legacy_task_aliases"] + +class Workflow(Message[_WorkflowFields]): + """ + Workflow represents a logical grouping for a set of tasks. It can have multiple releases, each comprising a different + set of tasks. + + ```proto + message workflows.v1.Workflow + ``` + + Attributes: + id: + The immutable workflow ID used to scope task identifiers. Only used to resolve TaskIdentifier.workflow_id for + scoped tasks in the runner. Not used for user-facing addressing, which is always by workflow reference. + + ```proto + optional tilebox.v1.ID id = 1; + ``` + reference: + Allocated remote address: owner-local, or namespace:reference for another namespace's workflow. + Public listings always qualify references. Pass this value to workflow_reference request fields. + + ```proto + string reference = 2; + ``` + project_slug: + Portable, non-unique project slug. Use reference to address a remote workflow. + + ```proto + string project_slug = 8; + ``` + name: + ```proto + string name = 3; + ``` + summary: + short text summary of the workflow + + ```proto + string summary = 6; + ``` + description: + optional longer description of the workflow, in markdown format + + ```proto + string description = 4; + ``` + releases: + ```proto + repeated workflows.v1.WorkflowRelease releases = 5; + ``` + presentation: + additional presentation metadata for public workflows, to display in the public workflow gallery. + + ```proto + optional workflows.v1.WorkflowPresentation presentation = 7; + ``` + legacy_task_aliases: + Whether managed runners must advertise unscoped task aliases in addition to workflow-scoped addresses. + Unscoped task aliases must not conflict with other deployed task addresses on the same cluster. + + ```proto + optional bool legacy_task_aliases = 10; + ``` + """ + + __slots__ = ("id", "reference", "project_slug", "name", "summary", "description", "releases", "presentation", "legacy_task_aliases") + + if TYPE_CHECKING: + + def __init__( + self, + *, + id: ID | None = None, + reference: str = "", + project_slug: str = "", + name: str = "", + summary: str = "", + description: str = "", + releases: list[WorkflowRelease] | None = None, + presentation: WorkflowPresentation | None = None, + legacy_task_aliases: bool | None = None, + ) -> None: + pass + + id: ID | None + reference: str + project_slug: str + name: str + summary: str + description: str + releases: list[WorkflowRelease] + presentation: WorkflowPresentation | None + legacy_task_aliases: bool + +_WorkflowPresentationFields: TypeAlias = Literal["image_url", "image_alt", "imagery_type", "use_case", "dataset_label"] + +class WorkflowPresentation(Message[_WorkflowPresentationFields]): + """ + WorkflowPresentation is an extended metadata message for a workflow, providing additional presentation properties + for a public workflow. + + ```proto + message workflows.v1.WorkflowPresentation + ``` + + Attributes: + image_url: + ```proto + string image_url = 1; + ``` + image_alt: + ```proto + string image_alt = 2; + ``` + imagery_type: + e.g. "optical", "radar", "hyperspectral", "thermal", "atmosphere" + + ```proto + repeated string imagery_type = 3; + ``` + use_case: + e.g. "mosaic", "change-detection", "object-detection" + + ```proto + repeated string use_case = 4; + ``` + dataset_label: + e.g. "Sentinel-2 L2A ยท 10 m" + + ```proto + repeated string dataset_label = 5; + ``` + """ + + __slots__ = ("image_url", "image_alt", "imagery_type", "use_case", "dataset_label") + + if TYPE_CHECKING: + + def __init__( + self, + *, + image_url: str = "", + image_alt: str = "", + imagery_type: list[str] | None = None, + use_case: list[str] | None = None, + dataset_label: list[str] | None = None, + ) -> None: + pass + + image_url: str + image_alt: str + imagery_type: list[str] + use_case: list[str] + dataset_label: list[str] + +_WorkflowReleaseFields: TypeAlias = Literal["id", "artifact", "content", "created_at", "clusters"] + +class WorkflowRelease(Message[_WorkflowReleaseFields]): + """ + WorkflowRelease represents an immutable release of a workflow, which includes a set of tasks and an artifact. + Cluster deployment manifests omit deleted releases and workflows the caller can no longer read. + + ```proto + message workflows.v1.WorkflowRelease + ``` + + Attributes: + id: + ```proto + optional tilebox.v1.ID id = 1; + ``` + artifact: + ```proto + optional workflows.v1.Artifact artifact = 2; + ``` + content: + ```proto + optional workflows.v1.ReleaseContent content = 3; + ``` + created_at: + ```proto + optional google.protobuf.Timestamp created_at = 4; + ``` + clusters: + List of clusters that the workflow is published to + + ```proto + repeated workflows.v1.Cluster clusters = 5; + ``` + """ + + __slots__ = ("id", "artifact", "content", "created_at", "clusters") + + if TYPE_CHECKING: + + def __init__( + self, + *, + id: ID | None = None, + artifact: Artifact | None = None, + content: ReleaseContent | None = None, + created_at: Timestamp | None = None, + clusters: list[Cluster] | None = None, + ) -> None: + pass + + id: ID | None + artifact: Artifact | None + content: ReleaseContent | None + created_at: Timestamp | None + clusters: list[Cluster] + +_ArtifactFields: TypeAlias = Literal["id", "digest"] + +class Artifact(Message[_ArtifactFields]): + """ + Artifact represents the artifact associated with a workflow release, + which is the file that contains the code and dependencies needed to execute the tasks in the release. + + ```proto + message workflows.v1.Artifact + ``` + + Attributes: + id: + ```proto + optional tilebox.v1.ID id = 1; + ``` + digest: + ```proto + string digest = 2; + ``` + """ + + __slots__ = ("id", "digest") + + if TYPE_CHECKING: + + def __init__( + self, + *, + id: ID | None = None, + digest: str = "", + ) -> None: + pass + + id: ID | None + digest: str + +_ReleaseContentFields: TypeAlias = Literal["fingerprint", "tasks", "files", "runner_object_path", "command_override", "build_config"] + +class ReleaseContent(Message[_ReleaseContentFields]): + """ + ReleaseContent represents the content of a workflow release, which is the set of files that are included, + the list of task identifiers, and the command to start a worker runtime for executing the tasks in the release. + The fingerprint is a hash of the release content. + + ```proto + message workflows.v1.ReleaseContent + ``` + + Attributes: + fingerprint: + A fingerprint of the release content, which is a hash of the task identifiers, files, runner object path, + command override, and build config. Computed by the client for idempotent builds. + + ```proto + string fingerprint = 1; + ``` + tasks: + Unbound task definitions included in the release artifact, without workflow IDs in both requests and responses. + The managed runner binds them to the owning workflow when advertising capabilities; release content stays unbound. + + ```proto + repeated workflows.v1.UnboundTaskIdentifier tasks = 2; + ``` + files: + List of files/directories included in the release artifact. + + ```proto + repeated workflows.v1.Path files = 3; + ``` + runner_object_path: + The python module/object path to the runner instance, e.g. "my_module.my_runner:runner". + + ```proto + string runner_object_path = 4; + ``` + command_override: + A custom command override for starting a worker runtime. This is optional, and if not set defaults to + "uv run python -m tilebox.workflows.runner ". This allows users to specify a custom command + for starting the worker runtime, e.g. to set environment variables or use a different entrypoint. + + ```proto + repeated string command_override = 5; + ``` + build_config: + Build rules used for this release, relative to its build root. + + ```proto + optional workflows.v1.BuildConfig build_config = 6; + ``` + """ + + __slots__ = ("fingerprint", "tasks", "files", "runner_object_path", "command_override", "build_config") + + if TYPE_CHECKING: + + def __init__( + self, + *, + fingerprint: str = "", + tasks: list[UnboundTaskIdentifier] | None = None, + files: list[Path] | None = None, + runner_object_path: str = "", + command_override: list[str] | None = None, + build_config: BuildConfig | None = None, + ) -> None: + pass + + fingerprint: str + tasks: list[UnboundTaskIdentifier] + files: list[Path] + runner_object_path: str + command_override: list[str] + build_config: BuildConfig | None + +_BuildConfigFields: TypeAlias = Literal["include", "exclude", "use_gitignore"] + +class BuildConfig(Message[_BuildConfigFields]): + """ + BuildConfig preserves the build rules needed to rebuild a project from a release. + + ```proto + message workflows.v1.BuildConfig + ``` + + Attributes: + include: + File patterns to include, relative to the build root. + + ```proto + repeated string include = 1; + ``` + exclude: + File patterns to exclude, relative to the build root. + + ```proto + repeated string exclude = 2; + ``` + use_gitignore: + Whether to apply Git ignore rules. Omitted means true; preserve presence when reconstructing configuration. + Artifact builders must preserve the applicable ignore files or normalize the rules to remove that dependency. + + ```proto + optional bool use_gitignore = 3; + ``` + """ + + __slots__ = ("include", "exclude", "use_gitignore") + + if TYPE_CHECKING: + + def __init__( + self, + *, + include: list[str] | None = None, + exclude: list[str] | None = None, + use_gitignore: bool | None = None, + ) -> None: + pass + + include: list[str] + exclude: list[str] + use_gitignore: bool + +_PathFields: TypeAlias = Literal["path", "directory", "children"] + +class Path(Message[_PathFields]): + """ + Path represents a file or directory path in the release content. If it is a directory, it can have child paths. + + ```proto + message workflows.v1.Path + ``` + + Attributes: + path: + ```proto + string path = 1; + ``` + directory: + ```proto + bool directory = 2; + ``` + children: + ```proto + repeated workflows.v1.Path children = 3; + ``` + """ + + __slots__ = ("path", "directory", "children") + + if TYPE_CHECKING: + + def __init__( + self, + *, + path: str = "", + directory: bool = False, + children: list[Path] | None = None, + ) -> None: + pass + + path: str + directory: bool + children: list[Path] + +_DeployWorkflowReleaseRequestFields: TypeAlias = Literal["workflow_reference", "release_id", "cluster_slugs"] + +class DeployWorkflowReleaseRequest(Message[_DeployWorkflowReleaseRequestFields]): + """ + DeployWorkflowReleaseRequest deploys a workflow release to a set of clusters, making the tasks available for + execution on those clusters. + + ```proto + message workflows.v1.DeployWorkflowReleaseRequest + ``` + + Attributes: + workflow_reference: + The unique workflow reference, potentially suffixed, and potentially namespace-qualified remote reference, which + acts as the unique external identifier for a workflow. + e.g. `my-worklow`, `my-workflow-AGimUihHGGahtT`, or `other-namespace:my-workflow`. + + ```proto + string workflow_reference = 1; + ``` + release_id: + ```proto + optional tilebox.v1.ID release_id = 2; + ``` + cluster_slugs: + ```proto + repeated string cluster_slugs = 3; + ``` + """ + + __slots__ = ("workflow_reference", "release_id", "cluster_slugs") + + if TYPE_CHECKING: + + def __init__( + self, + *, + workflow_reference: str = "", + release_id: ID | None = None, + cluster_slugs: list[str] | None = None, + ) -> None: + pass + + workflow_reference: str + release_id: ID | None + cluster_slugs: list[str] + +_DeployWorkflowReleaseResponseFields: TypeAlias = Literal["release", "clusters"] + +class DeployWorkflowReleaseResponse(Message[_DeployWorkflowReleaseResponseFields]): + """ + DeployWorkflowReleaseResponse is the response to DeployWorkflowReleaseRequest, + containing the deployed release and the new state of all the clusters it was deployed to. + + ```proto + message workflows.v1.DeployWorkflowReleaseResponse + ``` + + Attributes: + release: + ```proto + optional workflows.v1.WorkflowRelease release = 1; + ``` + clusters: + ```proto + repeated workflows.v1.Cluster clusters = 2; + ``` + """ + + __slots__ = ("release", "clusters") + + if TYPE_CHECKING: + + def __init__( + self, + *, + release: WorkflowRelease | None = None, + clusters: list[Cluster] | None = None, + ) -> None: + pass + + release: WorkflowRelease | None + clusters: list[Cluster] + +_UndeployWorkflowReleaseRequestFields: TypeAlias = Literal["workflow_reference", "release_id", "cluster_slugs"] + +class UndeployWorkflowReleaseRequest(Message[_UndeployWorkflowReleaseRequestFields]): + """ + UndeployWorkflowReleaseRequest undeploys a workflow release from a set of clusters, making it no longer available + for execution on those clusters. When release_id is omitted, all releases of the workflow deployed on the target + clusters are removed. + + ```proto + message workflows.v1.UndeployWorkflowReleaseRequest + ``` + + Attributes: + workflow_reference: + The unique workflow reference, potentially suffixed, and potentially namespace-qualified remote reference, which + acts as the unique external identifier for a workflow. + e.g. `my-worklow`, `my-workflow-AGimUihHGGahtT`, or `other-namespace:my-workflow`. + + ```proto + string workflow_reference = 1; + ``` + release_id: + The optional ID of the release to undeploy. When omitted, all deployed releases of the workflow are removed. + + ```proto + optional tilebox.v1.ID release_id = 2; + ``` + cluster_slugs: + ```proto + repeated string cluster_slugs = 3; + ``` + """ + + __slots__ = ("workflow_reference", "release_id", "cluster_slugs") + + if TYPE_CHECKING: + + def __init__( + self, + *, + workflow_reference: str = "", + release_id: ID | None = None, + cluster_slugs: list[str] | None = None, + ) -> None: + pass + + workflow_reference: str + release_id: ID | None + cluster_slugs: list[str] + +_UndeployWorkflowReleaseResponseFields: TypeAlias = Literal["release", "clusters"] + +class UndeployWorkflowReleaseResponse(Message[_UndeployWorkflowReleaseResponseFields]): + """ + UndeployWorkflowReleaseResponse is the response to UndeployWorkflowReleaseRequest and contains the new cluster state. + + ```proto + message workflows.v1.UndeployWorkflowReleaseResponse + ``` + + Attributes: + release: + The undeployed release when exactly one release was removed. The response may contain only its ID. + + ```proto + optional workflows.v1.WorkflowRelease release = 1; + ``` + clusters: + ```proto + repeated workflows.v1.Cluster clusters = 2; + ``` + """ + + __slots__ = ("release", "clusters") + + if TYPE_CHECKING: + + def __init__( + self, + *, + release: WorkflowRelease | None = None, + clusters: list[Cluster] | None = None, + ) -> None: + pass + + release: WorkflowRelease | None + clusters: list[Cluster] + + +_DESC = file_desc( + b'\n\x1cworkflows/v1/workflows.proto\x12\x0cworkflows.v1\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\x13tilebox/v1/id.proto\x1a\x16tilebox/v1/query.proto\x1a\x17workflows/v1/core.proto"\xcb\x01\n\x07Cluster\x12\x12\n\x04slug\x18\x02 \x01(\tR\x04slug\x12!\n\x0cdisplay_name\x18\x03 \x01(\tR\x0bdisplayName\x12 \n\x0bdescription\x18\x06 \x01(\tR\x0bdescription\x12\x1c\n\tdeletable\x18\x04 \x01(\x08R\tdeletable\x12C\n\x11deployed_releases\x18\x05 \x03(\x0b2\x16.workflows.v1.WorkflowR\x10deployedReleasesJ\x04\x08\x01\x10\x02"`\n\x14CreateClusterRequest\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\x12 \n\x0bdescription\x18\x02 \x01(\tR\x0bdescription\x12\x12\n\x04slug\x18\x03 \x01(\tR\x04slug"6\n\x11GetClusterRequest\x12!\n\x0ccluster_slug\x18\x01 \x01(\tR\x0bclusterSlug"}\n\x14UpdateClusterRequest\x12!\n\x0ccluster_slug\x18\x01 \x01(\tR\x0bclusterSlug\x12\x19\n\x04name\x18\x02 \x01(\tR\x04nameB\x05\xaa\x01\x02\x08\x01\x12\'\n\x0bdescription\x18\x03 \x01(\tR\x0bdescriptionB\x05\xaa\x01\x02\x08\x01"9\n\x14DeleteClusterRequest\x12!\n\x0ccluster_slug\x18\x01 \x01(\tR\x0bclusterSlug"\x17\n\x15DeleteClusterResponse"\x15\n\x13ListClustersRequest"I\n\x14ListClustersResponse\x121\n\x08clusters\x18\x01 \x03(\x0b2\x15.workflows.v1.ClusterR\x08clusters"C\n\x12GetWorkflowRequest\x12-\n\x12workflow_reference\x18\x01 \x01(\tR\x11workflowReference"\xab\x01\n\x15UpdateWorkflowRequest\x12-\n\x12workflow_reference\x18\x01 \x01(\tR\x11workflowReference\x12\x19\n\x04name\x18\x02 \x01(\tR\x04nameB\x05\xaa\x01\x02\x08\x01\x12\x1f\n\x07summary\x18\x04 \x01(\tR\x07summaryB\x05\xaa\x01\x02\x08\x01\x12\'\n\x0bdescription\x18\x03 \x01(\tR\x0bdescriptionB\x05\xaa\x01\x02\x08\x01"F\n\x15DeleteWorkflowRequest\x12-\n\x12workflow_reference\x18\x01 \x01(\tR\x11workflowReference"\x18\n\x16DeleteWorkflowResponse"\xb7\x01\n\x1dPublishWorkflowReleaseRequest\x12-\n\x12workflow_reference\x18\x01 \x01(\tR\x11workflowReference\x12/\n\x0bartifact_id\x18\x02 \x01(\x0b2\x0e.tilebox.v1.IDR\nartifactId\x126\n\x07content\x18\x03 \x01(\x0b2\x1c.workflows.v1.ReleaseContentR\x07content"\x7f\n\x1fUnpublishWorkflowReleaseRequest\x12-\n\x12workflow_reference\x18\x01 \x01(\tR\x11workflowReference\x12-\n\nrelease_id\x18\x02 \x01(\x0b2\x0e.tilebox.v1.IDR\treleaseId""\n UnpublishWorkflowReleaseResponse"I\n\x14ListWorkflowsRequest\x121\n\x04page\x18\x01 \x01(\x0b2\x16.tilebox.v1.PaginationR\x04pageB\x05\xaa\x01\x02\x08\x01"\x89\x01\n\x15ListWorkflowsResponse\x124\n\tworkflows\x18\x01 \x03(\x0b2\x16.workflows.v1.WorkflowR\tworkflows\x12:\n\tnext_page\x18\x02 \x01(\x0b2\x16.tilebox.v1.PaginationR\x08nextPageB\x05\xaa\x01\x02\x08\x01"O\n\x1aListPublicWorkflowsRequest\x121\n\x04page\x18\x01 \x01(\x0b2\x16.tilebox.v1.PaginationR\x04pageB\x05\xaa\x01\x02\x08\x01"\x8f\x01\n\x1bListPublicWorkflowsResponse\x124\n\tworkflows\x18\x01 \x03(\x0b2\x16.workflows.v1.WorkflowR\tworkflows\x12:\n\tnext_page\x18\x02 \x01(\x0b2\x16.tilebox.v1.PaginationR\x08nextPageB\x05\xaa\x01\x02\x08\x01"\xc8\x01\n\x15CreateWorkflowRequest\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\x12!\n\x0cproject_slug\x18\x04 \x01(\tR\x0bprojectSlug\x12\x18\n\x07summary\x18\x03 \x01(\tR\x07summary\x12\'\n\x0bdescription\x18\x02 \x01(\tR\x0bdescriptionB\x05\xaa\x01\x02\x08\x01\x125\n\x13legacy_task_aliases\x18\x05 \x01(\x08R\x11legacyTaskAliasesB\x05\xaa\x01\x02\x08\x01"\xf5\x02\n\x08Workflow\x12\x1e\n\x02id\x18\x01 \x01(\x0b2\x0e.tilebox.v1.IDR\x02id\x12\x1c\n\treference\x18\x02 \x01(\tR\treference\x12!\n\x0cproject_slug\x18\x08 \x01(\tR\x0bprojectSlug\x12\x12\n\x04name\x18\x03 \x01(\tR\x04name\x12\x18\n\x07summary\x18\x06 \x01(\tR\x07summary\x12 \n\x0bdescription\x18\x04 \x01(\tR\x0bdescription\x129\n\x08releases\x18\x05 \x03(\x0b2\x1d.workflows.v1.WorkflowReleaseR\x08releases\x12F\n\x0cpresentation\x18\x07 \x01(\x0b2".workflows.v1.WorkflowPresentationR\x0cpresentation\x125\n\x13legacy_task_aliases\x18\n \x01(\x08R\x11legacyTaskAliasesB\x05\xaa\x01\x02\x08\x01"\xb3\x01\n\x14WorkflowPresentation\x12\x1b\n\timage_url\x18\x01 \x01(\tR\x08imageUrl\x12\x1b\n\timage_alt\x18\x02 \x01(\tR\x08imageAlt\x12!\n\x0cimagery_type\x18\x03 \x03(\tR\x0bimageryType\x12\x19\n\x08use_case\x18\x04 \x03(\tR\x07useCase\x12#\n\rdataset_label\x18\x05 \x03(\tR\x0cdatasetLabel"\x8b\x02\n\x0fWorkflowRelease\x12\x1e\n\x02id\x18\x01 \x01(\x0b2\x0e.tilebox.v1.IDR\x02id\x122\n\x08artifact\x18\x02 \x01(\x0b2\x16.workflows.v1.ArtifactR\x08artifact\x126\n\x07content\x18\x03 \x01(\x0b2\x1c.workflows.v1.ReleaseContentR\x07content\x129\n\ncreated_at\x18\x04 \x01(\x0b2\x1a.google.protobuf.TimestampR\tcreatedAt\x121\n\x08clusters\x18\x05 \x03(\x0b2\x15.workflows.v1.ClusterR\x08clusters"B\n\x08Artifact\x12\x1e\n\x02id\x18\x01 \x01(\x0b2\x0e.tilebox.v1.IDR\x02id\x12\x16\n\x06digest\x18\x02 \x01(\tR\x06digest"\xae\x02\n\x0eReleaseContent\x12 \n\x0bfingerprint\x18\x01 \x01(\tR\x0bfingerprint\x129\n\x05tasks\x18\x02 \x03(\x0b2#.workflows.v1.UnboundTaskIdentifierR\x05tasks\x12(\n\x05files\x18\x03 \x03(\x0b2\x12.workflows.v1.PathR\x05files\x12,\n\x12runner_object_path\x18\x04 \x01(\tR\x10runnerObjectPath\x12)\n\x10command_override\x18\x05 \x03(\tR\x0fcommandOverride\x12<\n\x0cbuild_config\x18\x06 \x01(\x0b2\x19.workflows.v1.BuildConfigR\x0bbuildConfig"m\n\x0bBuildConfig\x12\x18\n\x07include\x18\x01 \x03(\tR\x07include\x12\x18\n\x07exclude\x18\x02 \x03(\tR\x07exclude\x12*\n\ruse_gitignore\x18\x03 \x01(\x08R\x0cuseGitignoreB\x05\xaa\x01\x02\x08\x01"h\n\x04Path\x12\x12\n\x04path\x18\x01 \x01(\tR\x04path\x12\x1c\n\tdirectory\x18\x02 \x01(\x08R\tdirectory\x12.\n\x08children\x18\x03 \x03(\x0b2\x12.workflows.v1.PathR\x08children"\xa1\x01\n\x1cDeployWorkflowReleaseRequest\x12-\n\x12workflow_reference\x18\x01 \x01(\tR\x11workflowReference\x12-\n\nrelease_id\x18\x02 \x01(\x0b2\x0e.tilebox.v1.IDR\treleaseId\x12#\n\rcluster_slugs\x18\x03 \x03(\tR\x0cclusterSlugs"\x8b\x01\n\x1dDeployWorkflowReleaseResponse\x127\n\x07release\x18\x01 \x01(\x0b2\x1d.workflows.v1.WorkflowReleaseR\x07release\x121\n\x08clusters\x18\x02 \x03(\x0b2\x15.workflows.v1.ClusterR\x08clusters"\xa3\x01\n\x1eUndeployWorkflowReleaseRequest\x12-\n\x12workflow_reference\x18\x01 \x01(\tR\x11workflowReference\x12-\n\nrelease_id\x18\x02 \x01(\x0b2\x0e.tilebox.v1.IDR\treleaseId\x12#\n\rcluster_slugs\x18\x03 \x03(\tR\x0cclusterSlugs"\x8d\x01\n\x1fUndeployWorkflowReleaseResponse\x127\n\x07release\x18\x01 \x01(\x0b2\x1d.workflows.v1.WorkflowReleaseR\x07release\x121\n\x08clusters\x18\x02 \x03(\x0b2\x15.workflows.v1.ClusterR\x08clusters2\xf6\n\n\x10WorkflowsService\x12J\n\rCreateCluster\x12".workflows.v1.CreateClusterRequest\x1a\x15.workflows.v1.Cluster\x12D\n\nGetCluster\x12\x1f.workflows.v1.GetClusterRequest\x1a\x15.workflows.v1.Cluster\x12J\n\rUpdateCluster\x12".workflows.v1.UpdateClusterRequest\x1a\x15.workflows.v1.Cluster\x12X\n\rDeleteCluster\x12".workflows.v1.DeleteClusterRequest\x1a#.workflows.v1.DeleteClusterResponse\x12U\n\x0cListClusters\x12!.workflows.v1.ListClustersRequest\x1a".workflows.v1.ListClustersResponse\x12M\n\x0eCreateWorkflow\x12#.workflows.v1.CreateWorkflowRequest\x1a\x16.workflows.v1.Workflow\x12X\n\rListWorkflows\x12".workflows.v1.ListWorkflowsRequest\x1a#.workflows.v1.ListWorkflowsResponse\x12j\n\x13ListPublicWorkflows\x12(.workflows.v1.ListPublicWorkflowsRequest\x1a).workflows.v1.ListPublicWorkflowsResponse\x12G\n\x0bGetWorkflow\x12 .workflows.v1.GetWorkflowRequest\x1a\x16.workflows.v1.Workflow\x12M\n\x0eUpdateWorkflow\x12#.workflows.v1.UpdateWorkflowRequest\x1a\x16.workflows.v1.Workflow\x12[\n\x0eDeleteWorkflow\x12#.workflows.v1.DeleteWorkflowRequest\x1a$.workflows.v1.DeleteWorkflowResponse\x12d\n\x16PublishWorkflowRelease\x12+.workflows.v1.PublishWorkflowReleaseRequest\x1a\x1d.workflows.v1.WorkflowRelease\x12y\n\x18UnpublishWorkflowRelease\x12-.workflows.v1.UnpublishWorkflowReleaseRequest\x1a..workflows.v1.UnpublishWorkflowReleaseResponse\x12p\n\x15DeployWorkflowRelease\x12*.workflows.v1.DeployWorkflowReleaseRequest\x1a+.workflows.v1.DeployWorkflowReleaseResponse\x12v\n\x17UndeployWorkflowRelease\x12,.workflows.v1.UndeployWorkflowReleaseRequest\x1a-.workflows.v1.UndeployWorkflowReleaseResponseB\x05\x92\x03\x02\x08\x02b\x08editionsp\xe8\x07', + [ + timestamp_pb.desc(), + id_pb.desc(), + query_pb.desc(), + core_pb.desc(), + ], + { + "Cluster": Cluster, + "CreateClusterRequest": CreateClusterRequest, + "GetClusterRequest": GetClusterRequest, + "UpdateClusterRequest": UpdateClusterRequest, + "DeleteClusterRequest": DeleteClusterRequest, + "DeleteClusterResponse": DeleteClusterResponse, + "ListClustersRequest": ListClustersRequest, + "ListClustersResponse": ListClustersResponse, + "GetWorkflowRequest": GetWorkflowRequest, + "UpdateWorkflowRequest": UpdateWorkflowRequest, + "DeleteWorkflowRequest": DeleteWorkflowRequest, + "DeleteWorkflowResponse": DeleteWorkflowResponse, + "PublishWorkflowReleaseRequest": PublishWorkflowReleaseRequest, + "UnpublishWorkflowReleaseRequest": UnpublishWorkflowReleaseRequest, + "UnpublishWorkflowReleaseResponse": UnpublishWorkflowReleaseResponse, + "ListWorkflowsRequest": ListWorkflowsRequest, + "ListWorkflowsResponse": ListWorkflowsResponse, + "ListPublicWorkflowsRequest": ListPublicWorkflowsRequest, + "ListPublicWorkflowsResponse": ListPublicWorkflowsResponse, + "CreateWorkflowRequest": CreateWorkflowRequest, + "Workflow": Workflow, + "WorkflowPresentation": WorkflowPresentation, + "WorkflowRelease": WorkflowRelease, + "Artifact": Artifact, + "ReleaseContent": ReleaseContent, + "BuildConfig": BuildConfig, + "Path": Path, + "DeployWorkflowReleaseRequest": DeployWorkflowReleaseRequest, + "DeployWorkflowReleaseResponse": DeployWorkflowReleaseResponse, + "UndeployWorkflowReleaseRequest": UndeployWorkflowReleaseRequest, + "UndeployWorkflowReleaseResponse": UndeployWorkflowReleaseResponse, + }, +) + + +def desc() -> DescFile: + """Returns the descriptor for the file `workflows/v1/workflows.proto`.""" + return _DESC diff --git a/tilebox-workflows/tilebox/workflows/workflows/client.py b/tilebox-workflows/tilebox/workflows/workflows/client.py index 26f0047..b26e509 100644 --- a/tilebox-workflows/tilebox/workflows/workflows/client.py +++ b/tilebox-workflows/tilebox/workflows/workflows/client.py @@ -1,16 +1,27 @@ from typing import TypeAlias from uuid import UUID +from _tilebox.uuid import must_uuid_to_uuid_message from tilebox.workflows.clusters.client import ClusterSlugLike, to_cluster_slug from tilebox.workflows.data import Workflow, WorkflowRelease, WorkflowReleaseDeployment -from tilebox.workflows.workflows.service import WorkflowService, release_id +from tilebox.workflows.v1.workflows_connect import WorkflowsServiceClientSync +from tilebox.workflows.v1.workflows_pb import ( + CreateWorkflowRequest, + DeleteWorkflowRequest, + DeployWorkflowReleaseRequest, + GetWorkflowRequest, + ListWorkflowsRequest, + UndeployWorkflowReleaseRequest, + UnpublishWorkflowReleaseRequest, + UpdateWorkflowRequest, +) WorkflowSlugLike: TypeAlias = Workflow | str WorkflowReleaseLike: TypeAlias = WorkflowRelease | UUID | str class WorkflowClient: - def __init__(self, service: WorkflowService) -> None: + def __init__(self, service: WorkflowsServiceClientSync) -> None: """Create a new workflow client. Args: @@ -29,7 +40,9 @@ def create(self, name: str, summary: str, description: str = "") -> Workflow: Returns: The created workflow. """ - return self._service.create(name, summary, description) + return Workflow.from_message( + self._service.create_workflow(CreateWorkflowRequest(name=name, summary=summary, description=description)) + ) def all(self) -> list[Workflow]: """List all available workflows. @@ -37,7 +50,8 @@ def all(self) -> list[Workflow]: Returns: All available workflows. """ - return self._service.list_all() + response = self._service.list_workflows(ListWorkflowsRequest()) + return [Workflow.from_message(workflow) for workflow in response.workflows] def find(self, workflow_or_slug: WorkflowSlugLike) -> Workflow: """Find a workflow by slug. @@ -48,7 +62,9 @@ def find(self, workflow_or_slug: WorkflowSlugLike) -> Workflow: Returns: The workflow for the given workflow slug. """ - return self._service.get_by_slug(to_workflow_slug(workflow_or_slug)) + return Workflow.from_message( + self._service.get_workflow(GetWorkflowRequest(workflow_reference=to_workflow_slug(workflow_or_slug))) + ) def update( self, @@ -68,7 +84,10 @@ def update( Returns: The updated workflow. """ - return self._service.update(to_workflow_slug(workflow_or_slug), name, summary, description) + request = UpdateWorkflowRequest( + workflow_reference=to_workflow_slug(workflow_or_slug), name=name, summary=summary, description=description + ) + return Workflow.from_message(self._service.update_workflow(request)) def delete(self, workflow_or_slug: WorkflowSlugLike) -> None: """Delete a workflow by slug. @@ -76,7 +95,7 @@ def delete(self, workflow_or_slug: WorkflowSlugLike) -> None: Args: workflow_or_slug: The workflow or slug of the workflow to delete. """ - self._service.delete(to_workflow_slug(workflow_or_slug)) + self._service.delete_workflow(DeleteWorkflowRequest(workflow_reference=to_workflow_slug(workflow_or_slug))) def unpublish_release(self, workflow_or_slug: WorkflowSlugLike, release_or_id: WorkflowReleaseLike) -> None: """Unpublish a workflow release. @@ -85,7 +104,12 @@ def unpublish_release(self, workflow_or_slug: WorkflowSlugLike, release_or_id: W workflow_or_slug: The workflow or slug containing the release to unpublish. release_or_id: The workflow release or id of the release to unpublish. """ - self._service.unpublish_release(to_workflow_slug(workflow_or_slug), release_id(release_or_id)) + self._service.unpublish_workflow_release( + UnpublishWorkflowReleaseRequest( + workflow_reference=to_workflow_slug(workflow_or_slug), + release_id=must_uuid_to_uuid_message(release_id(release_or_id)), + ) + ) def deploy_release( self, @@ -103,11 +127,12 @@ def deploy_release( Returns: The deployed release and affected clusters. """ - return self._service.deploy_release( - to_workflow_slug(workflow_or_slug), - release_id(release_or_id), - _to_cluster_slugs(clusters), + request = DeployWorkflowReleaseRequest( + workflow_reference=to_workflow_slug(workflow_or_slug), + release_id=must_uuid_to_uuid_message(release_id(release_or_id)), + cluster_slugs=_to_cluster_slugs(clusters), ) + return WorkflowReleaseDeployment.from_deploy_message(self._service.deploy_workflow_release(request)) def undeploy_release( self, @@ -125,17 +150,24 @@ def undeploy_release( Returns: The undeployed release and affected clusters. """ - return self._service.undeploy_release( - to_workflow_slug(workflow_or_slug), - release_id(release_or_id), - _to_cluster_slugs(clusters), + request = UndeployWorkflowReleaseRequest( + workflow_reference=to_workflow_slug(workflow_or_slug), + release_id=must_uuid_to_uuid_message(release_id(release_or_id)), + cluster_slugs=_to_cluster_slugs(clusters), ) + return WorkflowReleaseDeployment.from_undeploy_message(self._service.undeploy_workflow_release(request)) def to_workflow_slug(workflow: WorkflowSlugLike) -> str: return workflow.slug if isinstance(workflow, Workflow) else workflow +def release_id(release_or_id: WorkflowReleaseLike) -> UUID: + if isinstance(release_or_id, WorkflowRelease): + return release_or_id.id + return UUID(release_or_id) if isinstance(release_or_id, str) else release_or_id + + def _to_cluster_slugs(clusters: ClusterSlugLike | list[ClusterSlugLike] | None) -> list[str]: clusters = clusters or [] if isinstance(clusters, ClusterSlugLike): diff --git a/tilebox-workflows/tilebox/workflows/workflows/service.py b/tilebox-workflows/tilebox/workflows/workflows/service.py deleted file mode 100644 index afc9b81..0000000 --- a/tilebox-workflows/tilebox/workflows/workflows/service.py +++ /dev/null @@ -1,110 +0,0 @@ -from __future__ import annotations - -from typing import Any -from uuid import UUID - -from grpc import Channel - -from _tilebox.grpc.error import with_pythonic_errors -from tilebox.datasets.uuid import must_uuid_to_uuid_message -from tilebox.workflows.data import Workflow, WorkflowRelease, WorkflowReleaseDeployment -from tilebox.workflows.workflows.v1.workflows_pb2 import ( - CreateWorkflowRequest, - DeleteWorkflowRequest, - DeployWorkflowReleaseRequest, - DeployWorkflowReleaseResponse, - GetWorkflowRequest, - ListWorkflowsRequest, - ListWorkflowsResponse, - UndeployWorkflowReleaseRequest, - UndeployWorkflowReleaseResponse, - UnpublishWorkflowReleaseRequest, - UpdateWorkflowRequest, -) -from tilebox.workflows.workflows.v1.workflows_pb2_grpc import WorkflowsServiceStub - - -class WorkflowService: - def __init__(self, channel: Channel | Any) -> None: - """ - A wrapper around the WorkflowsServiceStub that provides a more pythonic interface and converts protobuf messages - to and from the data classes used in the rest of the tilebox-workflows codebase. - - Args: - channel: The gRPC channel to use for the service. - """ - self.service = ( - with_pythonic_errors(WorkflowsServiceStub(channel)) if hasattr(channel, "unary_unary") else channel - ) - - def create(self, name: str, summary: str, description: str = "") -> Workflow: - request = CreateWorkflowRequest(name=name, summary=summary, description=description) - return Workflow.from_message(self.service.CreateWorkflow(request)) - - def list_all(self) -> list[Workflow]: - request = ListWorkflowsRequest() - response: ListWorkflowsResponse = self.service.ListWorkflows(request) - return [Workflow.from_message(workflow) for workflow in response.workflows] - - def get_by_slug(self, workflow_slug: str) -> Workflow: - request = GetWorkflowRequest(workflow_slug=workflow_slug) - return Workflow.from_message(self.service.GetWorkflow(request)) - - def update( - self, - workflow_slug: str, - name: str | None = None, - summary: str | None = None, - description: str | None = None, - ) -> Workflow: - request = UpdateWorkflowRequest( - workflow_slug=workflow_slug, name=name, summary=summary, description=description - ) - return Workflow.from_message(self.service.UpdateWorkflow(request)) - - def delete(self, workflow_slug: str) -> None: - request = DeleteWorkflowRequest(workflow_slug=workflow_slug) - self.service.DeleteWorkflow(request) - - def unpublish_release(self, workflow_slug: str, release_id: UUID) -> None: - request = UnpublishWorkflowReleaseRequest( - workflow_slug=workflow_slug, - release_id=must_uuid_to_uuid_message(release_id), - ) - self.service.UnpublishWorkflowRelease(request) - - def deploy_release( - self, - workflow_slug: str, - release_id: UUID, - cluster_slugs: list[str] | None = None, - ) -> WorkflowReleaseDeployment: - request = DeployWorkflowReleaseRequest( - workflow_slug=workflow_slug, - release_id=must_uuid_to_uuid_message(release_id), - cluster_slugs=cluster_slugs or [], - ) - response: DeployWorkflowReleaseResponse = self.service.DeployWorkflowRelease(request) - return WorkflowReleaseDeployment.from_deploy_message(response) - - def undeploy_release( - self, - workflow_slug: str, - release_id: UUID, - cluster_slugs: list[str] | None = None, - ) -> WorkflowReleaseDeployment: - request = UndeployWorkflowReleaseRequest( - workflow_slug=workflow_slug, - release_id=must_uuid_to_uuid_message(release_id), - cluster_slugs=cluster_slugs or [], - ) - response: UndeployWorkflowReleaseResponse = self.service.UndeployWorkflowRelease(request) - return WorkflowReleaseDeployment.from_undeploy_message(response) - - -def release_id(release_or_id: WorkflowRelease | UUID | str) -> UUID: - if isinstance(release_or_id, WorkflowRelease): - return release_or_id.id - if isinstance(release_or_id, str): - return UUID(release_or_id) - return release_or_id diff --git a/tilebox-workflows/tilebox/workflows/workflows/v1/automation_pb2.py b/tilebox-workflows/tilebox/workflows/workflows/v1/automation_pb2.py deleted file mode 100644 index 3930fd2..0000000 --- a/tilebox-workflows/tilebox/workflows/workflows/v1/automation_pb2.py +++ /dev/null @@ -1,59 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by the protocol buffer compiler. DO NOT EDIT! -# NO CHECKED-IN PROTOBUF GENCODE -# source: workflows/v1/automation.proto -# Protobuf Python Version: 6.30.0 -"""Generated protocol buffer code.""" -from google.protobuf import descriptor as _descriptor -from google.protobuf import descriptor_pool as _descriptor_pool -from google.protobuf import runtime_version as _runtime_version -from google.protobuf import symbol_database as _symbol_database -from google.protobuf.internal import builder as _builder -_runtime_version.ValidateProtobufRuntimeVersion( - _runtime_version.Domain.PUBLIC, - 6, - 30, - 0, - '', - 'workflows/v1/automation.proto' -) -# @@protoc_insertion_point(imports) - -_sym_db = _symbol_database.Default() - - -from google.api import field_behavior_pb2 as google_dot_api_dot_field__behavior__pb2 -from google.protobuf import empty_pb2 as google_dot_protobuf_dot_empty__pb2 -from google.protobuf import timestamp_pb2 as google_dot_protobuf_dot_timestamp__pb2 -from tilebox.datasets.tilebox.v1 import id_pb2 as tilebox_dot_v1_dot_id__pb2 -from tilebox.workflows.workflows.v1 import core_pb2 as workflows_dot_v1_dot_core__pb2 -from tilebox.workflows.workflows.v1 import storage_location_pb2 as workflows_dot_v1_dot_storage__location__pb2 - - -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1dworkflows/v1/automation.proto\x12\x0cworkflows.v1\x1a\x1fgoogle/api/field_behavior.proto\x1a\x1bgoogle/protobuf/empty.proto\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\x13tilebox/v1/id.proto\x1a\x17workflows/v1/core.proto\x1a#workflows/v1/storage_location.proto\"\xc0\x02\n\x13\x41utomationPrototype\x12\x1e\n\x02id\x18\x01 \x01(\x0b\x32\x0e.tilebox.v1.IDR\x02id\x12\x12\n\x04name\x18\x02 \x01(\tR\x04name\x12@\n\tprototype\x18\x03 \x01(\x0b\x32\".workflows.v1.SingleTaskSubmissionR\tprototype\x12W\n\x16storage_event_triggers\x18\x04 \x03(\x0b\x32!.workflows.v1.StorageEventTriggerR\x14storageEventTriggers\x12>\n\rcron_triggers\x18\x05 \x03(\x0b\x32\x19.workflows.v1.CronTriggerR\x0c\x63ronTriggers\x12\x1a\n\x08\x64isabled\x18\x06 \x01(\x08R\x08\x64isabled\"R\n\x0b\x41utomations\x12\x43\n\x0b\x61utomations\x18\x01 \x03(\x0b\x32!.workflows.v1.AutomationPrototypeR\x0b\x61utomations\"\xa2\x01\n\x13StorageEventTrigger\x12\x1e\n\x02id\x18\x01 \x01(\x0b\x32\x0e.tilebox.v1.IDR\x02id\x12H\n\x10storage_location\x18\x02 \x01(\x0b\x32\x1d.workflows.v1.StorageLocationR\x0fstorageLocation\x12!\n\x0cglob_pattern\x18\x03 \x01(\tR\x0bglobPattern\"\x96\x01\n\x0b\x43ronTrigger\x12\x1e\n\x02id\x18\x01 \x01(\x0b\x32\x0e.tilebox.v1.IDR\x02id\x12\x1a\n\x08schedule\x18\x02 \x01(\tR\x08schedule\x12K\n\x11next_scheduled_at\x18\x03 \x01(\x0b\x32\x1a.google.protobuf.TimestampB\x03\xe0\x41\x03R\x0fnextScheduledAt\"E\n\nAutomation\x12#\n\rtrigger_event\x18\x01 \x01(\x0cR\x0ctriggerEvent\x12\x12\n\x04\x61rgs\x18\x02 \x01(\x0cR\x04\x61rgs\"S\n\x12TriggeredCronEvent\x12=\n\x0ctrigger_time\x18\x01 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\x0btriggerTime\"o\n\x17\x44\x65leteAutomationRequest\x12\x33\n\rautomation_id\x18\x01 \x01(\x0b\x32\x0e.tilebox.v1.IDR\x0c\x61utomationId\x12\x1f\n\x0b\x63\x61ncel_jobs\x18\x02 \x01(\x08R\ncancelJobs2\xa4\x03\n\x11\x41utomationService\x12\x44\n\x0fListAutomations\x12\x16.google.protobuf.Empty\x1a\x19.workflows.v1.Automations\x12\x42\n\rGetAutomation\x12\x0e.tilebox.v1.ID\x1a!.workflows.v1.AutomationPrototype\x12X\n\x10\x43reateAutomation\x12!.workflows.v1.AutomationPrototype\x1a!.workflows.v1.AutomationPrototype\x12X\n\x10UpdateAutomation\x12!.workflows.v1.AutomationPrototype\x1a!.workflows.v1.AutomationPrototype\x12Q\n\x10\x44\x65leteAutomation\x12%.workflows.v1.DeleteAutomationRequest\x1a\x16.google.protobuf.EmptyBy\n\x10\x63om.workflows.v1B\x0f\x41utomationProtoP\x01\xa2\x02\x03WXX\xaa\x02\x0cWorkflows.V1\xca\x02\x0cWorkflows\\V1\xe2\x02\x18Workflows\\V1\\GPBMetadata\xea\x02\rWorkflows::V1\x92\x03\x02\x08\x02\x62\x08\x65\x64itionsp\xe8\x07') - -_globals = globals() -_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) -_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'workflows.v1.automation_pb2', _globals) -if not _descriptor._USE_C_DESCRIPTORS: - _globals['DESCRIPTOR']._loaded_options = None - _globals['DESCRIPTOR']._serialized_options = b'\n\020com.workflows.v1B\017AutomationProtoP\001\242\002\003WXX\252\002\014Workflows.V1\312\002\014Workflows\\V1\342\002\030Workflows\\V1\\GPBMetadata\352\002\rWorkflows::V1\222\003\002\010\002' - _globals['_CRONTRIGGER'].fields_by_name['next_scheduled_at']._loaded_options = None - _globals['_CRONTRIGGER'].fields_by_name['next_scheduled_at']._serialized_options = b'\340A\003' - _globals['_AUTOMATIONPROTOTYPE']._serialized_start=226 - _globals['_AUTOMATIONPROTOTYPE']._serialized_end=546 - _globals['_AUTOMATIONS']._serialized_start=548 - _globals['_AUTOMATIONS']._serialized_end=630 - _globals['_STORAGEEVENTTRIGGER']._serialized_start=633 - _globals['_STORAGEEVENTTRIGGER']._serialized_end=795 - _globals['_CRONTRIGGER']._serialized_start=798 - _globals['_CRONTRIGGER']._serialized_end=948 - _globals['_AUTOMATION']._serialized_start=950 - _globals['_AUTOMATION']._serialized_end=1019 - _globals['_TRIGGEREDCRONEVENT']._serialized_start=1021 - _globals['_TRIGGEREDCRONEVENT']._serialized_end=1104 - _globals['_DELETEAUTOMATIONREQUEST']._serialized_start=1106 - _globals['_DELETEAUTOMATIONREQUEST']._serialized_end=1217 - _globals['_AUTOMATIONSERVICE']._serialized_start=1220 - _globals['_AUTOMATIONSERVICE']._serialized_end=1640 -# @@protoc_insertion_point(module_scope) diff --git a/tilebox-workflows/tilebox/workflows/workflows/v1/automation_pb2.pyi b/tilebox-workflows/tilebox/workflows/workflows/v1/automation_pb2.pyi deleted file mode 100644 index 74be9b9..0000000 --- a/tilebox-workflows/tilebox/workflows/workflows/v1/automation_pb2.pyi +++ /dev/null @@ -1,77 +0,0 @@ -from google.api import field_behavior_pb2 as _field_behavior_pb2 -from google.protobuf import empty_pb2 as _empty_pb2 -from google.protobuf import timestamp_pb2 as _timestamp_pb2 -from tilebox.datasets.tilebox.v1 import id_pb2 as _id_pb2 -from tilebox.workflows.workflows.v1 import core_pb2 as _core_pb2 -from tilebox.workflows.workflows.v1 import storage_location_pb2 as _storage_location_pb2 -from google.protobuf.internal import containers as _containers -from google.protobuf import descriptor as _descriptor -from google.protobuf import message as _message -from collections.abc import Iterable as _Iterable, Mapping as _Mapping -from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union - -DESCRIPTOR: _descriptor.FileDescriptor - -class AutomationPrototype(_message.Message): - __slots__ = ("id", "name", "prototype", "storage_event_triggers", "cron_triggers", "disabled") - ID_FIELD_NUMBER: _ClassVar[int] - NAME_FIELD_NUMBER: _ClassVar[int] - PROTOTYPE_FIELD_NUMBER: _ClassVar[int] - STORAGE_EVENT_TRIGGERS_FIELD_NUMBER: _ClassVar[int] - CRON_TRIGGERS_FIELD_NUMBER: _ClassVar[int] - DISABLED_FIELD_NUMBER: _ClassVar[int] - id: _id_pb2.ID - name: str - prototype: _core_pb2.SingleTaskSubmission - storage_event_triggers: _containers.RepeatedCompositeFieldContainer[StorageEventTrigger] - cron_triggers: _containers.RepeatedCompositeFieldContainer[CronTrigger] - disabled: bool - def __init__(self, id: _Optional[_Union[_id_pb2.ID, _Mapping]] = ..., name: _Optional[str] = ..., prototype: _Optional[_Union[_core_pb2.SingleTaskSubmission, _Mapping]] = ..., storage_event_triggers: _Optional[_Iterable[_Union[StorageEventTrigger, _Mapping]]] = ..., cron_triggers: _Optional[_Iterable[_Union[CronTrigger, _Mapping]]] = ..., disabled: bool = ...) -> None: ... - -class Automations(_message.Message): - __slots__ = ("automations",) - AUTOMATIONS_FIELD_NUMBER: _ClassVar[int] - automations: _containers.RepeatedCompositeFieldContainer[AutomationPrototype] - def __init__(self, automations: _Optional[_Iterable[_Union[AutomationPrototype, _Mapping]]] = ...) -> None: ... - -class StorageEventTrigger(_message.Message): - __slots__ = ("id", "storage_location", "glob_pattern") - ID_FIELD_NUMBER: _ClassVar[int] - STORAGE_LOCATION_FIELD_NUMBER: _ClassVar[int] - GLOB_PATTERN_FIELD_NUMBER: _ClassVar[int] - id: _id_pb2.ID - storage_location: _storage_location_pb2.StorageLocation - glob_pattern: str - def __init__(self, id: _Optional[_Union[_id_pb2.ID, _Mapping]] = ..., storage_location: _Optional[_Union[_storage_location_pb2.StorageLocation, _Mapping]] = ..., glob_pattern: _Optional[str] = ...) -> None: ... - -class CronTrigger(_message.Message): - __slots__ = ("id", "schedule", "next_scheduled_at") - ID_FIELD_NUMBER: _ClassVar[int] - SCHEDULE_FIELD_NUMBER: _ClassVar[int] - NEXT_SCHEDULED_AT_FIELD_NUMBER: _ClassVar[int] - id: _id_pb2.ID - schedule: str - next_scheduled_at: _timestamp_pb2.Timestamp - def __init__(self, id: _Optional[_Union[_id_pb2.ID, _Mapping]] = ..., schedule: _Optional[str] = ..., next_scheduled_at: _Optional[_Union[_timestamp_pb2.Timestamp, _Mapping]] = ...) -> None: ... - -class Automation(_message.Message): - __slots__ = ("trigger_event", "args") - TRIGGER_EVENT_FIELD_NUMBER: _ClassVar[int] - ARGS_FIELD_NUMBER: _ClassVar[int] - trigger_event: bytes - args: bytes - def __init__(self, trigger_event: _Optional[bytes] = ..., args: _Optional[bytes] = ...) -> None: ... - -class TriggeredCronEvent(_message.Message): - __slots__ = ("trigger_time",) - TRIGGER_TIME_FIELD_NUMBER: _ClassVar[int] - trigger_time: _timestamp_pb2.Timestamp - def __init__(self, trigger_time: _Optional[_Union[_timestamp_pb2.Timestamp, _Mapping]] = ...) -> None: ... - -class DeleteAutomationRequest(_message.Message): - __slots__ = ("automation_id", "cancel_jobs") - AUTOMATION_ID_FIELD_NUMBER: _ClassVar[int] - CANCEL_JOBS_FIELD_NUMBER: _ClassVar[int] - automation_id: _id_pb2.ID - cancel_jobs: bool - def __init__(self, automation_id: _Optional[_Union[_id_pb2.ID, _Mapping]] = ..., cancel_jobs: bool = ...) -> None: ... diff --git a/tilebox-workflows/tilebox/workflows/workflows/v1/automation_pb2_grpc.py b/tilebox-workflows/tilebox/workflows/workflows/v1/automation_pb2_grpc.py deleted file mode 100644 index 288c874..0000000 --- a/tilebox-workflows/tilebox/workflows/workflows/v1/automation_pb2_grpc.py +++ /dev/null @@ -1,265 +0,0 @@ -# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! -"""Client and server classes corresponding to protobuf-defined services.""" -import grpc - -from google.protobuf import empty_pb2 as google_dot_protobuf_dot_empty__pb2 -from tilebox.datasets.tilebox.v1 import id_pb2 as tilebox_dot_v1_dot_id__pb2 -from tilebox.workflows.workflows.v1 import automation_pb2 as workflows_dot_v1_dot_automation__pb2 - - -class AutomationServiceStub: - """AutomationService is a service for managing automations. Currently, we support two types of triggers for automations: - - Bucket triggers, which triggers tasks when an object is uploaded to a storage bucket that matches a glob pattern - - Cron triggers, which triggers tasks on a schedule - """ - - def __init__(self, channel): - """Constructor. - - Args: - channel: A grpc.Channel. - """ - self.ListAutomations = channel.unary_unary( - '/workflows.v1.AutomationService/ListAutomations', - request_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, - response_deserializer=workflows_dot_v1_dot_automation__pb2.Automations.FromString, - _registered_method=True) - self.GetAutomation = channel.unary_unary( - '/workflows.v1.AutomationService/GetAutomation', - request_serializer=tilebox_dot_v1_dot_id__pb2.ID.SerializeToString, - response_deserializer=workflows_dot_v1_dot_automation__pb2.AutomationPrototype.FromString, - _registered_method=True) - self.CreateAutomation = channel.unary_unary( - '/workflows.v1.AutomationService/CreateAutomation', - request_serializer=workflows_dot_v1_dot_automation__pb2.AutomationPrototype.SerializeToString, - response_deserializer=workflows_dot_v1_dot_automation__pb2.AutomationPrototype.FromString, - _registered_method=True) - self.UpdateAutomation = channel.unary_unary( - '/workflows.v1.AutomationService/UpdateAutomation', - request_serializer=workflows_dot_v1_dot_automation__pb2.AutomationPrototype.SerializeToString, - response_deserializer=workflows_dot_v1_dot_automation__pb2.AutomationPrototype.FromString, - _registered_method=True) - self.DeleteAutomation = channel.unary_unary( - '/workflows.v1.AutomationService/DeleteAutomation', - request_serializer=workflows_dot_v1_dot_automation__pb2.DeleteAutomationRequest.SerializeToString, - response_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, - _registered_method=True) - - -class AutomationServiceServicer: - """AutomationService is a service for managing automations. Currently, we support two types of triggers for automations: - - Bucket triggers, which triggers tasks when an object is uploaded to a storage bucket that matches a glob pattern - - Cron triggers, which triggers tasks on a schedule - """ - - def ListAutomations(self, request, context): - """ListAutomations lists all the automations that are currently registered in a namespace. - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def GetAutomation(self, request, context): - """GetAutomation gets an automation by its ID. - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def CreateAutomation(self, request, context): - """CreateAutomation creates a new automation in a namespace. - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def UpdateAutomation(self, request, context): - """UpdateAutomation updates an automation in a namespace. - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def DeleteAutomation(self, request, context): - """DeleteAutomation deletes an automation from a namespace. - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - -def add_AutomationServiceServicer_to_server(servicer, server): - rpc_method_handlers = { - 'ListAutomations': grpc.unary_unary_rpc_method_handler( - servicer.ListAutomations, - request_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, - response_serializer=workflows_dot_v1_dot_automation__pb2.Automations.SerializeToString, - ), - 'GetAutomation': grpc.unary_unary_rpc_method_handler( - servicer.GetAutomation, - request_deserializer=tilebox_dot_v1_dot_id__pb2.ID.FromString, - response_serializer=workflows_dot_v1_dot_automation__pb2.AutomationPrototype.SerializeToString, - ), - 'CreateAutomation': grpc.unary_unary_rpc_method_handler( - servicer.CreateAutomation, - request_deserializer=workflows_dot_v1_dot_automation__pb2.AutomationPrototype.FromString, - response_serializer=workflows_dot_v1_dot_automation__pb2.AutomationPrototype.SerializeToString, - ), - 'UpdateAutomation': grpc.unary_unary_rpc_method_handler( - servicer.UpdateAutomation, - request_deserializer=workflows_dot_v1_dot_automation__pb2.AutomationPrototype.FromString, - response_serializer=workflows_dot_v1_dot_automation__pb2.AutomationPrototype.SerializeToString, - ), - 'DeleteAutomation': grpc.unary_unary_rpc_method_handler( - servicer.DeleteAutomation, - request_deserializer=workflows_dot_v1_dot_automation__pb2.DeleteAutomationRequest.FromString, - response_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, - ), - } - generic_handler = grpc.method_handlers_generic_handler( - 'workflows.v1.AutomationService', rpc_method_handlers) - server.add_generic_rpc_handlers((generic_handler,)) - server.add_registered_method_handlers('workflows.v1.AutomationService', rpc_method_handlers) - - - # This class is part of an EXPERIMENTAL API. -class AutomationService: - """AutomationService is a service for managing automations. Currently, we support two types of triggers for automations: - - Bucket triggers, which triggers tasks when an object is uploaded to a storage bucket that matches a glob pattern - - Cron triggers, which triggers tasks on a schedule - """ - - @staticmethod - def ListAutomations(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary( - request, - target, - '/workflows.v1.AutomationService/ListAutomations', - google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, - workflows_dot_v1_dot_automation__pb2.Automations.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) - - @staticmethod - def GetAutomation(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary( - request, - target, - '/workflows.v1.AutomationService/GetAutomation', - tilebox_dot_v1_dot_id__pb2.ID.SerializeToString, - workflows_dot_v1_dot_automation__pb2.AutomationPrototype.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) - - @staticmethod - def CreateAutomation(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary( - request, - target, - '/workflows.v1.AutomationService/CreateAutomation', - workflows_dot_v1_dot_automation__pb2.AutomationPrototype.SerializeToString, - workflows_dot_v1_dot_automation__pb2.AutomationPrototype.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) - - @staticmethod - def UpdateAutomation(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary( - request, - target, - '/workflows.v1.AutomationService/UpdateAutomation', - workflows_dot_v1_dot_automation__pb2.AutomationPrototype.SerializeToString, - workflows_dot_v1_dot_automation__pb2.AutomationPrototype.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) - - @staticmethod - def DeleteAutomation(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary( - request, - target, - '/workflows.v1.AutomationService/DeleteAutomation', - workflows_dot_v1_dot_automation__pb2.DeleteAutomationRequest.SerializeToString, - google_dot_protobuf_dot_empty__pb2.Empty.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) diff --git a/tilebox-workflows/tilebox/workflows/workflows/v1/core_pb2.py b/tilebox-workflows/tilebox/workflows/workflows/v1/core_pb2.py deleted file mode 100644 index a8aeb5f..0000000 --- a/tilebox-workflows/tilebox/workflows/workflows/v1/core_pb2.py +++ /dev/null @@ -1,78 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by the protocol buffer compiler. DO NOT EDIT! -# NO CHECKED-IN PROTOBUF GENCODE -# source: workflows/v1/core.proto -# Protobuf Python Version: 6.30.0 -"""Generated protocol buffer code.""" -from google.protobuf import descriptor as _descriptor -from google.protobuf import descriptor_pool as _descriptor_pool -from google.protobuf import runtime_version as _runtime_version -from google.protobuf import symbol_database as _symbol_database -from google.protobuf.internal import builder as _builder -_runtime_version.ValidateProtobufRuntimeVersion( - _runtime_version.Domain.PUBLIC, - 6, - 30, - 0, - '', - 'workflows/v1/core.proto' -) -# @@protoc_insertion_point(imports) - -_sym_db = _symbol_database.Default() - - -from google.protobuf import duration_pb2 as google_dot_protobuf_dot_duration__pb2 -from google.protobuf import timestamp_pb2 as google_dot_protobuf_dot_timestamp__pb2 -from tilebox.datasets.tilebox.v1 import id_pb2 as tilebox_dot_v1_dot_id__pb2 - - -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x17workflows/v1/core.proto\x12\x0cworkflows.v1\x1a\x1egoogle/protobuf/duration.proto\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\x13tilebox/v1/id.proto\"\xb9\x04\n\x03Job\x12\x1e\n\x02id\x18\x01 \x01(\x0b\x32\x0e.tilebox.v1.IDR\x02id\x12\x12\n\x04name\x18\x02 \x01(\tR\x04name\x12!\n\x0ctrace_parent\x18\x03 \x01(\tR\x0btraceParent\x12\x1e\n\x08\x63\x61nceled\x18\x05 \x01(\x08\x42\x02\x18\x01R\x08\x63\x61nceled\x12\x43\n\x0clegacy_state\x18\x06 \x01(\x0e\x32\x1c.workflows.v1.LegacyJobStateB\x02\x18\x01R\x0blegacyState\x12=\n\x0csubmitted_at\x18\x07 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\x0bsubmittedAt\x12=\n\nstarted_at\x18\x08 \x01(\x0b\x32\x1a.google.protobuf.TimestampB\x02\x18\x01R\tstartedAt\x12\x33\n\rautomation_id\x18\n \x01(\x0b\x32\x0e.tilebox.v1.IDR\x0c\x61utomationId\x12\x32\n\x08progress\x18\x0b \x03(\x0b\x32\x16.workflows.v1.ProgressR\x08progress\x12,\n\x05state\x18\x0c \x01(\x0e\x32\x16.workflows.v1.JobStateR\x05state\x12\x45\n\x0f\x65xecution_stats\x18\r \x01(\x0b\x32\x1c.workflows.v1.ExecutionStatsR\x0e\x65xecutionStatsJ\x04\x08\x04\x10\x05J\x04\x08\t\x10\nR\x0etask_summaries\"\xaf\x03\n\x0e\x45xecutionStats\x12M\n\x15\x66irst_task_started_at\x18\x01 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\x12\x66irstTaskStartedAt\x12K\n\x14last_task_stopped_at\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\x11lastTaskStoppedAt\x12<\n\x0c\x63ompute_time\x18\x03 \x01(\x0b\x32\x19.google.protobuf.DurationR\x0b\x63omputeTime\x12<\n\x0c\x65lapsed_time\x18\x04 \x01(\x0b\x32\x19.google.protobuf.DurationR\x0b\x65lapsedTime\x12 \n\x0bparallelism\x18\x05 \x01(\x01R\x0bparallelism\x12\x1f\n\x0btotal_tasks\x18\x06 \x01(\x04R\ntotalTasks\x12\x42\n\x0etasks_by_state\x18\x07 \x03(\x0b\x32\x1c.workflows.v1.TaskStateCountR\x0ctasksByState\"U\n\x0eTaskStateCount\x12-\n\x05state\x18\x01 \x01(\x0e\x32\x17.workflows.v1.TaskStateR\x05state\x12\x14\n\x05\x63ount\x18\x02 \x01(\x04R\x05\x63ount\"J\n\x08Progress\x12\x14\n\x05label\x18\x01 \x01(\tR\x05label\x12\x14\n\x05total\x18\x02 \x01(\x04R\x05total\x12\x12\n\x04\x64one\x18\x03 \x01(\x04R\x04\x64one\"\xa2\x03\n\x04Task\x12\x1e\n\x02id\x18\x01 \x01(\x0b\x32\x0e.tilebox.v1.IDR\x02id\x12<\n\nidentifier\x18\x02 \x01(\x0b\x32\x1c.workflows.v1.TaskIdentifierR\nidentifier\x12-\n\x05state\x18\x03 \x01(\x0e\x32\x17.workflows.v1.TaskStateR\x05state\x12\x1b\n\x05input\x18\x04 \x01(\x0c\x42\x05\xaa\x01\x02\x08\x01R\x05input\x12\x1f\n\x07\x64isplay\x18\x05 \x01(\tB\x05\xaa\x01\x02\x08\x01R\x07\x64isplay\x12#\n\x03job\x18\x06 \x01(\x0b\x32\x11.workflows.v1.JobR\x03job\x12+\n\tparent_id\x18\x07 \x01(\x0b\x32\x0e.tilebox.v1.IDR\x08parentId\x12-\n\ndepends_on\x18\x08 \x03(\x0b\x32\x0e.tilebox.v1.IDR\tdependsOn\x12-\n\x05lease\x18\t \x01(\x0b\x32\x17.workflows.v1.TaskLeaseR\x05lease\x12\x1f\n\x0bretry_count\x18\n \x01(\x03R\nretryCount\">\n\x0eTaskIdentifier\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\x12\x18\n\x07version\x18\x02 \x01(\tR\x07version\"Q\n\x0fTaskIdentifiers\x12>\n\x0bidentifiers\x18\x01 \x03(\x0b\x32\x1c.workflows.v1.TaskIdentifierR\x0bidentifiers\"1\n\x05Tasks\x12(\n\x05tasks\x18\x01 \x03(\x0b\x32\x12.workflows.v1.TaskR\x05tasks\"\xec\x01\n\x14SingleTaskSubmission\x12!\n\x0c\x63luster_slug\x18\x01 \x01(\tR\x0b\x63lusterSlug\x12<\n\nidentifier\x18\x02 \x01(\x0b\x32\x1c.workflows.v1.TaskIdentifierR\nidentifier\x12\x18\n\x07\x64isplay\x18\x04 \x01(\tR\x07\x64isplay\x12\"\n\x0c\x64\x65pendencies\x18\x05 \x03(\x03R\x0c\x64\x65pendencies\x12\x1f\n\x0bmax_retries\x18\x06 \x01(\x03R\nmaxRetries\x12\x14\n\x05input\x18\x03 \x01(\x0cR\x05input\"\xf7\x01\n\x0fTaskSubmissions\x12\x42\n\x0btask_groups\x18\x01 \x03(\x0b\x32!.workflows.v1.TaskSubmissionGroupR\ntaskGroups\x12.\n\x13\x63luster_slug_lookup\x18\x02 \x03(\tR\x11\x63lusterSlugLookup\x12I\n\x11identifier_lookup\x18\x03 \x03(\x0b\x32\x1c.workflows.v1.TaskIdentifierR\x10identifierLookup\x12%\n\x0e\x64isplay_lookup\x18\x04 \x03(\tR\rdisplayLookup\"\xd5\x02\n\x13TaskSubmissionGroup\x12?\n\x1c\x64\x65pendencies_on_other_groups\x18\x01 \x03(\rR\x19\x64\x65pendenciesOnOtherGroups\x12\x16\n\x06inputs\x18\x02 \x03(\x0cR\x06inputs\x12/\n\x13identifier_pointers\x18\x03 \x03(\x04R\x12identifierPointers\x12\x32\n\x15\x63luster_slug_pointers\x18\x04 \x03(\x04R\x13\x63lusterSlugPointers\x12)\n\x10\x64isplay_pointers\x18\x05 \x03(\x04R\x0f\x64isplayPointers\x12,\n\x12max_retries_values\x18\x06 \x03(\x03R\x10maxRetriesValues\x12\'\n\x0foptional_values\x18\x07 \x03(\x08R\x0eoptionalValues\"\xa9\x01\n\tTaskLease\x12/\n\x05lease\x18\x01 \x01(\x0b\x32\x19.google.protobuf.DurationR\x05lease\x12k\n%recommended_wait_until_next_extension\x18\x02 \x01(\x0b\x32\x19.google.protobuf.DurationR!recommendedWaitUntilNextExtension*\x8d\x01\n\x0eLegacyJobState\x12 \n\x1cLEGACY_JOB_STATE_UNSPECIFIED\x10\x00\x12\x1b\n\x17LEGACY_JOB_STATE_QUEUED\x10\x01\x12\x1c\n\x18LEGACY_JOB_STATE_STARTED\x10\x02\x12\x1e\n\x1aLEGACY_JOB_STATE_COMPLETED\x10\x03*\xb3\x01\n\x08JobState\x12\x19\n\x15JOB_STATE_UNSPECIFIED\x10\x00\x12\x17\n\x13JOB_STATE_SUBMITTED\x10\x01\x12\x15\n\x11JOB_STATE_RUNNING\x10\x02\x12\x15\n\x11JOB_STATE_STARTED\x10\x03\x12\x17\n\x13JOB_STATE_COMPLETED\x10\x04\x12\x14\n\x10JOB_STATE_FAILED\x10\x05\x12\x16\n\x12JOB_STATE_CANCELED\x10\x06*\xbe\x01\n\tTaskState\x12\x1a\n\x16TASK_STATE_UNSPECIFIED\x10\x00\x12\x15\n\x11TASK_STATE_QUEUED\x10\x01\x12\x16\n\x12TASK_STATE_RUNNING\x10\x02\x12\x17\n\x13TASK_STATE_COMPUTED\x10\x03\x12\x15\n\x11TASK_STATE_FAILED\x10\x04\x12\x16\n\x12TASK_STATE_SKIPPED\x10\x05\x12\x1e\n\x1aTASK_STATE_FAILED_OPTIONAL\x10\x06\x42s\n\x10\x63om.workflows.v1B\tCoreProtoP\x01\xa2\x02\x03WXX\xaa\x02\x0cWorkflows.V1\xca\x02\x0cWorkflows\\V1\xe2\x02\x18Workflows\\V1\\GPBMetadata\xea\x02\rWorkflows::V1\x92\x03\x02\x08\x02\x62\x08\x65\x64itionsp\xe8\x07') - -_globals = globals() -_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) -_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'workflows.v1.core_pb2', _globals) -if not _descriptor._USE_C_DESCRIPTORS: - _globals['DESCRIPTOR']._loaded_options = None - _globals['DESCRIPTOR']._serialized_options = b'\n\020com.workflows.v1B\tCoreProtoP\001\242\002\003WXX\252\002\014Workflows.V1\312\002\014Workflows\\V1\342\002\030Workflows\\V1\\GPBMetadata\352\002\rWorkflows::V1\222\003\002\010\002' - _globals['_JOB'].fields_by_name['canceled']._loaded_options = None - _globals['_JOB'].fields_by_name['canceled']._serialized_options = b'\030\001' - _globals['_JOB'].fields_by_name['legacy_state']._loaded_options = None - _globals['_JOB'].fields_by_name['legacy_state']._serialized_options = b'\030\001' - _globals['_JOB'].fields_by_name['started_at']._loaded_options = None - _globals['_JOB'].fields_by_name['started_at']._serialized_options = b'\030\001' - _globals['_TASK'].fields_by_name['input']._loaded_options = None - _globals['_TASK'].fields_by_name['input']._serialized_options = b'\252\001\002\010\001' - _globals['_TASK'].fields_by_name['display']._loaded_options = None - _globals['_TASK'].fields_by_name['display']._serialized_options = b'\252\001\002\010\001' - _globals['_LEGACYJOBSTATE']._serialized_start=2921 - _globals['_LEGACYJOBSTATE']._serialized_end=3062 - _globals['_JOBSTATE']._serialized_start=3065 - _globals['_JOBSTATE']._serialized_end=3244 - _globals['_TASKSTATE']._serialized_start=3247 - _globals['_TASKSTATE']._serialized_end=3437 - _globals['_JOB']._serialized_start=128 - _globals['_JOB']._serialized_end=697 - _globals['_EXECUTIONSTATS']._serialized_start=700 - _globals['_EXECUTIONSTATS']._serialized_end=1131 - _globals['_TASKSTATECOUNT']._serialized_start=1133 - _globals['_TASKSTATECOUNT']._serialized_end=1218 - _globals['_PROGRESS']._serialized_start=1220 - _globals['_PROGRESS']._serialized_end=1294 - _globals['_TASK']._serialized_start=1297 - _globals['_TASK']._serialized_end=1715 - _globals['_TASKIDENTIFIER']._serialized_start=1717 - _globals['_TASKIDENTIFIER']._serialized_end=1779 - _globals['_TASKIDENTIFIERS']._serialized_start=1781 - _globals['_TASKIDENTIFIERS']._serialized_end=1862 - _globals['_TASKS']._serialized_start=1864 - _globals['_TASKS']._serialized_end=1913 - _globals['_SINGLETASKSUBMISSION']._serialized_start=1916 - _globals['_SINGLETASKSUBMISSION']._serialized_end=2152 - _globals['_TASKSUBMISSIONS']._serialized_start=2155 - _globals['_TASKSUBMISSIONS']._serialized_end=2402 - _globals['_TASKSUBMISSIONGROUP']._serialized_start=2405 - _globals['_TASKSUBMISSIONGROUP']._serialized_end=2746 - _globals['_TASKLEASE']._serialized_start=2749 - _globals['_TASKLEASE']._serialized_end=2918 -# @@protoc_insertion_point(module_scope) diff --git a/tilebox-workflows/tilebox/workflows/workflows/v1/core_pb2.pyi b/tilebox-workflows/tilebox/workflows/workflows/v1/core_pb2.pyi deleted file mode 100644 index 6b24d59..0000000 --- a/tilebox-workflows/tilebox/workflows/workflows/v1/core_pb2.pyi +++ /dev/null @@ -1,216 +0,0 @@ -from google.protobuf import duration_pb2 as _duration_pb2 -from google.protobuf import timestamp_pb2 as _timestamp_pb2 -from tilebox.datasets.tilebox.v1 import id_pb2 as _id_pb2 -from google.protobuf.internal import containers as _containers -from google.protobuf.internal import enum_type_wrapper as _enum_type_wrapper -from google.protobuf import descriptor as _descriptor -from google.protobuf import message as _message -from collections.abc import Iterable as _Iterable, Mapping as _Mapping -from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union - -DESCRIPTOR: _descriptor.FileDescriptor - -class LegacyJobState(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): - __slots__ = () - LEGACY_JOB_STATE_UNSPECIFIED: _ClassVar[LegacyJobState] - LEGACY_JOB_STATE_QUEUED: _ClassVar[LegacyJobState] - LEGACY_JOB_STATE_STARTED: _ClassVar[LegacyJobState] - LEGACY_JOB_STATE_COMPLETED: _ClassVar[LegacyJobState] - -class JobState(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): - __slots__ = () - JOB_STATE_UNSPECIFIED: _ClassVar[JobState] - JOB_STATE_SUBMITTED: _ClassVar[JobState] - JOB_STATE_RUNNING: _ClassVar[JobState] - JOB_STATE_STARTED: _ClassVar[JobState] - JOB_STATE_COMPLETED: _ClassVar[JobState] - JOB_STATE_FAILED: _ClassVar[JobState] - JOB_STATE_CANCELED: _ClassVar[JobState] - -class TaskState(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): - __slots__ = () - TASK_STATE_UNSPECIFIED: _ClassVar[TaskState] - TASK_STATE_QUEUED: _ClassVar[TaskState] - TASK_STATE_RUNNING: _ClassVar[TaskState] - TASK_STATE_COMPUTED: _ClassVar[TaskState] - TASK_STATE_FAILED: _ClassVar[TaskState] - TASK_STATE_SKIPPED: _ClassVar[TaskState] - TASK_STATE_FAILED_OPTIONAL: _ClassVar[TaskState] -LEGACY_JOB_STATE_UNSPECIFIED: LegacyJobState -LEGACY_JOB_STATE_QUEUED: LegacyJobState -LEGACY_JOB_STATE_STARTED: LegacyJobState -LEGACY_JOB_STATE_COMPLETED: LegacyJobState -JOB_STATE_UNSPECIFIED: JobState -JOB_STATE_SUBMITTED: JobState -JOB_STATE_RUNNING: JobState -JOB_STATE_STARTED: JobState -JOB_STATE_COMPLETED: JobState -JOB_STATE_FAILED: JobState -JOB_STATE_CANCELED: JobState -TASK_STATE_UNSPECIFIED: TaskState -TASK_STATE_QUEUED: TaskState -TASK_STATE_RUNNING: TaskState -TASK_STATE_COMPUTED: TaskState -TASK_STATE_FAILED: TaskState -TASK_STATE_SKIPPED: TaskState -TASK_STATE_FAILED_OPTIONAL: TaskState - -class Job(_message.Message): - __slots__ = ("id", "name", "trace_parent", "canceled", "legacy_state", "submitted_at", "started_at", "automation_id", "progress", "state", "execution_stats") - ID_FIELD_NUMBER: _ClassVar[int] - NAME_FIELD_NUMBER: _ClassVar[int] - TRACE_PARENT_FIELD_NUMBER: _ClassVar[int] - CANCELED_FIELD_NUMBER: _ClassVar[int] - LEGACY_STATE_FIELD_NUMBER: _ClassVar[int] - SUBMITTED_AT_FIELD_NUMBER: _ClassVar[int] - STARTED_AT_FIELD_NUMBER: _ClassVar[int] - AUTOMATION_ID_FIELD_NUMBER: _ClassVar[int] - PROGRESS_FIELD_NUMBER: _ClassVar[int] - STATE_FIELD_NUMBER: _ClassVar[int] - EXECUTION_STATS_FIELD_NUMBER: _ClassVar[int] - id: _id_pb2.ID - name: str - trace_parent: str - canceled: bool - legacy_state: LegacyJobState - submitted_at: _timestamp_pb2.Timestamp - started_at: _timestamp_pb2.Timestamp - automation_id: _id_pb2.ID - progress: _containers.RepeatedCompositeFieldContainer[Progress] - state: JobState - execution_stats: ExecutionStats - def __init__(self, id: _Optional[_Union[_id_pb2.ID, _Mapping]] = ..., name: _Optional[str] = ..., trace_parent: _Optional[str] = ..., canceled: bool = ..., legacy_state: _Optional[_Union[LegacyJobState, str]] = ..., submitted_at: _Optional[_Union[_timestamp_pb2.Timestamp, _Mapping]] = ..., started_at: _Optional[_Union[_timestamp_pb2.Timestamp, _Mapping]] = ..., automation_id: _Optional[_Union[_id_pb2.ID, _Mapping]] = ..., progress: _Optional[_Iterable[_Union[Progress, _Mapping]]] = ..., state: _Optional[_Union[JobState, str]] = ..., execution_stats: _Optional[_Union[ExecutionStats, _Mapping]] = ...) -> None: ... - -class ExecutionStats(_message.Message): - __slots__ = ("first_task_started_at", "last_task_stopped_at", "compute_time", "elapsed_time", "parallelism", "total_tasks", "tasks_by_state") - FIRST_TASK_STARTED_AT_FIELD_NUMBER: _ClassVar[int] - LAST_TASK_STOPPED_AT_FIELD_NUMBER: _ClassVar[int] - COMPUTE_TIME_FIELD_NUMBER: _ClassVar[int] - ELAPSED_TIME_FIELD_NUMBER: _ClassVar[int] - PARALLELISM_FIELD_NUMBER: _ClassVar[int] - TOTAL_TASKS_FIELD_NUMBER: _ClassVar[int] - TASKS_BY_STATE_FIELD_NUMBER: _ClassVar[int] - first_task_started_at: _timestamp_pb2.Timestamp - last_task_stopped_at: _timestamp_pb2.Timestamp - compute_time: _duration_pb2.Duration - elapsed_time: _duration_pb2.Duration - parallelism: float - total_tasks: int - tasks_by_state: _containers.RepeatedCompositeFieldContainer[TaskStateCount] - def __init__(self, first_task_started_at: _Optional[_Union[_timestamp_pb2.Timestamp, _Mapping]] = ..., last_task_stopped_at: _Optional[_Union[_timestamp_pb2.Timestamp, _Mapping]] = ..., compute_time: _Optional[_Union[_duration_pb2.Duration, _Mapping]] = ..., elapsed_time: _Optional[_Union[_duration_pb2.Duration, _Mapping]] = ..., parallelism: _Optional[float] = ..., total_tasks: _Optional[int] = ..., tasks_by_state: _Optional[_Iterable[_Union[TaskStateCount, _Mapping]]] = ...) -> None: ... - -class TaskStateCount(_message.Message): - __slots__ = ("state", "count") - STATE_FIELD_NUMBER: _ClassVar[int] - COUNT_FIELD_NUMBER: _ClassVar[int] - state: TaskState - count: int - def __init__(self, state: _Optional[_Union[TaskState, str]] = ..., count: _Optional[int] = ...) -> None: ... - -class Progress(_message.Message): - __slots__ = ("label", "total", "done") - LABEL_FIELD_NUMBER: _ClassVar[int] - TOTAL_FIELD_NUMBER: _ClassVar[int] - DONE_FIELD_NUMBER: _ClassVar[int] - label: str - total: int - done: int - def __init__(self, label: _Optional[str] = ..., total: _Optional[int] = ..., done: _Optional[int] = ...) -> None: ... - -class Task(_message.Message): - __slots__ = ("id", "identifier", "state", "input", "display", "job", "parent_id", "depends_on", "lease", "retry_count") - ID_FIELD_NUMBER: _ClassVar[int] - IDENTIFIER_FIELD_NUMBER: _ClassVar[int] - STATE_FIELD_NUMBER: _ClassVar[int] - INPUT_FIELD_NUMBER: _ClassVar[int] - DISPLAY_FIELD_NUMBER: _ClassVar[int] - JOB_FIELD_NUMBER: _ClassVar[int] - PARENT_ID_FIELD_NUMBER: _ClassVar[int] - DEPENDS_ON_FIELD_NUMBER: _ClassVar[int] - LEASE_FIELD_NUMBER: _ClassVar[int] - RETRY_COUNT_FIELD_NUMBER: _ClassVar[int] - id: _id_pb2.ID - identifier: TaskIdentifier - state: TaskState - input: bytes - display: str - job: Job - parent_id: _id_pb2.ID - depends_on: _containers.RepeatedCompositeFieldContainer[_id_pb2.ID] - lease: TaskLease - retry_count: int - def __init__(self, id: _Optional[_Union[_id_pb2.ID, _Mapping]] = ..., identifier: _Optional[_Union[TaskIdentifier, _Mapping]] = ..., state: _Optional[_Union[TaskState, str]] = ..., input: _Optional[bytes] = ..., display: _Optional[str] = ..., job: _Optional[_Union[Job, _Mapping]] = ..., parent_id: _Optional[_Union[_id_pb2.ID, _Mapping]] = ..., depends_on: _Optional[_Iterable[_Union[_id_pb2.ID, _Mapping]]] = ..., lease: _Optional[_Union[TaskLease, _Mapping]] = ..., retry_count: _Optional[int] = ...) -> None: ... - -class TaskIdentifier(_message.Message): - __slots__ = ("name", "version") - NAME_FIELD_NUMBER: _ClassVar[int] - VERSION_FIELD_NUMBER: _ClassVar[int] - name: str - version: str - def __init__(self, name: _Optional[str] = ..., version: _Optional[str] = ...) -> None: ... - -class TaskIdentifiers(_message.Message): - __slots__ = ("identifiers",) - IDENTIFIERS_FIELD_NUMBER: _ClassVar[int] - identifiers: _containers.RepeatedCompositeFieldContainer[TaskIdentifier] - def __init__(self, identifiers: _Optional[_Iterable[_Union[TaskIdentifier, _Mapping]]] = ...) -> None: ... - -class Tasks(_message.Message): - __slots__ = ("tasks",) - TASKS_FIELD_NUMBER: _ClassVar[int] - tasks: _containers.RepeatedCompositeFieldContainer[Task] - def __init__(self, tasks: _Optional[_Iterable[_Union[Task, _Mapping]]] = ...) -> None: ... - -class SingleTaskSubmission(_message.Message): - __slots__ = ("cluster_slug", "identifier", "display", "dependencies", "max_retries", "input") - CLUSTER_SLUG_FIELD_NUMBER: _ClassVar[int] - IDENTIFIER_FIELD_NUMBER: _ClassVar[int] - DISPLAY_FIELD_NUMBER: _ClassVar[int] - DEPENDENCIES_FIELD_NUMBER: _ClassVar[int] - MAX_RETRIES_FIELD_NUMBER: _ClassVar[int] - INPUT_FIELD_NUMBER: _ClassVar[int] - cluster_slug: str - identifier: TaskIdentifier - display: str - dependencies: _containers.RepeatedScalarFieldContainer[int] - max_retries: int - input: bytes - def __init__(self, cluster_slug: _Optional[str] = ..., identifier: _Optional[_Union[TaskIdentifier, _Mapping]] = ..., display: _Optional[str] = ..., dependencies: _Optional[_Iterable[int]] = ..., max_retries: _Optional[int] = ..., input: _Optional[bytes] = ...) -> None: ... - -class TaskSubmissions(_message.Message): - __slots__ = ("task_groups", "cluster_slug_lookup", "identifier_lookup", "display_lookup") - TASK_GROUPS_FIELD_NUMBER: _ClassVar[int] - CLUSTER_SLUG_LOOKUP_FIELD_NUMBER: _ClassVar[int] - IDENTIFIER_LOOKUP_FIELD_NUMBER: _ClassVar[int] - DISPLAY_LOOKUP_FIELD_NUMBER: _ClassVar[int] - task_groups: _containers.RepeatedCompositeFieldContainer[TaskSubmissionGroup] - cluster_slug_lookup: _containers.RepeatedScalarFieldContainer[str] - identifier_lookup: _containers.RepeatedCompositeFieldContainer[TaskIdentifier] - display_lookup: _containers.RepeatedScalarFieldContainer[str] - def __init__(self, task_groups: _Optional[_Iterable[_Union[TaskSubmissionGroup, _Mapping]]] = ..., cluster_slug_lookup: _Optional[_Iterable[str]] = ..., identifier_lookup: _Optional[_Iterable[_Union[TaskIdentifier, _Mapping]]] = ..., display_lookup: _Optional[_Iterable[str]] = ...) -> None: ... - -class TaskSubmissionGroup(_message.Message): - __slots__ = ("dependencies_on_other_groups", "inputs", "identifier_pointers", "cluster_slug_pointers", "display_pointers", "max_retries_values", "optional_values") - DEPENDENCIES_ON_OTHER_GROUPS_FIELD_NUMBER: _ClassVar[int] - INPUTS_FIELD_NUMBER: _ClassVar[int] - IDENTIFIER_POINTERS_FIELD_NUMBER: _ClassVar[int] - CLUSTER_SLUG_POINTERS_FIELD_NUMBER: _ClassVar[int] - DISPLAY_POINTERS_FIELD_NUMBER: _ClassVar[int] - MAX_RETRIES_VALUES_FIELD_NUMBER: _ClassVar[int] - OPTIONAL_VALUES_FIELD_NUMBER: _ClassVar[int] - dependencies_on_other_groups: _containers.RepeatedScalarFieldContainer[int] - inputs: _containers.RepeatedScalarFieldContainer[bytes] - identifier_pointers: _containers.RepeatedScalarFieldContainer[int] - cluster_slug_pointers: _containers.RepeatedScalarFieldContainer[int] - display_pointers: _containers.RepeatedScalarFieldContainer[int] - max_retries_values: _containers.RepeatedScalarFieldContainer[int] - optional_values: _containers.RepeatedScalarFieldContainer[bool] - def __init__(self, dependencies_on_other_groups: _Optional[_Iterable[int]] = ..., inputs: _Optional[_Iterable[bytes]] = ..., identifier_pointers: _Optional[_Iterable[int]] = ..., cluster_slug_pointers: _Optional[_Iterable[int]] = ..., display_pointers: _Optional[_Iterable[int]] = ..., max_retries_values: _Optional[_Iterable[int]] = ..., optional_values: _Optional[_Iterable[bool]] = ...) -> None: ... - -class TaskLease(_message.Message): - __slots__ = ("lease", "recommended_wait_until_next_extension") - LEASE_FIELD_NUMBER: _ClassVar[int] - RECOMMENDED_WAIT_UNTIL_NEXT_EXTENSION_FIELD_NUMBER: _ClassVar[int] - lease: _duration_pb2.Duration - recommended_wait_until_next_extension: _duration_pb2.Duration - def __init__(self, lease: _Optional[_Union[_duration_pb2.Duration, _Mapping]] = ..., recommended_wait_until_next_extension: _Optional[_Union[_duration_pb2.Duration, _Mapping]] = ...) -> None: ... diff --git a/tilebox-workflows/tilebox/workflows/workflows/v1/core_pb2_grpc.py b/tilebox-workflows/tilebox/workflows/workflows/v1/core_pb2_grpc.py deleted file mode 100644 index 8a93939..0000000 --- a/tilebox-workflows/tilebox/workflows/workflows/v1/core_pb2_grpc.py +++ /dev/null @@ -1,3 +0,0 @@ -# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! -"""Client and server classes corresponding to protobuf-defined services.""" -import grpc diff --git a/tilebox-workflows/tilebox/workflows/workflows/v1/diagram_pb2.py b/tilebox-workflows/tilebox/workflows/workflows/v1/diagram_pb2.py deleted file mode 100644 index c452c78..0000000 --- a/tilebox-workflows/tilebox/workflows/workflows/v1/diagram_pb2.py +++ /dev/null @@ -1,45 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by the protocol buffer compiler. DO NOT EDIT! -# NO CHECKED-IN PROTOBUF GENCODE -# source: workflows/v1/diagram.proto -# Protobuf Python Version: 6.30.0 -"""Generated protocol buffer code.""" -from google.protobuf import descriptor as _descriptor -from google.protobuf import descriptor_pool as _descriptor_pool -from google.protobuf import runtime_version as _runtime_version -from google.protobuf import symbol_database as _symbol_database -from google.protobuf.internal import builder as _builder -_runtime_version.ValidateProtobufRuntimeVersion( - _runtime_version.Domain.PUBLIC, - 6, - 30, - 0, - '', - 'workflows/v1/diagram.proto' -) -# @@protoc_insertion_point(imports) - -_sym_db = _symbol_database.Default() - - - - -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1aworkflows/v1/diagram.proto\x12\x0cworkflows.v1\"t\n\x14RenderDiagramRequest\x12\x18\n\x07\x64iagram\x18\x01 \x01(\tR\x07\x64iagram\x12\x42\n\x0erender_options\x18\x02 \x01(\x0b\x32\x1b.workflows.v1.RenderOptionsR\rrenderOptions\"\x9b\x01\n\rRenderOptions\x12\x16\n\x06layout\x18\x01 \x01(\tR\x06layout\x12 \n\x08theme_id\x18\x02 \x01(\x03\x42\x05\xaa\x01\x02\x08\x01R\x07themeId\x12\x18\n\x07sketchy\x18\x03 \x01(\x08R\x07sketchy\x12\x18\n\x07padding\x18\x04 \x01(\x03R\x07padding\x12\x1c\n\tdirection\x18\x05 \x01(\tR\tdirection\"\x1b\n\x07\x44iagram\x12\x10\n\x03svg\x18\x01 \x01(\x0cR\x03svg2U\n\x0e\x44iagramService\x12\x43\n\x06Render\x12\".workflows.v1.RenderDiagramRequest\x1a\x15.workflows.v1.DiagramBv\n\x10\x63om.workflows.v1B\x0c\x44iagramProtoP\x01\xa2\x02\x03WXX\xaa\x02\x0cWorkflows.V1\xca\x02\x0cWorkflows\\V1\xe2\x02\x18Workflows\\V1\\GPBMetadata\xea\x02\rWorkflows::V1\x92\x03\x02\x08\x02\x62\x08\x65\x64itionsp\xe8\x07') - -_globals = globals() -_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) -_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'workflows.v1.diagram_pb2', _globals) -if not _descriptor._USE_C_DESCRIPTORS: - _globals['DESCRIPTOR']._loaded_options = None - _globals['DESCRIPTOR']._serialized_options = b'\n\020com.workflows.v1B\014DiagramProtoP\001\242\002\003WXX\252\002\014Workflows.V1\312\002\014Workflows\\V1\342\002\030Workflows\\V1\\GPBMetadata\352\002\rWorkflows::V1\222\003\002\010\002' - _globals['_RENDEROPTIONS'].fields_by_name['theme_id']._loaded_options = None - _globals['_RENDEROPTIONS'].fields_by_name['theme_id']._serialized_options = b'\252\001\002\010\001' - _globals['_RENDERDIAGRAMREQUEST']._serialized_start=44 - _globals['_RENDERDIAGRAMREQUEST']._serialized_end=160 - _globals['_RENDEROPTIONS']._serialized_start=163 - _globals['_RENDEROPTIONS']._serialized_end=318 - _globals['_DIAGRAM']._serialized_start=320 - _globals['_DIAGRAM']._serialized_end=347 - _globals['_DIAGRAMSERVICE']._serialized_start=349 - _globals['_DIAGRAMSERVICE']._serialized_end=434 -# @@protoc_insertion_point(module_scope) diff --git a/tilebox-workflows/tilebox/workflows/workflows/v1/diagram_pb2.pyi b/tilebox-workflows/tilebox/workflows/workflows/v1/diagram_pb2.pyi deleted file mode 100644 index 9e27339..0000000 --- a/tilebox-workflows/tilebox/workflows/workflows/v1/diagram_pb2.pyi +++ /dev/null @@ -1,34 +0,0 @@ -from google.protobuf import descriptor as _descriptor -from google.protobuf import message as _message -from collections.abc import Mapping as _Mapping -from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union - -DESCRIPTOR: _descriptor.FileDescriptor - -class RenderDiagramRequest(_message.Message): - __slots__ = ("diagram", "render_options") - DIAGRAM_FIELD_NUMBER: _ClassVar[int] - RENDER_OPTIONS_FIELD_NUMBER: _ClassVar[int] - diagram: str - render_options: RenderOptions - def __init__(self, diagram: _Optional[str] = ..., render_options: _Optional[_Union[RenderOptions, _Mapping]] = ...) -> None: ... - -class RenderOptions(_message.Message): - __slots__ = ("layout", "theme_id", "sketchy", "padding", "direction") - LAYOUT_FIELD_NUMBER: _ClassVar[int] - THEME_ID_FIELD_NUMBER: _ClassVar[int] - SKETCHY_FIELD_NUMBER: _ClassVar[int] - PADDING_FIELD_NUMBER: _ClassVar[int] - DIRECTION_FIELD_NUMBER: _ClassVar[int] - layout: str - theme_id: int - sketchy: bool - padding: int - direction: str - def __init__(self, layout: _Optional[str] = ..., theme_id: _Optional[int] = ..., sketchy: bool = ..., padding: _Optional[int] = ..., direction: _Optional[str] = ...) -> None: ... - -class Diagram(_message.Message): - __slots__ = ("svg",) - SVG_FIELD_NUMBER: _ClassVar[int] - svg: bytes - def __init__(self, svg: _Optional[bytes] = ...) -> None: ... diff --git a/tilebox-workflows/tilebox/workflows/workflows/v1/diagram_pb2_grpc.py b/tilebox-workflows/tilebox/workflows/workflows/v1/diagram_pb2_grpc.py deleted file mode 100644 index ed0577b..0000000 --- a/tilebox-workflows/tilebox/workflows/workflows/v1/diagram_pb2_grpc.py +++ /dev/null @@ -1,80 +0,0 @@ -# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! -"""Client and server classes corresponding to protobuf-defined services.""" -import grpc - -from tilebox.workflows.workflows.v1 import diagram_pb2 as workflows_dot_v1_dot_diagram__pb2 - - -class DiagramServiceStub: - """The diagram service - """ - - def __init__(self, channel): - """Constructor. - - Args: - channel: A grpc.Channel. - """ - self.Render = channel.unary_unary( - '/workflows.v1.DiagramService/Render', - request_serializer=workflows_dot_v1_dot_diagram__pb2.RenderDiagramRequest.SerializeToString, - response_deserializer=workflows_dot_v1_dot_diagram__pb2.Diagram.FromString, - _registered_method=True) - - -class DiagramServiceServicer: - """The diagram service - """ - - def Render(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - -def add_DiagramServiceServicer_to_server(servicer, server): - rpc_method_handlers = { - 'Render': grpc.unary_unary_rpc_method_handler( - servicer.Render, - request_deserializer=workflows_dot_v1_dot_diagram__pb2.RenderDiagramRequest.FromString, - response_serializer=workflows_dot_v1_dot_diagram__pb2.Diagram.SerializeToString, - ), - } - generic_handler = grpc.method_handlers_generic_handler( - 'workflows.v1.DiagramService', rpc_method_handlers) - server.add_generic_rpc_handlers((generic_handler,)) - server.add_registered_method_handlers('workflows.v1.DiagramService', rpc_method_handlers) - - - # This class is part of an EXPERIMENTAL API. -class DiagramService: - """The diagram service - """ - - @staticmethod - def Render(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary( - request, - target, - '/workflows.v1.DiagramService/Render', - workflows_dot_v1_dot_diagram__pb2.RenderDiagramRequest.SerializeToString, - workflows_dot_v1_dot_diagram__pb2.Diagram.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) diff --git a/tilebox-workflows/tilebox/workflows/workflows/v1/job_pb2.py b/tilebox-workflows/tilebox/workflows/workflows/v1/job_pb2.py deleted file mode 100644 index 145a291..0000000 --- a/tilebox-workflows/tilebox/workflows/workflows/v1/job_pb2.py +++ /dev/null @@ -1,111 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by the protocol buffer compiler. DO NOT EDIT! -# NO CHECKED-IN PROTOBUF GENCODE -# source: workflows/v1/job.proto -# Protobuf Python Version: 6.30.0 -"""Generated protocol buffer code.""" -from google.protobuf import descriptor as _descriptor -from google.protobuf import descriptor_pool as _descriptor_pool -from google.protobuf import runtime_version as _runtime_version -from google.protobuf import symbol_database as _symbol_database -from google.protobuf.internal import builder as _builder -_runtime_version.ValidateProtobufRuntimeVersion( - _runtime_version.Domain.PUBLIC, - 6, - 30, - 0, - '', - 'workflows/v1/job.proto' -) -# @@protoc_insertion_point(imports) - -_sym_db = _symbol_database.Default() - - -from google.protobuf import duration_pb2 as google_dot_protobuf_dot_duration__pb2 -from google.protobuf import timestamp_pb2 as google_dot_protobuf_dot_timestamp__pb2 -from tilebox.datasets.tilebox.v1 import id_pb2 as tilebox_dot_v1_dot_id__pb2 -from tilebox.datasets.tilebox.v1 import query_pb2 as tilebox_dot_v1_dot_query__pb2 -from tilebox.workflows.workflows.v1 import core_pb2 as workflows_dot_v1_dot_core__pb2 -from tilebox.workflows.workflows.v1 import diagram_pb2 as workflows_dot_v1_dot_diagram__pb2 - - -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x16workflows/v1/job.proto\x12\x0cworkflows.v1\x1a\x1egoogle/protobuf/duration.proto\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\x13tilebox/v1/id.proto\x1a\x16tilebox/v1/query.proto\x1a\x17workflows/v1/core.proto\x1a\x1aworkflows/v1/diagram.proto\"\x85\x02\n\x10SubmitJobRequest\x12\x33\n\x05tasks\x18\x05 \x01(\x0b\x32\x1d.workflows.v1.TaskSubmissionsR\x05tasks\x12\x19\n\x08job_name\x18\x02 \x01(\tR\x07jobName\x12!\n\x0ctrace_parent\x18\x03 \x01(\tR\x0btraceParent\x12\x33\n\rautomation_id\x18\x04 \x01(\x0b\x32\x0e.tilebox.v1.IDR\x0c\x61utomationId\x12I\n\x0clegacy_tasks\x18\x01 \x03(\x0b\x32\".workflows.v1.SingleTaskSubmissionB\x02\x18\x01R\x0blegacyTasks\"6\n\rGetJobRequest\x12%\n\x06job_id\x18\x01 \x01(\x0b\x32\x0e.tilebox.v1.IDR\x05jobId\"\xa0\x04\n\x0bTaskSummary\x12\x1e\n\x02id\x18\x01 \x01(\x0b\x32\x0e.tilebox.v1.IDR\x02id\x12\x32\n\tparent_id\x18\x02 \x01(\x0b\x32\x0e.tilebox.v1.IDB\x05\xaa\x01\x02\x08\x01R\x08parentId\x12\x18\n\x07\x64isplay\x18\x03 \x01(\tR\x07\x64isplay\x12-\n\x05state\x18\x04 \x01(\x0e\x32\x17.workflows.v1.TaskStateR\x05state\x12=\n\x0csubmitted_at\x18\x05 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\x0bsubmittedAt\x12\x39\n\nstarted_at\x18\x06 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\tstartedAt\x12\x39\n\nstopped_at\x18\x07 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\tstoppedAt\x12\x1b\n\x05input\x18\x08 \x01(\x0c\x42\x05\xaa\x01\x02\x08\x01R\x05input\x12\x1f\n\x0bretry_count\x18\t \x01(\x03R\nretryCount\x12\x1f\n\x0bmax_retries\x18\n \x01(\x03R\nmaxRetries\x12!\n\x0chas_children\x18\x0b \x01(\x08R\x0bhasChildren\x12!\n\x0c\x63luster_slug\x18\x0c \x01(\tR\x0b\x63lusterSlug\x12\x1a\n\x08optional\x18\r \x01(\x08R\x08optional\"/\n\x17JobTaskChildrenPrefetch\x12\x14\n\x05limit\x18\x01 \x01(\x03R\x05limit\"\x87\x02\n\x13ListJobTasksRequest\x12%\n\x06job_id\x18\x01 \x01(\x0b\x32\x0e.tilebox.v1.IDR\x05jobId\x12;\n\x0eparent_task_id\x18\x02 \x01(\x0b\x32\x0e.tilebox.v1.IDB\x05\xaa\x01\x02\x08\x01R\x0cparentTaskId\x12\x31\n\x04page\x18\x03 \x01(\x0b\x32\x16.tilebox.v1.PaginationB\x05\xaa\x01\x02\x08\x01R\x04page\x12Y\n\x11prefetch_children\x18\x04 \x01(\x0b\x32%.workflows.v1.JobTaskChildrenPrefetchB\x05\xaa\x01\x02\x08\x01R\x10prefetchChildren\"\xb7\x01\n\x0bJobTaskPage\x12;\n\x0eparent_task_id\x18\x01 \x01(\x0b\x32\x0e.tilebox.v1.IDB\x05\xaa\x01\x02\x08\x01R\x0cparentTaskId\x12/\n\x05tasks\x18\x02 \x03(\x0b\x32\x19.workflows.v1.TaskSummaryR\x05tasks\x12:\n\tnext_page\x18\x03 \x01(\x0b\x32\x16.tilebox.v1.PaginationB\x05\xaa\x01\x02\x08\x01R\x08nextPage\"\x96\x01\n\x14ListJobTasksResponse\x12-\n\x04page\x18\x01 \x01(\x0b\x32\x19.workflows.v1.JobTaskPageR\x04page\x12O\n\x16prefetched_child_pages\x18\x02 \x03(\x0b\x32\x19.workflows.v1.JobTaskPageR\x14prefetchedChildPages\"8\n\x0fRetryJobRequest\x12%\n\x06job_id\x18\x01 \x01(\x0b\x32\x0e.tilebox.v1.IDR\x05jobId\"F\n\x10RetryJobResponse\x12\x32\n\x15num_tasks_rescheduled\x18\x01 \x01(\x03R\x13numTasksRescheduled\"9\n\x10\x43\x61ncelJobRequest\x12%\n\x06job_id\x18\x01 \x01(\x0b\x32\x0e.tilebox.v1.IDR\x05jobId\"\x13\n\x11\x43\x61ncelJobResponse\"\xe4\x01\n\x13VisualizeJobRequest\x12%\n\x06job_id\x18\x01 \x01(\x0b\x32\x0e.tilebox.v1.IDR\x05jobId\x12\x42\n\x0erender_options\x18\x02 \x01(\x0b\x32\x1b.workflows.v1.RenderOptionsR\rrenderOptions\x12\x38\n\x05theme\x18\x03 \x01(\x0e\x32\".workflows.v1.WorkflowDiagramThemeR\x05theme\x12(\n\x10include_job_name\x18\x04 \x01(\x08R\x0eincludeJobName\"\xe0\x02\n\x0cQueryFilters\x12=\n\rtime_interval\x18\x01 \x01(\x0b\x32\x18.tilebox.v1.TimeIntervalR\x0ctimeInterval\x12\x37\n\x0bid_interval\x18\x02 \x01(\x0b\x32\x16.tilebox.v1.IDIntervalR\nidInterval\x12\x35\n\x0e\x61utomation_ids\x18\x03 \x03(\x0b\x32\x0e.tilebox.v1.IDR\rautomationIds\x12.\n\x06states\x18\x04 \x03(\x0e\x32\x16.workflows.v1.JobStateR\x06states\x12\x12\n\x04name\x18\x05 \x01(\tR\x04name\x12\x38\n\x0btask_states\x18\x06 \x03(\x0e\x32\x17.workflows.v1.TaskStateR\ntaskStates\x12#\n\rcluster_slugs\x18\x07 \x03(\tR\x0c\x63lusterSlugs\"\xbd\x01\n\x10QueryJobsRequest\x12\x34\n\x07\x66ilters\x18\x01 \x01(\x0b\x32\x1a.workflows.v1.QueryFiltersR\x07\x66ilters\x12\x31\n\x04page\x18\x02 \x01(\x0b\x32\x16.tilebox.v1.PaginationB\x05\xaa\x01\x02\x08\x01R\x04page\x12@\n\x0esort_direction\x18\x03 \x01(\x0e\x32\x19.tilebox.v1.SortDirectionR\rsortDirection\"v\n\x11QueryJobsResponse\x12%\n\x04jobs\x18\x01 \x03(\x0b\x32\x11.workflows.v1.JobR\x04jobs\x12:\n\tnext_page\x18\x03 \x01(\x0b\x32\x16.tilebox.v1.PaginationB\x05\xaa\x01\x02\x08\x01R\x08nextPage\"?\n\x16GetJobPrototypeRequest\x12%\n\x06job_id\x18\x01 \x01(\x0b\x32\x0e.tilebox.v1.IDR\x05jobId\"w\n\x17GetJobPrototypeResponse\x12\x41\n\nroot_tasks\x18\x01 \x03(\x0b\x32\".workflows.v1.SingleTaskSubmissionR\trootTasks\x12\x19\n\x08job_name\x18\x02 \x01(\tR\x07jobName\"\xa9\x01\n\x0f\x43loneJobRequest\x12%\n\x06job_id\x18\x01 \x01(\x0b\x32\x0e.tilebox.v1.IDR\x05jobId\x12T\n\x14root_tasks_overrides\x18\x02 \x03(\x0b\x32\".workflows.v1.SingleTaskSubmissionR\x12rootTasksOverrides\x12\x19\n\x08job_name\x18\x03 \x01(\tR\x07jobName\"Y\n\x18GetJobStateCountsRequest\x12=\n\rtime_interval\x18\x01 \x01(\x0b\x32\x18.tilebox.v1.TimeIntervalR\x0ctimeInterval\"z\n\x19GetJobStateCountsResponse\x12>\n\x0cstate_counts\x18\x01 \x03(\x0b\x32\x1b.workflows.v1.JobStateCountR\x0bstateCounts\x12\x1d\n\ntotal_jobs\x18\x02 \x01(\x03R\ttotalJobs\"S\n\rJobStateCount\x12,\n\x05state\x18\x01 \x01(\x0e\x32\x16.workflows.v1.JobStateR\x05state\x12\x14\n\x05\x63ount\x18\x02 \x01(\x04R\x05\x63ount\"\x9b\x01\n\x18GetTaskQueueStatsRequest\x12\x44\n\x11job_time_interval\x18\x01 \x01(\x0b\x32\x18.tilebox.v1.TimeIntervalR\x0fjobTimeInterval\x12\x39\n\x0bjob_max_age\x18\x02 \x01(\x0b\x32\x19.google.protobuf.DurationR\tjobMaxAge\"\xc5\x01\n\x19GetTaskQueueStatsResponse\x12!\n\x0cwaiting_jobs\x18\x01 \x01(\x04R\x0bwaitingJobs\x12!\n\x0crunning_jobs\x18\x02 \x01(\x04R\x0brunningJobs\x12!\n\x0cqueued_tasks\x18\x03 \x01(\x04R\x0bqueuedTasks\x12?\n\x12oldest_waiting_job\x18\x04 \x01(\x0b\x32\x11.workflows.v1.JobR\x10oldestWaitingJob*\xd4\x01\n\x14WorkflowDiagramTheme\x12&\n\"WORKFLOW_DIAGRAM_THEME_UNSPECIFIED\x10\x00\x12 \n\x1cWORKFLOW_DIAGRAM_THEME_LIGHT\x10\x01\x12\x1f\n\x1bWORKFLOW_DIAGRAM_THEME_DARK\x10\x02\x12(\n$WORKFLOW_DIAGRAM_THEME_CONSOLE_LIGHT\x10\x03\x12\'\n#WORKFLOW_DIAGRAM_THEME_CONSOLE_DARK\x10\x04\x32\xf8\x06\n\nJobService\x12>\n\tSubmitJob\x12\x1e.workflows.v1.SubmitJobRequest\x1a\x11.workflows.v1.Job\x12\x38\n\x06GetJob\x12\x1b.workflows.v1.GetJobRequest\x1a\x11.workflows.v1.Job\x12U\n\x0cListJobTasks\x12!.workflows.v1.ListJobTasksRequest\x1a\".workflows.v1.ListJobTasksResponse\x12I\n\x08RetryJob\x12\x1d.workflows.v1.RetryJobRequest\x1a\x1e.workflows.v1.RetryJobResponse\x12L\n\tCancelJob\x12\x1e.workflows.v1.CancelJobRequest\x1a\x1f.workflows.v1.CancelJobResponse\x12H\n\x0cVisualizeJob\x12!.workflows.v1.VisualizeJobRequest\x1a\x15.workflows.v1.Diagram\x12L\n\tQueryJobs\x12\x1e.workflows.v1.QueryJobsRequest\x1a\x1f.workflows.v1.QueryJobsResponse\x12^\n\x0fGetJobPrototype\x12$.workflows.v1.GetJobPrototypeRequest\x1a%.workflows.v1.GetJobPrototypeResponse\x12<\n\x08\x43loneJob\x12\x1d.workflows.v1.CloneJobRequest\x1a\x11.workflows.v1.Job\x12\x64\n\x11GetJobStateCounts\x12&.workflows.v1.GetJobStateCountsRequest\x1a\'.workflows.v1.GetJobStateCountsResponse\x12\x64\n\x11GetTaskQueueStats\x12&.workflows.v1.GetTaskQueueStatsRequest\x1a\'.workflows.v1.GetTaskQueueStatsResponseBr\n\x10\x63om.workflows.v1B\x08JobProtoP\x01\xa2\x02\x03WXX\xaa\x02\x0cWorkflows.V1\xca\x02\x0cWorkflows\\V1\xe2\x02\x18Workflows\\V1\\GPBMetadata\xea\x02\rWorkflows::V1\x92\x03\x02\x08\x02\x62\x08\x65\x64itionsp\xe8\x07') - -_globals = globals() -_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) -_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'workflows.v1.job_pb2', _globals) -if not _descriptor._USE_C_DESCRIPTORS: - _globals['DESCRIPTOR']._loaded_options = None - _globals['DESCRIPTOR']._serialized_options = b'\n\020com.workflows.v1B\010JobProtoP\001\242\002\003WXX\252\002\014Workflows.V1\312\002\014Workflows\\V1\342\002\030Workflows\\V1\\GPBMetadata\352\002\rWorkflows::V1\222\003\002\010\002' - _globals['_SUBMITJOBREQUEST'].fields_by_name['legacy_tasks']._loaded_options = None - _globals['_SUBMITJOBREQUEST'].fields_by_name['legacy_tasks']._serialized_options = b'\030\001' - _globals['_TASKSUMMARY'].fields_by_name['parent_id']._loaded_options = None - _globals['_TASKSUMMARY'].fields_by_name['parent_id']._serialized_options = b'\252\001\002\010\001' - _globals['_TASKSUMMARY'].fields_by_name['input']._loaded_options = None - _globals['_TASKSUMMARY'].fields_by_name['input']._serialized_options = b'\252\001\002\010\001' - _globals['_LISTJOBTASKSREQUEST'].fields_by_name['parent_task_id']._loaded_options = None - _globals['_LISTJOBTASKSREQUEST'].fields_by_name['parent_task_id']._serialized_options = b'\252\001\002\010\001' - _globals['_LISTJOBTASKSREQUEST'].fields_by_name['page']._loaded_options = None - _globals['_LISTJOBTASKSREQUEST'].fields_by_name['page']._serialized_options = b'\252\001\002\010\001' - _globals['_LISTJOBTASKSREQUEST'].fields_by_name['prefetch_children']._loaded_options = None - _globals['_LISTJOBTASKSREQUEST'].fields_by_name['prefetch_children']._serialized_options = b'\252\001\002\010\001' - _globals['_JOBTASKPAGE'].fields_by_name['parent_task_id']._loaded_options = None - _globals['_JOBTASKPAGE'].fields_by_name['parent_task_id']._serialized_options = b'\252\001\002\010\001' - _globals['_JOBTASKPAGE'].fields_by_name['next_page']._loaded_options = None - _globals['_JOBTASKPAGE'].fields_by_name['next_page']._serialized_options = b'\252\001\002\010\001' - _globals['_QUERYJOBSREQUEST'].fields_by_name['page']._loaded_options = None - _globals['_QUERYJOBSREQUEST'].fields_by_name['page']._serialized_options = b'\252\001\002\010\001' - _globals['_QUERYJOBSRESPONSE'].fields_by_name['next_page']._loaded_options = None - _globals['_QUERYJOBSRESPONSE'].fields_by_name['next_page']._serialized_options = b'\252\001\002\010\001' - _globals['_WORKFLOWDIAGRAMTHEME']._serialized_start=3849 - _globals['_WORKFLOWDIAGRAMTHEME']._serialized_end=4061 - _globals['_SUBMITJOBREQUEST']._serialized_start=204 - _globals['_SUBMITJOBREQUEST']._serialized_end=465 - _globals['_GETJOBREQUEST']._serialized_start=467 - _globals['_GETJOBREQUEST']._serialized_end=521 - _globals['_TASKSUMMARY']._serialized_start=524 - _globals['_TASKSUMMARY']._serialized_end=1068 - _globals['_JOBTASKCHILDRENPREFETCH']._serialized_start=1070 - _globals['_JOBTASKCHILDRENPREFETCH']._serialized_end=1117 - _globals['_LISTJOBTASKSREQUEST']._serialized_start=1120 - _globals['_LISTJOBTASKSREQUEST']._serialized_end=1383 - _globals['_JOBTASKPAGE']._serialized_start=1386 - _globals['_JOBTASKPAGE']._serialized_end=1569 - _globals['_LISTJOBTASKSRESPONSE']._serialized_start=1572 - _globals['_LISTJOBTASKSRESPONSE']._serialized_end=1722 - _globals['_RETRYJOBREQUEST']._serialized_start=1724 - _globals['_RETRYJOBREQUEST']._serialized_end=1780 - _globals['_RETRYJOBRESPONSE']._serialized_start=1782 - _globals['_RETRYJOBRESPONSE']._serialized_end=1852 - _globals['_CANCELJOBREQUEST']._serialized_start=1854 - _globals['_CANCELJOBREQUEST']._serialized_end=1911 - _globals['_CANCELJOBRESPONSE']._serialized_start=1913 - _globals['_CANCELJOBRESPONSE']._serialized_end=1932 - _globals['_VISUALIZEJOBREQUEST']._serialized_start=1935 - _globals['_VISUALIZEJOBREQUEST']._serialized_end=2163 - _globals['_QUERYFILTERS']._serialized_start=2166 - _globals['_QUERYFILTERS']._serialized_end=2518 - _globals['_QUERYJOBSREQUEST']._serialized_start=2521 - _globals['_QUERYJOBSREQUEST']._serialized_end=2710 - _globals['_QUERYJOBSRESPONSE']._serialized_start=2712 - _globals['_QUERYJOBSRESPONSE']._serialized_end=2830 - _globals['_GETJOBPROTOTYPEREQUEST']._serialized_start=2832 - _globals['_GETJOBPROTOTYPEREQUEST']._serialized_end=2895 - _globals['_GETJOBPROTOTYPERESPONSE']._serialized_start=2897 - _globals['_GETJOBPROTOTYPERESPONSE']._serialized_end=3016 - _globals['_CLONEJOBREQUEST']._serialized_start=3019 - _globals['_CLONEJOBREQUEST']._serialized_end=3188 - _globals['_GETJOBSTATECOUNTSREQUEST']._serialized_start=3190 - _globals['_GETJOBSTATECOUNTSREQUEST']._serialized_end=3279 - _globals['_GETJOBSTATECOUNTSRESPONSE']._serialized_start=3281 - _globals['_GETJOBSTATECOUNTSRESPONSE']._serialized_end=3403 - _globals['_JOBSTATECOUNT']._serialized_start=3405 - _globals['_JOBSTATECOUNT']._serialized_end=3488 - _globals['_GETTASKQUEUESTATSREQUEST']._serialized_start=3491 - _globals['_GETTASKQUEUESTATSREQUEST']._serialized_end=3646 - _globals['_GETTASKQUEUESTATSRESPONSE']._serialized_start=3649 - _globals['_GETTASKQUEUESTATSRESPONSE']._serialized_end=3846 - _globals['_JOBSERVICE']._serialized_start=4064 - _globals['_JOBSERVICE']._serialized_end=4952 -# @@protoc_insertion_point(module_scope) diff --git a/tilebox-workflows/tilebox/workflows/workflows/v1/job_pb2.pyi b/tilebox-workflows/tilebox/workflows/workflows/v1/job_pb2.pyi deleted file mode 100644 index 506de7a..0000000 --- a/tilebox-workflows/tilebox/workflows/workflows/v1/job_pb2.pyi +++ /dev/null @@ -1,249 +0,0 @@ -from google.protobuf import duration_pb2 as _duration_pb2 -from google.protobuf import timestamp_pb2 as _timestamp_pb2 -from tilebox.datasets.tilebox.v1 import id_pb2 as _id_pb2 -from tilebox.datasets.tilebox.v1 import query_pb2 as _query_pb2 -from tilebox.workflows.workflows.v1 import core_pb2 as _core_pb2 -from tilebox.workflows.workflows.v1 import diagram_pb2 as _diagram_pb2 -from google.protobuf.internal import containers as _containers -from google.protobuf.internal import enum_type_wrapper as _enum_type_wrapper -from google.protobuf import descriptor as _descriptor -from google.protobuf import message as _message -from collections.abc import Iterable as _Iterable, Mapping as _Mapping -from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union - -DESCRIPTOR: _descriptor.FileDescriptor - -class WorkflowDiagramTheme(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): - __slots__ = () - WORKFLOW_DIAGRAM_THEME_UNSPECIFIED: _ClassVar[WorkflowDiagramTheme] - WORKFLOW_DIAGRAM_THEME_LIGHT: _ClassVar[WorkflowDiagramTheme] - WORKFLOW_DIAGRAM_THEME_DARK: _ClassVar[WorkflowDiagramTheme] - WORKFLOW_DIAGRAM_THEME_CONSOLE_LIGHT: _ClassVar[WorkflowDiagramTheme] - WORKFLOW_DIAGRAM_THEME_CONSOLE_DARK: _ClassVar[WorkflowDiagramTheme] -WORKFLOW_DIAGRAM_THEME_UNSPECIFIED: WorkflowDiagramTheme -WORKFLOW_DIAGRAM_THEME_LIGHT: WorkflowDiagramTheme -WORKFLOW_DIAGRAM_THEME_DARK: WorkflowDiagramTheme -WORKFLOW_DIAGRAM_THEME_CONSOLE_LIGHT: WorkflowDiagramTheme -WORKFLOW_DIAGRAM_THEME_CONSOLE_DARK: WorkflowDiagramTheme - -class SubmitJobRequest(_message.Message): - __slots__ = ("tasks", "job_name", "trace_parent", "automation_id", "legacy_tasks") - TASKS_FIELD_NUMBER: _ClassVar[int] - JOB_NAME_FIELD_NUMBER: _ClassVar[int] - TRACE_PARENT_FIELD_NUMBER: _ClassVar[int] - AUTOMATION_ID_FIELD_NUMBER: _ClassVar[int] - LEGACY_TASKS_FIELD_NUMBER: _ClassVar[int] - tasks: _core_pb2.TaskSubmissions - job_name: str - trace_parent: str - automation_id: _id_pb2.ID - legacy_tasks: _containers.RepeatedCompositeFieldContainer[_core_pb2.SingleTaskSubmission] - def __init__(self, tasks: _Optional[_Union[_core_pb2.TaskSubmissions, _Mapping]] = ..., job_name: _Optional[str] = ..., trace_parent: _Optional[str] = ..., automation_id: _Optional[_Union[_id_pb2.ID, _Mapping]] = ..., legacy_tasks: _Optional[_Iterable[_Union[_core_pb2.SingleTaskSubmission, _Mapping]]] = ...) -> None: ... - -class GetJobRequest(_message.Message): - __slots__ = ("job_id",) - JOB_ID_FIELD_NUMBER: _ClassVar[int] - job_id: _id_pb2.ID - def __init__(self, job_id: _Optional[_Union[_id_pb2.ID, _Mapping]] = ...) -> None: ... - -class TaskSummary(_message.Message): - __slots__ = ("id", "parent_id", "display", "state", "submitted_at", "started_at", "stopped_at", "input", "retry_count", "max_retries", "has_children", "cluster_slug", "optional") - ID_FIELD_NUMBER: _ClassVar[int] - PARENT_ID_FIELD_NUMBER: _ClassVar[int] - DISPLAY_FIELD_NUMBER: _ClassVar[int] - STATE_FIELD_NUMBER: _ClassVar[int] - SUBMITTED_AT_FIELD_NUMBER: _ClassVar[int] - STARTED_AT_FIELD_NUMBER: _ClassVar[int] - STOPPED_AT_FIELD_NUMBER: _ClassVar[int] - INPUT_FIELD_NUMBER: _ClassVar[int] - RETRY_COUNT_FIELD_NUMBER: _ClassVar[int] - MAX_RETRIES_FIELD_NUMBER: _ClassVar[int] - HAS_CHILDREN_FIELD_NUMBER: _ClassVar[int] - CLUSTER_SLUG_FIELD_NUMBER: _ClassVar[int] - OPTIONAL_FIELD_NUMBER: _ClassVar[int] - id: _id_pb2.ID - parent_id: _id_pb2.ID - display: str - state: _core_pb2.TaskState - submitted_at: _timestamp_pb2.Timestamp - started_at: _timestamp_pb2.Timestamp - stopped_at: _timestamp_pb2.Timestamp - input: bytes - retry_count: int - max_retries: int - has_children: bool - cluster_slug: str - optional: bool - def __init__(self, id: _Optional[_Union[_id_pb2.ID, _Mapping]] = ..., parent_id: _Optional[_Union[_id_pb2.ID, _Mapping]] = ..., display: _Optional[str] = ..., state: _Optional[_Union[_core_pb2.TaskState, str]] = ..., submitted_at: _Optional[_Union[_timestamp_pb2.Timestamp, _Mapping]] = ..., started_at: _Optional[_Union[_timestamp_pb2.Timestamp, _Mapping]] = ..., stopped_at: _Optional[_Union[_timestamp_pb2.Timestamp, _Mapping]] = ..., input: _Optional[bytes] = ..., retry_count: _Optional[int] = ..., max_retries: _Optional[int] = ..., has_children: bool = ..., cluster_slug: _Optional[str] = ..., optional: bool = ...) -> None: ... - -class JobTaskChildrenPrefetch(_message.Message): - __slots__ = ("limit",) - LIMIT_FIELD_NUMBER: _ClassVar[int] - limit: int - def __init__(self, limit: _Optional[int] = ...) -> None: ... - -class ListJobTasksRequest(_message.Message): - __slots__ = ("job_id", "parent_task_id", "page", "prefetch_children") - JOB_ID_FIELD_NUMBER: _ClassVar[int] - PARENT_TASK_ID_FIELD_NUMBER: _ClassVar[int] - PAGE_FIELD_NUMBER: _ClassVar[int] - PREFETCH_CHILDREN_FIELD_NUMBER: _ClassVar[int] - job_id: _id_pb2.ID - parent_task_id: _id_pb2.ID - page: _query_pb2.Pagination - prefetch_children: JobTaskChildrenPrefetch - def __init__(self, job_id: _Optional[_Union[_id_pb2.ID, _Mapping]] = ..., parent_task_id: _Optional[_Union[_id_pb2.ID, _Mapping]] = ..., page: _Optional[_Union[_query_pb2.Pagination, _Mapping]] = ..., prefetch_children: _Optional[_Union[JobTaskChildrenPrefetch, _Mapping]] = ...) -> None: ... - -class JobTaskPage(_message.Message): - __slots__ = ("parent_task_id", "tasks", "next_page") - PARENT_TASK_ID_FIELD_NUMBER: _ClassVar[int] - TASKS_FIELD_NUMBER: _ClassVar[int] - NEXT_PAGE_FIELD_NUMBER: _ClassVar[int] - parent_task_id: _id_pb2.ID - tasks: _containers.RepeatedCompositeFieldContainer[TaskSummary] - next_page: _query_pb2.Pagination - def __init__(self, parent_task_id: _Optional[_Union[_id_pb2.ID, _Mapping]] = ..., tasks: _Optional[_Iterable[_Union[TaskSummary, _Mapping]]] = ..., next_page: _Optional[_Union[_query_pb2.Pagination, _Mapping]] = ...) -> None: ... - -class ListJobTasksResponse(_message.Message): - __slots__ = ("page", "prefetched_child_pages") - PAGE_FIELD_NUMBER: _ClassVar[int] - PREFETCHED_CHILD_PAGES_FIELD_NUMBER: _ClassVar[int] - page: JobTaskPage - prefetched_child_pages: _containers.RepeatedCompositeFieldContainer[JobTaskPage] - def __init__(self, page: _Optional[_Union[JobTaskPage, _Mapping]] = ..., prefetched_child_pages: _Optional[_Iterable[_Union[JobTaskPage, _Mapping]]] = ...) -> None: ... - -class RetryJobRequest(_message.Message): - __slots__ = ("job_id",) - JOB_ID_FIELD_NUMBER: _ClassVar[int] - job_id: _id_pb2.ID - def __init__(self, job_id: _Optional[_Union[_id_pb2.ID, _Mapping]] = ...) -> None: ... - -class RetryJobResponse(_message.Message): - __slots__ = ("num_tasks_rescheduled",) - NUM_TASKS_RESCHEDULED_FIELD_NUMBER: _ClassVar[int] - num_tasks_rescheduled: int - def __init__(self, num_tasks_rescheduled: _Optional[int] = ...) -> None: ... - -class CancelJobRequest(_message.Message): - __slots__ = ("job_id",) - JOB_ID_FIELD_NUMBER: _ClassVar[int] - job_id: _id_pb2.ID - def __init__(self, job_id: _Optional[_Union[_id_pb2.ID, _Mapping]] = ...) -> None: ... - -class CancelJobResponse(_message.Message): - __slots__ = () - def __init__(self) -> None: ... - -class VisualizeJobRequest(_message.Message): - __slots__ = ("job_id", "render_options", "theme", "include_job_name") - JOB_ID_FIELD_NUMBER: _ClassVar[int] - RENDER_OPTIONS_FIELD_NUMBER: _ClassVar[int] - THEME_FIELD_NUMBER: _ClassVar[int] - INCLUDE_JOB_NAME_FIELD_NUMBER: _ClassVar[int] - job_id: _id_pb2.ID - render_options: _diagram_pb2.RenderOptions - theme: WorkflowDiagramTheme - include_job_name: bool - def __init__(self, job_id: _Optional[_Union[_id_pb2.ID, _Mapping]] = ..., render_options: _Optional[_Union[_diagram_pb2.RenderOptions, _Mapping]] = ..., theme: _Optional[_Union[WorkflowDiagramTheme, str]] = ..., include_job_name: bool = ...) -> None: ... - -class QueryFilters(_message.Message): - __slots__ = ("time_interval", "id_interval", "automation_ids", "states", "name", "task_states", "cluster_slugs") - TIME_INTERVAL_FIELD_NUMBER: _ClassVar[int] - ID_INTERVAL_FIELD_NUMBER: _ClassVar[int] - AUTOMATION_IDS_FIELD_NUMBER: _ClassVar[int] - STATES_FIELD_NUMBER: _ClassVar[int] - NAME_FIELD_NUMBER: _ClassVar[int] - TASK_STATES_FIELD_NUMBER: _ClassVar[int] - CLUSTER_SLUGS_FIELD_NUMBER: _ClassVar[int] - time_interval: _query_pb2.TimeInterval - id_interval: _query_pb2.IDInterval - automation_ids: _containers.RepeatedCompositeFieldContainer[_id_pb2.ID] - states: _containers.RepeatedScalarFieldContainer[_core_pb2.JobState] - name: str - task_states: _containers.RepeatedScalarFieldContainer[_core_pb2.TaskState] - cluster_slugs: _containers.RepeatedScalarFieldContainer[str] - def __init__(self, time_interval: _Optional[_Union[_query_pb2.TimeInterval, _Mapping]] = ..., id_interval: _Optional[_Union[_query_pb2.IDInterval, _Mapping]] = ..., automation_ids: _Optional[_Iterable[_Union[_id_pb2.ID, _Mapping]]] = ..., states: _Optional[_Iterable[_Union[_core_pb2.JobState, str]]] = ..., name: _Optional[str] = ..., task_states: _Optional[_Iterable[_Union[_core_pb2.TaskState, str]]] = ..., cluster_slugs: _Optional[_Iterable[str]] = ...) -> None: ... - -class QueryJobsRequest(_message.Message): - __slots__ = ("filters", "page", "sort_direction") - FILTERS_FIELD_NUMBER: _ClassVar[int] - PAGE_FIELD_NUMBER: _ClassVar[int] - SORT_DIRECTION_FIELD_NUMBER: _ClassVar[int] - filters: QueryFilters - page: _query_pb2.Pagination - sort_direction: _query_pb2.SortDirection - def __init__(self, filters: _Optional[_Union[QueryFilters, _Mapping]] = ..., page: _Optional[_Union[_query_pb2.Pagination, _Mapping]] = ..., sort_direction: _Optional[_Union[_query_pb2.SortDirection, str]] = ...) -> None: ... - -class QueryJobsResponse(_message.Message): - __slots__ = ("jobs", "next_page") - JOBS_FIELD_NUMBER: _ClassVar[int] - NEXT_PAGE_FIELD_NUMBER: _ClassVar[int] - jobs: _containers.RepeatedCompositeFieldContainer[_core_pb2.Job] - next_page: _query_pb2.Pagination - def __init__(self, jobs: _Optional[_Iterable[_Union[_core_pb2.Job, _Mapping]]] = ..., next_page: _Optional[_Union[_query_pb2.Pagination, _Mapping]] = ...) -> None: ... - -class GetJobPrototypeRequest(_message.Message): - __slots__ = ("job_id",) - JOB_ID_FIELD_NUMBER: _ClassVar[int] - job_id: _id_pb2.ID - def __init__(self, job_id: _Optional[_Union[_id_pb2.ID, _Mapping]] = ...) -> None: ... - -class GetJobPrototypeResponse(_message.Message): - __slots__ = ("root_tasks", "job_name") - ROOT_TASKS_FIELD_NUMBER: _ClassVar[int] - JOB_NAME_FIELD_NUMBER: _ClassVar[int] - root_tasks: _containers.RepeatedCompositeFieldContainer[_core_pb2.SingleTaskSubmission] - job_name: str - def __init__(self, root_tasks: _Optional[_Iterable[_Union[_core_pb2.SingleTaskSubmission, _Mapping]]] = ..., job_name: _Optional[str] = ...) -> None: ... - -class CloneJobRequest(_message.Message): - __slots__ = ("job_id", "root_tasks_overrides", "job_name") - JOB_ID_FIELD_NUMBER: _ClassVar[int] - ROOT_TASKS_OVERRIDES_FIELD_NUMBER: _ClassVar[int] - JOB_NAME_FIELD_NUMBER: _ClassVar[int] - job_id: _id_pb2.ID - root_tasks_overrides: _containers.RepeatedCompositeFieldContainer[_core_pb2.SingleTaskSubmission] - job_name: str - def __init__(self, job_id: _Optional[_Union[_id_pb2.ID, _Mapping]] = ..., root_tasks_overrides: _Optional[_Iterable[_Union[_core_pb2.SingleTaskSubmission, _Mapping]]] = ..., job_name: _Optional[str] = ...) -> None: ... - -class GetJobStateCountsRequest(_message.Message): - __slots__ = ("time_interval",) - TIME_INTERVAL_FIELD_NUMBER: _ClassVar[int] - time_interval: _query_pb2.TimeInterval - def __init__(self, time_interval: _Optional[_Union[_query_pb2.TimeInterval, _Mapping]] = ...) -> None: ... - -class GetJobStateCountsResponse(_message.Message): - __slots__ = ("state_counts", "total_jobs") - STATE_COUNTS_FIELD_NUMBER: _ClassVar[int] - TOTAL_JOBS_FIELD_NUMBER: _ClassVar[int] - state_counts: _containers.RepeatedCompositeFieldContainer[JobStateCount] - total_jobs: int - def __init__(self, state_counts: _Optional[_Iterable[_Union[JobStateCount, _Mapping]]] = ..., total_jobs: _Optional[int] = ...) -> None: ... - -class JobStateCount(_message.Message): - __slots__ = ("state", "count") - STATE_FIELD_NUMBER: _ClassVar[int] - COUNT_FIELD_NUMBER: _ClassVar[int] - state: _core_pb2.JobState - count: int - def __init__(self, state: _Optional[_Union[_core_pb2.JobState, str]] = ..., count: _Optional[int] = ...) -> None: ... - -class GetTaskQueueStatsRequest(_message.Message): - __slots__ = ("job_time_interval", "job_max_age") - JOB_TIME_INTERVAL_FIELD_NUMBER: _ClassVar[int] - JOB_MAX_AGE_FIELD_NUMBER: _ClassVar[int] - job_time_interval: _query_pb2.TimeInterval - job_max_age: _duration_pb2.Duration - def __init__(self, job_time_interval: _Optional[_Union[_query_pb2.TimeInterval, _Mapping]] = ..., job_max_age: _Optional[_Union[_duration_pb2.Duration, _Mapping]] = ...) -> None: ... - -class GetTaskQueueStatsResponse(_message.Message): - __slots__ = ("waiting_jobs", "running_jobs", "queued_tasks", "oldest_waiting_job") - WAITING_JOBS_FIELD_NUMBER: _ClassVar[int] - RUNNING_JOBS_FIELD_NUMBER: _ClassVar[int] - QUEUED_TASKS_FIELD_NUMBER: _ClassVar[int] - OLDEST_WAITING_JOB_FIELD_NUMBER: _ClassVar[int] - waiting_jobs: int - running_jobs: int - queued_tasks: int - oldest_waiting_job: _core_pb2.Job - def __init__(self, waiting_jobs: _Optional[int] = ..., running_jobs: _Optional[int] = ..., queued_tasks: _Optional[int] = ..., oldest_waiting_job: _Optional[_Union[_core_pb2.Job, _Mapping]] = ...) -> None: ... diff --git a/tilebox-workflows/tilebox/workflows/workflows/v1/job_pb2_grpc.py b/tilebox-workflows/tilebox/workflows/workflows/v1/job_pb2_grpc.py deleted file mode 100644 index d686241..0000000 --- a/tilebox-workflows/tilebox/workflows/workflows/v1/job_pb2_grpc.py +++ /dev/null @@ -1,512 +0,0 @@ -# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! -"""Client and server classes corresponding to protobuf-defined services.""" -import grpc - -from tilebox.workflows.workflows.v1 import core_pb2 as workflows_dot_v1_dot_core__pb2 -from tilebox.workflows.workflows.v1 import diagram_pb2 as workflows_dot_v1_dot_diagram__pb2 -from tilebox.workflows.workflows.v1 import job_pb2 as workflows_dot_v1_dot_job__pb2 - - -class JobServiceStub: - """A service for interacting with jobs. - """ - - def __init__(self, channel): - """Constructor. - - Args: - channel: A grpc.Channel. - """ - self.SubmitJob = channel.unary_unary( - '/workflows.v1.JobService/SubmitJob', - request_serializer=workflows_dot_v1_dot_job__pb2.SubmitJobRequest.SerializeToString, - response_deserializer=workflows_dot_v1_dot_core__pb2.Job.FromString, - _registered_method=True) - self.GetJob = channel.unary_unary( - '/workflows.v1.JobService/GetJob', - request_serializer=workflows_dot_v1_dot_job__pb2.GetJobRequest.SerializeToString, - response_deserializer=workflows_dot_v1_dot_core__pb2.Job.FromString, - _registered_method=True) - self.ListJobTasks = channel.unary_unary( - '/workflows.v1.JobService/ListJobTasks', - request_serializer=workflows_dot_v1_dot_job__pb2.ListJobTasksRequest.SerializeToString, - response_deserializer=workflows_dot_v1_dot_job__pb2.ListJobTasksResponse.FromString, - _registered_method=True) - self.RetryJob = channel.unary_unary( - '/workflows.v1.JobService/RetryJob', - request_serializer=workflows_dot_v1_dot_job__pb2.RetryJobRequest.SerializeToString, - response_deserializer=workflows_dot_v1_dot_job__pb2.RetryJobResponse.FromString, - _registered_method=True) - self.CancelJob = channel.unary_unary( - '/workflows.v1.JobService/CancelJob', - request_serializer=workflows_dot_v1_dot_job__pb2.CancelJobRequest.SerializeToString, - response_deserializer=workflows_dot_v1_dot_job__pb2.CancelJobResponse.FromString, - _registered_method=True) - self.VisualizeJob = channel.unary_unary( - '/workflows.v1.JobService/VisualizeJob', - request_serializer=workflows_dot_v1_dot_job__pb2.VisualizeJobRequest.SerializeToString, - response_deserializer=workflows_dot_v1_dot_diagram__pb2.Diagram.FromString, - _registered_method=True) - self.QueryJobs = channel.unary_unary( - '/workflows.v1.JobService/QueryJobs', - request_serializer=workflows_dot_v1_dot_job__pb2.QueryJobsRequest.SerializeToString, - response_deserializer=workflows_dot_v1_dot_job__pb2.QueryJobsResponse.FromString, - _registered_method=True) - self.GetJobPrototype = channel.unary_unary( - '/workflows.v1.JobService/GetJobPrototype', - request_serializer=workflows_dot_v1_dot_job__pb2.GetJobPrototypeRequest.SerializeToString, - response_deserializer=workflows_dot_v1_dot_job__pb2.GetJobPrototypeResponse.FromString, - _registered_method=True) - self.CloneJob = channel.unary_unary( - '/workflows.v1.JobService/CloneJob', - request_serializer=workflows_dot_v1_dot_job__pb2.CloneJobRequest.SerializeToString, - response_deserializer=workflows_dot_v1_dot_core__pb2.Job.FromString, - _registered_method=True) - self.GetJobStateCounts = channel.unary_unary( - '/workflows.v1.JobService/GetJobStateCounts', - request_serializer=workflows_dot_v1_dot_job__pb2.GetJobStateCountsRequest.SerializeToString, - response_deserializer=workflows_dot_v1_dot_job__pb2.GetJobStateCountsResponse.FromString, - _registered_method=True) - self.GetTaskQueueStats = channel.unary_unary( - '/workflows.v1.JobService/GetTaskQueueStats', - request_serializer=workflows_dot_v1_dot_job__pb2.GetTaskQueueStatsRequest.SerializeToString, - response_deserializer=workflows_dot_v1_dot_job__pb2.GetTaskQueueStatsResponse.FromString, - _registered_method=True) - - -class JobServiceServicer: - """A service for interacting with jobs. - """ - - def SubmitJob(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def GetJob(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def ListJobTasks(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def RetryJob(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def CancelJob(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def VisualizeJob(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def QueryJobs(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def GetJobPrototype(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def CloneJob(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def GetJobStateCounts(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def GetTaskQueueStats(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - -def add_JobServiceServicer_to_server(servicer, server): - rpc_method_handlers = { - 'SubmitJob': grpc.unary_unary_rpc_method_handler( - servicer.SubmitJob, - request_deserializer=workflows_dot_v1_dot_job__pb2.SubmitJobRequest.FromString, - response_serializer=workflows_dot_v1_dot_core__pb2.Job.SerializeToString, - ), - 'GetJob': grpc.unary_unary_rpc_method_handler( - servicer.GetJob, - request_deserializer=workflows_dot_v1_dot_job__pb2.GetJobRequest.FromString, - response_serializer=workflows_dot_v1_dot_core__pb2.Job.SerializeToString, - ), - 'ListJobTasks': grpc.unary_unary_rpc_method_handler( - servicer.ListJobTasks, - request_deserializer=workflows_dot_v1_dot_job__pb2.ListJobTasksRequest.FromString, - response_serializer=workflows_dot_v1_dot_job__pb2.ListJobTasksResponse.SerializeToString, - ), - 'RetryJob': grpc.unary_unary_rpc_method_handler( - servicer.RetryJob, - request_deserializer=workflows_dot_v1_dot_job__pb2.RetryJobRequest.FromString, - response_serializer=workflows_dot_v1_dot_job__pb2.RetryJobResponse.SerializeToString, - ), - 'CancelJob': grpc.unary_unary_rpc_method_handler( - servicer.CancelJob, - request_deserializer=workflows_dot_v1_dot_job__pb2.CancelJobRequest.FromString, - response_serializer=workflows_dot_v1_dot_job__pb2.CancelJobResponse.SerializeToString, - ), - 'VisualizeJob': grpc.unary_unary_rpc_method_handler( - servicer.VisualizeJob, - request_deserializer=workflows_dot_v1_dot_job__pb2.VisualizeJobRequest.FromString, - response_serializer=workflows_dot_v1_dot_diagram__pb2.Diagram.SerializeToString, - ), - 'QueryJobs': grpc.unary_unary_rpc_method_handler( - servicer.QueryJobs, - request_deserializer=workflows_dot_v1_dot_job__pb2.QueryJobsRequest.FromString, - response_serializer=workflows_dot_v1_dot_job__pb2.QueryJobsResponse.SerializeToString, - ), - 'GetJobPrototype': grpc.unary_unary_rpc_method_handler( - servicer.GetJobPrototype, - request_deserializer=workflows_dot_v1_dot_job__pb2.GetJobPrototypeRequest.FromString, - response_serializer=workflows_dot_v1_dot_job__pb2.GetJobPrototypeResponse.SerializeToString, - ), - 'CloneJob': grpc.unary_unary_rpc_method_handler( - servicer.CloneJob, - request_deserializer=workflows_dot_v1_dot_job__pb2.CloneJobRequest.FromString, - response_serializer=workflows_dot_v1_dot_core__pb2.Job.SerializeToString, - ), - 'GetJobStateCounts': grpc.unary_unary_rpc_method_handler( - servicer.GetJobStateCounts, - request_deserializer=workflows_dot_v1_dot_job__pb2.GetJobStateCountsRequest.FromString, - response_serializer=workflows_dot_v1_dot_job__pb2.GetJobStateCountsResponse.SerializeToString, - ), - 'GetTaskQueueStats': grpc.unary_unary_rpc_method_handler( - servicer.GetTaskQueueStats, - request_deserializer=workflows_dot_v1_dot_job__pb2.GetTaskQueueStatsRequest.FromString, - response_serializer=workflows_dot_v1_dot_job__pb2.GetTaskQueueStatsResponse.SerializeToString, - ), - } - generic_handler = grpc.method_handlers_generic_handler( - 'workflows.v1.JobService', rpc_method_handlers) - server.add_generic_rpc_handlers((generic_handler,)) - server.add_registered_method_handlers('workflows.v1.JobService', rpc_method_handlers) - - - # This class is part of an EXPERIMENTAL API. -class JobService: - """A service for interacting with jobs. - """ - - @staticmethod - def SubmitJob(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary( - request, - target, - '/workflows.v1.JobService/SubmitJob', - workflows_dot_v1_dot_job__pb2.SubmitJobRequest.SerializeToString, - workflows_dot_v1_dot_core__pb2.Job.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) - - @staticmethod - def GetJob(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary( - request, - target, - '/workflows.v1.JobService/GetJob', - workflows_dot_v1_dot_job__pb2.GetJobRequest.SerializeToString, - workflows_dot_v1_dot_core__pb2.Job.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) - - @staticmethod - def ListJobTasks(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary( - request, - target, - '/workflows.v1.JobService/ListJobTasks', - workflows_dot_v1_dot_job__pb2.ListJobTasksRequest.SerializeToString, - workflows_dot_v1_dot_job__pb2.ListJobTasksResponse.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) - - @staticmethod - def RetryJob(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary( - request, - target, - '/workflows.v1.JobService/RetryJob', - workflows_dot_v1_dot_job__pb2.RetryJobRequest.SerializeToString, - workflows_dot_v1_dot_job__pb2.RetryJobResponse.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) - - @staticmethod - def CancelJob(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary( - request, - target, - '/workflows.v1.JobService/CancelJob', - workflows_dot_v1_dot_job__pb2.CancelJobRequest.SerializeToString, - workflows_dot_v1_dot_job__pb2.CancelJobResponse.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) - - @staticmethod - def VisualizeJob(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary( - request, - target, - '/workflows.v1.JobService/VisualizeJob', - workflows_dot_v1_dot_job__pb2.VisualizeJobRequest.SerializeToString, - workflows_dot_v1_dot_diagram__pb2.Diagram.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) - - @staticmethod - def QueryJobs(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary( - request, - target, - '/workflows.v1.JobService/QueryJobs', - workflows_dot_v1_dot_job__pb2.QueryJobsRequest.SerializeToString, - workflows_dot_v1_dot_job__pb2.QueryJobsResponse.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) - - @staticmethod - def GetJobPrototype(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary( - request, - target, - '/workflows.v1.JobService/GetJobPrototype', - workflows_dot_v1_dot_job__pb2.GetJobPrototypeRequest.SerializeToString, - workflows_dot_v1_dot_job__pb2.GetJobPrototypeResponse.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) - - @staticmethod - def CloneJob(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary( - request, - target, - '/workflows.v1.JobService/CloneJob', - workflows_dot_v1_dot_job__pb2.CloneJobRequest.SerializeToString, - workflows_dot_v1_dot_core__pb2.Job.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) - - @staticmethod - def GetJobStateCounts(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary( - request, - target, - '/workflows.v1.JobService/GetJobStateCounts', - workflows_dot_v1_dot_job__pb2.GetJobStateCountsRequest.SerializeToString, - workflows_dot_v1_dot_job__pb2.GetJobStateCountsResponse.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) - - @staticmethod - def GetTaskQueueStats(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary( - request, - target, - '/workflows.v1.JobService/GetTaskQueueStats', - workflows_dot_v1_dot_job__pb2.GetTaskQueueStatsRequest.SerializeToString, - workflows_dot_v1_dot_job__pb2.GetTaskQueueStatsResponse.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) diff --git a/tilebox-workflows/tilebox/workflows/workflows/v1/storage_location_pb2.py b/tilebox-workflows/tilebox/workflows/workflows/v1/storage_location_pb2.py deleted file mode 100644 index 67849d3..0000000 --- a/tilebox-workflows/tilebox/workflows/workflows/v1/storage_location_pb2.py +++ /dev/null @@ -1,116 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by the protocol buffer compiler. DO NOT EDIT! -# NO CHECKED-IN PROTOBUF GENCODE -# source: workflows/v1/storage_location.proto -# Protobuf Python Version: 6.30.0 -"""Generated protocol buffer code.""" -from google.protobuf import descriptor as _descriptor -from google.protobuf import descriptor_pool as _descriptor_pool -from google.protobuf import runtime_version as _runtime_version -from google.protobuf import symbol_database as _symbol_database -from google.protobuf.internal import builder as _builder -_runtime_version.ValidateProtobufRuntimeVersion( - _runtime_version.Domain.PUBLIC, - 6, - 30, - 0, - '', - 'workflows/v1/storage_location.proto' -) -# @@protoc_insertion_point(imports) - -_sym_db = _symbol_database.Default() - - -from google.api import field_behavior_pb2 as google_dot_api_dot_field__behavior__pb2 -from google.protobuf import empty_pb2 as google_dot_protobuf_dot_empty__pb2 -from google.protobuf import timestamp_pb2 as google_dot_protobuf_dot_timestamp__pb2 -from tilebox.datasets.tilebox.v1 import id_pb2 as tilebox_dot_v1_dot_id__pb2 -from tilebox.datasets.tilebox.v1 import query_pb2 as tilebox_dot_v1_dot_query__pb2 - - -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n#workflows/v1/storage_location.proto\x12\x0cworkflows.v1\x1a\x1fgoogle/api/field_behavior.proto\x1a\x1bgoogle/protobuf/empty.proto\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\x13tilebox/v1/id.proto\x1a\x16tilebox/v1/query.proto\"\xde\x01\n\x0fStorageLocation\x12\x1e\n\x02id\x18\x01 \x01(\x0b\x32\x0e.tilebox.v1.IDR\x02id\x12\x1e\n\x08location\x18\x02 \x01(\tB\x02\x18\x01R\x08location\x12\x31\n\x04type\x18\x03 \x01(\x0e\x32\x19.workflows.v1.StorageTypeB\x02\x18\x01R\x04type\x12\x12\n\x04name\x18\x04 \x01(\tR\x04name\x12\x44\n\treference\x18\x05 \x01(\x0b\x32&.workflows.v1.StorageLocationReferenceR\treference\"\xc7\x02\n\x18StorageLocationReference\x12\x46\n\raws_s3_bucket\x18\x01 \x01(\x0b\x32\".workflows.v1.AWSS3BucketReferenceR\x0b\x61wsS3Bucket\x12?\n\ngcs_bucket\x18\x02 \x01(\x0b\x32 .workflows.v1.GCSBucketReferenceR\tgcsBucket\x12?\n\nazure_blob\x18\x03 \x01(\x0b\x32 .workflows.v1.AzureBlobReferenceR\tazureBlob\x12\x32\n\x05local\x18\x04 \x01(\x0b\x32\x1c.workflows.v1.LocalReferenceR\x05local\x12-\n\x04type\x18\x05 \x01(\x0e\x32\x19.workflows.v1.StorageTypeR\x04type\"F\n\x14\x41WSS3BucketReference\x12\x16\n\x06\x62ucket\x18\x01 \x01(\tR\x06\x62ucket\x12\x16\n\x06region\x18\x02 \x01(\tR\x06region\"g\n\x12GCSBucketReference\x12\x16\n\x06\x62ucket\x18\x01 \x01(\tR\x06\x62ucket\x12\x1d\n\nproject_id\x18\x02 \x01(\tR\tprojectId\x12\x1a\n\x08location\x18\x03 \x01(\tR\x08location\"\x89\x01\n\x12\x41zureBlobReference\x12=\n\x1bstorage_account_resource_id\x18\x01 \x01(\tR\x18storageAccountResourceId\x12\x1c\n\tcontainer\x18\x02 \x01(\tR\tcontainer\x12\x16\n\x06region\x18\x03 \x01(\tR\x06region\"$\n\x0eLocalReference\x12\x12\n\x04path\x18\x01 \x01(\tR\x04path\"\x8d\x01\n\x0cTriggeredJob\x12%\n\x06job_id\x18\x01 \x01(\x0b\x32\x0e.tilebox.v1.IDR\x05jobId\x12\x33\n\rautomation_id\x18\x02 \x01(\x0b\x32\x0e.tilebox.v1.IDR\x0c\x61utomationId\x12!\n\x0cglob_pattern\x18\x03 \x01(\tR\x0bglobPattern\"R\n\rTriggeredJobs\x12\x41\n\x0etriggered_jobs\x18\x01 \x03(\x0b\x32\x1a.workflows.v1.TriggeredJobR\rtriggeredJobs\"x\n\x1c\x43reateStorageLocationRequest\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\x12\x44\n\treference\x18\x02 \x01(\x0b\x32&.workflows.v1.StorageLocationReferenceR\treference\"r\n\x1cUpdateStorageLocationRequest\x12>\n\x13storage_location_id\x18\x01 \x01(\x0b\x32\x0e.tilebox.v1.IDR\x11storageLocationId\x12\x12\n\x04name\x18\x02 \x01(\tR\x04name\"_\n\x19\x41WSSNSStorageSubscription\x12\x1b\n\ttopic_arn\x18\x01 \x01(\tR\x08topicArn\x12%\n\x0emessage_format\x18\x02 \x01(\tR\rmessageFormat\"\x9a\x01\n\x1fGooglePubSubStorageSubscription\x12\"\n\x0csubscription\x18\x01 \x01(\tR\x0csubscription\x12\x32\n\x15service_account_email\x18\x02 \x01(\tR\x13serviceAccountEmail\x12\x1f\n\x08\x61udience\x18\x03 \x01(\tB\x03\xe0\x41\x03R\x08\x61udience\"\xbd\x01\n!AzureEventGridStorageSubscription\x12\x37\n\x15webhook_secret_header\x18\x01 \x01(\tB\x03\xe0\x41\x03R\x13webhookSecretHeader\x12*\n\x0ewebhook_secret\x18\x02 \x01(\tB\x03\xe0\x41\x03R\rwebhookSecret\x12\x33\n\x13webhook_secret_hash\x18\x03 \x01(\x0c\x42\x03\xe0\x41\x03R\x11webhookSecretHash\"\x87\x04\n\x13StorageSubscription\x12#\n\x02id\x18\x01 \x01(\x0b\x32\x0e.tilebox.v1.IDB\x03\xe0\x41\x03R\x02id\x12>\n\x13storage_location_id\x18\x02 \x01(\x0b\x32\x0e.tilebox.v1.IDR\x11storageLocationId\x12\x39\n\x04type\x18\x03 \x01(\x0e\x32%.workflows.v1.StorageSubscriptionTypeR\x04type\x12\x1f\n\x08\x65ndpoint\x18\x04 \x01(\tB\x03\xe0\x41\x03R\x08\x65ndpoint\x12>\n\ncreated_at\x18\x06 \x01(\x0b\x32\x1a.google.protobuf.TimestampB\x03\xe0\x41\x03R\tcreatedAt\x12@\n\x07\x61ws_sns\x18\x07 \x01(\x0b\x32\'.workflows.v1.AWSSNSStorageSubscriptionR\x06\x61wsSns\x12R\n\rgoogle_pubsub\x18\x08 \x01(\x0b\x32-.workflows.v1.GooglePubSubStorageSubscriptionR\x0cgooglePubsub\x12Y\n\x10\x61zure_event_grid\x18\t \x01(\x0b\x32/.workflows.v1.AzureEventGridStorageSubscriptionR\x0e\x61zureEventGrid\"\x8e\x03\n CreateStorageSubscriptionRequest\x12>\n\x13storage_location_id\x18\x01 \x01(\x0b\x32\x0e.tilebox.v1.IDR\x11storageLocationId\x12\x39\n\x04type\x18\x02 \x01(\x0e\x32%.workflows.v1.StorageSubscriptionTypeR\x04type\x12@\n\x07\x61ws_sns\x18\x03 \x01(\x0b\x32\'.workflows.v1.AWSSNSStorageSubscriptionR\x06\x61wsSns\x12R\n\rgoogle_pubsub\x18\x04 \x01(\x0b\x32-.workflows.v1.GooglePubSubStorageSubscriptionR\x0cgooglePubsub\x12Y\n\x10\x61zure_event_grid\x18\x05 \x01(\x0b\x32/.workflows.v1.AzureEventGridStorageSubscriptionR\x0e\x61zureEventGrid\"a\n\x1fListStorageSubscriptionsRequest\x12>\n\x13storage_location_id\x18\x01 \x01(\x0b\x32\x0e.tilebox.v1.IDR\x11storageLocationId\"\xc0\x01\n\x1aNotifyObjectCreatedRequest\x12\x46\n\x17storage_subscription_id\x18\x01 \x01(\x0b\x32\x0e.tilebox.v1.IDR\x15storageSubscriptionId\x12\x1f\n\x0bobject_path\x18\x02 \x01(\tR\nobjectPath\x12\x39\n\nevent_time\x18\x03 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\teventTime\"_\n\x14StorageSubscriptions\x12G\n\rsubscriptions\x18\x01 \x03(\x0b\x32!.workflows.v1.StorageSubscriptionR\rsubscriptions\"\x90\x03\n\x18StorageSubscriptionEvent\x12\x1e\n\x02id\x18\x01 \x01(\x0b\x32\x0e.tilebox.v1.IDR\x02id\x12\x1d\n\nobject_key\x18\x02 \x01(\tR\tobjectKey\x12\x32\n\x04type\x18\x03 \x01(\x0e\x32\x1e.workflows.v1.StorageEventTypeR\x04type\x12\x39\n\nevent_time\x18\x04 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\teventTime\x12;\n\x0breceived_at\x18\x05 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\nreceivedAt\x12\x41\n\x0etriggered_jobs\x18\x06 \x03(\x0b\x32\x1a.workflows.v1.TriggeredJobR\rtriggeredJobs\x12\x46\n\x17storage_subscription_id\x18\x07 \x01(\x0b\x32\x0e.tilebox.v1.IDR\x15storageSubscriptionId\"\x99\x01\n$ListStorageSubscriptionEventsRequest\x12>\n\x13storage_location_id\x18\x01 \x01(\x0b\x32\x0e.tilebox.v1.IDR\x11storageLocationId\x12\x31\n\x04page\x18\x02 \x01(\x0b\x32\x16.tilebox.v1.PaginationB\x05\xaa\x01\x02\x08\x01R\x04page\"\x97\x01\n\x19StorageSubscriptionEvents\x12>\n\x06\x65vents\x18\x01 \x03(\x0b\x32&.workflows.v1.StorageSubscriptionEventR\x06\x65vents\x12:\n\tnext_page\x18\x02 \x01(\x0b\x32\x16.tilebox.v1.PaginationB\x05\xaa\x01\x02\x08\x01R\x08nextPage\"O\n\x10StorageLocations\x12;\n\tlocations\x18\x01 \x03(\x0b\x32\x1d.workflows.v1.StorageLocationR\tlocations\"\xa7\x01\n\x15TriggeredStorageEvent\x12>\n\x13storage_location_id\x18\x01 \x01(\x0b\x32\x0e.tilebox.v1.IDR\x11storageLocationId\x12\x32\n\x04type\x18\x02 \x01(\x0e\x32\x1e.workflows.v1.StorageEventTypeR\x04type\x12\x1a\n\x08location\x18\x03 \x01(\tR\x08location*\x8f\x01\n\x0bStorageType\x12\x1c\n\x18STORAGE_TYPE_UNSPECIFIED\x10\x00\x12\x14\n\x10STORAGE_TYPE_GCS\x10\x01\x12\x17\n\x13STORAGE_TYPE_AWS_S3\x10\x02\x12\x16\n\x12STORAGE_TYPE_LOCAL\x10\x03\x12\x1b\n\x17STORAGE_TYPE_AZURE_BLOB\x10\x04*\xf3\x01\n\x17StorageSubscriptionType\x12)\n%STORAGE_SUBSCRIPTION_TYPE_UNSPECIFIED\x10\x00\x12%\n!STORAGE_SUBSCRIPTION_TYPE_AWS_SNS\x10\x01\x12+\n\'STORAGE_SUBSCRIPTION_TYPE_GOOGLE_PUBSUB\x10\x02\x12.\n*STORAGE_SUBSCRIPTION_TYPE_AZURE_EVENT_GRID\x10\x03\x12)\n%STORAGE_SUBSCRIPTION_TYPE_TILEBOX_CLI\x10\x04*V\n\x10StorageEventType\x12\"\n\x1eSTORAGE_EVENT_TYPE_UNSPECIFIED\x10\x00\x12\x1e\n\x1aSTORAGE_EVENT_TYPE_CREATED\x10\x01\x32\xfe\x07\n\x16StorageLocationService\x12N\n\x14ListStorageLocations\x12\x16.google.protobuf.Empty\x1a\x1e.workflows.v1.StorageLocations\x12\x43\n\x12GetStorageLocation\x12\x0e.tilebox.v1.ID\x1a\x1d.workflows.v1.StorageLocation\x12\x62\n\x15\x43reateStorageLocation\x12*.workflows.v1.CreateStorageLocationRequest\x1a\x1d.workflows.v1.StorageLocation\x12\x62\n\x15UpdateStorageLocation\x12*.workflows.v1.UpdateStorageLocationRequest\x1a\x1d.workflows.v1.StorageLocation\x12?\n\x15\x44\x65leteStorageLocation\x12\x0e.tilebox.v1.ID\x1a\x16.google.protobuf.Empty\x12n\n\x19\x43reateStorageSubscription\x12..workflows.v1.CreateStorageSubscriptionRequest\x1a!.workflows.v1.StorageSubscription\x12m\n\x18ListStorageSubscriptions\x12-.workflows.v1.ListStorageSubscriptionsRequest\x1a\".workflows.v1.StorageSubscriptions\x12K\n\x16GetStorageSubscription\x12\x0e.tilebox.v1.ID\x1a!.workflows.v1.StorageSubscription\x12\x43\n\x19\x44\x65leteStorageSubscription\x12\x0e.tilebox.v1.ID\x1a\x16.google.protobuf.Empty\x12|\n\x1dListStorageSubscriptionEvents\x12\x32.workflows.v1.ListStorageSubscriptionEventsRequest\x1a\'.workflows.v1.StorageSubscriptionEvents\x12W\n\x13NotifyObjectCreated\x12(.workflows.v1.NotifyObjectCreatedRequest\x1a\x16.google.protobuf.EmptyB~\n\x10\x63om.workflows.v1B\x14StorageLocationProtoP\x01\xa2\x02\x03WXX\xaa\x02\x0cWorkflows.V1\xca\x02\x0cWorkflows\\V1\xe2\x02\x18Workflows\\V1\\GPBMetadata\xea\x02\rWorkflows::V1\x92\x03\x02\x08\x02\x62\x08\x65\x64itionsp\xe8\x07') - -_globals = globals() -_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) -_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'workflows.v1.storage_location_pb2', _globals) -if not _descriptor._USE_C_DESCRIPTORS: - _globals['DESCRIPTOR']._loaded_options = None - _globals['DESCRIPTOR']._serialized_options = b'\n\020com.workflows.v1B\024StorageLocationProtoP\001\242\002\003WXX\252\002\014Workflows.V1\312\002\014Workflows\\V1\342\002\030Workflows\\V1\\GPBMetadata\352\002\rWorkflows::V1\222\003\002\010\002' - _globals['_STORAGELOCATION'].fields_by_name['location']._loaded_options = None - _globals['_STORAGELOCATION'].fields_by_name['location']._serialized_options = b'\030\001' - _globals['_STORAGELOCATION'].fields_by_name['type']._loaded_options = None - _globals['_STORAGELOCATION'].fields_by_name['type']._serialized_options = b'\030\001' - _globals['_GOOGLEPUBSUBSTORAGESUBSCRIPTION'].fields_by_name['audience']._loaded_options = None - _globals['_GOOGLEPUBSUBSTORAGESUBSCRIPTION'].fields_by_name['audience']._serialized_options = b'\340A\003' - _globals['_AZUREEVENTGRIDSTORAGESUBSCRIPTION'].fields_by_name['webhook_secret_header']._loaded_options = None - _globals['_AZUREEVENTGRIDSTORAGESUBSCRIPTION'].fields_by_name['webhook_secret_header']._serialized_options = b'\340A\003' - _globals['_AZUREEVENTGRIDSTORAGESUBSCRIPTION'].fields_by_name['webhook_secret']._loaded_options = None - _globals['_AZUREEVENTGRIDSTORAGESUBSCRIPTION'].fields_by_name['webhook_secret']._serialized_options = b'\340A\003' - _globals['_AZUREEVENTGRIDSTORAGESUBSCRIPTION'].fields_by_name['webhook_secret_hash']._loaded_options = None - _globals['_AZUREEVENTGRIDSTORAGESUBSCRIPTION'].fields_by_name['webhook_secret_hash']._serialized_options = b'\340A\003' - _globals['_STORAGESUBSCRIPTION'].fields_by_name['id']._loaded_options = None - _globals['_STORAGESUBSCRIPTION'].fields_by_name['id']._serialized_options = b'\340A\003' - _globals['_STORAGESUBSCRIPTION'].fields_by_name['endpoint']._loaded_options = None - _globals['_STORAGESUBSCRIPTION'].fields_by_name['endpoint']._serialized_options = b'\340A\003' - _globals['_STORAGESUBSCRIPTION'].fields_by_name['created_at']._loaded_options = None - _globals['_STORAGESUBSCRIPTION'].fields_by_name['created_at']._serialized_options = b'\340A\003' - _globals['_LISTSTORAGESUBSCRIPTIONEVENTSREQUEST'].fields_by_name['page']._loaded_options = None - _globals['_LISTSTORAGESUBSCRIPTIONEVENTSREQUEST'].fields_by_name['page']._serialized_options = b'\252\001\002\010\001' - _globals['_STORAGESUBSCRIPTIONEVENTS'].fields_by_name['next_page']._loaded_options = None - _globals['_STORAGESUBSCRIPTIONEVENTS'].fields_by_name['next_page']._serialized_options = b'\252\001\002\010\001' - _globals['_STORAGETYPE']._serialized_start=4294 - _globals['_STORAGETYPE']._serialized_end=4437 - _globals['_STORAGESUBSCRIPTIONTYPE']._serialized_start=4440 - _globals['_STORAGESUBSCRIPTIONTYPE']._serialized_end=4683 - _globals['_STORAGEEVENTTYPE']._serialized_start=4685 - _globals['_STORAGEEVENTTYPE']._serialized_end=4771 - _globals['_STORAGELOCATION']._serialized_start=194 - _globals['_STORAGELOCATION']._serialized_end=416 - _globals['_STORAGELOCATIONREFERENCE']._serialized_start=419 - _globals['_STORAGELOCATIONREFERENCE']._serialized_end=746 - _globals['_AWSS3BUCKETREFERENCE']._serialized_start=748 - _globals['_AWSS3BUCKETREFERENCE']._serialized_end=818 - _globals['_GCSBUCKETREFERENCE']._serialized_start=820 - _globals['_GCSBUCKETREFERENCE']._serialized_end=923 - _globals['_AZUREBLOBREFERENCE']._serialized_start=926 - _globals['_AZUREBLOBREFERENCE']._serialized_end=1063 - _globals['_LOCALREFERENCE']._serialized_start=1065 - _globals['_LOCALREFERENCE']._serialized_end=1101 - _globals['_TRIGGEREDJOB']._serialized_start=1104 - _globals['_TRIGGEREDJOB']._serialized_end=1245 - _globals['_TRIGGEREDJOBS']._serialized_start=1247 - _globals['_TRIGGEREDJOBS']._serialized_end=1329 - _globals['_CREATESTORAGELOCATIONREQUEST']._serialized_start=1331 - _globals['_CREATESTORAGELOCATIONREQUEST']._serialized_end=1451 - _globals['_UPDATESTORAGELOCATIONREQUEST']._serialized_start=1453 - _globals['_UPDATESTORAGELOCATIONREQUEST']._serialized_end=1567 - _globals['_AWSSNSSTORAGESUBSCRIPTION']._serialized_start=1569 - _globals['_AWSSNSSTORAGESUBSCRIPTION']._serialized_end=1664 - _globals['_GOOGLEPUBSUBSTORAGESUBSCRIPTION']._serialized_start=1667 - _globals['_GOOGLEPUBSUBSTORAGESUBSCRIPTION']._serialized_end=1821 - _globals['_AZUREEVENTGRIDSTORAGESUBSCRIPTION']._serialized_start=1824 - _globals['_AZUREEVENTGRIDSTORAGESUBSCRIPTION']._serialized_end=2013 - _globals['_STORAGESUBSCRIPTION']._serialized_start=2016 - _globals['_STORAGESUBSCRIPTION']._serialized_end=2535 - _globals['_CREATESTORAGESUBSCRIPTIONREQUEST']._serialized_start=2538 - _globals['_CREATESTORAGESUBSCRIPTIONREQUEST']._serialized_end=2936 - _globals['_LISTSTORAGESUBSCRIPTIONSREQUEST']._serialized_start=2938 - _globals['_LISTSTORAGESUBSCRIPTIONSREQUEST']._serialized_end=3035 - _globals['_NOTIFYOBJECTCREATEDREQUEST']._serialized_start=3038 - _globals['_NOTIFYOBJECTCREATEDREQUEST']._serialized_end=3230 - _globals['_STORAGESUBSCRIPTIONS']._serialized_start=3232 - _globals['_STORAGESUBSCRIPTIONS']._serialized_end=3327 - _globals['_STORAGESUBSCRIPTIONEVENT']._serialized_start=3330 - _globals['_STORAGESUBSCRIPTIONEVENT']._serialized_end=3730 - _globals['_LISTSTORAGESUBSCRIPTIONEVENTSREQUEST']._serialized_start=3733 - _globals['_LISTSTORAGESUBSCRIPTIONEVENTSREQUEST']._serialized_end=3886 - _globals['_STORAGESUBSCRIPTIONEVENTS']._serialized_start=3889 - _globals['_STORAGESUBSCRIPTIONEVENTS']._serialized_end=4040 - _globals['_STORAGELOCATIONS']._serialized_start=4042 - _globals['_STORAGELOCATIONS']._serialized_end=4121 - _globals['_TRIGGEREDSTORAGEEVENT']._serialized_start=4124 - _globals['_TRIGGEREDSTORAGEEVENT']._serialized_end=4291 - _globals['_STORAGELOCATIONSERVICE']._serialized_start=4774 - _globals['_STORAGELOCATIONSERVICE']._serialized_end=5796 -# @@protoc_insertion_point(module_scope) diff --git a/tilebox-workflows/tilebox/workflows/workflows/v1/storage_location_pb2.pyi b/tilebox-workflows/tilebox/workflows/workflows/v1/storage_location_pb2.pyi deleted file mode 100644 index 255ab00..0000000 --- a/tilebox-workflows/tilebox/workflows/workflows/v1/storage_location_pb2.pyi +++ /dev/null @@ -1,274 +0,0 @@ -from google.api import field_behavior_pb2 as _field_behavior_pb2 -from google.protobuf import empty_pb2 as _empty_pb2 -from google.protobuf import timestamp_pb2 as _timestamp_pb2 -from tilebox.datasets.tilebox.v1 import id_pb2 as _id_pb2 -from tilebox.datasets.tilebox.v1 import query_pb2 as _query_pb2 -from google.protobuf.internal import containers as _containers -from google.protobuf.internal import enum_type_wrapper as _enum_type_wrapper -from google.protobuf import descriptor as _descriptor -from google.protobuf import message as _message -from collections.abc import Iterable as _Iterable, Mapping as _Mapping -from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union - -DESCRIPTOR: _descriptor.FileDescriptor - -class StorageType(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): - __slots__ = () - STORAGE_TYPE_UNSPECIFIED: _ClassVar[StorageType] - STORAGE_TYPE_GCS: _ClassVar[StorageType] - STORAGE_TYPE_AWS_S3: _ClassVar[StorageType] - STORAGE_TYPE_LOCAL: _ClassVar[StorageType] - STORAGE_TYPE_AZURE_BLOB: _ClassVar[StorageType] - -class StorageSubscriptionType(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): - __slots__ = () - STORAGE_SUBSCRIPTION_TYPE_UNSPECIFIED: _ClassVar[StorageSubscriptionType] - STORAGE_SUBSCRIPTION_TYPE_AWS_SNS: _ClassVar[StorageSubscriptionType] - STORAGE_SUBSCRIPTION_TYPE_GOOGLE_PUBSUB: _ClassVar[StorageSubscriptionType] - STORAGE_SUBSCRIPTION_TYPE_AZURE_EVENT_GRID: _ClassVar[StorageSubscriptionType] - STORAGE_SUBSCRIPTION_TYPE_TILEBOX_CLI: _ClassVar[StorageSubscriptionType] - -class StorageEventType(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): - __slots__ = () - STORAGE_EVENT_TYPE_UNSPECIFIED: _ClassVar[StorageEventType] - STORAGE_EVENT_TYPE_CREATED: _ClassVar[StorageEventType] -STORAGE_TYPE_UNSPECIFIED: StorageType -STORAGE_TYPE_GCS: StorageType -STORAGE_TYPE_AWS_S3: StorageType -STORAGE_TYPE_LOCAL: StorageType -STORAGE_TYPE_AZURE_BLOB: StorageType -STORAGE_SUBSCRIPTION_TYPE_UNSPECIFIED: StorageSubscriptionType -STORAGE_SUBSCRIPTION_TYPE_AWS_SNS: StorageSubscriptionType -STORAGE_SUBSCRIPTION_TYPE_GOOGLE_PUBSUB: StorageSubscriptionType -STORAGE_SUBSCRIPTION_TYPE_AZURE_EVENT_GRID: StorageSubscriptionType -STORAGE_SUBSCRIPTION_TYPE_TILEBOX_CLI: StorageSubscriptionType -STORAGE_EVENT_TYPE_UNSPECIFIED: StorageEventType -STORAGE_EVENT_TYPE_CREATED: StorageEventType - -class StorageLocation(_message.Message): - __slots__ = ("id", "location", "type", "name", "reference") - ID_FIELD_NUMBER: _ClassVar[int] - LOCATION_FIELD_NUMBER: _ClassVar[int] - TYPE_FIELD_NUMBER: _ClassVar[int] - NAME_FIELD_NUMBER: _ClassVar[int] - REFERENCE_FIELD_NUMBER: _ClassVar[int] - id: _id_pb2.ID - location: str - type: StorageType - name: str - reference: StorageLocationReference - def __init__(self, id: _Optional[_Union[_id_pb2.ID, _Mapping]] = ..., location: _Optional[str] = ..., type: _Optional[_Union[StorageType, str]] = ..., name: _Optional[str] = ..., reference: _Optional[_Union[StorageLocationReference, _Mapping]] = ...) -> None: ... - -class StorageLocationReference(_message.Message): - __slots__ = ("aws_s3_bucket", "gcs_bucket", "azure_blob", "local", "type") - AWS_S3_BUCKET_FIELD_NUMBER: _ClassVar[int] - GCS_BUCKET_FIELD_NUMBER: _ClassVar[int] - AZURE_BLOB_FIELD_NUMBER: _ClassVar[int] - LOCAL_FIELD_NUMBER: _ClassVar[int] - TYPE_FIELD_NUMBER: _ClassVar[int] - aws_s3_bucket: AWSS3BucketReference - gcs_bucket: GCSBucketReference - azure_blob: AzureBlobReference - local: LocalReference - type: StorageType - def __init__(self, aws_s3_bucket: _Optional[_Union[AWSS3BucketReference, _Mapping]] = ..., gcs_bucket: _Optional[_Union[GCSBucketReference, _Mapping]] = ..., azure_blob: _Optional[_Union[AzureBlobReference, _Mapping]] = ..., local: _Optional[_Union[LocalReference, _Mapping]] = ..., type: _Optional[_Union[StorageType, str]] = ...) -> None: ... - -class AWSS3BucketReference(_message.Message): - __slots__ = ("bucket", "region") - BUCKET_FIELD_NUMBER: _ClassVar[int] - REGION_FIELD_NUMBER: _ClassVar[int] - bucket: str - region: str - def __init__(self, bucket: _Optional[str] = ..., region: _Optional[str] = ...) -> None: ... - -class GCSBucketReference(_message.Message): - __slots__ = ("bucket", "project_id", "location") - BUCKET_FIELD_NUMBER: _ClassVar[int] - PROJECT_ID_FIELD_NUMBER: _ClassVar[int] - LOCATION_FIELD_NUMBER: _ClassVar[int] - bucket: str - project_id: str - location: str - def __init__(self, bucket: _Optional[str] = ..., project_id: _Optional[str] = ..., location: _Optional[str] = ...) -> None: ... - -class AzureBlobReference(_message.Message): - __slots__ = ("storage_account_resource_id", "container", "region") - STORAGE_ACCOUNT_RESOURCE_ID_FIELD_NUMBER: _ClassVar[int] - CONTAINER_FIELD_NUMBER: _ClassVar[int] - REGION_FIELD_NUMBER: _ClassVar[int] - storage_account_resource_id: str - container: str - region: str - def __init__(self, storage_account_resource_id: _Optional[str] = ..., container: _Optional[str] = ..., region: _Optional[str] = ...) -> None: ... - -class LocalReference(_message.Message): - __slots__ = ("path",) - PATH_FIELD_NUMBER: _ClassVar[int] - path: str - def __init__(self, path: _Optional[str] = ...) -> None: ... - -class TriggeredJob(_message.Message): - __slots__ = ("job_id", "automation_id", "glob_pattern") - JOB_ID_FIELD_NUMBER: _ClassVar[int] - AUTOMATION_ID_FIELD_NUMBER: _ClassVar[int] - GLOB_PATTERN_FIELD_NUMBER: _ClassVar[int] - job_id: _id_pb2.ID - automation_id: _id_pb2.ID - glob_pattern: str - def __init__(self, job_id: _Optional[_Union[_id_pb2.ID, _Mapping]] = ..., automation_id: _Optional[_Union[_id_pb2.ID, _Mapping]] = ..., glob_pattern: _Optional[str] = ...) -> None: ... - -class TriggeredJobs(_message.Message): - __slots__ = ("triggered_jobs",) - TRIGGERED_JOBS_FIELD_NUMBER: _ClassVar[int] - triggered_jobs: _containers.RepeatedCompositeFieldContainer[TriggeredJob] - def __init__(self, triggered_jobs: _Optional[_Iterable[_Union[TriggeredJob, _Mapping]]] = ...) -> None: ... - -class CreateStorageLocationRequest(_message.Message): - __slots__ = ("name", "reference") - NAME_FIELD_NUMBER: _ClassVar[int] - REFERENCE_FIELD_NUMBER: _ClassVar[int] - name: str - reference: StorageLocationReference - def __init__(self, name: _Optional[str] = ..., reference: _Optional[_Union[StorageLocationReference, _Mapping]] = ...) -> None: ... - -class UpdateStorageLocationRequest(_message.Message): - __slots__ = ("storage_location_id", "name") - STORAGE_LOCATION_ID_FIELD_NUMBER: _ClassVar[int] - NAME_FIELD_NUMBER: _ClassVar[int] - storage_location_id: _id_pb2.ID - name: str - def __init__(self, storage_location_id: _Optional[_Union[_id_pb2.ID, _Mapping]] = ..., name: _Optional[str] = ...) -> None: ... - -class AWSSNSStorageSubscription(_message.Message): - __slots__ = ("topic_arn", "message_format") - TOPIC_ARN_FIELD_NUMBER: _ClassVar[int] - MESSAGE_FORMAT_FIELD_NUMBER: _ClassVar[int] - topic_arn: str - message_format: str - def __init__(self, topic_arn: _Optional[str] = ..., message_format: _Optional[str] = ...) -> None: ... - -class GooglePubSubStorageSubscription(_message.Message): - __slots__ = ("subscription", "service_account_email", "audience") - SUBSCRIPTION_FIELD_NUMBER: _ClassVar[int] - SERVICE_ACCOUNT_EMAIL_FIELD_NUMBER: _ClassVar[int] - AUDIENCE_FIELD_NUMBER: _ClassVar[int] - subscription: str - service_account_email: str - audience: str - def __init__(self, subscription: _Optional[str] = ..., service_account_email: _Optional[str] = ..., audience: _Optional[str] = ...) -> None: ... - -class AzureEventGridStorageSubscription(_message.Message): - __slots__ = ("webhook_secret_header", "webhook_secret", "webhook_secret_hash") - WEBHOOK_SECRET_HEADER_FIELD_NUMBER: _ClassVar[int] - WEBHOOK_SECRET_FIELD_NUMBER: _ClassVar[int] - WEBHOOK_SECRET_HASH_FIELD_NUMBER: _ClassVar[int] - webhook_secret_header: str - webhook_secret: str - webhook_secret_hash: bytes - def __init__(self, webhook_secret_header: _Optional[str] = ..., webhook_secret: _Optional[str] = ..., webhook_secret_hash: _Optional[bytes] = ...) -> None: ... - -class StorageSubscription(_message.Message): - __slots__ = ("id", "storage_location_id", "type", "endpoint", "created_at", "aws_sns", "google_pubsub", "azure_event_grid") - ID_FIELD_NUMBER: _ClassVar[int] - STORAGE_LOCATION_ID_FIELD_NUMBER: _ClassVar[int] - TYPE_FIELD_NUMBER: _ClassVar[int] - ENDPOINT_FIELD_NUMBER: _ClassVar[int] - CREATED_AT_FIELD_NUMBER: _ClassVar[int] - AWS_SNS_FIELD_NUMBER: _ClassVar[int] - GOOGLE_PUBSUB_FIELD_NUMBER: _ClassVar[int] - AZURE_EVENT_GRID_FIELD_NUMBER: _ClassVar[int] - id: _id_pb2.ID - storage_location_id: _id_pb2.ID - type: StorageSubscriptionType - endpoint: str - created_at: _timestamp_pb2.Timestamp - aws_sns: AWSSNSStorageSubscription - google_pubsub: GooglePubSubStorageSubscription - azure_event_grid: AzureEventGridStorageSubscription - def __init__(self, id: _Optional[_Union[_id_pb2.ID, _Mapping]] = ..., storage_location_id: _Optional[_Union[_id_pb2.ID, _Mapping]] = ..., type: _Optional[_Union[StorageSubscriptionType, str]] = ..., endpoint: _Optional[str] = ..., created_at: _Optional[_Union[_timestamp_pb2.Timestamp, _Mapping]] = ..., aws_sns: _Optional[_Union[AWSSNSStorageSubscription, _Mapping]] = ..., google_pubsub: _Optional[_Union[GooglePubSubStorageSubscription, _Mapping]] = ..., azure_event_grid: _Optional[_Union[AzureEventGridStorageSubscription, _Mapping]] = ...) -> None: ... - -class CreateStorageSubscriptionRequest(_message.Message): - __slots__ = ("storage_location_id", "type", "aws_sns", "google_pubsub", "azure_event_grid") - STORAGE_LOCATION_ID_FIELD_NUMBER: _ClassVar[int] - TYPE_FIELD_NUMBER: _ClassVar[int] - AWS_SNS_FIELD_NUMBER: _ClassVar[int] - GOOGLE_PUBSUB_FIELD_NUMBER: _ClassVar[int] - AZURE_EVENT_GRID_FIELD_NUMBER: _ClassVar[int] - storage_location_id: _id_pb2.ID - type: StorageSubscriptionType - aws_sns: AWSSNSStorageSubscription - google_pubsub: GooglePubSubStorageSubscription - azure_event_grid: AzureEventGridStorageSubscription - def __init__(self, storage_location_id: _Optional[_Union[_id_pb2.ID, _Mapping]] = ..., type: _Optional[_Union[StorageSubscriptionType, str]] = ..., aws_sns: _Optional[_Union[AWSSNSStorageSubscription, _Mapping]] = ..., google_pubsub: _Optional[_Union[GooglePubSubStorageSubscription, _Mapping]] = ..., azure_event_grid: _Optional[_Union[AzureEventGridStorageSubscription, _Mapping]] = ...) -> None: ... - -class ListStorageSubscriptionsRequest(_message.Message): - __slots__ = ("storage_location_id",) - STORAGE_LOCATION_ID_FIELD_NUMBER: _ClassVar[int] - storage_location_id: _id_pb2.ID - def __init__(self, storage_location_id: _Optional[_Union[_id_pb2.ID, _Mapping]] = ...) -> None: ... - -class NotifyObjectCreatedRequest(_message.Message): - __slots__ = ("storage_subscription_id", "object_path", "event_time") - STORAGE_SUBSCRIPTION_ID_FIELD_NUMBER: _ClassVar[int] - OBJECT_PATH_FIELD_NUMBER: _ClassVar[int] - EVENT_TIME_FIELD_NUMBER: _ClassVar[int] - storage_subscription_id: _id_pb2.ID - object_path: str - event_time: _timestamp_pb2.Timestamp - def __init__(self, storage_subscription_id: _Optional[_Union[_id_pb2.ID, _Mapping]] = ..., object_path: _Optional[str] = ..., event_time: _Optional[_Union[_timestamp_pb2.Timestamp, _Mapping]] = ...) -> None: ... - -class StorageSubscriptions(_message.Message): - __slots__ = ("subscriptions",) - SUBSCRIPTIONS_FIELD_NUMBER: _ClassVar[int] - subscriptions: _containers.RepeatedCompositeFieldContainer[StorageSubscription] - def __init__(self, subscriptions: _Optional[_Iterable[_Union[StorageSubscription, _Mapping]]] = ...) -> None: ... - -class StorageSubscriptionEvent(_message.Message): - __slots__ = ("id", "object_key", "type", "event_time", "received_at", "triggered_jobs", "storage_subscription_id") - ID_FIELD_NUMBER: _ClassVar[int] - OBJECT_KEY_FIELD_NUMBER: _ClassVar[int] - TYPE_FIELD_NUMBER: _ClassVar[int] - EVENT_TIME_FIELD_NUMBER: _ClassVar[int] - RECEIVED_AT_FIELD_NUMBER: _ClassVar[int] - TRIGGERED_JOBS_FIELD_NUMBER: _ClassVar[int] - STORAGE_SUBSCRIPTION_ID_FIELD_NUMBER: _ClassVar[int] - id: _id_pb2.ID - object_key: str - type: StorageEventType - event_time: _timestamp_pb2.Timestamp - received_at: _timestamp_pb2.Timestamp - triggered_jobs: _containers.RepeatedCompositeFieldContainer[TriggeredJob] - storage_subscription_id: _id_pb2.ID - def __init__(self, id: _Optional[_Union[_id_pb2.ID, _Mapping]] = ..., object_key: _Optional[str] = ..., type: _Optional[_Union[StorageEventType, str]] = ..., event_time: _Optional[_Union[_timestamp_pb2.Timestamp, _Mapping]] = ..., received_at: _Optional[_Union[_timestamp_pb2.Timestamp, _Mapping]] = ..., triggered_jobs: _Optional[_Iterable[_Union[TriggeredJob, _Mapping]]] = ..., storage_subscription_id: _Optional[_Union[_id_pb2.ID, _Mapping]] = ...) -> None: ... - -class ListStorageSubscriptionEventsRequest(_message.Message): - __slots__ = ("storage_location_id", "page") - STORAGE_LOCATION_ID_FIELD_NUMBER: _ClassVar[int] - PAGE_FIELD_NUMBER: _ClassVar[int] - storage_location_id: _id_pb2.ID - page: _query_pb2.Pagination - def __init__(self, storage_location_id: _Optional[_Union[_id_pb2.ID, _Mapping]] = ..., page: _Optional[_Union[_query_pb2.Pagination, _Mapping]] = ...) -> None: ... - -class StorageSubscriptionEvents(_message.Message): - __slots__ = ("events", "next_page") - EVENTS_FIELD_NUMBER: _ClassVar[int] - NEXT_PAGE_FIELD_NUMBER: _ClassVar[int] - events: _containers.RepeatedCompositeFieldContainer[StorageSubscriptionEvent] - next_page: _query_pb2.Pagination - def __init__(self, events: _Optional[_Iterable[_Union[StorageSubscriptionEvent, _Mapping]]] = ..., next_page: _Optional[_Union[_query_pb2.Pagination, _Mapping]] = ...) -> None: ... - -class StorageLocations(_message.Message): - __slots__ = ("locations",) - LOCATIONS_FIELD_NUMBER: _ClassVar[int] - locations: _containers.RepeatedCompositeFieldContainer[StorageLocation] - def __init__(self, locations: _Optional[_Iterable[_Union[StorageLocation, _Mapping]]] = ...) -> None: ... - -class TriggeredStorageEvent(_message.Message): - __slots__ = ("storage_location_id", "type", "location") - STORAGE_LOCATION_ID_FIELD_NUMBER: _ClassVar[int] - TYPE_FIELD_NUMBER: _ClassVar[int] - LOCATION_FIELD_NUMBER: _ClassVar[int] - storage_location_id: _id_pb2.ID - type: StorageEventType - location: str - def __init__(self, storage_location_id: _Optional[_Union[_id_pb2.ID, _Mapping]] = ..., type: _Optional[_Union[StorageEventType, str]] = ..., location: _Optional[str] = ...) -> None: ... diff --git a/tilebox-workflows/tilebox/workflows/workflows/v1/storage_location_pb2_grpc.py b/tilebox-workflows/tilebox/workflows/workflows/v1/storage_location_pb2_grpc.py deleted file mode 100644 index 3964e0d..0000000 --- a/tilebox-workflows/tilebox/workflows/workflows/v1/storage_location_pb2_grpc.py +++ /dev/null @@ -1,524 +0,0 @@ -# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! -"""Client and server classes corresponding to protobuf-defined services.""" -import grpc - -from google.protobuf import empty_pb2 as google_dot_protobuf_dot_empty__pb2 -from tilebox.datasets.tilebox.v1 import id_pb2 as tilebox_dot_v1_dot_id__pb2 -from tilebox.workflows.workflows.v1 import storage_location_pb2 as workflows_dot_v1_dot_storage__location__pb2 - - -class StorageLocationServiceStub: - """StorageLocationService manages storage locations, notification subscriptions, and received events. - """ - - def __init__(self, channel): - """Constructor. - - Args: - channel: A grpc.Channel. - """ - self.ListStorageLocations = channel.unary_unary( - '/workflows.v1.StorageLocationService/ListStorageLocations', - request_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, - response_deserializer=workflows_dot_v1_dot_storage__location__pb2.StorageLocations.FromString, - _registered_method=True) - self.GetStorageLocation = channel.unary_unary( - '/workflows.v1.StorageLocationService/GetStorageLocation', - request_serializer=tilebox_dot_v1_dot_id__pb2.ID.SerializeToString, - response_deserializer=workflows_dot_v1_dot_storage__location__pb2.StorageLocation.FromString, - _registered_method=True) - self.CreateStorageLocation = channel.unary_unary( - '/workflows.v1.StorageLocationService/CreateStorageLocation', - request_serializer=workflows_dot_v1_dot_storage__location__pb2.CreateStorageLocationRequest.SerializeToString, - response_deserializer=workflows_dot_v1_dot_storage__location__pb2.StorageLocation.FromString, - _registered_method=True) - self.UpdateStorageLocation = channel.unary_unary( - '/workflows.v1.StorageLocationService/UpdateStorageLocation', - request_serializer=workflows_dot_v1_dot_storage__location__pb2.UpdateStorageLocationRequest.SerializeToString, - response_deserializer=workflows_dot_v1_dot_storage__location__pb2.StorageLocation.FromString, - _registered_method=True) - self.DeleteStorageLocation = channel.unary_unary( - '/workflows.v1.StorageLocationService/DeleteStorageLocation', - request_serializer=tilebox_dot_v1_dot_id__pb2.ID.SerializeToString, - response_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, - _registered_method=True) - self.CreateStorageSubscription = channel.unary_unary( - '/workflows.v1.StorageLocationService/CreateStorageSubscription', - request_serializer=workflows_dot_v1_dot_storage__location__pb2.CreateStorageSubscriptionRequest.SerializeToString, - response_deserializer=workflows_dot_v1_dot_storage__location__pb2.StorageSubscription.FromString, - _registered_method=True) - self.ListStorageSubscriptions = channel.unary_unary( - '/workflows.v1.StorageLocationService/ListStorageSubscriptions', - request_serializer=workflows_dot_v1_dot_storage__location__pb2.ListStorageSubscriptionsRequest.SerializeToString, - response_deserializer=workflows_dot_v1_dot_storage__location__pb2.StorageSubscriptions.FromString, - _registered_method=True) - self.GetStorageSubscription = channel.unary_unary( - '/workflows.v1.StorageLocationService/GetStorageSubscription', - request_serializer=tilebox_dot_v1_dot_id__pb2.ID.SerializeToString, - response_deserializer=workflows_dot_v1_dot_storage__location__pb2.StorageSubscription.FromString, - _registered_method=True) - self.DeleteStorageSubscription = channel.unary_unary( - '/workflows.v1.StorageLocationService/DeleteStorageSubscription', - request_serializer=tilebox_dot_v1_dot_id__pb2.ID.SerializeToString, - response_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, - _registered_method=True) - self.ListStorageSubscriptionEvents = channel.unary_unary( - '/workflows.v1.StorageLocationService/ListStorageSubscriptionEvents', - request_serializer=workflows_dot_v1_dot_storage__location__pb2.ListStorageSubscriptionEventsRequest.SerializeToString, - response_deserializer=workflows_dot_v1_dot_storage__location__pb2.StorageSubscriptionEvents.FromString, - _registered_method=True) - self.NotifyObjectCreated = channel.unary_unary( - '/workflows.v1.StorageLocationService/NotifyObjectCreated', - request_serializer=workflows_dot_v1_dot_storage__location__pb2.NotifyObjectCreatedRequest.SerializeToString, - response_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, - _registered_method=True) - - -class StorageLocationServiceServicer: - """StorageLocationService manages storage locations, notification subscriptions, and received events. - """ - - def ListStorageLocations(self, request, context): - """ListStorageLocations lists storage locations available for automation triggers. - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def GetStorageLocation(self, request, context): - """GetStorageLocation gets a storage location by its ID. - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def CreateStorageLocation(self, request, context): - """CreateStorageLocation registers an existing bucket, container, or directory for automation triggers. - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def UpdateStorageLocation(self, request, context): - """UpdateStorageLocation changes the display name of a storage location. - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def DeleteStorageLocation(self, request, context): - """DeleteStorageLocation removes the location and its subscriptions from Tilebox without deleting cloud resources. - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def CreateStorageSubscription(self, request, context): - """CreateStorageSubscription registers notification delivery and returns the provider setup values. - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def ListStorageSubscriptions(self, request, context): - """ListStorageSubscriptions lists notification subscriptions for a storage location. - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def GetStorageSubscription(self, request, context): - """GetStorageSubscription gets a subscription by its ID. - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def DeleteStorageSubscription(self, request, context): - """DeleteStorageSubscription stops accepting notifications at its endpoint without deleting cloud resources. - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def ListStorageSubscriptionEvents(self, request, context): - """ListStorageSubscriptionEvents lists received object notifications and the jobs they triggered. - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def NotifyObjectCreated(self, request, context): - """NotifyObjectCreated delivers a storage event of a local storage location. Typically called by the Tilebox CLI - during the `tilebox storage-location watch` command. The subscription must be of type TILEBOX_CLI. - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - -def add_StorageLocationServiceServicer_to_server(servicer, server): - rpc_method_handlers = { - 'ListStorageLocations': grpc.unary_unary_rpc_method_handler( - servicer.ListStorageLocations, - request_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, - response_serializer=workflows_dot_v1_dot_storage__location__pb2.StorageLocations.SerializeToString, - ), - 'GetStorageLocation': grpc.unary_unary_rpc_method_handler( - servicer.GetStorageLocation, - request_deserializer=tilebox_dot_v1_dot_id__pb2.ID.FromString, - response_serializer=workflows_dot_v1_dot_storage__location__pb2.StorageLocation.SerializeToString, - ), - 'CreateStorageLocation': grpc.unary_unary_rpc_method_handler( - servicer.CreateStorageLocation, - request_deserializer=workflows_dot_v1_dot_storage__location__pb2.CreateStorageLocationRequest.FromString, - response_serializer=workflows_dot_v1_dot_storage__location__pb2.StorageLocation.SerializeToString, - ), - 'UpdateStorageLocation': grpc.unary_unary_rpc_method_handler( - servicer.UpdateStorageLocation, - request_deserializer=workflows_dot_v1_dot_storage__location__pb2.UpdateStorageLocationRequest.FromString, - response_serializer=workflows_dot_v1_dot_storage__location__pb2.StorageLocation.SerializeToString, - ), - 'DeleteStorageLocation': grpc.unary_unary_rpc_method_handler( - servicer.DeleteStorageLocation, - request_deserializer=tilebox_dot_v1_dot_id__pb2.ID.FromString, - response_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, - ), - 'CreateStorageSubscription': grpc.unary_unary_rpc_method_handler( - servicer.CreateStorageSubscription, - request_deserializer=workflows_dot_v1_dot_storage__location__pb2.CreateStorageSubscriptionRequest.FromString, - response_serializer=workflows_dot_v1_dot_storage__location__pb2.StorageSubscription.SerializeToString, - ), - 'ListStorageSubscriptions': grpc.unary_unary_rpc_method_handler( - servicer.ListStorageSubscriptions, - request_deserializer=workflows_dot_v1_dot_storage__location__pb2.ListStorageSubscriptionsRequest.FromString, - response_serializer=workflows_dot_v1_dot_storage__location__pb2.StorageSubscriptions.SerializeToString, - ), - 'GetStorageSubscription': grpc.unary_unary_rpc_method_handler( - servicer.GetStorageSubscription, - request_deserializer=tilebox_dot_v1_dot_id__pb2.ID.FromString, - response_serializer=workflows_dot_v1_dot_storage__location__pb2.StorageSubscription.SerializeToString, - ), - 'DeleteStorageSubscription': grpc.unary_unary_rpc_method_handler( - servicer.DeleteStorageSubscription, - request_deserializer=tilebox_dot_v1_dot_id__pb2.ID.FromString, - response_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, - ), - 'ListStorageSubscriptionEvents': grpc.unary_unary_rpc_method_handler( - servicer.ListStorageSubscriptionEvents, - request_deserializer=workflows_dot_v1_dot_storage__location__pb2.ListStorageSubscriptionEventsRequest.FromString, - response_serializer=workflows_dot_v1_dot_storage__location__pb2.StorageSubscriptionEvents.SerializeToString, - ), - 'NotifyObjectCreated': grpc.unary_unary_rpc_method_handler( - servicer.NotifyObjectCreated, - request_deserializer=workflows_dot_v1_dot_storage__location__pb2.NotifyObjectCreatedRequest.FromString, - response_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, - ), - } - generic_handler = grpc.method_handlers_generic_handler( - 'workflows.v1.StorageLocationService', rpc_method_handlers) - server.add_generic_rpc_handlers((generic_handler,)) - server.add_registered_method_handlers('workflows.v1.StorageLocationService', rpc_method_handlers) - - - # This class is part of an EXPERIMENTAL API. -class StorageLocationService: - """StorageLocationService manages storage locations, notification subscriptions, and received events. - """ - - @staticmethod - def ListStorageLocations(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary( - request, - target, - '/workflows.v1.StorageLocationService/ListStorageLocations', - google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, - workflows_dot_v1_dot_storage__location__pb2.StorageLocations.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) - - @staticmethod - def GetStorageLocation(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary( - request, - target, - '/workflows.v1.StorageLocationService/GetStorageLocation', - tilebox_dot_v1_dot_id__pb2.ID.SerializeToString, - workflows_dot_v1_dot_storage__location__pb2.StorageLocation.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) - - @staticmethod - def CreateStorageLocation(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary( - request, - target, - '/workflows.v1.StorageLocationService/CreateStorageLocation', - workflows_dot_v1_dot_storage__location__pb2.CreateStorageLocationRequest.SerializeToString, - workflows_dot_v1_dot_storage__location__pb2.StorageLocation.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) - - @staticmethod - def UpdateStorageLocation(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary( - request, - target, - '/workflows.v1.StorageLocationService/UpdateStorageLocation', - workflows_dot_v1_dot_storage__location__pb2.UpdateStorageLocationRequest.SerializeToString, - workflows_dot_v1_dot_storage__location__pb2.StorageLocation.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) - - @staticmethod - def DeleteStorageLocation(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary( - request, - target, - '/workflows.v1.StorageLocationService/DeleteStorageLocation', - tilebox_dot_v1_dot_id__pb2.ID.SerializeToString, - google_dot_protobuf_dot_empty__pb2.Empty.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) - - @staticmethod - def CreateStorageSubscription(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary( - request, - target, - '/workflows.v1.StorageLocationService/CreateStorageSubscription', - workflows_dot_v1_dot_storage__location__pb2.CreateStorageSubscriptionRequest.SerializeToString, - workflows_dot_v1_dot_storage__location__pb2.StorageSubscription.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) - - @staticmethod - def ListStorageSubscriptions(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary( - request, - target, - '/workflows.v1.StorageLocationService/ListStorageSubscriptions', - workflows_dot_v1_dot_storage__location__pb2.ListStorageSubscriptionsRequest.SerializeToString, - workflows_dot_v1_dot_storage__location__pb2.StorageSubscriptions.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) - - @staticmethod - def GetStorageSubscription(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary( - request, - target, - '/workflows.v1.StorageLocationService/GetStorageSubscription', - tilebox_dot_v1_dot_id__pb2.ID.SerializeToString, - workflows_dot_v1_dot_storage__location__pb2.StorageSubscription.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) - - @staticmethod - def DeleteStorageSubscription(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary( - request, - target, - '/workflows.v1.StorageLocationService/DeleteStorageSubscription', - tilebox_dot_v1_dot_id__pb2.ID.SerializeToString, - google_dot_protobuf_dot_empty__pb2.Empty.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) - - @staticmethod - def ListStorageSubscriptionEvents(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary( - request, - target, - '/workflows.v1.StorageLocationService/ListStorageSubscriptionEvents', - workflows_dot_v1_dot_storage__location__pb2.ListStorageSubscriptionEventsRequest.SerializeToString, - workflows_dot_v1_dot_storage__location__pb2.StorageSubscriptionEvents.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) - - @staticmethod - def NotifyObjectCreated(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary( - request, - target, - '/workflows.v1.StorageLocationService/NotifyObjectCreated', - workflows_dot_v1_dot_storage__location__pb2.NotifyObjectCreatedRequest.SerializeToString, - google_dot_protobuf_dot_empty__pb2.Empty.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) diff --git a/tilebox-workflows/tilebox/workflows/workflows/v1/task_connect.py b/tilebox-workflows/tilebox/workflows/workflows/v1/task_connect.py deleted file mode 100644 index 9aed15d..0000000 --- a/tilebox-workflows/tilebox/workflows/workflows/v1/task_connect.py +++ /dev/null @@ -1,260 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by https://github.com/connectrpc/connect-python. DO NOT EDIT! -# source: workflows/v1/task.proto - -from collections.abc import AsyncGenerator, AsyncIterator, Iterable, Iterator, Mapping -from typing import Protocol - -from connectrpc.client import ConnectClient, ConnectClientSync -from connectrpc.code import Code -from connectrpc.codec import Codec -from connectrpc.compression import Compression -from connectrpc.errors import ConnectError -from connectrpc.interceptor import Interceptor, InterceptorSync -from connectrpc.method import IdempotencyLevel, MethodInfo -from connectrpc.request import Headers, RequestContext -from connectrpc.server import ConnectASGIApplication, ConnectWSGIApplication, Endpoint, EndpointSync -import tilebox.workflows.workflows.v1.core_pb2 as workflows_dot_v1_dot_core__pb2 -import tilebox.workflows.workflows.v1.task_pb2 as workflows_dot_v1_dot_task__pb2 - - -class TaskService(Protocol): - async def next_task(self, request: workflows_dot_v1_dot_task__pb2.NextTaskRequest, ctx: RequestContext) -> workflows_dot_v1_dot_task__pb2.NextTaskResponse: - raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") - - async def task_failed(self, request: workflows_dot_v1_dot_task__pb2.TaskFailedRequest, ctx: RequestContext) -> workflows_dot_v1_dot_task__pb2.TaskStateResponse: - raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") - - async def extend_task_lease(self, request: workflows_dot_v1_dot_task__pb2.TaskLeaseRequest, ctx: RequestContext) -> workflows_dot_v1_dot_core__pb2.TaskLease: - raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") - - -class TaskServiceASGIApplication(ConnectASGIApplication[TaskService]): - def __init__(self, service: TaskService | AsyncGenerator[TaskService], *, interceptors: Iterable[Interceptor]=(), read_max_bytes: int | None = None, compressions: Iterable[Compression] | None = None, codecs: Iterable[Codec] | None = None) -> None: - super().__init__( - service=service, - endpoints=lambda svc: { - "/workflows.v1.TaskService/NextTask": Endpoint.unary( - method=MethodInfo( - name="NextTask", - service_name="workflows.v1.TaskService", - input=workflows_dot_v1_dot_task__pb2.NextTaskRequest, - output=workflows_dot_v1_dot_task__pb2.NextTaskResponse, - idempotency_level=IdempotencyLevel.UNKNOWN, - ), - function=svc.next_task, - ), - "/workflows.v1.TaskService/TaskFailed": Endpoint.unary( - method=MethodInfo( - name="TaskFailed", - service_name="workflows.v1.TaskService", - input=workflows_dot_v1_dot_task__pb2.TaskFailedRequest, - output=workflows_dot_v1_dot_task__pb2.TaskStateResponse, - idempotency_level=IdempotencyLevel.UNKNOWN, - ), - function=svc.task_failed, - ), - "/workflows.v1.TaskService/ExtendTaskLease": Endpoint.unary( - method=MethodInfo( - name="ExtendTaskLease", - service_name="workflows.v1.TaskService", - input=workflows_dot_v1_dot_task__pb2.TaskLeaseRequest, - output=workflows_dot_v1_dot_core__pb2.TaskLease, - idempotency_level=IdempotencyLevel.UNKNOWN, - ), - function=svc.extend_task_lease, - ), - }, - interceptors=interceptors, - read_max_bytes=read_max_bytes, - compressions=compressions, - codecs=codecs, - ) - - @property - def path(self) -> str: - """Returns the URL path to mount the application to when serving multiple applications.""" - return "/workflows.v1.TaskService" - - -class TaskServiceClient(ConnectClient): - async def next_task( - self, - request: workflows_dot_v1_dot_task__pb2.NextTaskRequest, - *, - headers: Headers | Mapping[str, str] | None = None, - timeout_ms: int | None = None, - ) -> workflows_dot_v1_dot_task__pb2.NextTaskResponse: - return await self.execute_unary( - request=request, - method=MethodInfo( - name="NextTask", - service_name="workflows.v1.TaskService", - input=workflows_dot_v1_dot_task__pb2.NextTaskRequest, - output=workflows_dot_v1_dot_task__pb2.NextTaskResponse, - idempotency_level=IdempotencyLevel.UNKNOWN, - ), - headers=headers, - timeout_ms=timeout_ms, - ) - - async def task_failed( - self, - request: workflows_dot_v1_dot_task__pb2.TaskFailedRequest, - *, - headers: Headers | Mapping[str, str] | None = None, - timeout_ms: int | None = None, - ) -> workflows_dot_v1_dot_task__pb2.TaskStateResponse: - return await self.execute_unary( - request=request, - method=MethodInfo( - name="TaskFailed", - service_name="workflows.v1.TaskService", - input=workflows_dot_v1_dot_task__pb2.TaskFailedRequest, - output=workflows_dot_v1_dot_task__pb2.TaskStateResponse, - idempotency_level=IdempotencyLevel.UNKNOWN, - ), - headers=headers, - timeout_ms=timeout_ms, - ) - - async def extend_task_lease( - self, - request: workflows_dot_v1_dot_task__pb2.TaskLeaseRequest, - *, - headers: Headers | Mapping[str, str] | None = None, - timeout_ms: int | None = None, - ) -> workflows_dot_v1_dot_core__pb2.TaskLease: - return await self.execute_unary( - request=request, - method=MethodInfo( - name="ExtendTaskLease", - service_name="workflows.v1.TaskService", - input=workflows_dot_v1_dot_task__pb2.TaskLeaseRequest, - output=workflows_dot_v1_dot_core__pb2.TaskLease, - idempotency_level=IdempotencyLevel.UNKNOWN, - ), - headers=headers, - timeout_ms=timeout_ms, - ) - - - - - -class TaskServiceSync(Protocol): - def next_task(self, request: workflows_dot_v1_dot_task__pb2.NextTaskRequest, ctx: RequestContext) -> workflows_dot_v1_dot_task__pb2.NextTaskResponse: - raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") - def task_failed(self, request: workflows_dot_v1_dot_task__pb2.TaskFailedRequest, ctx: RequestContext) -> workflows_dot_v1_dot_task__pb2.TaskStateResponse: - raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") - def extend_task_lease(self, request: workflows_dot_v1_dot_task__pb2.TaskLeaseRequest, ctx: RequestContext) -> workflows_dot_v1_dot_core__pb2.TaskLease: - raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") - - -class TaskServiceWSGIApplication(ConnectWSGIApplication): - def __init__(self, service: TaskServiceSync, interceptors: Iterable[InterceptorSync]=(), read_max_bytes: int | None = None, compressions: Iterable[Compression] | None = None, codecs: Iterable[Codec] | None = None) -> None: - super().__init__( - endpoints={ - "/workflows.v1.TaskService/NextTask": EndpointSync.unary( - method=MethodInfo( - name="NextTask", - service_name="workflows.v1.TaskService", - input=workflows_dot_v1_dot_task__pb2.NextTaskRequest, - output=workflows_dot_v1_dot_task__pb2.NextTaskResponse, - idempotency_level=IdempotencyLevel.UNKNOWN, - ), - function=service.next_task, - ), - "/workflows.v1.TaskService/TaskFailed": EndpointSync.unary( - method=MethodInfo( - name="TaskFailed", - service_name="workflows.v1.TaskService", - input=workflows_dot_v1_dot_task__pb2.TaskFailedRequest, - output=workflows_dot_v1_dot_task__pb2.TaskStateResponse, - idempotency_level=IdempotencyLevel.UNKNOWN, - ), - function=service.task_failed, - ), - "/workflows.v1.TaskService/ExtendTaskLease": EndpointSync.unary( - method=MethodInfo( - name="ExtendTaskLease", - service_name="workflows.v1.TaskService", - input=workflows_dot_v1_dot_task__pb2.TaskLeaseRequest, - output=workflows_dot_v1_dot_core__pb2.TaskLease, - idempotency_level=IdempotencyLevel.UNKNOWN, - ), - function=service.extend_task_lease, - ), - }, - interceptors=interceptors, - read_max_bytes=read_max_bytes, - compressions=compressions, - codecs=codecs, - ) - - @property - def path(self) -> str: - """Returns the URL path to mount the application to when serving multiple applications.""" - return "/workflows.v1.TaskService" - - -class TaskServiceClientSync(ConnectClientSync): - def next_task( - self, - request: workflows_dot_v1_dot_task__pb2.NextTaskRequest, - *, - headers: Headers | Mapping[str, str] | None = None, - timeout_ms: int | None = None, - ) -> workflows_dot_v1_dot_task__pb2.NextTaskResponse: - return self.execute_unary( - request=request, - method=MethodInfo( - name="NextTask", - service_name="workflows.v1.TaskService", - input=workflows_dot_v1_dot_task__pb2.NextTaskRequest, - output=workflows_dot_v1_dot_task__pb2.NextTaskResponse, - idempotency_level=IdempotencyLevel.UNKNOWN, - ), - headers=headers, - timeout_ms=timeout_ms, - ) - - def task_failed( - self, - request: workflows_dot_v1_dot_task__pb2.TaskFailedRequest, - *, - headers: Headers | Mapping[str, str] | None = None, - timeout_ms: int | None = None, - ) -> workflows_dot_v1_dot_task__pb2.TaskStateResponse: - return self.execute_unary( - request=request, - method=MethodInfo( - name="TaskFailed", - service_name="workflows.v1.TaskService", - input=workflows_dot_v1_dot_task__pb2.TaskFailedRequest, - output=workflows_dot_v1_dot_task__pb2.TaskStateResponse, - idempotency_level=IdempotencyLevel.UNKNOWN, - ), - headers=headers, - timeout_ms=timeout_ms, - ) - - def extend_task_lease( - self, - request: workflows_dot_v1_dot_task__pb2.TaskLeaseRequest, - *, - headers: Headers | Mapping[str, str] | None = None, - timeout_ms: int | None = None, - ) -> workflows_dot_v1_dot_core__pb2.TaskLease: - return self.execute_unary( - request=request, - method=MethodInfo( - name="ExtendTaskLease", - service_name="workflows.v1.TaskService", - input=workflows_dot_v1_dot_task__pb2.TaskLeaseRequest, - output=workflows_dot_v1_dot_core__pb2.TaskLease, - idempotency_level=IdempotencyLevel.UNKNOWN, - ), - headers=headers, - timeout_ms=timeout_ms, - ) diff --git a/tilebox-workflows/tilebox/workflows/workflows/v1/task_pb2.py b/tilebox-workflows/tilebox/workflows/workflows/v1/task_pb2.py deleted file mode 100644 index f9cd007..0000000 --- a/tilebox-workflows/tilebox/workflows/workflows/v1/task_pb2.py +++ /dev/null @@ -1,62 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by the protocol buffer compiler. DO NOT EDIT! -# NO CHECKED-IN PROTOBUF GENCODE -# source: workflows/v1/task.proto -# Protobuf Python Version: 6.30.0 -"""Generated protocol buffer code.""" -from google.protobuf import descriptor as _descriptor -from google.protobuf import descriptor_pool as _descriptor_pool -from google.protobuf import runtime_version as _runtime_version -from google.protobuf import symbol_database as _symbol_database -from google.protobuf.internal import builder as _builder -_runtime_version.ValidateProtobufRuntimeVersion( - _runtime_version.Domain.PUBLIC, - 6, - 30, - 0, - '', - 'workflows/v1/task.proto' -) -# @@protoc_insertion_point(imports) - -_sym_db = _symbol_database.Default() - - -from google.protobuf import duration_pb2 as google_dot_protobuf_dot_duration__pb2 -from tilebox.datasets.tilebox.v1 import id_pb2 as tilebox_dot_v1_dot_id__pb2 -from tilebox.workflows.workflows.v1 import core_pb2 as workflows_dot_v1_dot_core__pb2 - - -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x17workflows/v1/task.proto\x12\x0cworkflows.v1\x1a\x1egoogle/protobuf/duration.proto\x1a\x13tilebox/v1/id.proto\x1a\x17workflows/v1/core.proto\"\xa6\x01\n\x0fNextTaskRequest\x12\x46\n\rcomputed_task\x18\x01 \x01(\x0b\x32\x1a.workflows.v1.ComputedTaskB\x05\xaa\x01\x02\x08\x01R\x0c\x63omputedTask\x12K\n\x10next_task_to_run\x18\x02 \x01(\x0b\x32\x1b.workflows.v1.NextTaskToRunB\x05\xaa\x01\x02\x08\x01R\rnextTaskToRun\"r\n\rNextTaskToRun\x12!\n\x0c\x63luster_slug\x18\x01 \x01(\tR\x0b\x63lusterSlug\x12>\n\x0bidentifiers\x18\x02 \x03(\x0b\x32\x1c.workflows.v1.TaskIdentifierR\x0bidentifiers\"\x99\x02\n\x0c\x43omputedTask\x12\x1e\n\x02id\x18\x01 \x01(\x0b\x32\x0e.tilebox.v1.IDR\x02id\x12\x18\n\x07\x64isplay\x18\x02 \x01(\tR\x07\x64isplay\x12:\n\tsub_tasks\x18\x05 \x01(\x0b\x32\x1d.workflows.v1.TaskSubmissionsR\x08subTasks\x12\x41\n\x10progress_updates\x18\x04 \x03(\x0b\x32\x16.workflows.v1.ProgressR\x0fprogressUpdates\x12P\n\x10legacy_sub_tasks\x18\x03 \x03(\x0b\x32\".workflows.v1.SingleTaskSubmissionB\x02\x18\x01R\x0elegacySubTasks\"g\n\x0eIdlingResponse\x12U\n\x19suggested_idling_duration\x18\x01 \x01(\x0b\x32\x19.google.protobuf.DurationR\x17suggestedIdlingDuration\"y\n\x10NextTaskResponse\x12/\n\tnext_task\x18\x01 \x01(\x0b\x32\x12.workflows.v1.TaskR\x08nextTask\x12\x34\n\x06idling\x18\x02 \x01(\x0b\x32\x1c.workflows.v1.IdlingResponseR\x06idling\"\xc7\x01\n\x11TaskFailedRequest\x12\'\n\x07task_id\x18\x01 \x01(\x0b\x32\x0e.tilebox.v1.IDR\x06taskId\x12\x18\n\x07\x64isplay\x18\x02 \x01(\tR\x07\x64isplay\x12,\n\x12was_workflow_error\x18\x03 \x01(\x08R\x10wasWorkflowError\x12\x41\n\x10progress_updates\x18\x04 \x03(\x0b\x32\x16.workflows.v1.ProgressR\x0fprogressUpdates\"B\n\x11TaskStateResponse\x12-\n\x05state\x18\x01 \x01(\x0e\x32\x17.workflows.v1.TaskStateR\x05state\"\x7f\n\x10TaskLeaseRequest\x12\'\n\x07task_id\x18\x01 \x01(\x0b\x32\x0e.tilebox.v1.IDR\x06taskId\x12\x42\n\x0frequested_lease\x18\x02 \x01(\x0b\x32\x19.google.protobuf.DurationR\x0erequestedLease2\xf4\x01\n\x0bTaskService\x12I\n\x08NextTask\x12\x1d.workflows.v1.NextTaskRequest\x1a\x1e.workflows.v1.NextTaskResponse\x12N\n\nTaskFailed\x12\x1f.workflows.v1.TaskFailedRequest\x1a\x1f.workflows.v1.TaskStateResponse\x12J\n\x0f\x45xtendTaskLease\x12\x1e.workflows.v1.TaskLeaseRequest\x1a\x17.workflows.v1.TaskLeaseBs\n\x10\x63om.workflows.v1B\tTaskProtoP\x01\xa2\x02\x03WXX\xaa\x02\x0cWorkflows.V1\xca\x02\x0cWorkflows\\V1\xe2\x02\x18Workflows\\V1\\GPBMetadata\xea\x02\rWorkflows::V1\x92\x03\x02\x08\x02\x62\x08\x65\x64itionsp\xe8\x07') - -_globals = globals() -_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) -_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'workflows.v1.task_pb2', _globals) -if not _descriptor._USE_C_DESCRIPTORS: - _globals['DESCRIPTOR']._loaded_options = None - _globals['DESCRIPTOR']._serialized_options = b'\n\020com.workflows.v1B\tTaskProtoP\001\242\002\003WXX\252\002\014Workflows.V1\312\002\014Workflows\\V1\342\002\030Workflows\\V1\\GPBMetadata\352\002\rWorkflows::V1\222\003\002\010\002' - _globals['_NEXTTASKREQUEST'].fields_by_name['computed_task']._loaded_options = None - _globals['_NEXTTASKREQUEST'].fields_by_name['computed_task']._serialized_options = b'\252\001\002\010\001' - _globals['_NEXTTASKREQUEST'].fields_by_name['next_task_to_run']._loaded_options = None - _globals['_NEXTTASKREQUEST'].fields_by_name['next_task_to_run']._serialized_options = b'\252\001\002\010\001' - _globals['_COMPUTEDTASK'].fields_by_name['legacy_sub_tasks']._loaded_options = None - _globals['_COMPUTEDTASK'].fields_by_name['legacy_sub_tasks']._serialized_options = b'\030\001' - _globals['_NEXTTASKREQUEST']._serialized_start=120 - _globals['_NEXTTASKREQUEST']._serialized_end=286 - _globals['_NEXTTASKTORUN']._serialized_start=288 - _globals['_NEXTTASKTORUN']._serialized_end=402 - _globals['_COMPUTEDTASK']._serialized_start=405 - _globals['_COMPUTEDTASK']._serialized_end=686 - _globals['_IDLINGRESPONSE']._serialized_start=688 - _globals['_IDLINGRESPONSE']._serialized_end=791 - _globals['_NEXTTASKRESPONSE']._serialized_start=793 - _globals['_NEXTTASKRESPONSE']._serialized_end=914 - _globals['_TASKFAILEDREQUEST']._serialized_start=917 - _globals['_TASKFAILEDREQUEST']._serialized_end=1116 - _globals['_TASKSTATERESPONSE']._serialized_start=1118 - _globals['_TASKSTATERESPONSE']._serialized_end=1184 - _globals['_TASKLEASEREQUEST']._serialized_start=1186 - _globals['_TASKLEASEREQUEST']._serialized_end=1313 - _globals['_TASKSERVICE']._serialized_start=1316 - _globals['_TASKSERVICE']._serialized_end=1560 -# @@protoc_insertion_point(module_scope) diff --git a/tilebox-workflows/tilebox/workflows/workflows/v1/task_pb2.pyi b/tilebox-workflows/tilebox/workflows/workflows/v1/task_pb2.pyi deleted file mode 100644 index e56ad97..0000000 --- a/tilebox-workflows/tilebox/workflows/workflows/v1/task_pb2.pyi +++ /dev/null @@ -1,80 +0,0 @@ -from google.protobuf import duration_pb2 as _duration_pb2 -from tilebox.datasets.tilebox.v1 import id_pb2 as _id_pb2 -from tilebox.workflows.workflows.v1 import core_pb2 as _core_pb2 -from google.protobuf.internal import containers as _containers -from google.protobuf import descriptor as _descriptor -from google.protobuf import message as _message -from collections.abc import Iterable as _Iterable, Mapping as _Mapping -from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union - -DESCRIPTOR: _descriptor.FileDescriptor - -class NextTaskRequest(_message.Message): - __slots__ = ("computed_task", "next_task_to_run") - COMPUTED_TASK_FIELD_NUMBER: _ClassVar[int] - NEXT_TASK_TO_RUN_FIELD_NUMBER: _ClassVar[int] - computed_task: ComputedTask - next_task_to_run: NextTaskToRun - def __init__(self, computed_task: _Optional[_Union[ComputedTask, _Mapping]] = ..., next_task_to_run: _Optional[_Union[NextTaskToRun, _Mapping]] = ...) -> None: ... - -class NextTaskToRun(_message.Message): - __slots__ = ("cluster_slug", "identifiers") - CLUSTER_SLUG_FIELD_NUMBER: _ClassVar[int] - IDENTIFIERS_FIELD_NUMBER: _ClassVar[int] - cluster_slug: str - identifiers: _containers.RepeatedCompositeFieldContainer[_core_pb2.TaskIdentifier] - def __init__(self, cluster_slug: _Optional[str] = ..., identifiers: _Optional[_Iterable[_Union[_core_pb2.TaskIdentifier, _Mapping]]] = ...) -> None: ... - -class ComputedTask(_message.Message): - __slots__ = ("id", "display", "sub_tasks", "progress_updates", "legacy_sub_tasks") - ID_FIELD_NUMBER: _ClassVar[int] - DISPLAY_FIELD_NUMBER: _ClassVar[int] - SUB_TASKS_FIELD_NUMBER: _ClassVar[int] - PROGRESS_UPDATES_FIELD_NUMBER: _ClassVar[int] - LEGACY_SUB_TASKS_FIELD_NUMBER: _ClassVar[int] - id: _id_pb2.ID - display: str - sub_tasks: _core_pb2.TaskSubmissions - progress_updates: _containers.RepeatedCompositeFieldContainer[_core_pb2.Progress] - legacy_sub_tasks: _containers.RepeatedCompositeFieldContainer[_core_pb2.SingleTaskSubmission] - def __init__(self, id: _Optional[_Union[_id_pb2.ID, _Mapping]] = ..., display: _Optional[str] = ..., sub_tasks: _Optional[_Union[_core_pb2.TaskSubmissions, _Mapping]] = ..., progress_updates: _Optional[_Iterable[_Union[_core_pb2.Progress, _Mapping]]] = ..., legacy_sub_tasks: _Optional[_Iterable[_Union[_core_pb2.SingleTaskSubmission, _Mapping]]] = ...) -> None: ... - -class IdlingResponse(_message.Message): - __slots__ = ("suggested_idling_duration",) - SUGGESTED_IDLING_DURATION_FIELD_NUMBER: _ClassVar[int] - suggested_idling_duration: _duration_pb2.Duration - def __init__(self, suggested_idling_duration: _Optional[_Union[_duration_pb2.Duration, _Mapping]] = ...) -> None: ... - -class NextTaskResponse(_message.Message): - __slots__ = ("next_task", "idling") - NEXT_TASK_FIELD_NUMBER: _ClassVar[int] - IDLING_FIELD_NUMBER: _ClassVar[int] - next_task: _core_pb2.Task - idling: IdlingResponse - def __init__(self, next_task: _Optional[_Union[_core_pb2.Task, _Mapping]] = ..., idling: _Optional[_Union[IdlingResponse, _Mapping]] = ...) -> None: ... - -class TaskFailedRequest(_message.Message): - __slots__ = ("task_id", "display", "was_workflow_error", "progress_updates") - TASK_ID_FIELD_NUMBER: _ClassVar[int] - DISPLAY_FIELD_NUMBER: _ClassVar[int] - WAS_WORKFLOW_ERROR_FIELD_NUMBER: _ClassVar[int] - PROGRESS_UPDATES_FIELD_NUMBER: _ClassVar[int] - task_id: _id_pb2.ID - display: str - was_workflow_error: bool - progress_updates: _containers.RepeatedCompositeFieldContainer[_core_pb2.Progress] - def __init__(self, task_id: _Optional[_Union[_id_pb2.ID, _Mapping]] = ..., display: _Optional[str] = ..., was_workflow_error: bool = ..., progress_updates: _Optional[_Iterable[_Union[_core_pb2.Progress, _Mapping]]] = ...) -> None: ... - -class TaskStateResponse(_message.Message): - __slots__ = ("state",) - STATE_FIELD_NUMBER: _ClassVar[int] - state: _core_pb2.TaskState - def __init__(self, state: _Optional[_Union[_core_pb2.TaskState, str]] = ...) -> None: ... - -class TaskLeaseRequest(_message.Message): - __slots__ = ("task_id", "requested_lease") - TASK_ID_FIELD_NUMBER: _ClassVar[int] - REQUESTED_LEASE_FIELD_NUMBER: _ClassVar[int] - task_id: _id_pb2.ID - requested_lease: _duration_pb2.Duration - def __init__(self, task_id: _Optional[_Union[_id_pb2.ID, _Mapping]] = ..., requested_lease: _Optional[_Union[_duration_pb2.Duration, _Mapping]] = ...) -> None: ... diff --git a/tilebox-workflows/tilebox/workflows/workflows/v1/task_pb2_grpc.py b/tilebox-workflows/tilebox/workflows/workflows/v1/task_pb2_grpc.py deleted file mode 100644 index 6646265..0000000 --- a/tilebox-workflows/tilebox/workflows/workflows/v1/task_pb2_grpc.py +++ /dev/null @@ -1,181 +0,0 @@ -# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! -"""Client and server classes corresponding to protobuf-defined services.""" -import grpc - -from tilebox.workflows.workflows.v1 import core_pb2 as workflows_dot_v1_dot_core__pb2 -from tilebox.workflows.workflows.v1 import task_pb2 as workflows_dot_v1_dot_task__pb2 - - -class TaskServiceStub: - """A service for task runners to communicate with the workflows service. - """ - - def __init__(self, channel): - """Constructor. - - Args: - channel: A grpc.Channel. - """ - self.NextTask = channel.unary_unary( - '/workflows.v1.TaskService/NextTask', - request_serializer=workflows_dot_v1_dot_task__pb2.NextTaskRequest.SerializeToString, - response_deserializer=workflows_dot_v1_dot_task__pb2.NextTaskResponse.FromString, - _registered_method=True) - self.TaskFailed = channel.unary_unary( - '/workflows.v1.TaskService/TaskFailed', - request_serializer=workflows_dot_v1_dot_task__pb2.TaskFailedRequest.SerializeToString, - response_deserializer=workflows_dot_v1_dot_task__pb2.TaskStateResponse.FromString, - _registered_method=True) - self.ExtendTaskLease = channel.unary_unary( - '/workflows.v1.TaskService/ExtendTaskLease', - request_serializer=workflows_dot_v1_dot_task__pb2.TaskLeaseRequest.SerializeToString, - response_deserializer=workflows_dot_v1_dot_core__pb2.TaskLease.FromString, - _registered_method=True) - - -class TaskServiceServicer: - """A service for task runners to communicate with the workflows service. - """ - - def NextTask(self, request, context): - """NextTask marks a task as computed and asks for the next task to run. - If no task marked as computed is sent, it is assumed that the task runner just started up or was idling so - the task server will send a task to run using a work-stealing algorithm. - If a task marked as computed is sent, the task server will send a next task to run using a depth first execution - algorithm, and only fall back to work-stealing if otherwise no tasks are available. - If the next_task_to_run field of the request is not set, a next task will never be returned, but a task - can still be marked as computed this way. - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def TaskFailed(self, request, context): - """TaskFailed tells the task server that we have failed to compute a task. - The task server will then mark the task as queued or failed, depending on the retry policy, - and possibly cancel the job. - If a task runner wants to continue executing tasks, it should afterwards fetch a new one using GetTaskToRun. - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def ExtendTaskLease(self, request, context): - """ExtendTaskLease is called by the task runner to extend the lease on a task. - On success, the response will contain the new lease expiration time. - If the task does not need to be extended, the response will be empty. - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - -def add_TaskServiceServicer_to_server(servicer, server): - rpc_method_handlers = { - 'NextTask': grpc.unary_unary_rpc_method_handler( - servicer.NextTask, - request_deserializer=workflows_dot_v1_dot_task__pb2.NextTaskRequest.FromString, - response_serializer=workflows_dot_v1_dot_task__pb2.NextTaskResponse.SerializeToString, - ), - 'TaskFailed': grpc.unary_unary_rpc_method_handler( - servicer.TaskFailed, - request_deserializer=workflows_dot_v1_dot_task__pb2.TaskFailedRequest.FromString, - response_serializer=workflows_dot_v1_dot_task__pb2.TaskStateResponse.SerializeToString, - ), - 'ExtendTaskLease': grpc.unary_unary_rpc_method_handler( - servicer.ExtendTaskLease, - request_deserializer=workflows_dot_v1_dot_task__pb2.TaskLeaseRequest.FromString, - response_serializer=workflows_dot_v1_dot_core__pb2.TaskLease.SerializeToString, - ), - } - generic_handler = grpc.method_handlers_generic_handler( - 'workflows.v1.TaskService', rpc_method_handlers) - server.add_generic_rpc_handlers((generic_handler,)) - server.add_registered_method_handlers('workflows.v1.TaskService', rpc_method_handlers) - - - # This class is part of an EXPERIMENTAL API. -class TaskService: - """A service for task runners to communicate with the workflows service. - """ - - @staticmethod - def NextTask(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary( - request, - target, - '/workflows.v1.TaskService/NextTask', - workflows_dot_v1_dot_task__pb2.NextTaskRequest.SerializeToString, - workflows_dot_v1_dot_task__pb2.NextTaskResponse.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) - - @staticmethod - def TaskFailed(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary( - request, - target, - '/workflows.v1.TaskService/TaskFailed', - workflows_dot_v1_dot_task__pb2.TaskFailedRequest.SerializeToString, - workflows_dot_v1_dot_task__pb2.TaskStateResponse.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) - - @staticmethod - def ExtendTaskLease(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary( - request, - target, - '/workflows.v1.TaskService/ExtendTaskLease', - workflows_dot_v1_dot_task__pb2.TaskLeaseRequest.SerializeToString, - workflows_dot_v1_dot_core__pb2.TaskLease.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) diff --git a/tilebox-workflows/tilebox/workflows/workflows/v1/task_release_pb2.py b/tilebox-workflows/tilebox/workflows/workflows/v1/task_release_pb2.py deleted file mode 100644 index 4b6457a..0000000 --- a/tilebox-workflows/tilebox/workflows/workflows/v1/task_release_pb2.py +++ /dev/null @@ -1,60 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by the protocol buffer compiler. DO NOT EDIT! -# NO CHECKED-IN PROTOBUF GENCODE -# source: workflows/v1/task_release.proto -# Protobuf Python Version: 6.30.0 -"""Generated protocol buffer code.""" -from google.protobuf import descriptor as _descriptor -from google.protobuf import descriptor_pool as _descriptor_pool -from google.protobuf import runtime_version as _runtime_version -from google.protobuf import symbol_database as _symbol_database -from google.protobuf.internal import builder as _builder -_runtime_version.ValidateProtobufRuntimeVersion( - _runtime_version.Domain.PUBLIC, - 6, - 30, - 0, - '', - 'workflows/v1/task_release.proto' -) -# @@protoc_insertion_point(imports) - -_sym_db = _symbol_database.Default() - - -from google.protobuf import timestamp_pb2 as google_dot_protobuf_dot_timestamp__pb2 -from tilebox.datasets.tilebox.v1 import id_pb2 as tilebox_dot_v1_dot_id__pb2 -from tilebox.workflows.workflows.v1 import core_pb2 as workflows_dot_v1_dot_core__pb2 - - -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1fworkflows/v1/task_release.proto\x12\x0cworkflows.v1\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\x13tilebox/v1/id.proto\x1a\x17workflows/v1/core.proto\"\x95\x02\n\x15PublishReleaseRequest\x12\x32\n\x05tasks\x18\x01 \x03(\x0b\x32\x1c.workflows.v1.TaskIdentifierR\x05tasks\x12!\n\x0c\x61rtifact_uri\x18\x02 \x01(\tR\x0b\x61rtifactUri\x12\'\n\x0f\x61rtifact_digest\x18\x03 \x01(\tR\x0e\x61rtifactDigest\x12!\n\x0cruntime_kind\x18\x04 \x01(\tR\x0bruntimeKind\x12-\n\x12\x65nvironment_digest\x18\x05 \x01(\tR\x11\x65nvironmentDigest\x12*\n\rmetadata_json\x18\x06 \x01(\x0c\x42\x05\xaa\x01\x02\x08\x01R\x0cmetadataJson\"\xb2\x01\n\x16PublishReleaseResponse\x12-\n\nrelease_id\x18\x01 \x01(\x0b\x32\x0e.tilebox.v1.IDR\treleaseId\x12\x18\n\x07\x63reated\x18\x02 \x01(\x08R\x07\x63reated\x12=\n\x07\x63hanges\x18\x04 \x01(\x0b\x32#.workflows.v1.PublishReleaseChangesR\x07\x63hangesJ\x04\x08\x03\x10\x04R\nidempotent\"k\n\x15PublishReleaseChanges\x12\x14\n\x05tasks\x18\x01 \x01(\x08R\x05tasks\x12\x1a\n\x08\x61rtifact\x18\x02 \x01(\x08R\x08\x61rtifact\x12 \n\x0b\x65nvironment\x18\x03 \x01(\x08R\x0b\x65nvironment\"h\n\x14\x44\x65ployReleaseRequest\x12!\n\x0c\x63luster_slug\x18\x01 \x01(\tR\x0b\x63lusterSlug\x12-\n\nrelease_id\x18\x02 \x01(\x0b\x32\x0e.tilebox.v1.IDR\treleaseId\"\xb0\x01\n\x15\x44\x65ployReleaseResponse\x12!\n\x0c\x63luster_slug\x18\x01 \x01(\tR\x0b\x63lusterSlug\x12-\n\nrelease_id\x18\x02 \x01(\x0b\x32\x0e.tilebox.v1.IDR\treleaseId\x12+\n\x11manifest_revision\x18\x03 \x01(\x04R\x10manifestRevision\x12\x18\n\x07\x63hanged\x18\x04 \x01(\x08R\x07\x63hanged\"o\n#GetClusterDeploymentManifestRequest\x12!\n\x0c\x63luster_slug\x18\x01 \x01(\tR\x0b\x63lusterSlug\x12%\n\x0esince_revision\x18\x02 \x01(\x04R\rsinceRevision\"\xa4\x03\n\x0f\x44\x65ployedRelease\x12-\n\nrelease_id\x18\x01 \x01(\x0b\x32\x0e.tilebox.v1.IDR\treleaseId\x12!\n\x0c\x61rtifact_uri\x18\x02 \x01(\tR\x0b\x61rtifactUri\x12\'\n\x0f\x61rtifact_digest\x18\x03 \x01(\tR\x0e\x61rtifactDigest\x12!\n\x0cruntime_kind\x18\x04 \x01(\tR\x0bruntimeKind\x12-\n\x12\x65nvironment_digest\x18\x05 \x01(\tR\x11\x65nvironmentDigest\x12*\n\rmetadata_json\x18\x06 \x01(\x0c\x42\x05\xaa\x01\x02\x08\x01R\x0cmetadataJson\x12\'\n\x0f\x63hange_revision\x18\x07 \x01(\x04R\x0e\x63hangeRevision\x12\x32\n\x05tasks\x18\x08 \x03(\x0b\x32\x1c.workflows.v1.TaskIdentifierR\x05tasks\x12;\n\x0b\x64\x65ployed_at\x18\t \x01(\x0b\x32\x1a.google.protobuf.TimestampR\ndeployedAt\"\xaf\x01\n$GetClusterDeploymentManifestResponse\x12!\n\x0c\x63luster_slug\x18\x01 \x01(\tR\x0b\x63lusterSlug\x12+\n\x11manifest_revision\x18\x02 \x01(\x04R\x10manifestRevision\x12\x37\n\x07release\x18\x03 \x01(\x0b\x32\x1d.workflows.v1.DeployedReleaseR\x07release2\xd3\x02\n\x12TaskReleaseService\x12[\n\x0ePublishRelease\x12#.workflows.v1.PublishReleaseRequest\x1a$.workflows.v1.PublishReleaseResponse\x12X\n\rDeployRelease\x12\".workflows.v1.DeployReleaseRequest\x1a#.workflows.v1.DeployReleaseResponse\x12\x85\x01\n\x1cGetClusterDeploymentManifest\x12\x31.workflows.v1.GetClusterDeploymentManifestRequest\x1a\x32.workflows.v1.GetClusterDeploymentManifestResponseBz\n\x10\x63om.workflows.v1B\x10TaskReleaseProtoP\x01\xa2\x02\x03WXX\xaa\x02\x0cWorkflows.V1\xca\x02\x0cWorkflows\\V1\xe2\x02\x18Workflows\\V1\\GPBMetadata\xea\x02\rWorkflows::V1\x92\x03\x02\x08\x02\x62\x08\x65\x64itionsp\xe8\x07') - -_globals = globals() -_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) -_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'workflows.v1.task_release_pb2', _globals) -if not _descriptor._USE_C_DESCRIPTORS: - _globals['DESCRIPTOR']._loaded_options = None - _globals['DESCRIPTOR']._serialized_options = b'\n\020com.workflows.v1B\020TaskReleaseProtoP\001\242\002\003WXX\252\002\014Workflows.V1\312\002\014Workflows\\V1\342\002\030Workflows\\V1\\GPBMetadata\352\002\rWorkflows::V1\222\003\002\010\002' - _globals['_PUBLISHRELEASEREQUEST'].fields_by_name['metadata_json']._loaded_options = None - _globals['_PUBLISHRELEASEREQUEST'].fields_by_name['metadata_json']._serialized_options = b'\252\001\002\010\001' - _globals['_DEPLOYEDRELEASE'].fields_by_name['metadata_json']._loaded_options = None - _globals['_DEPLOYEDRELEASE'].fields_by_name['metadata_json']._serialized_options = b'\252\001\002\010\001' - _globals['_PUBLISHRELEASEREQUEST']._serialized_start=129 - _globals['_PUBLISHRELEASEREQUEST']._serialized_end=406 - _globals['_PUBLISHRELEASERESPONSE']._serialized_start=409 - _globals['_PUBLISHRELEASERESPONSE']._serialized_end=587 - _globals['_PUBLISHRELEASECHANGES']._serialized_start=589 - _globals['_PUBLISHRELEASECHANGES']._serialized_end=696 - _globals['_DEPLOYRELEASEREQUEST']._serialized_start=698 - _globals['_DEPLOYRELEASEREQUEST']._serialized_end=802 - _globals['_DEPLOYRELEASERESPONSE']._serialized_start=805 - _globals['_DEPLOYRELEASERESPONSE']._serialized_end=981 - _globals['_GETCLUSTERDEPLOYMENTMANIFESTREQUEST']._serialized_start=983 - _globals['_GETCLUSTERDEPLOYMENTMANIFESTREQUEST']._serialized_end=1094 - _globals['_DEPLOYEDRELEASE']._serialized_start=1097 - _globals['_DEPLOYEDRELEASE']._serialized_end=1517 - _globals['_GETCLUSTERDEPLOYMENTMANIFESTRESPONSE']._serialized_start=1520 - _globals['_GETCLUSTERDEPLOYMENTMANIFESTRESPONSE']._serialized_end=1695 - _globals['_TASKRELEASESERVICE']._serialized_start=1698 - _globals['_TASKRELEASESERVICE']._serialized_end=2037 -# @@protoc_insertion_point(module_scope) diff --git a/tilebox-workflows/tilebox/workflows/workflows/v1/task_release_pb2.pyi b/tilebox-workflows/tilebox/workflows/workflows/v1/task_release_pb2.pyi deleted file mode 100644 index 333f048..0000000 --- a/tilebox-workflows/tilebox/workflows/workflows/v1/task_release_pb2.pyi +++ /dev/null @@ -1,106 +0,0 @@ -from google.protobuf import timestamp_pb2 as _timestamp_pb2 -from tilebox.datasets.tilebox.v1 import id_pb2 as _id_pb2 -from tilebox.workflows.workflows.v1 import core_pb2 as _core_pb2 -from google.protobuf.internal import containers as _containers -from google.protobuf import descriptor as _descriptor -from google.protobuf import message as _message -from collections.abc import Iterable as _Iterable, Mapping as _Mapping -from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union - -DESCRIPTOR: _descriptor.FileDescriptor - -class PublishReleaseRequest(_message.Message): - __slots__ = ("tasks", "artifact_uri", "artifact_digest", "runtime_kind", "environment_digest", "metadata_json") - TASKS_FIELD_NUMBER: _ClassVar[int] - ARTIFACT_URI_FIELD_NUMBER: _ClassVar[int] - ARTIFACT_DIGEST_FIELD_NUMBER: _ClassVar[int] - RUNTIME_KIND_FIELD_NUMBER: _ClassVar[int] - ENVIRONMENT_DIGEST_FIELD_NUMBER: _ClassVar[int] - METADATA_JSON_FIELD_NUMBER: _ClassVar[int] - tasks: _containers.RepeatedCompositeFieldContainer[_core_pb2.TaskIdentifier] - artifact_uri: str - artifact_digest: str - runtime_kind: str - environment_digest: str - metadata_json: bytes - def __init__(self, tasks: _Optional[_Iterable[_Union[_core_pb2.TaskIdentifier, _Mapping]]] = ..., artifact_uri: _Optional[str] = ..., artifact_digest: _Optional[str] = ..., runtime_kind: _Optional[str] = ..., environment_digest: _Optional[str] = ..., metadata_json: _Optional[bytes] = ...) -> None: ... - -class PublishReleaseResponse(_message.Message): - __slots__ = ("release_id", "created", "changes") - RELEASE_ID_FIELD_NUMBER: _ClassVar[int] - CREATED_FIELD_NUMBER: _ClassVar[int] - CHANGES_FIELD_NUMBER: _ClassVar[int] - release_id: _id_pb2.ID - created: bool - changes: PublishReleaseChanges - def __init__(self, release_id: _Optional[_Union[_id_pb2.ID, _Mapping]] = ..., created: bool = ..., changes: _Optional[_Union[PublishReleaseChanges, _Mapping]] = ...) -> None: ... - -class PublishReleaseChanges(_message.Message): - __slots__ = ("tasks", "artifact", "environment") - TASKS_FIELD_NUMBER: _ClassVar[int] - ARTIFACT_FIELD_NUMBER: _ClassVar[int] - ENVIRONMENT_FIELD_NUMBER: _ClassVar[int] - tasks: bool - artifact: bool - environment: bool - def __init__(self, tasks: bool = ..., artifact: bool = ..., environment: bool = ...) -> None: ... - -class DeployReleaseRequest(_message.Message): - __slots__ = ("cluster_slug", "release_id") - CLUSTER_SLUG_FIELD_NUMBER: _ClassVar[int] - RELEASE_ID_FIELD_NUMBER: _ClassVar[int] - cluster_slug: str - release_id: _id_pb2.ID - def __init__(self, cluster_slug: _Optional[str] = ..., release_id: _Optional[_Union[_id_pb2.ID, _Mapping]] = ...) -> None: ... - -class DeployReleaseResponse(_message.Message): - __slots__ = ("cluster_slug", "release_id", "manifest_revision", "changed") - CLUSTER_SLUG_FIELD_NUMBER: _ClassVar[int] - RELEASE_ID_FIELD_NUMBER: _ClassVar[int] - MANIFEST_REVISION_FIELD_NUMBER: _ClassVar[int] - CHANGED_FIELD_NUMBER: _ClassVar[int] - cluster_slug: str - release_id: _id_pb2.ID - manifest_revision: int - changed: bool - def __init__(self, cluster_slug: _Optional[str] = ..., release_id: _Optional[_Union[_id_pb2.ID, _Mapping]] = ..., manifest_revision: _Optional[int] = ..., changed: bool = ...) -> None: ... - -class GetClusterDeploymentManifestRequest(_message.Message): - __slots__ = ("cluster_slug", "since_revision") - CLUSTER_SLUG_FIELD_NUMBER: _ClassVar[int] - SINCE_REVISION_FIELD_NUMBER: _ClassVar[int] - cluster_slug: str - since_revision: int - def __init__(self, cluster_slug: _Optional[str] = ..., since_revision: _Optional[int] = ...) -> None: ... - -class DeployedRelease(_message.Message): - __slots__ = ("release_id", "artifact_uri", "artifact_digest", "runtime_kind", "environment_digest", "metadata_json", "change_revision", "tasks", "deployed_at") - RELEASE_ID_FIELD_NUMBER: _ClassVar[int] - ARTIFACT_URI_FIELD_NUMBER: _ClassVar[int] - ARTIFACT_DIGEST_FIELD_NUMBER: _ClassVar[int] - RUNTIME_KIND_FIELD_NUMBER: _ClassVar[int] - ENVIRONMENT_DIGEST_FIELD_NUMBER: _ClassVar[int] - METADATA_JSON_FIELD_NUMBER: _ClassVar[int] - CHANGE_REVISION_FIELD_NUMBER: _ClassVar[int] - TASKS_FIELD_NUMBER: _ClassVar[int] - DEPLOYED_AT_FIELD_NUMBER: _ClassVar[int] - release_id: _id_pb2.ID - artifact_uri: str - artifact_digest: str - runtime_kind: str - environment_digest: str - metadata_json: bytes - change_revision: int - tasks: _containers.RepeatedCompositeFieldContainer[_core_pb2.TaskIdentifier] - deployed_at: _timestamp_pb2.Timestamp - def __init__(self, release_id: _Optional[_Union[_id_pb2.ID, _Mapping]] = ..., artifact_uri: _Optional[str] = ..., artifact_digest: _Optional[str] = ..., runtime_kind: _Optional[str] = ..., environment_digest: _Optional[str] = ..., metadata_json: _Optional[bytes] = ..., change_revision: _Optional[int] = ..., tasks: _Optional[_Iterable[_Union[_core_pb2.TaskIdentifier, _Mapping]]] = ..., deployed_at: _Optional[_Union[_timestamp_pb2.Timestamp, _Mapping]] = ...) -> None: ... - -class GetClusterDeploymentManifestResponse(_message.Message): - __slots__ = ("cluster_slug", "manifest_revision", "release") - CLUSTER_SLUG_FIELD_NUMBER: _ClassVar[int] - MANIFEST_REVISION_FIELD_NUMBER: _ClassVar[int] - RELEASE_FIELD_NUMBER: _ClassVar[int] - cluster_slug: str - manifest_revision: int - release: DeployedRelease - def __init__(self, cluster_slug: _Optional[str] = ..., manifest_revision: _Optional[int] = ..., release: _Optional[_Union[DeployedRelease, _Mapping]] = ...) -> None: ... diff --git a/tilebox-workflows/tilebox/workflows/workflows/v1/task_release_pb2_grpc.py b/tilebox-workflows/tilebox/workflows/workflows/v1/task_release_pb2_grpc.py deleted file mode 100644 index 279ef95..0000000 --- a/tilebox-workflows/tilebox/workflows/workflows/v1/task_release_pb2_grpc.py +++ /dev/null @@ -1,166 +0,0 @@ -# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! -"""Client and server classes corresponding to protobuf-defined services.""" -import grpc - -from tilebox.workflows.workflows.v1 import task_release_pb2 as workflows_dot_v1_dot_task__release__pb2 - - -class TaskReleaseServiceStub: - """A service for managing workflows. - """ - - def __init__(self, channel): - """Constructor. - - Args: - channel: A grpc.Channel. - """ - self.PublishRelease = channel.unary_unary( - '/workflows.v1.TaskReleaseService/PublishRelease', - request_serializer=workflows_dot_v1_dot_task__release__pb2.PublishReleaseRequest.SerializeToString, - response_deserializer=workflows_dot_v1_dot_task__release__pb2.PublishReleaseResponse.FromString, - _registered_method=True) - self.DeployRelease = channel.unary_unary( - '/workflows.v1.TaskReleaseService/DeployRelease', - request_serializer=workflows_dot_v1_dot_task__release__pb2.DeployReleaseRequest.SerializeToString, - response_deserializer=workflows_dot_v1_dot_task__release__pb2.DeployReleaseResponse.FromString, - _registered_method=True) - self.GetClusterDeploymentManifest = channel.unary_unary( - '/workflows.v1.TaskReleaseService/GetClusterDeploymentManifest', - request_serializer=workflows_dot_v1_dot_task__release__pb2.GetClusterDeploymentManifestRequest.SerializeToString, - response_deserializer=workflows_dot_v1_dot_task__release__pb2.GetClusterDeploymentManifestResponse.FromString, - _registered_method=True) - - -class TaskReleaseServiceServicer: - """A service for managing workflows. - """ - - def PublishRelease(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def DeployRelease(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def GetClusterDeploymentManifest(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - -def add_TaskReleaseServiceServicer_to_server(servicer, server): - rpc_method_handlers = { - 'PublishRelease': grpc.unary_unary_rpc_method_handler( - servicer.PublishRelease, - request_deserializer=workflows_dot_v1_dot_task__release__pb2.PublishReleaseRequest.FromString, - response_serializer=workflows_dot_v1_dot_task__release__pb2.PublishReleaseResponse.SerializeToString, - ), - 'DeployRelease': grpc.unary_unary_rpc_method_handler( - servicer.DeployRelease, - request_deserializer=workflows_dot_v1_dot_task__release__pb2.DeployReleaseRequest.FromString, - response_serializer=workflows_dot_v1_dot_task__release__pb2.DeployReleaseResponse.SerializeToString, - ), - 'GetClusterDeploymentManifest': grpc.unary_unary_rpc_method_handler( - servicer.GetClusterDeploymentManifest, - request_deserializer=workflows_dot_v1_dot_task__release__pb2.GetClusterDeploymentManifestRequest.FromString, - response_serializer=workflows_dot_v1_dot_task__release__pb2.GetClusterDeploymentManifestResponse.SerializeToString, - ), - } - generic_handler = grpc.method_handlers_generic_handler( - 'workflows.v1.TaskReleaseService', rpc_method_handlers) - server.add_generic_rpc_handlers((generic_handler,)) - server.add_registered_method_handlers('workflows.v1.TaskReleaseService', rpc_method_handlers) - - - # This class is part of an EXPERIMENTAL API. -class TaskReleaseService: - """A service for managing workflows. - """ - - @staticmethod - def PublishRelease(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary( - request, - target, - '/workflows.v1.TaskReleaseService/PublishRelease', - workflows_dot_v1_dot_task__release__pb2.PublishReleaseRequest.SerializeToString, - workflows_dot_v1_dot_task__release__pb2.PublishReleaseResponse.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) - - @staticmethod - def DeployRelease(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary( - request, - target, - '/workflows.v1.TaskReleaseService/DeployRelease', - workflows_dot_v1_dot_task__release__pb2.DeployReleaseRequest.SerializeToString, - workflows_dot_v1_dot_task__release__pb2.DeployReleaseResponse.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) - - @staticmethod - def GetClusterDeploymentManifest(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary( - request, - target, - '/workflows.v1.TaskReleaseService/GetClusterDeploymentManifest', - workflows_dot_v1_dot_task__release__pb2.GetClusterDeploymentManifestRequest.SerializeToString, - workflows_dot_v1_dot_task__release__pb2.GetClusterDeploymentManifestResponse.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) diff --git a/tilebox-workflows/tilebox/workflows/workflows/v1/telemetry_connect.py b/tilebox-workflows/tilebox/workflows/workflows/v1/telemetry_connect.py deleted file mode 100644 index 6245e27..0000000 --- a/tilebox-workflows/tilebox/workflows/workflows/v1/telemetry_connect.py +++ /dev/null @@ -1,324 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by https://github.com/connectrpc/connect-python. DO NOT EDIT! -# source: workflows/v1/telemetry.proto - -from collections.abc import AsyncGenerator, AsyncIterator, Iterable, Iterator, Mapping -from typing import Protocol - -from connectrpc.client import ConnectClient, ConnectClientSync -from connectrpc.code import Code -from connectrpc.codec import Codec -from connectrpc.compression import Compression -from connectrpc.errors import ConnectError -from connectrpc.interceptor import Interceptor, InterceptorSync -from connectrpc.method import IdempotencyLevel, MethodInfo -from connectrpc.request import Headers, RequestContext -from connectrpc.server import ConnectASGIApplication, ConnectWSGIApplication, Endpoint, EndpointSync -import tilebox.workflows.workflows.v1.telemetry_pb2 as workflows_dot_v1_dot_telemetry__pb2 - - -class TelemetryQueryService(Protocol): - async def query_job_logs(self, request: workflows_dot_v1_dot_telemetry__pb2.QueryJobLogsRequest, ctx: RequestContext) -> workflows_dot_v1_dot_telemetry__pb2.PaginatedLogsData: - raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") - - async def query_logs_in_interval(self, request: workflows_dot_v1_dot_telemetry__pb2.QueryLogsInIntervalRequest, ctx: RequestContext) -> workflows_dot_v1_dot_telemetry__pb2.PaginatedLogsData: - raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") - - async def get_log_message_counts(self, request: workflows_dot_v1_dot_telemetry__pb2.GetLogMessageCountsRequest, ctx: RequestContext) -> workflows_dot_v1_dot_telemetry__pb2.GetLogMessageCountsResponse: - raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") - - async def query_job_spans(self, request: workflows_dot_v1_dot_telemetry__pb2.QueryJobSpansRequest, ctx: RequestContext) -> workflows_dot_v1_dot_telemetry__pb2.PaginatedSpansData: - raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") - - -class TelemetryQueryServiceASGIApplication(ConnectASGIApplication[TelemetryQueryService]): - def __init__(self, service: TelemetryQueryService | AsyncGenerator[TelemetryQueryService], *, interceptors: Iterable[Interceptor]=(), read_max_bytes: int | None = None, compressions: Iterable[Compression] | None = None, codecs: Iterable[Codec] | None = None) -> None: - super().__init__( - service=service, - endpoints=lambda svc: { - "/workflows.v1.TelemetryQueryService/QueryJobLogs": Endpoint.unary( - method=MethodInfo( - name="QueryJobLogs", - service_name="workflows.v1.TelemetryQueryService", - input=workflows_dot_v1_dot_telemetry__pb2.QueryJobLogsRequest, - output=workflows_dot_v1_dot_telemetry__pb2.PaginatedLogsData, - idempotency_level=IdempotencyLevel.UNKNOWN, - ), - function=svc.query_job_logs, - ), - "/workflows.v1.TelemetryQueryService/QueryLogsInInterval": Endpoint.unary( - method=MethodInfo( - name="QueryLogsInInterval", - service_name="workflows.v1.TelemetryQueryService", - input=workflows_dot_v1_dot_telemetry__pb2.QueryLogsInIntervalRequest, - output=workflows_dot_v1_dot_telemetry__pb2.PaginatedLogsData, - idempotency_level=IdempotencyLevel.UNKNOWN, - ), - function=svc.query_logs_in_interval, - ), - "/workflows.v1.TelemetryQueryService/GetLogMessageCounts": Endpoint.unary( - method=MethodInfo( - name="GetLogMessageCounts", - service_name="workflows.v1.TelemetryQueryService", - input=workflows_dot_v1_dot_telemetry__pb2.GetLogMessageCountsRequest, - output=workflows_dot_v1_dot_telemetry__pb2.GetLogMessageCountsResponse, - idempotency_level=IdempotencyLevel.UNKNOWN, - ), - function=svc.get_log_message_counts, - ), - "/workflows.v1.TelemetryQueryService/QueryJobSpans": Endpoint.unary( - method=MethodInfo( - name="QueryJobSpans", - service_name="workflows.v1.TelemetryQueryService", - input=workflows_dot_v1_dot_telemetry__pb2.QueryJobSpansRequest, - output=workflows_dot_v1_dot_telemetry__pb2.PaginatedSpansData, - idempotency_level=IdempotencyLevel.UNKNOWN, - ), - function=svc.query_job_spans, - ), - }, - interceptors=interceptors, - read_max_bytes=read_max_bytes, - compressions=compressions, - codecs=codecs, - ) - - @property - def path(self) -> str: - """Returns the URL path to mount the application to when serving multiple applications.""" - return "/workflows.v1.TelemetryQueryService" - - -class TelemetryQueryServiceClient(ConnectClient): - async def query_job_logs( - self, - request: workflows_dot_v1_dot_telemetry__pb2.QueryJobLogsRequest, - *, - headers: Headers | Mapping[str, str] | None = None, - timeout_ms: int | None = None, - ) -> workflows_dot_v1_dot_telemetry__pb2.PaginatedLogsData: - return await self.execute_unary( - request=request, - method=MethodInfo( - name="QueryJobLogs", - service_name="workflows.v1.TelemetryQueryService", - input=workflows_dot_v1_dot_telemetry__pb2.QueryJobLogsRequest, - output=workflows_dot_v1_dot_telemetry__pb2.PaginatedLogsData, - idempotency_level=IdempotencyLevel.UNKNOWN, - ), - headers=headers, - timeout_ms=timeout_ms, - ) - - async def query_logs_in_interval( - self, - request: workflows_dot_v1_dot_telemetry__pb2.QueryLogsInIntervalRequest, - *, - headers: Headers | Mapping[str, str] | None = None, - timeout_ms: int | None = None, - ) -> workflows_dot_v1_dot_telemetry__pb2.PaginatedLogsData: - return await self.execute_unary( - request=request, - method=MethodInfo( - name="QueryLogsInInterval", - service_name="workflows.v1.TelemetryQueryService", - input=workflows_dot_v1_dot_telemetry__pb2.QueryLogsInIntervalRequest, - output=workflows_dot_v1_dot_telemetry__pb2.PaginatedLogsData, - idempotency_level=IdempotencyLevel.UNKNOWN, - ), - headers=headers, - timeout_ms=timeout_ms, - ) - - async def get_log_message_counts( - self, - request: workflows_dot_v1_dot_telemetry__pb2.GetLogMessageCountsRequest, - *, - headers: Headers | Mapping[str, str] | None = None, - timeout_ms: int | None = None, - ) -> workflows_dot_v1_dot_telemetry__pb2.GetLogMessageCountsResponse: - return await self.execute_unary( - request=request, - method=MethodInfo( - name="GetLogMessageCounts", - service_name="workflows.v1.TelemetryQueryService", - input=workflows_dot_v1_dot_telemetry__pb2.GetLogMessageCountsRequest, - output=workflows_dot_v1_dot_telemetry__pb2.GetLogMessageCountsResponse, - idempotency_level=IdempotencyLevel.UNKNOWN, - ), - headers=headers, - timeout_ms=timeout_ms, - ) - - async def query_job_spans( - self, - request: workflows_dot_v1_dot_telemetry__pb2.QueryJobSpansRequest, - *, - headers: Headers | Mapping[str, str] | None = None, - timeout_ms: int | None = None, - ) -> workflows_dot_v1_dot_telemetry__pb2.PaginatedSpansData: - return await self.execute_unary( - request=request, - method=MethodInfo( - name="QueryJobSpans", - service_name="workflows.v1.TelemetryQueryService", - input=workflows_dot_v1_dot_telemetry__pb2.QueryJobSpansRequest, - output=workflows_dot_v1_dot_telemetry__pb2.PaginatedSpansData, - idempotency_level=IdempotencyLevel.UNKNOWN, - ), - headers=headers, - timeout_ms=timeout_ms, - ) - - - - - -class TelemetryQueryServiceSync(Protocol): - def query_job_logs(self, request: workflows_dot_v1_dot_telemetry__pb2.QueryJobLogsRequest, ctx: RequestContext) -> workflows_dot_v1_dot_telemetry__pb2.PaginatedLogsData: - raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") - def query_logs_in_interval(self, request: workflows_dot_v1_dot_telemetry__pb2.QueryLogsInIntervalRequest, ctx: RequestContext) -> workflows_dot_v1_dot_telemetry__pb2.PaginatedLogsData: - raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") - def get_log_message_counts(self, request: workflows_dot_v1_dot_telemetry__pb2.GetLogMessageCountsRequest, ctx: RequestContext) -> workflows_dot_v1_dot_telemetry__pb2.GetLogMessageCountsResponse: - raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") - def query_job_spans(self, request: workflows_dot_v1_dot_telemetry__pb2.QueryJobSpansRequest, ctx: RequestContext) -> workflows_dot_v1_dot_telemetry__pb2.PaginatedSpansData: - raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") - - -class TelemetryQueryServiceWSGIApplication(ConnectWSGIApplication): - def __init__(self, service: TelemetryQueryServiceSync, interceptors: Iterable[InterceptorSync]=(), read_max_bytes: int | None = None, compressions: Iterable[Compression] | None = None, codecs: Iterable[Codec] | None = None) -> None: - super().__init__( - endpoints={ - "/workflows.v1.TelemetryQueryService/QueryJobLogs": EndpointSync.unary( - method=MethodInfo( - name="QueryJobLogs", - service_name="workflows.v1.TelemetryQueryService", - input=workflows_dot_v1_dot_telemetry__pb2.QueryJobLogsRequest, - output=workflows_dot_v1_dot_telemetry__pb2.PaginatedLogsData, - idempotency_level=IdempotencyLevel.UNKNOWN, - ), - function=service.query_job_logs, - ), - "/workflows.v1.TelemetryQueryService/QueryLogsInInterval": EndpointSync.unary( - method=MethodInfo( - name="QueryLogsInInterval", - service_name="workflows.v1.TelemetryQueryService", - input=workflows_dot_v1_dot_telemetry__pb2.QueryLogsInIntervalRequest, - output=workflows_dot_v1_dot_telemetry__pb2.PaginatedLogsData, - idempotency_level=IdempotencyLevel.UNKNOWN, - ), - function=service.query_logs_in_interval, - ), - "/workflows.v1.TelemetryQueryService/GetLogMessageCounts": EndpointSync.unary( - method=MethodInfo( - name="GetLogMessageCounts", - service_name="workflows.v1.TelemetryQueryService", - input=workflows_dot_v1_dot_telemetry__pb2.GetLogMessageCountsRequest, - output=workflows_dot_v1_dot_telemetry__pb2.GetLogMessageCountsResponse, - idempotency_level=IdempotencyLevel.UNKNOWN, - ), - function=service.get_log_message_counts, - ), - "/workflows.v1.TelemetryQueryService/QueryJobSpans": EndpointSync.unary( - method=MethodInfo( - name="QueryJobSpans", - service_name="workflows.v1.TelemetryQueryService", - input=workflows_dot_v1_dot_telemetry__pb2.QueryJobSpansRequest, - output=workflows_dot_v1_dot_telemetry__pb2.PaginatedSpansData, - idempotency_level=IdempotencyLevel.UNKNOWN, - ), - function=service.query_job_spans, - ), - }, - interceptors=interceptors, - read_max_bytes=read_max_bytes, - compressions=compressions, - codecs=codecs, - ) - - @property - def path(self) -> str: - """Returns the URL path to mount the application to when serving multiple applications.""" - return "/workflows.v1.TelemetryQueryService" - - -class TelemetryQueryServiceClientSync(ConnectClientSync): - def query_job_logs( - self, - request: workflows_dot_v1_dot_telemetry__pb2.QueryJobLogsRequest, - *, - headers: Headers | Mapping[str, str] | None = None, - timeout_ms: int | None = None, - ) -> workflows_dot_v1_dot_telemetry__pb2.PaginatedLogsData: - return self.execute_unary( - request=request, - method=MethodInfo( - name="QueryJobLogs", - service_name="workflows.v1.TelemetryQueryService", - input=workflows_dot_v1_dot_telemetry__pb2.QueryJobLogsRequest, - output=workflows_dot_v1_dot_telemetry__pb2.PaginatedLogsData, - idempotency_level=IdempotencyLevel.UNKNOWN, - ), - headers=headers, - timeout_ms=timeout_ms, - ) - - def query_logs_in_interval( - self, - request: workflows_dot_v1_dot_telemetry__pb2.QueryLogsInIntervalRequest, - *, - headers: Headers | Mapping[str, str] | None = None, - timeout_ms: int | None = None, - ) -> workflows_dot_v1_dot_telemetry__pb2.PaginatedLogsData: - return self.execute_unary( - request=request, - method=MethodInfo( - name="QueryLogsInInterval", - service_name="workflows.v1.TelemetryQueryService", - input=workflows_dot_v1_dot_telemetry__pb2.QueryLogsInIntervalRequest, - output=workflows_dot_v1_dot_telemetry__pb2.PaginatedLogsData, - idempotency_level=IdempotencyLevel.UNKNOWN, - ), - headers=headers, - timeout_ms=timeout_ms, - ) - - def get_log_message_counts( - self, - request: workflows_dot_v1_dot_telemetry__pb2.GetLogMessageCountsRequest, - *, - headers: Headers | Mapping[str, str] | None = None, - timeout_ms: int | None = None, - ) -> workflows_dot_v1_dot_telemetry__pb2.GetLogMessageCountsResponse: - return self.execute_unary( - request=request, - method=MethodInfo( - name="GetLogMessageCounts", - service_name="workflows.v1.TelemetryQueryService", - input=workflows_dot_v1_dot_telemetry__pb2.GetLogMessageCountsRequest, - output=workflows_dot_v1_dot_telemetry__pb2.GetLogMessageCountsResponse, - idempotency_level=IdempotencyLevel.UNKNOWN, - ), - headers=headers, - timeout_ms=timeout_ms, - ) - - def query_job_spans( - self, - request: workflows_dot_v1_dot_telemetry__pb2.QueryJobSpansRequest, - *, - headers: Headers | Mapping[str, str] | None = None, - timeout_ms: int | None = None, - ) -> workflows_dot_v1_dot_telemetry__pb2.PaginatedSpansData: - return self.execute_unary( - request=request, - method=MethodInfo( - name="QueryJobSpans", - service_name="workflows.v1.TelemetryQueryService", - input=workflows_dot_v1_dot_telemetry__pb2.QueryJobSpansRequest, - output=workflows_dot_v1_dot_telemetry__pb2.PaginatedSpansData, - idempotency_level=IdempotencyLevel.UNKNOWN, - ), - headers=headers, - timeout_ms=timeout_ms, - ) diff --git a/tilebox-workflows/tilebox/workflows/workflows/v1/telemetry_pb2.py b/tilebox-workflows/tilebox/workflows/workflows/v1/telemetry_pb2.py deleted file mode 100644 index b1d8554..0000000 --- a/tilebox-workflows/tilebox/workflows/workflows/v1/telemetry_pb2.py +++ /dev/null @@ -1,75 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by the protocol buffer compiler. DO NOT EDIT! -# NO CHECKED-IN PROTOBUF GENCODE -# source: workflows/v1/telemetry.proto -# Protobuf Python Version: 6.30.0 -"""Generated protocol buffer code.""" -from google.protobuf import descriptor as _descriptor -from google.protobuf import descriptor_pool as _descriptor_pool -from google.protobuf import runtime_version as _runtime_version -from google.protobuf import symbol_database as _symbol_database -from google.protobuf.internal import builder as _builder -_runtime_version.ValidateProtobufRuntimeVersion( - _runtime_version.Domain.PUBLIC, - 6, - 30, - 0, - '', - 'workflows/v1/telemetry.proto' -) -# @@protoc_insertion_point(imports) - -_sym_db = _symbol_database.Default() - - -from opentelemetry.proto.logs.v1 import logs_pb2 as opentelemetry_dot_proto_dot_logs_dot_v1_dot_logs__pb2 -from opentelemetry.proto.trace.v1 import trace_pb2 as opentelemetry_dot_proto_dot_trace_dot_v1_dot_trace__pb2 -from tilebox.datasets.tilebox.v1 import id_pb2 as tilebox_dot_v1_dot_id__pb2 -from tilebox.datasets.tilebox.v1 import query_pb2 as tilebox_dot_v1_dot_query__pb2 - - -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1cworkflows/v1/telemetry.proto\x12\x0cworkflows.v1\x1a&opentelemetry/proto/logs/v1/logs.proto\x1a(opentelemetry/proto/trace/v1/trace.proto\x1a\x13tilebox/v1/id.proto\x1a\x16tilebox/v1/query.proto\"Z\n\x0fLogQueryFilters\x12G\n\x0fseverity_levels\x18\x01 \x03(\x0e\x32\x1e.workflows.v1.LogSeverityGroupR\x0eseverityLevels\"\x9a\x02\n\x13QueryJobLogsRequest\x12%\n\x06job_id\x18\x01 \x01(\x0b\x32\x0e.tilebox.v1.IDR\x05jobId\x12\x31\n\x04page\x18\x02 \x01(\x0b\x32\x16.tilebox.v1.PaginationB\x05\xaa\x01\x02\x08\x01R\x04page\x12@\n\x0esort_direction\x18\x03 \x01(\x0e\x32\x19.tilebox.v1.SortDirectionR\rsortDirection\x12.\n\x07task_id\x18\x04 \x01(\x0b\x32\x0e.tilebox.v1.IDB\x05\xaa\x01\x02\x08\x01R\x06taskId\x12\x37\n\x07\x66ilters\x18\x05 \x01(\x0b\x32\x1d.workflows.v1.LogQueryFiltersR\x07\x66ilters\"\x89\x02\n\x1aQueryLogsInIntervalRequest\x12=\n\rtime_interval\x18\x01 \x01(\x0b\x32\x18.tilebox.v1.TimeIntervalR\x0ctimeInterval\x12\x31\n\x04page\x18\x02 \x01(\x0b\x32\x16.tilebox.v1.PaginationB\x05\xaa\x01\x02\x08\x01R\x04page\x12@\n\x0esort_direction\x18\x03 \x01(\x0e\x32\x19.tilebox.v1.SortDirectionR\rsortDirection\x12\x37\n\x07\x66ilters\x18\x04 \x01(\x0b\x32\x1d.workflows.v1.LogQueryFiltersR\x07\x66ilters\"\x9f\x01\n\x11PaginatedLogsData\x12N\n\rresource_logs\x18\x01 \x03(\x0b\x32).opentelemetry.proto.logs.v1.ResourceLogsR\x0cresourceLogs\x12:\n\tnext_page\x18\x02 \x01(\x0b\x32\x16.tilebox.v1.PaginationB\x05\xaa\x01\x02\x08\x01R\x08nextPage\"[\n\x1aGetLogMessageCountsRequest\x12=\n\rtime_interval\x18\x01 \x01(\x0b\x32\x18.tilebox.v1.TimeIntervalR\x0ctimeInterval\"|\n\x1bGetLogMessageCountsResponse\x12\x36\n\x06\x63ounts\x18\x01 \x03(\x0b\x32\x1e.workflows.v1.LogSeverityCountR\x06\x63ounts\x12%\n\x0etotal_messages\x18\x02 \x01(\x03R\rtotalMessages\"d\n\x10LogSeverityCount\x12:\n\x08severity\x18\x01 \x01(\x0e\x32\x1e.workflows.v1.LogSeverityGroupR\x08severity\x12\x14\n\x05\x63ount\x18\x02 \x01(\x03R\x05\x63ount\"\xe2\x01\n\x14QueryJobSpansRequest\x12%\n\x06job_id\x18\x01 \x01(\x0b\x32\x0e.tilebox.v1.IDR\x05jobId\x12\x31\n\x04page\x18\x02 \x01(\x0b\x32\x16.tilebox.v1.PaginationB\x05\xaa\x01\x02\x08\x01R\x04page\x12@\n\x0esort_direction\x18\x03 \x01(\x0e\x32\x19.tilebox.v1.SortDirectionR\rsortDirection\x12.\n\x07task_id\x18\x04 \x01(\x0b\x32\x0e.tilebox.v1.IDB\x05\xaa\x01\x02\x08\x01R\x06taskId\"\xa4\x01\n\x12PaginatedSpansData\x12R\n\x0eresource_spans\x18\x01 \x03(\x0b\x32+.opentelemetry.proto.trace.v1.ResourceSpansR\rresourceSpans\x12:\n\tnext_page\x18\x02 \x01(\x0b\x32\x16.tilebox.v1.PaginationB\x05\xaa\x01\x02\x08\x01R\x08nextPage*\xcd\x01\n\x10LogSeverityGroup\x12\"\n\x1eLOG_SEVERITY_GROUP_UNSPECIFIED\x10\x00\x12\x1c\n\x18LOG_SEVERITY_GROUP_TRACE\x10\x01\x12\x1c\n\x18LOG_SEVERITY_GROUP_DEBUG\x10\x02\x12\x1b\n\x17LOG_SEVERITY_GROUP_INFO\x10\x03\x12\x1e\n\x1aLOG_SEVERITY_GROUP_WARNING\x10\x04\x12\x1c\n\x18LOG_SEVERITY_GROUP_ERROR\x10\x05\x32\x90\x03\n\x15TelemetryQueryService\x12R\n\x0cQueryJobLogs\x12!.workflows.v1.QueryJobLogsRequest\x1a\x1f.workflows.v1.PaginatedLogsData\x12`\n\x13QueryLogsInInterval\x12(.workflows.v1.QueryLogsInIntervalRequest\x1a\x1f.workflows.v1.PaginatedLogsData\x12j\n\x13GetLogMessageCounts\x12(.workflows.v1.GetLogMessageCountsRequest\x1a).workflows.v1.GetLogMessageCountsResponse\x12U\n\rQueryJobSpans\x12\".workflows.v1.QueryJobSpansRequest\x1a .workflows.v1.PaginatedSpansDataBs\n\x10\x63om.workflows.v1B\x0eTelemetryProtoP\x01\xa2\x02\x03WXX\xaa\x02\x0cWorkflows.V1\xca\x02\x0cWorkflows\\V1\xe2\x02\x18Workflows\\V1\\GPBMetadata\xea\x02\rWorkflows::V1b\x08\x65\x64itionsp\xe8\x07') - -_globals = globals() -_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) -_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'workflows.v1.telemetry_pb2', _globals) -if not _descriptor._USE_C_DESCRIPTORS: - _globals['DESCRIPTOR']._loaded_options = None - _globals['DESCRIPTOR']._serialized_options = b'\n\020com.workflows.v1B\016TelemetryProtoP\001\242\002\003WXX\252\002\014Workflows.V1\312\002\014Workflows\\V1\342\002\030Workflows\\V1\\GPBMetadata\352\002\rWorkflows::V1' - _globals['_QUERYJOBLOGSREQUEST'].fields_by_name['page']._loaded_options = None - _globals['_QUERYJOBLOGSREQUEST'].fields_by_name['page']._serialized_options = b'\252\001\002\010\001' - _globals['_QUERYJOBLOGSREQUEST'].fields_by_name['task_id']._loaded_options = None - _globals['_QUERYJOBLOGSREQUEST'].fields_by_name['task_id']._serialized_options = b'\252\001\002\010\001' - _globals['_QUERYLOGSININTERVALREQUEST'].fields_by_name['page']._loaded_options = None - _globals['_QUERYLOGSININTERVALREQUEST'].fields_by_name['page']._serialized_options = b'\252\001\002\010\001' - _globals['_PAGINATEDLOGSDATA'].fields_by_name['next_page']._loaded_options = None - _globals['_PAGINATEDLOGSDATA'].fields_by_name['next_page']._serialized_options = b'\252\001\002\010\001' - _globals['_QUERYJOBSPANSREQUEST'].fields_by_name['page']._loaded_options = None - _globals['_QUERYJOBSPANSREQUEST'].fields_by_name['page']._serialized_options = b'\252\001\002\010\001' - _globals['_QUERYJOBSPANSREQUEST'].fields_by_name['task_id']._loaded_options = None - _globals['_QUERYJOBSPANSREQUEST'].fields_by_name['task_id']._serialized_options = b'\252\001\002\010\001' - _globals['_PAGINATEDSPANSDATA'].fields_by_name['next_page']._loaded_options = None - _globals['_PAGINATEDSPANSDATA'].fields_by_name['next_page']._serialized_options = b'\252\001\002\010\001' - _globals['_LOGSEVERITYGROUP']._serialized_start=1698 - _globals['_LOGSEVERITYGROUP']._serialized_end=1903 - _globals['_LOGQUERYFILTERS']._serialized_start=173 - _globals['_LOGQUERYFILTERS']._serialized_end=263 - _globals['_QUERYJOBLOGSREQUEST']._serialized_start=266 - _globals['_QUERYJOBLOGSREQUEST']._serialized_end=548 - _globals['_QUERYLOGSININTERVALREQUEST']._serialized_start=551 - _globals['_QUERYLOGSININTERVALREQUEST']._serialized_end=816 - _globals['_PAGINATEDLOGSDATA']._serialized_start=819 - _globals['_PAGINATEDLOGSDATA']._serialized_end=978 - _globals['_GETLOGMESSAGECOUNTSREQUEST']._serialized_start=980 - _globals['_GETLOGMESSAGECOUNTSREQUEST']._serialized_end=1071 - _globals['_GETLOGMESSAGECOUNTSRESPONSE']._serialized_start=1073 - _globals['_GETLOGMESSAGECOUNTSRESPONSE']._serialized_end=1197 - _globals['_LOGSEVERITYCOUNT']._serialized_start=1199 - _globals['_LOGSEVERITYCOUNT']._serialized_end=1299 - _globals['_QUERYJOBSPANSREQUEST']._serialized_start=1302 - _globals['_QUERYJOBSPANSREQUEST']._serialized_end=1528 - _globals['_PAGINATEDSPANSDATA']._serialized_start=1531 - _globals['_PAGINATEDSPANSDATA']._serialized_end=1695 - _globals['_TELEMETRYQUERYSERVICE']._serialized_start=1906 - _globals['_TELEMETRYQUERYSERVICE']._serialized_end=2306 -# @@protoc_insertion_point(module_scope) diff --git a/tilebox-workflows/tilebox/workflows/workflows/v1/telemetry_pb2.pyi b/tilebox-workflows/tilebox/workflows/workflows/v1/telemetry_pb2.pyi deleted file mode 100644 index 2f494e9..0000000 --- a/tilebox-workflows/tilebox/workflows/workflows/v1/telemetry_pb2.pyi +++ /dev/null @@ -1,109 +0,0 @@ -from opentelemetry.proto.logs.v1 import logs_pb2 as _logs_pb2 -from opentelemetry.proto.trace.v1 import trace_pb2 as _trace_pb2 -from tilebox.datasets.tilebox.v1 import id_pb2 as _id_pb2 -from tilebox.datasets.tilebox.v1 import query_pb2 as _query_pb2 -from google.protobuf.internal import containers as _containers -from google.protobuf.internal import enum_type_wrapper as _enum_type_wrapper -from google.protobuf import descriptor as _descriptor -from google.protobuf import message as _message -from collections.abc import Iterable as _Iterable, Mapping as _Mapping -from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union - -DESCRIPTOR: _descriptor.FileDescriptor - -class LogSeverityGroup(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): - __slots__ = () - LOG_SEVERITY_GROUP_UNSPECIFIED: _ClassVar[LogSeverityGroup] - LOG_SEVERITY_GROUP_TRACE: _ClassVar[LogSeverityGroup] - LOG_SEVERITY_GROUP_DEBUG: _ClassVar[LogSeverityGroup] - LOG_SEVERITY_GROUP_INFO: _ClassVar[LogSeverityGroup] - LOG_SEVERITY_GROUP_WARNING: _ClassVar[LogSeverityGroup] - LOG_SEVERITY_GROUP_ERROR: _ClassVar[LogSeverityGroup] -LOG_SEVERITY_GROUP_UNSPECIFIED: LogSeverityGroup -LOG_SEVERITY_GROUP_TRACE: LogSeverityGroup -LOG_SEVERITY_GROUP_DEBUG: LogSeverityGroup -LOG_SEVERITY_GROUP_INFO: LogSeverityGroup -LOG_SEVERITY_GROUP_WARNING: LogSeverityGroup -LOG_SEVERITY_GROUP_ERROR: LogSeverityGroup - -class LogQueryFilters(_message.Message): - __slots__ = ("severity_levels",) - SEVERITY_LEVELS_FIELD_NUMBER: _ClassVar[int] - severity_levels: _containers.RepeatedScalarFieldContainer[LogSeverityGroup] - def __init__(self, severity_levels: _Optional[_Iterable[_Union[LogSeverityGroup, str]]] = ...) -> None: ... - -class QueryJobLogsRequest(_message.Message): - __slots__ = ("job_id", "page", "sort_direction", "task_id", "filters") - JOB_ID_FIELD_NUMBER: _ClassVar[int] - PAGE_FIELD_NUMBER: _ClassVar[int] - SORT_DIRECTION_FIELD_NUMBER: _ClassVar[int] - TASK_ID_FIELD_NUMBER: _ClassVar[int] - FILTERS_FIELD_NUMBER: _ClassVar[int] - job_id: _id_pb2.ID - page: _query_pb2.Pagination - sort_direction: _query_pb2.SortDirection - task_id: _id_pb2.ID - filters: LogQueryFilters - def __init__(self, job_id: _Optional[_Union[_id_pb2.ID, _Mapping]] = ..., page: _Optional[_Union[_query_pb2.Pagination, _Mapping]] = ..., sort_direction: _Optional[_Union[_query_pb2.SortDirection, str]] = ..., task_id: _Optional[_Union[_id_pb2.ID, _Mapping]] = ..., filters: _Optional[_Union[LogQueryFilters, _Mapping]] = ...) -> None: ... - -class QueryLogsInIntervalRequest(_message.Message): - __slots__ = ("time_interval", "page", "sort_direction", "filters") - TIME_INTERVAL_FIELD_NUMBER: _ClassVar[int] - PAGE_FIELD_NUMBER: _ClassVar[int] - SORT_DIRECTION_FIELD_NUMBER: _ClassVar[int] - FILTERS_FIELD_NUMBER: _ClassVar[int] - time_interval: _query_pb2.TimeInterval - page: _query_pb2.Pagination - sort_direction: _query_pb2.SortDirection - filters: LogQueryFilters - def __init__(self, time_interval: _Optional[_Union[_query_pb2.TimeInterval, _Mapping]] = ..., page: _Optional[_Union[_query_pb2.Pagination, _Mapping]] = ..., sort_direction: _Optional[_Union[_query_pb2.SortDirection, str]] = ..., filters: _Optional[_Union[LogQueryFilters, _Mapping]] = ...) -> None: ... - -class PaginatedLogsData(_message.Message): - __slots__ = ("resource_logs", "next_page") - RESOURCE_LOGS_FIELD_NUMBER: _ClassVar[int] - NEXT_PAGE_FIELD_NUMBER: _ClassVar[int] - resource_logs: _containers.RepeatedCompositeFieldContainer[_logs_pb2.ResourceLogs] - next_page: _query_pb2.Pagination - def __init__(self, resource_logs: _Optional[_Iterable[_Union[_logs_pb2.ResourceLogs, _Mapping]]] = ..., next_page: _Optional[_Union[_query_pb2.Pagination, _Mapping]] = ...) -> None: ... - -class GetLogMessageCountsRequest(_message.Message): - __slots__ = ("time_interval",) - TIME_INTERVAL_FIELD_NUMBER: _ClassVar[int] - time_interval: _query_pb2.TimeInterval - def __init__(self, time_interval: _Optional[_Union[_query_pb2.TimeInterval, _Mapping]] = ...) -> None: ... - -class GetLogMessageCountsResponse(_message.Message): - __slots__ = ("counts", "total_messages") - COUNTS_FIELD_NUMBER: _ClassVar[int] - TOTAL_MESSAGES_FIELD_NUMBER: _ClassVar[int] - counts: _containers.RepeatedCompositeFieldContainer[LogSeverityCount] - total_messages: int - def __init__(self, counts: _Optional[_Iterable[_Union[LogSeverityCount, _Mapping]]] = ..., total_messages: _Optional[int] = ...) -> None: ... - -class LogSeverityCount(_message.Message): - __slots__ = ("severity", "count") - SEVERITY_FIELD_NUMBER: _ClassVar[int] - COUNT_FIELD_NUMBER: _ClassVar[int] - severity: LogSeverityGroup - count: int - def __init__(self, severity: _Optional[_Union[LogSeverityGroup, str]] = ..., count: _Optional[int] = ...) -> None: ... - -class QueryJobSpansRequest(_message.Message): - __slots__ = ("job_id", "page", "sort_direction", "task_id") - JOB_ID_FIELD_NUMBER: _ClassVar[int] - PAGE_FIELD_NUMBER: _ClassVar[int] - SORT_DIRECTION_FIELD_NUMBER: _ClassVar[int] - TASK_ID_FIELD_NUMBER: _ClassVar[int] - job_id: _id_pb2.ID - page: _query_pb2.Pagination - sort_direction: _query_pb2.SortDirection - task_id: _id_pb2.ID - def __init__(self, job_id: _Optional[_Union[_id_pb2.ID, _Mapping]] = ..., page: _Optional[_Union[_query_pb2.Pagination, _Mapping]] = ..., sort_direction: _Optional[_Union[_query_pb2.SortDirection, str]] = ..., task_id: _Optional[_Union[_id_pb2.ID, _Mapping]] = ...) -> None: ... - -class PaginatedSpansData(_message.Message): - __slots__ = ("resource_spans", "next_page") - RESOURCE_SPANS_FIELD_NUMBER: _ClassVar[int] - NEXT_PAGE_FIELD_NUMBER: _ClassVar[int] - resource_spans: _containers.RepeatedCompositeFieldContainer[_trace_pb2.ResourceSpans] - next_page: _query_pb2.Pagination - def __init__(self, resource_spans: _Optional[_Iterable[_Union[_trace_pb2.ResourceSpans, _Mapping]]] = ..., next_page: _Optional[_Union[_query_pb2.Pagination, _Mapping]] = ...) -> None: ... diff --git a/tilebox-workflows/tilebox/workflows/workflows/v1/telemetry_pb2_grpc.py b/tilebox-workflows/tilebox/workflows/workflows/v1/telemetry_pb2_grpc.py deleted file mode 100644 index bd23ed8..0000000 --- a/tilebox-workflows/tilebox/workflows/workflows/v1/telemetry_pb2_grpc.py +++ /dev/null @@ -1,212 +0,0 @@ -# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! -"""Client and server classes corresponding to protobuf-defined services.""" -import grpc - -from tilebox.workflows.workflows.v1 import telemetry_pb2 as workflows_dot_v1_dot_telemetry__pb2 - - -class TelemetryQueryServiceStub: - """TelemetryQueryService is the service definition for querying telemetry data about workflows such as logs, traces - and metrics. - """ - - def __init__(self, channel): - """Constructor. - - Args: - channel: A grpc.Channel. - """ - self.QueryJobLogs = channel.unary_unary( - '/workflows.v1.TelemetryQueryService/QueryJobLogs', - request_serializer=workflows_dot_v1_dot_telemetry__pb2.QueryJobLogsRequest.SerializeToString, - response_deserializer=workflows_dot_v1_dot_telemetry__pb2.PaginatedLogsData.FromString, - _registered_method=True) - self.QueryLogsInInterval = channel.unary_unary( - '/workflows.v1.TelemetryQueryService/QueryLogsInInterval', - request_serializer=workflows_dot_v1_dot_telemetry__pb2.QueryLogsInIntervalRequest.SerializeToString, - response_deserializer=workflows_dot_v1_dot_telemetry__pb2.PaginatedLogsData.FromString, - _registered_method=True) - self.GetLogMessageCounts = channel.unary_unary( - '/workflows.v1.TelemetryQueryService/GetLogMessageCounts', - request_serializer=workflows_dot_v1_dot_telemetry__pb2.GetLogMessageCountsRequest.SerializeToString, - response_deserializer=workflows_dot_v1_dot_telemetry__pb2.GetLogMessageCountsResponse.FromString, - _registered_method=True) - self.QueryJobSpans = channel.unary_unary( - '/workflows.v1.TelemetryQueryService/QueryJobSpans', - request_serializer=workflows_dot_v1_dot_telemetry__pb2.QueryJobSpansRequest.SerializeToString, - response_deserializer=workflows_dot_v1_dot_telemetry__pb2.PaginatedSpansData.FromString, - _registered_method=True) - - -class TelemetryQueryServiceServicer: - """TelemetryQueryService is the service definition for querying telemetry data about workflows such as logs, traces - and metrics. - """ - - def QueryJobLogs(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def QueryLogsInInterval(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def GetLogMessageCounts(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def QueryJobSpans(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - -def add_TelemetryQueryServiceServicer_to_server(servicer, server): - rpc_method_handlers = { - 'QueryJobLogs': grpc.unary_unary_rpc_method_handler( - servicer.QueryJobLogs, - request_deserializer=workflows_dot_v1_dot_telemetry__pb2.QueryJobLogsRequest.FromString, - response_serializer=workflows_dot_v1_dot_telemetry__pb2.PaginatedLogsData.SerializeToString, - ), - 'QueryLogsInInterval': grpc.unary_unary_rpc_method_handler( - servicer.QueryLogsInInterval, - request_deserializer=workflows_dot_v1_dot_telemetry__pb2.QueryLogsInIntervalRequest.FromString, - response_serializer=workflows_dot_v1_dot_telemetry__pb2.PaginatedLogsData.SerializeToString, - ), - 'GetLogMessageCounts': grpc.unary_unary_rpc_method_handler( - servicer.GetLogMessageCounts, - request_deserializer=workflows_dot_v1_dot_telemetry__pb2.GetLogMessageCountsRequest.FromString, - response_serializer=workflows_dot_v1_dot_telemetry__pb2.GetLogMessageCountsResponse.SerializeToString, - ), - 'QueryJobSpans': grpc.unary_unary_rpc_method_handler( - servicer.QueryJobSpans, - request_deserializer=workflows_dot_v1_dot_telemetry__pb2.QueryJobSpansRequest.FromString, - response_serializer=workflows_dot_v1_dot_telemetry__pb2.PaginatedSpansData.SerializeToString, - ), - } - generic_handler = grpc.method_handlers_generic_handler( - 'workflows.v1.TelemetryQueryService', rpc_method_handlers) - server.add_generic_rpc_handlers((generic_handler,)) - server.add_registered_method_handlers('workflows.v1.TelemetryQueryService', rpc_method_handlers) - - - # This class is part of an EXPERIMENTAL API. -class TelemetryQueryService: - """TelemetryQueryService is the service definition for querying telemetry data about workflows such as logs, traces - and metrics. - """ - - @staticmethod - def QueryJobLogs(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary( - request, - target, - '/workflows.v1.TelemetryQueryService/QueryJobLogs', - workflows_dot_v1_dot_telemetry__pb2.QueryJobLogsRequest.SerializeToString, - workflows_dot_v1_dot_telemetry__pb2.PaginatedLogsData.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) - - @staticmethod - def QueryLogsInInterval(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary( - request, - target, - '/workflows.v1.TelemetryQueryService/QueryLogsInInterval', - workflows_dot_v1_dot_telemetry__pb2.QueryLogsInIntervalRequest.SerializeToString, - workflows_dot_v1_dot_telemetry__pb2.PaginatedLogsData.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) - - @staticmethod - def GetLogMessageCounts(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary( - request, - target, - '/workflows.v1.TelemetryQueryService/GetLogMessageCounts', - workflows_dot_v1_dot_telemetry__pb2.GetLogMessageCountsRequest.SerializeToString, - workflows_dot_v1_dot_telemetry__pb2.GetLogMessageCountsResponse.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) - - @staticmethod - def QueryJobSpans(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary( - request, - target, - '/workflows.v1.TelemetryQueryService/QueryJobSpans', - workflows_dot_v1_dot_telemetry__pb2.QueryJobSpansRequest.SerializeToString, - workflows_dot_v1_dot_telemetry__pb2.PaginatedSpansData.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) diff --git a/tilebox-workflows/tilebox/workflows/workflows/v1/worker_connect.py b/tilebox-workflows/tilebox/workflows/workflows/v1/worker_connect.py deleted file mode 100644 index 82406c8..0000000 --- a/tilebox-workflows/tilebox/workflows/workflows/v1/worker_connect.py +++ /dev/null @@ -1,392 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by https://github.com/connectrpc/connect-python. DO NOT EDIT! -# source: workflows/v1/worker.proto - -from collections.abc import AsyncGenerator, AsyncIterator, Iterable, Iterator, Mapping -from typing import Protocol - -from connectrpc.client import ConnectClient, ConnectClientSync -from connectrpc.code import Code -from connectrpc.codec import Codec -from connectrpc.compression import Compression -from connectrpc.errors import ConnectError -from connectrpc.interceptor import Interceptor, InterceptorSync -from connectrpc.method import IdempotencyLevel, MethodInfo -from connectrpc.request import Headers, RequestContext -from connectrpc.server import ConnectASGIApplication, ConnectWSGIApplication, Endpoint, EndpointSync -import google.protobuf.empty_pb2 as google_dot_protobuf_dot_empty__pb2 -import opentelemetry.proto.logs.v1.logs_pb2 as opentelemetry_dot_proto_dot_logs_dot_v1_dot_logs__pb2 -import tilebox.workflows.workflows.v1.core_pb2 as workflows_dot_v1_dot_core__pb2 -import tilebox.workflows.workflows.v1.worker_pb2 as workflows_dot_v1_dot_worker__pb2 - - -class WorkerService(Protocol): - async def list_registered_tasks(self, request: google_dot_protobuf_dot_empty__pb2.Empty, ctx: RequestContext) -> workflows_dot_v1_dot_core__pb2.TaskIdentifiers: - raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") - - async def initialize_worker(self, request: workflows_dot_v1_dot_worker__pb2.InitializeRunnerRequest, ctx: RequestContext) -> workflows_dot_v1_dot_worker__pb2.InitializeRunnerResponse: - raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") - - async def execute_task(self, request: workflows_dot_v1_dot_core__pb2.Task, ctx: RequestContext) -> workflows_dot_v1_dot_worker__pb2.ExecuteTaskResponse: - raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") - - def watch_logs(self, request: google_dot_protobuf_dot_empty__pb2.Empty, ctx: RequestContext) -> AsyncIterator[opentelemetry_dot_proto_dot_logs_dot_v1_dot_logs__pb2.LogRecord]: - raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") - - async def shutdown_worker(self, request: google_dot_protobuf_dot_empty__pb2.Empty, ctx: RequestContext) -> google_dot_protobuf_dot_empty__pb2.Empty: - raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") - - -class WorkerServiceASGIApplication(ConnectASGIApplication[WorkerService]): - def __init__(self, service: WorkerService | AsyncGenerator[WorkerService], *, interceptors: Iterable[Interceptor]=(), read_max_bytes: int | None = None, compressions: Iterable[Compression] | None = None, codecs: Iterable[Codec] | None = None) -> None: - super().__init__( - service=service, - endpoints=lambda svc: { - "/workflows.v1.WorkerService/ListRegisteredTasks": Endpoint.unary( - method=MethodInfo( - name="ListRegisteredTasks", - service_name="workflows.v1.WorkerService", - input=google_dot_protobuf_dot_empty__pb2.Empty, - output=workflows_dot_v1_dot_core__pb2.TaskIdentifiers, - idempotency_level=IdempotencyLevel.UNKNOWN, - ), - function=svc.list_registered_tasks, - ), - "/workflows.v1.WorkerService/InitializeWorker": Endpoint.unary( - method=MethodInfo( - name="InitializeWorker", - service_name="workflows.v1.WorkerService", - input=workflows_dot_v1_dot_worker__pb2.InitializeRunnerRequest, - output=workflows_dot_v1_dot_worker__pb2.InitializeRunnerResponse, - idempotency_level=IdempotencyLevel.UNKNOWN, - ), - function=svc.initialize_worker, - ), - "/workflows.v1.WorkerService/ExecuteTask": Endpoint.unary( - method=MethodInfo( - name="ExecuteTask", - service_name="workflows.v1.WorkerService", - input=workflows_dot_v1_dot_core__pb2.Task, - output=workflows_dot_v1_dot_worker__pb2.ExecuteTaskResponse, - idempotency_level=IdempotencyLevel.UNKNOWN, - ), - function=svc.execute_task, - ), - "/workflows.v1.WorkerService/WatchLogs": Endpoint.server_stream( - method=MethodInfo( - name="WatchLogs", - service_name="workflows.v1.WorkerService", - input=google_dot_protobuf_dot_empty__pb2.Empty, - output=opentelemetry_dot_proto_dot_logs_dot_v1_dot_logs__pb2.LogRecord, - idempotency_level=IdempotencyLevel.UNKNOWN, - ), - function=svc.watch_logs, - ), - "/workflows.v1.WorkerService/ShutdownWorker": Endpoint.unary( - method=MethodInfo( - name="ShutdownWorker", - service_name="workflows.v1.WorkerService", - input=google_dot_protobuf_dot_empty__pb2.Empty, - output=google_dot_protobuf_dot_empty__pb2.Empty, - idempotency_level=IdempotencyLevel.UNKNOWN, - ), - function=svc.shutdown_worker, - ), - }, - interceptors=interceptors, - read_max_bytes=read_max_bytes, - compressions=compressions, - codecs=codecs, - ) - - @property - def path(self) -> str: - """Returns the URL path to mount the application to when serving multiple applications.""" - return "/workflows.v1.WorkerService" - - -class WorkerServiceClient(ConnectClient): - async def list_registered_tasks( - self, - request: google_dot_protobuf_dot_empty__pb2.Empty, - *, - headers: Headers | Mapping[str, str] | None = None, - timeout_ms: int | None = None, - ) -> workflows_dot_v1_dot_core__pb2.TaskIdentifiers: - return await self.execute_unary( - request=request, - method=MethodInfo( - name="ListRegisteredTasks", - service_name="workflows.v1.WorkerService", - input=google_dot_protobuf_dot_empty__pb2.Empty, - output=workflows_dot_v1_dot_core__pb2.TaskIdentifiers, - idempotency_level=IdempotencyLevel.UNKNOWN, - ), - headers=headers, - timeout_ms=timeout_ms, - ) - - async def initialize_worker( - self, - request: workflows_dot_v1_dot_worker__pb2.InitializeRunnerRequest, - *, - headers: Headers | Mapping[str, str] | None = None, - timeout_ms: int | None = None, - ) -> workflows_dot_v1_dot_worker__pb2.InitializeRunnerResponse: - return await self.execute_unary( - request=request, - method=MethodInfo( - name="InitializeWorker", - service_name="workflows.v1.WorkerService", - input=workflows_dot_v1_dot_worker__pb2.InitializeRunnerRequest, - output=workflows_dot_v1_dot_worker__pb2.InitializeRunnerResponse, - idempotency_level=IdempotencyLevel.UNKNOWN, - ), - headers=headers, - timeout_ms=timeout_ms, - ) - - async def execute_task( - self, - request: workflows_dot_v1_dot_core__pb2.Task, - *, - headers: Headers | Mapping[str, str] | None = None, - timeout_ms: int | None = None, - ) -> workflows_dot_v1_dot_worker__pb2.ExecuteTaskResponse: - return await self.execute_unary( - request=request, - method=MethodInfo( - name="ExecuteTask", - service_name="workflows.v1.WorkerService", - input=workflows_dot_v1_dot_core__pb2.Task, - output=workflows_dot_v1_dot_worker__pb2.ExecuteTaskResponse, - idempotency_level=IdempotencyLevel.UNKNOWN, - ), - headers=headers, - timeout_ms=timeout_ms, - ) - - def watch_logs( - self, - request: google_dot_protobuf_dot_empty__pb2.Empty, - *, - headers: Headers | Mapping[str, str] | None = None, - timeout_ms: int | None = None, - ) -> AsyncIterator[opentelemetry_dot_proto_dot_logs_dot_v1_dot_logs__pb2.LogRecord]: - return self.execute_server_stream( - request=request, - method=MethodInfo( - name="WatchLogs", - service_name="workflows.v1.WorkerService", - input=google_dot_protobuf_dot_empty__pb2.Empty, - output=opentelemetry_dot_proto_dot_logs_dot_v1_dot_logs__pb2.LogRecord, - idempotency_level=IdempotencyLevel.UNKNOWN, - ), - headers=headers, - timeout_ms=timeout_ms, - ) - - async def shutdown_worker( - self, - request: google_dot_protobuf_dot_empty__pb2.Empty, - *, - headers: Headers | Mapping[str, str] | None = None, - timeout_ms: int | None = None, - ) -> google_dot_protobuf_dot_empty__pb2.Empty: - return await self.execute_unary( - request=request, - method=MethodInfo( - name="ShutdownWorker", - service_name="workflows.v1.WorkerService", - input=google_dot_protobuf_dot_empty__pb2.Empty, - output=google_dot_protobuf_dot_empty__pb2.Empty, - idempotency_level=IdempotencyLevel.UNKNOWN, - ), - headers=headers, - timeout_ms=timeout_ms, - ) - - - - - -class WorkerServiceSync(Protocol): - def list_registered_tasks(self, request: google_dot_protobuf_dot_empty__pb2.Empty, ctx: RequestContext) -> workflows_dot_v1_dot_core__pb2.TaskIdentifiers: - raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") - def initialize_worker(self, request: workflows_dot_v1_dot_worker__pb2.InitializeRunnerRequest, ctx: RequestContext) -> workflows_dot_v1_dot_worker__pb2.InitializeRunnerResponse: - raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") - def execute_task(self, request: workflows_dot_v1_dot_core__pb2.Task, ctx: RequestContext) -> workflows_dot_v1_dot_worker__pb2.ExecuteTaskResponse: - raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") - def watch_logs(self, request: google_dot_protobuf_dot_empty__pb2.Empty, ctx: RequestContext) -> Iterator[opentelemetry_dot_proto_dot_logs_dot_v1_dot_logs__pb2.LogRecord]: - raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") - def shutdown_worker(self, request: google_dot_protobuf_dot_empty__pb2.Empty, ctx: RequestContext) -> google_dot_protobuf_dot_empty__pb2.Empty: - raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") - - -class WorkerServiceWSGIApplication(ConnectWSGIApplication): - def __init__(self, service: WorkerServiceSync, interceptors: Iterable[InterceptorSync]=(), read_max_bytes: int | None = None, compressions: Iterable[Compression] | None = None, codecs: Iterable[Codec] | None = None) -> None: - super().__init__( - endpoints={ - "/workflows.v1.WorkerService/ListRegisteredTasks": EndpointSync.unary( - method=MethodInfo( - name="ListRegisteredTasks", - service_name="workflows.v1.WorkerService", - input=google_dot_protobuf_dot_empty__pb2.Empty, - output=workflows_dot_v1_dot_core__pb2.TaskIdentifiers, - idempotency_level=IdempotencyLevel.UNKNOWN, - ), - function=service.list_registered_tasks, - ), - "/workflows.v1.WorkerService/InitializeWorker": EndpointSync.unary( - method=MethodInfo( - name="InitializeWorker", - service_name="workflows.v1.WorkerService", - input=workflows_dot_v1_dot_worker__pb2.InitializeRunnerRequest, - output=workflows_dot_v1_dot_worker__pb2.InitializeRunnerResponse, - idempotency_level=IdempotencyLevel.UNKNOWN, - ), - function=service.initialize_worker, - ), - "/workflows.v1.WorkerService/ExecuteTask": EndpointSync.unary( - method=MethodInfo( - name="ExecuteTask", - service_name="workflows.v1.WorkerService", - input=workflows_dot_v1_dot_core__pb2.Task, - output=workflows_dot_v1_dot_worker__pb2.ExecuteTaskResponse, - idempotency_level=IdempotencyLevel.UNKNOWN, - ), - function=service.execute_task, - ), - "/workflows.v1.WorkerService/WatchLogs": EndpointSync.server_stream( - method=MethodInfo( - name="WatchLogs", - service_name="workflows.v1.WorkerService", - input=google_dot_protobuf_dot_empty__pb2.Empty, - output=opentelemetry_dot_proto_dot_logs_dot_v1_dot_logs__pb2.LogRecord, - idempotency_level=IdempotencyLevel.UNKNOWN, - ), - function=service.watch_logs, - ), - "/workflows.v1.WorkerService/ShutdownWorker": EndpointSync.unary( - method=MethodInfo( - name="ShutdownWorker", - service_name="workflows.v1.WorkerService", - input=google_dot_protobuf_dot_empty__pb2.Empty, - output=google_dot_protobuf_dot_empty__pb2.Empty, - idempotency_level=IdempotencyLevel.UNKNOWN, - ), - function=service.shutdown_worker, - ), - }, - interceptors=interceptors, - read_max_bytes=read_max_bytes, - compressions=compressions, - codecs=codecs, - ) - - @property - def path(self) -> str: - """Returns the URL path to mount the application to when serving multiple applications.""" - return "/workflows.v1.WorkerService" - - -class WorkerServiceClientSync(ConnectClientSync): - def list_registered_tasks( - self, - request: google_dot_protobuf_dot_empty__pb2.Empty, - *, - headers: Headers | Mapping[str, str] | None = None, - timeout_ms: int | None = None, - ) -> workflows_dot_v1_dot_core__pb2.TaskIdentifiers: - return self.execute_unary( - request=request, - method=MethodInfo( - name="ListRegisteredTasks", - service_name="workflows.v1.WorkerService", - input=google_dot_protobuf_dot_empty__pb2.Empty, - output=workflows_dot_v1_dot_core__pb2.TaskIdentifiers, - idempotency_level=IdempotencyLevel.UNKNOWN, - ), - headers=headers, - timeout_ms=timeout_ms, - ) - - def initialize_worker( - self, - request: workflows_dot_v1_dot_worker__pb2.InitializeRunnerRequest, - *, - headers: Headers | Mapping[str, str] | None = None, - timeout_ms: int | None = None, - ) -> workflows_dot_v1_dot_worker__pb2.InitializeRunnerResponse: - return self.execute_unary( - request=request, - method=MethodInfo( - name="InitializeWorker", - service_name="workflows.v1.WorkerService", - input=workflows_dot_v1_dot_worker__pb2.InitializeRunnerRequest, - output=workflows_dot_v1_dot_worker__pb2.InitializeRunnerResponse, - idempotency_level=IdempotencyLevel.UNKNOWN, - ), - headers=headers, - timeout_ms=timeout_ms, - ) - - def execute_task( - self, - request: workflows_dot_v1_dot_core__pb2.Task, - *, - headers: Headers | Mapping[str, str] | None = None, - timeout_ms: int | None = None, - ) -> workflows_dot_v1_dot_worker__pb2.ExecuteTaskResponse: - return self.execute_unary( - request=request, - method=MethodInfo( - name="ExecuteTask", - service_name="workflows.v1.WorkerService", - input=workflows_dot_v1_dot_core__pb2.Task, - output=workflows_dot_v1_dot_worker__pb2.ExecuteTaskResponse, - idempotency_level=IdempotencyLevel.UNKNOWN, - ), - headers=headers, - timeout_ms=timeout_ms, - ) - - def watch_logs( - self, - request: google_dot_protobuf_dot_empty__pb2.Empty, - *, - headers: Headers | Mapping[str, str] | None = None, - timeout_ms: int | None = None, - ) -> Iterator[opentelemetry_dot_proto_dot_logs_dot_v1_dot_logs__pb2.LogRecord]: - return self.execute_server_stream( - request=request, - method=MethodInfo( - name="WatchLogs", - service_name="workflows.v1.WorkerService", - input=google_dot_protobuf_dot_empty__pb2.Empty, - output=opentelemetry_dot_proto_dot_logs_dot_v1_dot_logs__pb2.LogRecord, - idempotency_level=IdempotencyLevel.UNKNOWN, - ), - headers=headers, - timeout_ms=timeout_ms, - ) - - def shutdown_worker( - self, - request: google_dot_protobuf_dot_empty__pb2.Empty, - *, - headers: Headers | Mapping[str, str] | None = None, - timeout_ms: int | None = None, - ) -> google_dot_protobuf_dot_empty__pb2.Empty: - return self.execute_unary( - request=request, - method=MethodInfo( - name="ShutdownWorker", - service_name="workflows.v1.WorkerService", - input=google_dot_protobuf_dot_empty__pb2.Empty, - output=google_dot_protobuf_dot_empty__pb2.Empty, - idempotency_level=IdempotencyLevel.UNKNOWN, - ), - headers=headers, - timeout_ms=timeout_ms, - ) diff --git a/tilebox-workflows/tilebox/workflows/workflows/v1/worker_pb2.py b/tilebox-workflows/tilebox/workflows/workflows/v1/worker_pb2.py deleted file mode 100644 index 7cfb16c..0000000 --- a/tilebox-workflows/tilebox/workflows/workflows/v1/worker_pb2.py +++ /dev/null @@ -1,51 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by the protocol buffer compiler. DO NOT EDIT! -# NO CHECKED-IN PROTOBUF GENCODE -# source: workflows/v1/worker.proto -# Protobuf Python Version: 6.30.0 -"""Generated protocol buffer code.""" -from google.protobuf import descriptor as _descriptor -from google.protobuf import descriptor_pool as _descriptor_pool -from google.protobuf import runtime_version as _runtime_version -from google.protobuf import symbol_database as _symbol_database -from google.protobuf.internal import builder as _builder -_runtime_version.ValidateProtobufRuntimeVersion( - _runtime_version.Domain.PUBLIC, - 6, - 30, - 0, - '', - 'workflows/v1/worker.proto' -) -# @@protoc_insertion_point(imports) - -_sym_db = _symbol_database.Default() - - -from google.protobuf import empty_pb2 as google_dot_protobuf_dot_empty__pb2 -from opentelemetry.proto.logs.v1 import logs_pb2 as opentelemetry_dot_proto_dot_logs_dot_v1_dot_logs__pb2 -from tilebox.datasets.tilebox.v1 import id_pb2 as tilebox_dot_v1_dot_id__pb2 -from tilebox.workflows.workflows.v1 import core_pb2 as workflows_dot_v1_dot_core__pb2 -from tilebox.workflows.workflows.v1 import task_pb2 as workflows_dot_v1_dot_task__pb2 -from tilebox.workflows.workflows.v1 import workflows_pb2 as workflows_dot_v1_dot_workflows__pb2 - - -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x19workflows/v1/worker.proto\x12\x0cworkflows.v1\x1a\x1bgoogle/protobuf/empty.proto\x1a&opentelemetry/proto/logs/v1/logs.proto\x1a\x13tilebox/v1/id.proto\x1a\x17workflows/v1/core.proto\x1a\x17workflows/v1/task.proto\x1a\x1cworkflows/v1/workflows.proto\"\x99\x02\n\x17InitializeRunnerRequest\x12+\n\trunner_id\x18\x01 \x01(\x0b\x32\x0e.tilebox.v1.IDR\x08runnerId\x12!\n\x0ctrace_parent\x18\x02 \x01(\tR\x0btraceParent\x12/\n\x07\x63luster\x18\x03 \x01(\x0b\x32\x15.workflows.v1.ClusterR\x07\x63luster\x12\x32\n\x08workflow\x18\x04 \x01(\x0b\x32\x16.workflows.v1.WorkflowR\x08workflow\x12I\n\x0e\x61pi_connection\x18\x05 \x01(\x0b\x32\".workflows.v1.TileboxAPIConnectionR\rapiConnection\">\n\x14TileboxAPIConnection\x12\x10\n\x03url\x18\x01 \x01(\tR\x03url\x12\x14\n\x05token\x18\x02 \x01(\tR\x05token\"\x1a\n\x18InitializeRunnerResponse\"\x98\x01\n\x13\x45xecuteTaskResponse\x12?\n\rcomputed_task\x18\x01 \x01(\x0b\x32\x1a.workflows.v1.ComputedTaskR\x0c\x63omputedTask\x12@\n\x0b\x66\x61iled_task\x18\x02 \x01(\x0b\x32\x1f.workflows.v1.TaskFailedRequestR\nfailedTask2\x97\x03\n\rWorkerService\x12L\n\x13ListRegisteredTasks\x12\x16.google.protobuf.Empty\x1a\x1d.workflows.v1.TaskIdentifiers\x12\x61\n\x10InitializeWorker\x12%.workflows.v1.InitializeRunnerRequest\x1a&.workflows.v1.InitializeRunnerResponse\x12\x44\n\x0b\x45xecuteTask\x12\x12.workflows.v1.Task\x1a!.workflows.v1.ExecuteTaskResponse\x12M\n\tWatchLogs\x12\x16.google.protobuf.Empty\x1a&.opentelemetry.proto.logs.v1.LogRecord0\x01\x12@\n\x0eShutdownWorker\x12\x16.google.protobuf.Empty\x1a\x16.google.protobuf.EmptyBp\n\x10\x63om.workflows.v1B\x0bWorkerProtoP\x01\xa2\x02\x03WXX\xaa\x02\x0cWorkflows.V1\xca\x02\x0cWorkflows\\V1\xe2\x02\x18Workflows\\V1\\GPBMetadata\xea\x02\rWorkflows::V1b\x08\x65\x64itionsp\xe8\x07') - -_globals = globals() -_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) -_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'workflows.v1.worker_pb2', _globals) -if not _descriptor._USE_C_DESCRIPTORS: - _globals['DESCRIPTOR']._loaded_options = None - _globals['DESCRIPTOR']._serialized_options = b'\n\020com.workflows.v1B\013WorkerProtoP\001\242\002\003WXX\252\002\014Workflows.V1\312\002\014Workflows\\V1\342\002\030Workflows\\V1\\GPBMetadata\352\002\rWorkflows::V1' - _globals['_INITIALIZERUNNERREQUEST']._serialized_start=214 - _globals['_INITIALIZERUNNERREQUEST']._serialized_end=495 - _globals['_TILEBOXAPICONNECTION']._serialized_start=497 - _globals['_TILEBOXAPICONNECTION']._serialized_end=559 - _globals['_INITIALIZERUNNERRESPONSE']._serialized_start=561 - _globals['_INITIALIZERUNNERRESPONSE']._serialized_end=587 - _globals['_EXECUTETASKRESPONSE']._serialized_start=590 - _globals['_EXECUTETASKRESPONSE']._serialized_end=742 - _globals['_WORKERSERVICE']._serialized_start=745 - _globals['_WORKERSERVICE']._serialized_end=1152 -# @@protoc_insertion_point(module_scope) diff --git a/tilebox-workflows/tilebox/workflows/workflows/v1/worker_pb2.pyi b/tilebox-workflows/tilebox/workflows/workflows/v1/worker_pb2.pyi deleted file mode 100644 index b952175..0000000 --- a/tilebox-workflows/tilebox/workflows/workflows/v1/worker_pb2.pyi +++ /dev/null @@ -1,46 +0,0 @@ -from google.protobuf import empty_pb2 as _empty_pb2 -from opentelemetry.proto.logs.v1 import logs_pb2 as _logs_pb2 -from tilebox.datasets.tilebox.v1 import id_pb2 as _id_pb2 -from tilebox.workflows.workflows.v1 import core_pb2 as _core_pb2 -from tilebox.workflows.workflows.v1 import task_pb2 as _task_pb2 -from tilebox.workflows.workflows.v1 import workflows_pb2 as _workflows_pb2 -from google.protobuf import descriptor as _descriptor -from google.protobuf import message as _message -from collections.abc import Mapping as _Mapping -from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union - -DESCRIPTOR: _descriptor.FileDescriptor - -class InitializeRunnerRequest(_message.Message): - __slots__ = ("runner_id", "trace_parent", "cluster", "workflow", "api_connection") - RUNNER_ID_FIELD_NUMBER: _ClassVar[int] - TRACE_PARENT_FIELD_NUMBER: _ClassVar[int] - CLUSTER_FIELD_NUMBER: _ClassVar[int] - WORKFLOW_FIELD_NUMBER: _ClassVar[int] - API_CONNECTION_FIELD_NUMBER: _ClassVar[int] - runner_id: _id_pb2.ID - trace_parent: str - cluster: _workflows_pb2.Cluster - workflow: _workflows_pb2.Workflow - api_connection: TileboxAPIConnection - def __init__(self, runner_id: _Optional[_Union[_id_pb2.ID, _Mapping]] = ..., trace_parent: _Optional[str] = ..., cluster: _Optional[_Union[_workflows_pb2.Cluster, _Mapping]] = ..., workflow: _Optional[_Union[_workflows_pb2.Workflow, _Mapping]] = ..., api_connection: _Optional[_Union[TileboxAPIConnection, _Mapping]] = ...) -> None: ... - -class TileboxAPIConnection(_message.Message): - __slots__ = ("url", "token") - URL_FIELD_NUMBER: _ClassVar[int] - TOKEN_FIELD_NUMBER: _ClassVar[int] - url: str - token: str - def __init__(self, url: _Optional[str] = ..., token: _Optional[str] = ...) -> None: ... - -class InitializeRunnerResponse(_message.Message): - __slots__ = () - def __init__(self) -> None: ... - -class ExecuteTaskResponse(_message.Message): - __slots__ = ("computed_task", "failed_task") - COMPUTED_TASK_FIELD_NUMBER: _ClassVar[int] - FAILED_TASK_FIELD_NUMBER: _ClassVar[int] - computed_task: _task_pb2.ComputedTask - failed_task: _task_pb2.TaskFailedRequest - def __init__(self, computed_task: _Optional[_Union[_task_pb2.ComputedTask, _Mapping]] = ..., failed_task: _Optional[_Union[_task_pb2.TaskFailedRequest, _Mapping]] = ...) -> None: ... diff --git a/tilebox-workflows/tilebox/workflows/workflows/v1/worker_pb2_grpc.py b/tilebox-workflows/tilebox/workflows/workflows/v1/worker_pb2_grpc.py deleted file mode 100644 index c34721b..0000000 --- a/tilebox-workflows/tilebox/workflows/workflows/v1/worker_pb2_grpc.py +++ /dev/null @@ -1,276 +0,0 @@ -# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! -"""Client and server classes corresponding to protobuf-defined services.""" -import grpc - -from google.protobuf import empty_pb2 as google_dot_protobuf_dot_empty__pb2 -from opentelemetry.proto.logs.v1 import logs_pb2 as opentelemetry_dot_proto_dot_logs_dot_v1_dot_logs__pb2 -from tilebox.workflows.workflows.v1 import core_pb2 as workflows_dot_v1_dot_core__pb2 -from tilebox.workflows.workflows.v1 import worker_pb2 as workflows_dot_v1_dot_worker__pb2 - - -class WorkerServiceStub: - """WorkerService defines the RPC interface between a runner and a a worker runtime that can execute workflow tasks. - """ - - def __init__(self, channel): - """Constructor. - - Args: - channel: A grpc.Channel. - """ - self.ListRegisteredTasks = channel.unary_unary( - '/workflows.v1.WorkerService/ListRegisteredTasks', - request_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, - response_deserializer=workflows_dot_v1_dot_core__pb2.TaskIdentifiers.FromString, - _registered_method=True) - self.InitializeWorker = channel.unary_unary( - '/workflows.v1.WorkerService/InitializeWorker', - request_serializer=workflows_dot_v1_dot_worker__pb2.InitializeRunnerRequest.SerializeToString, - response_deserializer=workflows_dot_v1_dot_worker__pb2.InitializeRunnerResponse.FromString, - _registered_method=True) - self.ExecuteTask = channel.unary_unary( - '/workflows.v1.WorkerService/ExecuteTask', - request_serializer=workflows_dot_v1_dot_core__pb2.Task.SerializeToString, - response_deserializer=workflows_dot_v1_dot_worker__pb2.ExecuteTaskResponse.FromString, - _registered_method=True) - self.WatchLogs = channel.unary_stream( - '/workflows.v1.WorkerService/WatchLogs', - request_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, - response_deserializer=opentelemetry_dot_proto_dot_logs_dot_v1_dot_logs__pb2.LogRecord.FromString, - _registered_method=True) - self.ShutdownWorker = channel.unary_unary( - '/workflows.v1.WorkerService/ShutdownWorker', - request_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, - response_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, - _registered_method=True) - - -class WorkerServiceServicer: - """WorkerService defines the RPC interface between a runner and a a worker runtime that can execute workflow tasks. - """ - - def ListRegisteredTasks(self, request, context): - """ListRegisteredTasks is called by the runner to discover the task identifiers that a worker runtime can execute. - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def InitializeWorker(self, request, context): - """InitializeWorker is called by the runner to initialize a worker runtime with some known metadata about the - runner. This allows us to add those metadata to any logs and traces emitted by the worker. - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def ExecuteTask(self, request, context): - """ExecuteTask is called by the runner to execute a task on the worker runtime. The worker runtime will execute the - task, and then respond with the result of the task execution, which can either be a computed task, or a failed - task. If the worker runtime crashes or becomes unreachable during task execution, the runner will treat the task - as failed with an unknown error. - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def WatchLogs(self, request, context): - """WatchLogs streams structured runtime logs to the local runner, independently of API log exports and raw - stdout/stderr. It is available before workflow import completes and before InitializeWorker is called. - The runner sends one request; the worker sends buffered startup records followed by live records in enqueue - order. The stream stays open until shutdown, cancellation, or a connection failure, even while no tasks run. - Only one subscriber is allowed per runtime; concurrent subscriptions fail with ALREADY_EXISTS. - Delivery is best-effort with bounded buffering: slow or disconnected subscribers must not block tasks. - Buffer overflow is reported as a warning record when delivery resumes. There is no acknowledgement or replay - of delivered records. Canceling this stream does not shut down the worker or disable its API log exports. - Messages use the log body, severity, trace/span IDs, and attributes from the OpenTelemetry log model; - exception details use exception.type, exception.message, and exception.stacktrace attributes. - buf:lint:ignore RPC_NO_SERVER_STREAMING - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def ShutdownWorker(self, request, context): - """Gracefully shuts down the worker runtime. After receiving this request, the worker runtime will - finish task cleanup, flush API logs, and drain buffered WatchLogs records within a bounded deadline. - The log stream ends before the worker stops its RPC server; an open subscription must not prevent shutdown. - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - -def add_WorkerServiceServicer_to_server(servicer, server): - rpc_method_handlers = { - 'ListRegisteredTasks': grpc.unary_unary_rpc_method_handler( - servicer.ListRegisteredTasks, - request_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, - response_serializer=workflows_dot_v1_dot_core__pb2.TaskIdentifiers.SerializeToString, - ), - 'InitializeWorker': grpc.unary_unary_rpc_method_handler( - servicer.InitializeWorker, - request_deserializer=workflows_dot_v1_dot_worker__pb2.InitializeRunnerRequest.FromString, - response_serializer=workflows_dot_v1_dot_worker__pb2.InitializeRunnerResponse.SerializeToString, - ), - 'ExecuteTask': grpc.unary_unary_rpc_method_handler( - servicer.ExecuteTask, - request_deserializer=workflows_dot_v1_dot_core__pb2.Task.FromString, - response_serializer=workflows_dot_v1_dot_worker__pb2.ExecuteTaskResponse.SerializeToString, - ), - 'WatchLogs': grpc.unary_stream_rpc_method_handler( - servicer.WatchLogs, - request_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, - response_serializer=opentelemetry_dot_proto_dot_logs_dot_v1_dot_logs__pb2.LogRecord.SerializeToString, - ), - 'ShutdownWorker': grpc.unary_unary_rpc_method_handler( - servicer.ShutdownWorker, - request_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, - response_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, - ), - } - generic_handler = grpc.method_handlers_generic_handler( - 'workflows.v1.WorkerService', rpc_method_handlers) - server.add_generic_rpc_handlers((generic_handler,)) - server.add_registered_method_handlers('workflows.v1.WorkerService', rpc_method_handlers) - - - # This class is part of an EXPERIMENTAL API. -class WorkerService: - """WorkerService defines the RPC interface between a runner and a a worker runtime that can execute workflow tasks. - """ - - @staticmethod - def ListRegisteredTasks(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary( - request, - target, - '/workflows.v1.WorkerService/ListRegisteredTasks', - google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, - workflows_dot_v1_dot_core__pb2.TaskIdentifiers.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) - - @staticmethod - def InitializeWorker(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary( - request, - target, - '/workflows.v1.WorkerService/InitializeWorker', - workflows_dot_v1_dot_worker__pb2.InitializeRunnerRequest.SerializeToString, - workflows_dot_v1_dot_worker__pb2.InitializeRunnerResponse.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) - - @staticmethod - def ExecuteTask(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary( - request, - target, - '/workflows.v1.WorkerService/ExecuteTask', - workflows_dot_v1_dot_core__pb2.Task.SerializeToString, - workflows_dot_v1_dot_worker__pb2.ExecuteTaskResponse.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) - - @staticmethod - def WatchLogs(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_stream( - request, - target, - '/workflows.v1.WorkerService/WatchLogs', - google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, - opentelemetry_dot_proto_dot_logs_dot_v1_dot_logs__pb2.LogRecord.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) - - @staticmethod - def ShutdownWorker(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary( - request, - target, - '/workflows.v1.WorkerService/ShutdownWorker', - google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, - google_dot_protobuf_dot_empty__pb2.Empty.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) diff --git a/tilebox-workflows/tilebox/workflows/workflows/v1/workflows_pb2.py b/tilebox-workflows/tilebox/workflows/workflows/v1/workflows_pb2.py deleted file mode 100644 index d994b78..0000000 --- a/tilebox-workflows/tilebox/workflows/workflows/v1/workflows_pb2.py +++ /dev/null @@ -1,121 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by the protocol buffer compiler. DO NOT EDIT! -# NO CHECKED-IN PROTOBUF GENCODE -# source: workflows/v1/workflows.proto -# Protobuf Python Version: 6.30.0 -"""Generated protocol buffer code.""" -from google.protobuf import descriptor as _descriptor -from google.protobuf import descriptor_pool as _descriptor_pool -from google.protobuf import runtime_version as _runtime_version -from google.protobuf import symbol_database as _symbol_database -from google.protobuf.internal import builder as _builder -_runtime_version.ValidateProtobufRuntimeVersion( - _runtime_version.Domain.PUBLIC, - 6, - 30, - 0, - '', - 'workflows/v1/workflows.proto' -) -# @@protoc_insertion_point(imports) - -_sym_db = _symbol_database.Default() - - -from google.protobuf import timestamp_pb2 as google_dot_protobuf_dot_timestamp__pb2 -from tilebox.datasets.tilebox.v1 import id_pb2 as tilebox_dot_v1_dot_id__pb2 -from tilebox.datasets.tilebox.v1 import query_pb2 as tilebox_dot_v1_dot_query__pb2 -from tilebox.workflows.workflows.v1 import core_pb2 as workflows_dot_v1_dot_core__pb2 - - -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1cworkflows/v1/workflows.proto\x12\x0cworkflows.v1\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\x13tilebox/v1/id.proto\x1a\x16tilebox/v1/query.proto\x1a\x17workflows/v1/core.proto\"\xcb\x01\n\x07\x43luster\x12\x12\n\x04slug\x18\x02 \x01(\tR\x04slug\x12!\n\x0c\x64isplay_name\x18\x03 \x01(\tR\x0b\x64isplayName\x12 \n\x0b\x64\x65scription\x18\x06 \x01(\tR\x0b\x64\x65scription\x12\x1c\n\tdeletable\x18\x04 \x01(\x08R\tdeletable\x12\x43\n\x11\x64\x65ployed_releases\x18\x05 \x03(\x0b\x32\x16.workflows.v1.WorkflowR\x10\x64\x65ployedReleasesJ\x04\x08\x01\x10\x02\"`\n\x14\x43reateClusterRequest\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\x12 \n\x0b\x64\x65scription\x18\x02 \x01(\tR\x0b\x64\x65scription\x12\x12\n\x04slug\x18\x03 \x01(\tR\x04slug\"6\n\x11GetClusterRequest\x12!\n\x0c\x63luster_slug\x18\x01 \x01(\tR\x0b\x63lusterSlug\"}\n\x14UpdateClusterRequest\x12!\n\x0c\x63luster_slug\x18\x01 \x01(\tR\x0b\x63lusterSlug\x12\x19\n\x04name\x18\x02 \x01(\tB\x05\xaa\x01\x02\x08\x01R\x04name\x12\'\n\x0b\x64\x65scription\x18\x03 \x01(\tB\x05\xaa\x01\x02\x08\x01R\x0b\x64\x65scription\"9\n\x14\x44\x65leteClusterRequest\x12!\n\x0c\x63luster_slug\x18\x01 \x01(\tR\x0b\x63lusterSlug\"\x17\n\x15\x44\x65leteClusterResponse\"\x15\n\x13ListClustersRequest\"I\n\x14ListClustersResponse\x12\x31\n\x08\x63lusters\x18\x01 \x03(\x0b\x32\x15.workflows.v1.ClusterR\x08\x63lusters\"9\n\x12GetWorkflowRequest\x12#\n\rworkflow_slug\x18\x01 \x01(\tR\x0cworkflowSlug\"\xa1\x01\n\x15UpdateWorkflowRequest\x12#\n\rworkflow_slug\x18\x01 \x01(\tR\x0cworkflowSlug\x12\x19\n\x04name\x18\x02 \x01(\tB\x05\xaa\x01\x02\x08\x01R\x04name\x12\x1f\n\x07summary\x18\x04 \x01(\tB\x05\xaa\x01\x02\x08\x01R\x07summary\x12\'\n\x0b\x64\x65scription\x18\x03 \x01(\tB\x05\xaa\x01\x02\x08\x01R\x0b\x64\x65scription\"<\n\x15\x44\x65leteWorkflowRequest\x12#\n\rworkflow_slug\x18\x01 \x01(\tR\x0cworkflowSlug\"\x18\n\x16\x44\x65leteWorkflowResponse\"\xad\x01\n\x1dPublishWorkflowReleaseRequest\x12#\n\rworkflow_slug\x18\x01 \x01(\tR\x0cworkflowSlug\x12/\n\x0b\x61rtifact_id\x18\x02 \x01(\x0b\x32\x0e.tilebox.v1.IDR\nartifactId\x12\x36\n\x07\x63ontent\x18\x03 \x01(\x0b\x32\x1c.workflows.v1.ReleaseContentR\x07\x63ontent\"u\n\x1fUnpublishWorkflowReleaseRequest\x12#\n\rworkflow_slug\x18\x01 \x01(\tR\x0cworkflowSlug\x12-\n\nrelease_id\x18\x02 \x01(\x0b\x32\x0e.tilebox.v1.IDR\treleaseId\"\"\n UnpublishWorkflowReleaseResponse\"I\n\x14ListWorkflowsRequest\x12\x31\n\x04page\x18\x01 \x01(\x0b\x32\x16.tilebox.v1.PaginationB\x05\xaa\x01\x02\x08\x01R\x04page\"\x89\x01\n\x15ListWorkflowsResponse\x12\x34\n\tworkflows\x18\x01 \x03(\x0b\x32\x16.workflows.v1.WorkflowR\tworkflows\x12:\n\tnext_page\x18\x02 \x01(\x0b\x32\x16.tilebox.v1.PaginationB\x05\xaa\x01\x02\x08\x01R\x08nextPage\"O\n\x1aListPublicWorkflowsRequest\x12\x31\n\x04page\x18\x01 \x01(\x0b\x32\x16.tilebox.v1.PaginationB\x05\xaa\x01\x02\x08\x01R\x04page\"\x8f\x01\n\x1bListPublicWorkflowsResponse\x12\x34\n\tworkflows\x18\x01 \x03(\x0b\x32\x16.workflows.v1.WorkflowR\tworkflows\x12:\n\tnext_page\x18\x02 \x01(\x0b\x32\x16.tilebox.v1.PaginationB\x05\xaa\x01\x02\x08\x01R\x08nextPage\"n\n\x15\x43reateWorkflowRequest\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\x12\x18\n\x07summary\x18\x03 \x01(\tR\x07summary\x12\'\n\x0b\x64\x65scription\x18\x02 \x01(\tB\x05\xaa\x01\x02\x08\x01R\x0b\x64\x65scription\"\xf7\x01\n\x08Workflow\x12\x12\n\x04slug\x18\x02 \x01(\tR\x04slug\x12\x12\n\x04name\x18\x03 \x01(\tR\x04name\x12\x18\n\x07summary\x18\x06 \x01(\tR\x07summary\x12 \n\x0b\x64\x65scription\x18\x04 \x01(\tR\x0b\x64\x65scription\x12\x39\n\x08releases\x18\x05 \x03(\x0b\x32\x1d.workflows.v1.WorkflowReleaseR\x08releases\x12\x46\n\x0cpresentation\x18\x07 \x01(\x0b\x32\".workflows.v1.WorkflowPresentationR\x0cpresentationJ\x04\x08\x01\x10\x02\"\xb3\x01\n\x14WorkflowPresentation\x12\x1b\n\timage_url\x18\x01 \x01(\tR\x08imageUrl\x12\x1b\n\timage_alt\x18\x02 \x01(\tR\x08imageAlt\x12!\n\x0cimagery_type\x18\x03 \x03(\tR\x0bimageryType\x12\x19\n\x08use_case\x18\x04 \x03(\tR\x07useCase\x12#\n\rdataset_label\x18\x05 \x03(\tR\x0c\x64\x61tasetLabel\"\x8b\x02\n\x0fWorkflowRelease\x12\x1e\n\x02id\x18\x01 \x01(\x0b\x32\x0e.tilebox.v1.IDR\x02id\x12\x32\n\x08\x61rtifact\x18\x02 \x01(\x0b\x32\x16.workflows.v1.ArtifactR\x08\x61rtifact\x12\x36\n\x07\x63ontent\x18\x03 \x01(\x0b\x32\x1c.workflows.v1.ReleaseContentR\x07\x63ontent\x12\x39\n\ncreated_at\x18\x04 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\tcreatedAt\x12\x31\n\x08\x63lusters\x18\x05 \x03(\x0b\x32\x15.workflows.v1.ClusterR\x08\x63lusters\"B\n\x08\x41rtifact\x12\x1e\n\x02id\x18\x01 \x01(\x0b\x32\x0e.tilebox.v1.IDR\x02id\x12\x16\n\x06\x64igest\x18\x02 \x01(\tR\x06\x64igest\"\xe9\x01\n\x0eReleaseContent\x12 \n\x0b\x66ingerprint\x18\x01 \x01(\tR\x0b\x66ingerprint\x12\x32\n\x05tasks\x18\x02 \x03(\x0b\x32\x1c.workflows.v1.TaskIdentifierR\x05tasks\x12(\n\x05\x66iles\x18\x03 \x03(\x0b\x32\x12.workflows.v1.PathR\x05\x66iles\x12,\n\x12runner_object_path\x18\x04 \x01(\tR\x10runnerObjectPath\x12)\n\x10\x63ommand_override\x18\x05 \x03(\tR\x0f\x63ommandOverride\"h\n\x04Path\x12\x12\n\x04path\x18\x01 \x01(\tR\x04path\x12\x1c\n\tdirectory\x18\x02 \x01(\x08R\tdirectory\x12.\n\x08\x63hildren\x18\x03 \x03(\x0b\x32\x12.workflows.v1.PathR\x08\x63hildren\"\x97\x01\n\x1c\x44\x65ployWorkflowReleaseRequest\x12#\n\rworkflow_slug\x18\x01 \x01(\tR\x0cworkflowSlug\x12-\n\nrelease_id\x18\x02 \x01(\x0b\x32\x0e.tilebox.v1.IDR\treleaseId\x12#\n\rcluster_slugs\x18\x03 \x03(\tR\x0c\x63lusterSlugs\"\x8b\x01\n\x1d\x44\x65ployWorkflowReleaseResponse\x12\x37\n\x07release\x18\x01 \x01(\x0b\x32\x1d.workflows.v1.WorkflowReleaseR\x07release\x12\x31\n\x08\x63lusters\x18\x02 \x03(\x0b\x32\x15.workflows.v1.ClusterR\x08\x63lusters\"\x99\x01\n\x1eUndeployWorkflowReleaseRequest\x12#\n\rworkflow_slug\x18\x01 \x01(\tR\x0cworkflowSlug\x12-\n\nrelease_id\x18\x02 \x01(\x0b\x32\x0e.tilebox.v1.IDR\treleaseId\x12#\n\rcluster_slugs\x18\x03 \x03(\tR\x0c\x63lusterSlugs\"\x8d\x01\n\x1fUndeployWorkflowReleaseResponse\x12\x37\n\x07release\x18\x01 \x01(\x0b\x32\x1d.workflows.v1.WorkflowReleaseR\x07release\x12\x31\n\x08\x63lusters\x18\x02 \x03(\x0b\x32\x15.workflows.v1.ClusterR\x08\x63lusters2\xf6\n\n\x10WorkflowsService\x12J\n\rCreateCluster\x12\".workflows.v1.CreateClusterRequest\x1a\x15.workflows.v1.Cluster\x12\x44\n\nGetCluster\x12\x1f.workflows.v1.GetClusterRequest\x1a\x15.workflows.v1.Cluster\x12J\n\rUpdateCluster\x12\".workflows.v1.UpdateClusterRequest\x1a\x15.workflows.v1.Cluster\x12X\n\rDeleteCluster\x12\".workflows.v1.DeleteClusterRequest\x1a#.workflows.v1.DeleteClusterResponse\x12U\n\x0cListClusters\x12!.workflows.v1.ListClustersRequest\x1a\".workflows.v1.ListClustersResponse\x12M\n\x0e\x43reateWorkflow\x12#.workflows.v1.CreateWorkflowRequest\x1a\x16.workflows.v1.Workflow\x12X\n\rListWorkflows\x12\".workflows.v1.ListWorkflowsRequest\x1a#.workflows.v1.ListWorkflowsResponse\x12j\n\x13ListPublicWorkflows\x12(.workflows.v1.ListPublicWorkflowsRequest\x1a).workflows.v1.ListPublicWorkflowsResponse\x12G\n\x0bGetWorkflow\x12 .workflows.v1.GetWorkflowRequest\x1a\x16.workflows.v1.Workflow\x12M\n\x0eUpdateWorkflow\x12#.workflows.v1.UpdateWorkflowRequest\x1a\x16.workflows.v1.Workflow\x12[\n\x0e\x44\x65leteWorkflow\x12#.workflows.v1.DeleteWorkflowRequest\x1a$.workflows.v1.DeleteWorkflowResponse\x12\x64\n\x16PublishWorkflowRelease\x12+.workflows.v1.PublishWorkflowReleaseRequest\x1a\x1d.workflows.v1.WorkflowRelease\x12y\n\x18UnpublishWorkflowRelease\x12-.workflows.v1.UnpublishWorkflowReleaseRequest\x1a..workflows.v1.UnpublishWorkflowReleaseResponse\x12p\n\x15\x44\x65ployWorkflowRelease\x12*.workflows.v1.DeployWorkflowReleaseRequest\x1a+.workflows.v1.DeployWorkflowReleaseResponse\x12v\n\x17UndeployWorkflowRelease\x12,.workflows.v1.UndeployWorkflowReleaseRequest\x1a-.workflows.v1.UndeployWorkflowReleaseResponseBx\n\x10\x63om.workflows.v1B\x0eWorkflowsProtoP\x01\xa2\x02\x03WXX\xaa\x02\x0cWorkflows.V1\xca\x02\x0cWorkflows\\V1\xe2\x02\x18Workflows\\V1\\GPBMetadata\xea\x02\rWorkflows::V1\x92\x03\x02\x08\x02\x62\x08\x65\x64itionsp\xe8\x07') - -_globals = globals() -_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) -_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'workflows.v1.workflows_pb2', _globals) -if not _descriptor._USE_C_DESCRIPTORS: - _globals['DESCRIPTOR']._loaded_options = None - _globals['DESCRIPTOR']._serialized_options = b'\n\020com.workflows.v1B\016WorkflowsProtoP\001\242\002\003WXX\252\002\014Workflows.V1\312\002\014Workflows\\V1\342\002\030Workflows\\V1\\GPBMetadata\352\002\rWorkflows::V1\222\003\002\010\002' - _globals['_UPDATECLUSTERREQUEST'].fields_by_name['name']._loaded_options = None - _globals['_UPDATECLUSTERREQUEST'].fields_by_name['name']._serialized_options = b'\252\001\002\010\001' - _globals['_UPDATECLUSTERREQUEST'].fields_by_name['description']._loaded_options = None - _globals['_UPDATECLUSTERREQUEST'].fields_by_name['description']._serialized_options = b'\252\001\002\010\001' - _globals['_UPDATEWORKFLOWREQUEST'].fields_by_name['name']._loaded_options = None - _globals['_UPDATEWORKFLOWREQUEST'].fields_by_name['name']._serialized_options = b'\252\001\002\010\001' - _globals['_UPDATEWORKFLOWREQUEST'].fields_by_name['summary']._loaded_options = None - _globals['_UPDATEWORKFLOWREQUEST'].fields_by_name['summary']._serialized_options = b'\252\001\002\010\001' - _globals['_UPDATEWORKFLOWREQUEST'].fields_by_name['description']._loaded_options = None - _globals['_UPDATEWORKFLOWREQUEST'].fields_by_name['description']._serialized_options = b'\252\001\002\010\001' - _globals['_LISTWORKFLOWSREQUEST'].fields_by_name['page']._loaded_options = None - _globals['_LISTWORKFLOWSREQUEST'].fields_by_name['page']._serialized_options = b'\252\001\002\010\001' - _globals['_LISTWORKFLOWSRESPONSE'].fields_by_name['next_page']._loaded_options = None - _globals['_LISTWORKFLOWSRESPONSE'].fields_by_name['next_page']._serialized_options = b'\252\001\002\010\001' - _globals['_LISTPUBLICWORKFLOWSREQUEST'].fields_by_name['page']._loaded_options = None - _globals['_LISTPUBLICWORKFLOWSREQUEST'].fields_by_name['page']._serialized_options = b'\252\001\002\010\001' - _globals['_LISTPUBLICWORKFLOWSRESPONSE'].fields_by_name['next_page']._loaded_options = None - _globals['_LISTPUBLICWORKFLOWSRESPONSE'].fields_by_name['next_page']._serialized_options = b'\252\001\002\010\001' - _globals['_CREATEWORKFLOWREQUEST'].fields_by_name['description']._loaded_options = None - _globals['_CREATEWORKFLOWREQUEST'].fields_by_name['description']._serialized_options = b'\252\001\002\010\001' - _globals['_CLUSTER']._serialized_start=150 - _globals['_CLUSTER']._serialized_end=353 - _globals['_CREATECLUSTERREQUEST']._serialized_start=355 - _globals['_CREATECLUSTERREQUEST']._serialized_end=451 - _globals['_GETCLUSTERREQUEST']._serialized_start=453 - _globals['_GETCLUSTERREQUEST']._serialized_end=507 - _globals['_UPDATECLUSTERREQUEST']._serialized_start=509 - _globals['_UPDATECLUSTERREQUEST']._serialized_end=634 - _globals['_DELETECLUSTERREQUEST']._serialized_start=636 - _globals['_DELETECLUSTERREQUEST']._serialized_end=693 - _globals['_DELETECLUSTERRESPONSE']._serialized_start=695 - _globals['_DELETECLUSTERRESPONSE']._serialized_end=718 - _globals['_LISTCLUSTERSREQUEST']._serialized_start=720 - _globals['_LISTCLUSTERSREQUEST']._serialized_end=741 - _globals['_LISTCLUSTERSRESPONSE']._serialized_start=743 - _globals['_LISTCLUSTERSRESPONSE']._serialized_end=816 - _globals['_GETWORKFLOWREQUEST']._serialized_start=818 - _globals['_GETWORKFLOWREQUEST']._serialized_end=875 - _globals['_UPDATEWORKFLOWREQUEST']._serialized_start=878 - _globals['_UPDATEWORKFLOWREQUEST']._serialized_end=1039 - _globals['_DELETEWORKFLOWREQUEST']._serialized_start=1041 - _globals['_DELETEWORKFLOWREQUEST']._serialized_end=1101 - _globals['_DELETEWORKFLOWRESPONSE']._serialized_start=1103 - _globals['_DELETEWORKFLOWRESPONSE']._serialized_end=1127 - _globals['_PUBLISHWORKFLOWRELEASEREQUEST']._serialized_start=1130 - _globals['_PUBLISHWORKFLOWRELEASEREQUEST']._serialized_end=1303 - _globals['_UNPUBLISHWORKFLOWRELEASEREQUEST']._serialized_start=1305 - _globals['_UNPUBLISHWORKFLOWRELEASEREQUEST']._serialized_end=1422 - _globals['_UNPUBLISHWORKFLOWRELEASERESPONSE']._serialized_start=1424 - _globals['_UNPUBLISHWORKFLOWRELEASERESPONSE']._serialized_end=1458 - _globals['_LISTWORKFLOWSREQUEST']._serialized_start=1460 - _globals['_LISTWORKFLOWSREQUEST']._serialized_end=1533 - _globals['_LISTWORKFLOWSRESPONSE']._serialized_start=1536 - _globals['_LISTWORKFLOWSRESPONSE']._serialized_end=1673 - _globals['_LISTPUBLICWORKFLOWSREQUEST']._serialized_start=1675 - _globals['_LISTPUBLICWORKFLOWSREQUEST']._serialized_end=1754 - _globals['_LISTPUBLICWORKFLOWSRESPONSE']._serialized_start=1757 - _globals['_LISTPUBLICWORKFLOWSRESPONSE']._serialized_end=1900 - _globals['_CREATEWORKFLOWREQUEST']._serialized_start=1902 - _globals['_CREATEWORKFLOWREQUEST']._serialized_end=2012 - _globals['_WORKFLOW']._serialized_start=2015 - _globals['_WORKFLOW']._serialized_end=2262 - _globals['_WORKFLOWPRESENTATION']._serialized_start=2265 - _globals['_WORKFLOWPRESENTATION']._serialized_end=2444 - _globals['_WORKFLOWRELEASE']._serialized_start=2447 - _globals['_WORKFLOWRELEASE']._serialized_end=2714 - _globals['_ARTIFACT']._serialized_start=2716 - _globals['_ARTIFACT']._serialized_end=2782 - _globals['_RELEASECONTENT']._serialized_start=2785 - _globals['_RELEASECONTENT']._serialized_end=3018 - _globals['_PATH']._serialized_start=3020 - _globals['_PATH']._serialized_end=3124 - _globals['_DEPLOYWORKFLOWRELEASEREQUEST']._serialized_start=3127 - _globals['_DEPLOYWORKFLOWRELEASEREQUEST']._serialized_end=3278 - _globals['_DEPLOYWORKFLOWRELEASERESPONSE']._serialized_start=3281 - _globals['_DEPLOYWORKFLOWRELEASERESPONSE']._serialized_end=3420 - _globals['_UNDEPLOYWORKFLOWRELEASEREQUEST']._serialized_start=3423 - _globals['_UNDEPLOYWORKFLOWRELEASEREQUEST']._serialized_end=3576 - _globals['_UNDEPLOYWORKFLOWRELEASERESPONSE']._serialized_start=3579 - _globals['_UNDEPLOYWORKFLOWRELEASERESPONSE']._serialized_end=3720 - _globals['_WORKFLOWSSERVICE']._serialized_start=3723 - _globals['_WORKFLOWSSERVICE']._serialized_end=5121 -# @@protoc_insertion_point(module_scope) diff --git a/tilebox-workflows/tilebox/workflows/workflows/v1/workflows_pb2.pyi b/tilebox-workflows/tilebox/workflows/workflows/v1/workflows_pb2.pyi deleted file mode 100644 index 59b154e..0000000 --- a/tilebox-workflows/tilebox/workflows/workflows/v1/workflows_pb2.pyi +++ /dev/null @@ -1,271 +0,0 @@ -from google.protobuf import timestamp_pb2 as _timestamp_pb2 -from tilebox.datasets.tilebox.v1 import id_pb2 as _id_pb2 -from tilebox.datasets.tilebox.v1 import query_pb2 as _query_pb2 -from tilebox.workflows.workflows.v1 import core_pb2 as _core_pb2 -from google.protobuf.internal import containers as _containers -from google.protobuf import descriptor as _descriptor -from google.protobuf import message as _message -from collections.abc import Iterable as _Iterable, Mapping as _Mapping -from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union - -DESCRIPTOR: _descriptor.FileDescriptor - -class Cluster(_message.Message): - __slots__ = ("slug", "display_name", "description", "deletable", "deployed_releases") - SLUG_FIELD_NUMBER: _ClassVar[int] - DISPLAY_NAME_FIELD_NUMBER: _ClassVar[int] - DESCRIPTION_FIELD_NUMBER: _ClassVar[int] - DELETABLE_FIELD_NUMBER: _ClassVar[int] - DEPLOYED_RELEASES_FIELD_NUMBER: _ClassVar[int] - slug: str - display_name: str - description: str - deletable: bool - deployed_releases: _containers.RepeatedCompositeFieldContainer[Workflow] - def __init__(self, slug: _Optional[str] = ..., display_name: _Optional[str] = ..., description: _Optional[str] = ..., deletable: bool = ..., deployed_releases: _Optional[_Iterable[_Union[Workflow, _Mapping]]] = ...) -> None: ... - -class CreateClusterRequest(_message.Message): - __slots__ = ("name", "description", "slug") - NAME_FIELD_NUMBER: _ClassVar[int] - DESCRIPTION_FIELD_NUMBER: _ClassVar[int] - SLUG_FIELD_NUMBER: _ClassVar[int] - name: str - description: str - slug: str - def __init__(self, name: _Optional[str] = ..., description: _Optional[str] = ..., slug: _Optional[str] = ...) -> None: ... - -class GetClusterRequest(_message.Message): - __slots__ = ("cluster_slug",) - CLUSTER_SLUG_FIELD_NUMBER: _ClassVar[int] - cluster_slug: str - def __init__(self, cluster_slug: _Optional[str] = ...) -> None: ... - -class UpdateClusterRequest(_message.Message): - __slots__ = ("cluster_slug", "name", "description") - CLUSTER_SLUG_FIELD_NUMBER: _ClassVar[int] - NAME_FIELD_NUMBER: _ClassVar[int] - DESCRIPTION_FIELD_NUMBER: _ClassVar[int] - cluster_slug: str - name: str - description: str - def __init__(self, cluster_slug: _Optional[str] = ..., name: _Optional[str] = ..., description: _Optional[str] = ...) -> None: ... - -class DeleteClusterRequest(_message.Message): - __slots__ = ("cluster_slug",) - CLUSTER_SLUG_FIELD_NUMBER: _ClassVar[int] - cluster_slug: str - def __init__(self, cluster_slug: _Optional[str] = ...) -> None: ... - -class DeleteClusterResponse(_message.Message): - __slots__ = () - def __init__(self) -> None: ... - -class ListClustersRequest(_message.Message): - __slots__ = () - def __init__(self) -> None: ... - -class ListClustersResponse(_message.Message): - __slots__ = ("clusters",) - CLUSTERS_FIELD_NUMBER: _ClassVar[int] - clusters: _containers.RepeatedCompositeFieldContainer[Cluster] - def __init__(self, clusters: _Optional[_Iterable[_Union[Cluster, _Mapping]]] = ...) -> None: ... - -class GetWorkflowRequest(_message.Message): - __slots__ = ("workflow_slug",) - WORKFLOW_SLUG_FIELD_NUMBER: _ClassVar[int] - workflow_slug: str - def __init__(self, workflow_slug: _Optional[str] = ...) -> None: ... - -class UpdateWorkflowRequest(_message.Message): - __slots__ = ("workflow_slug", "name", "summary", "description") - WORKFLOW_SLUG_FIELD_NUMBER: _ClassVar[int] - NAME_FIELD_NUMBER: _ClassVar[int] - SUMMARY_FIELD_NUMBER: _ClassVar[int] - DESCRIPTION_FIELD_NUMBER: _ClassVar[int] - workflow_slug: str - name: str - summary: str - description: str - def __init__(self, workflow_slug: _Optional[str] = ..., name: _Optional[str] = ..., summary: _Optional[str] = ..., description: _Optional[str] = ...) -> None: ... - -class DeleteWorkflowRequest(_message.Message): - __slots__ = ("workflow_slug",) - WORKFLOW_SLUG_FIELD_NUMBER: _ClassVar[int] - workflow_slug: str - def __init__(self, workflow_slug: _Optional[str] = ...) -> None: ... - -class DeleteWorkflowResponse(_message.Message): - __slots__ = () - def __init__(self) -> None: ... - -class PublishWorkflowReleaseRequest(_message.Message): - __slots__ = ("workflow_slug", "artifact_id", "content") - WORKFLOW_SLUG_FIELD_NUMBER: _ClassVar[int] - ARTIFACT_ID_FIELD_NUMBER: _ClassVar[int] - CONTENT_FIELD_NUMBER: _ClassVar[int] - workflow_slug: str - artifact_id: _id_pb2.ID - content: ReleaseContent - def __init__(self, workflow_slug: _Optional[str] = ..., artifact_id: _Optional[_Union[_id_pb2.ID, _Mapping]] = ..., content: _Optional[_Union[ReleaseContent, _Mapping]] = ...) -> None: ... - -class UnpublishWorkflowReleaseRequest(_message.Message): - __slots__ = ("workflow_slug", "release_id") - WORKFLOW_SLUG_FIELD_NUMBER: _ClassVar[int] - RELEASE_ID_FIELD_NUMBER: _ClassVar[int] - workflow_slug: str - release_id: _id_pb2.ID - def __init__(self, workflow_slug: _Optional[str] = ..., release_id: _Optional[_Union[_id_pb2.ID, _Mapping]] = ...) -> None: ... - -class UnpublishWorkflowReleaseResponse(_message.Message): - __slots__ = () - def __init__(self) -> None: ... - -class ListWorkflowsRequest(_message.Message): - __slots__ = ("page",) - PAGE_FIELD_NUMBER: _ClassVar[int] - page: _query_pb2.Pagination - def __init__(self, page: _Optional[_Union[_query_pb2.Pagination, _Mapping]] = ...) -> None: ... - -class ListWorkflowsResponse(_message.Message): - __slots__ = ("workflows", "next_page") - WORKFLOWS_FIELD_NUMBER: _ClassVar[int] - NEXT_PAGE_FIELD_NUMBER: _ClassVar[int] - workflows: _containers.RepeatedCompositeFieldContainer[Workflow] - next_page: _query_pb2.Pagination - def __init__(self, workflows: _Optional[_Iterable[_Union[Workflow, _Mapping]]] = ..., next_page: _Optional[_Union[_query_pb2.Pagination, _Mapping]] = ...) -> None: ... - -class ListPublicWorkflowsRequest(_message.Message): - __slots__ = ("page",) - PAGE_FIELD_NUMBER: _ClassVar[int] - page: _query_pb2.Pagination - def __init__(self, page: _Optional[_Union[_query_pb2.Pagination, _Mapping]] = ...) -> None: ... - -class ListPublicWorkflowsResponse(_message.Message): - __slots__ = ("workflows", "next_page") - WORKFLOWS_FIELD_NUMBER: _ClassVar[int] - NEXT_PAGE_FIELD_NUMBER: _ClassVar[int] - workflows: _containers.RepeatedCompositeFieldContainer[Workflow] - next_page: _query_pb2.Pagination - def __init__(self, workflows: _Optional[_Iterable[_Union[Workflow, _Mapping]]] = ..., next_page: _Optional[_Union[_query_pb2.Pagination, _Mapping]] = ...) -> None: ... - -class CreateWorkflowRequest(_message.Message): - __slots__ = ("name", "summary", "description") - NAME_FIELD_NUMBER: _ClassVar[int] - SUMMARY_FIELD_NUMBER: _ClassVar[int] - DESCRIPTION_FIELD_NUMBER: _ClassVar[int] - name: str - summary: str - description: str - def __init__(self, name: _Optional[str] = ..., summary: _Optional[str] = ..., description: _Optional[str] = ...) -> None: ... - -class Workflow(_message.Message): - __slots__ = ("slug", "name", "summary", "description", "releases", "presentation") - SLUG_FIELD_NUMBER: _ClassVar[int] - NAME_FIELD_NUMBER: _ClassVar[int] - SUMMARY_FIELD_NUMBER: _ClassVar[int] - DESCRIPTION_FIELD_NUMBER: _ClassVar[int] - RELEASES_FIELD_NUMBER: _ClassVar[int] - PRESENTATION_FIELD_NUMBER: _ClassVar[int] - slug: str - name: str - summary: str - description: str - releases: _containers.RepeatedCompositeFieldContainer[WorkflowRelease] - presentation: WorkflowPresentation - def __init__(self, slug: _Optional[str] = ..., name: _Optional[str] = ..., summary: _Optional[str] = ..., description: _Optional[str] = ..., releases: _Optional[_Iterable[_Union[WorkflowRelease, _Mapping]]] = ..., presentation: _Optional[_Union[WorkflowPresentation, _Mapping]] = ...) -> None: ... - -class WorkflowPresentation(_message.Message): - __slots__ = ("image_url", "image_alt", "imagery_type", "use_case", "dataset_label") - IMAGE_URL_FIELD_NUMBER: _ClassVar[int] - IMAGE_ALT_FIELD_NUMBER: _ClassVar[int] - IMAGERY_TYPE_FIELD_NUMBER: _ClassVar[int] - USE_CASE_FIELD_NUMBER: _ClassVar[int] - DATASET_LABEL_FIELD_NUMBER: _ClassVar[int] - image_url: str - image_alt: str - imagery_type: _containers.RepeatedScalarFieldContainer[str] - use_case: _containers.RepeatedScalarFieldContainer[str] - dataset_label: _containers.RepeatedScalarFieldContainer[str] - def __init__(self, image_url: _Optional[str] = ..., image_alt: _Optional[str] = ..., imagery_type: _Optional[_Iterable[str]] = ..., use_case: _Optional[_Iterable[str]] = ..., dataset_label: _Optional[_Iterable[str]] = ...) -> None: ... - -class WorkflowRelease(_message.Message): - __slots__ = ("id", "artifact", "content", "created_at", "clusters") - ID_FIELD_NUMBER: _ClassVar[int] - ARTIFACT_FIELD_NUMBER: _ClassVar[int] - CONTENT_FIELD_NUMBER: _ClassVar[int] - CREATED_AT_FIELD_NUMBER: _ClassVar[int] - CLUSTERS_FIELD_NUMBER: _ClassVar[int] - id: _id_pb2.ID - artifact: Artifact - content: ReleaseContent - created_at: _timestamp_pb2.Timestamp - clusters: _containers.RepeatedCompositeFieldContainer[Cluster] - def __init__(self, id: _Optional[_Union[_id_pb2.ID, _Mapping]] = ..., artifact: _Optional[_Union[Artifact, _Mapping]] = ..., content: _Optional[_Union[ReleaseContent, _Mapping]] = ..., created_at: _Optional[_Union[_timestamp_pb2.Timestamp, _Mapping]] = ..., clusters: _Optional[_Iterable[_Union[Cluster, _Mapping]]] = ...) -> None: ... - -class Artifact(_message.Message): - __slots__ = ("id", "digest") - ID_FIELD_NUMBER: _ClassVar[int] - DIGEST_FIELD_NUMBER: _ClassVar[int] - id: _id_pb2.ID - digest: str - def __init__(self, id: _Optional[_Union[_id_pb2.ID, _Mapping]] = ..., digest: _Optional[str] = ...) -> None: ... - -class ReleaseContent(_message.Message): - __slots__ = ("fingerprint", "tasks", "files", "runner_object_path", "command_override") - FINGERPRINT_FIELD_NUMBER: _ClassVar[int] - TASKS_FIELD_NUMBER: _ClassVar[int] - FILES_FIELD_NUMBER: _ClassVar[int] - RUNNER_OBJECT_PATH_FIELD_NUMBER: _ClassVar[int] - COMMAND_OVERRIDE_FIELD_NUMBER: _ClassVar[int] - fingerprint: str - tasks: _containers.RepeatedCompositeFieldContainer[_core_pb2.TaskIdentifier] - files: _containers.RepeatedCompositeFieldContainer[Path] - runner_object_path: str - command_override: _containers.RepeatedScalarFieldContainer[str] - def __init__(self, fingerprint: _Optional[str] = ..., tasks: _Optional[_Iterable[_Union[_core_pb2.TaskIdentifier, _Mapping]]] = ..., files: _Optional[_Iterable[_Union[Path, _Mapping]]] = ..., runner_object_path: _Optional[str] = ..., command_override: _Optional[_Iterable[str]] = ...) -> None: ... - -class Path(_message.Message): - __slots__ = ("path", "directory", "children") - PATH_FIELD_NUMBER: _ClassVar[int] - DIRECTORY_FIELD_NUMBER: _ClassVar[int] - CHILDREN_FIELD_NUMBER: _ClassVar[int] - path: str - directory: bool - children: _containers.RepeatedCompositeFieldContainer[Path] - def __init__(self, path: _Optional[str] = ..., directory: bool = ..., children: _Optional[_Iterable[_Union[Path, _Mapping]]] = ...) -> None: ... - -class DeployWorkflowReleaseRequest(_message.Message): - __slots__ = ("workflow_slug", "release_id", "cluster_slugs") - WORKFLOW_SLUG_FIELD_NUMBER: _ClassVar[int] - RELEASE_ID_FIELD_NUMBER: _ClassVar[int] - CLUSTER_SLUGS_FIELD_NUMBER: _ClassVar[int] - workflow_slug: str - release_id: _id_pb2.ID - cluster_slugs: _containers.RepeatedScalarFieldContainer[str] - def __init__(self, workflow_slug: _Optional[str] = ..., release_id: _Optional[_Union[_id_pb2.ID, _Mapping]] = ..., cluster_slugs: _Optional[_Iterable[str]] = ...) -> None: ... - -class DeployWorkflowReleaseResponse(_message.Message): - __slots__ = ("release", "clusters") - RELEASE_FIELD_NUMBER: _ClassVar[int] - CLUSTERS_FIELD_NUMBER: _ClassVar[int] - release: WorkflowRelease - clusters: _containers.RepeatedCompositeFieldContainer[Cluster] - def __init__(self, release: _Optional[_Union[WorkflowRelease, _Mapping]] = ..., clusters: _Optional[_Iterable[_Union[Cluster, _Mapping]]] = ...) -> None: ... - -class UndeployWorkflowReleaseRequest(_message.Message): - __slots__ = ("workflow_slug", "release_id", "cluster_slugs") - WORKFLOW_SLUG_FIELD_NUMBER: _ClassVar[int] - RELEASE_ID_FIELD_NUMBER: _ClassVar[int] - CLUSTER_SLUGS_FIELD_NUMBER: _ClassVar[int] - workflow_slug: str - release_id: _id_pb2.ID - cluster_slugs: _containers.RepeatedScalarFieldContainer[str] - def __init__(self, workflow_slug: _Optional[str] = ..., release_id: _Optional[_Union[_id_pb2.ID, _Mapping]] = ..., cluster_slugs: _Optional[_Iterable[str]] = ...) -> None: ... - -class UndeployWorkflowReleaseResponse(_message.Message): - __slots__ = ("release", "clusters") - RELEASE_FIELD_NUMBER: _ClassVar[int] - CLUSTERS_FIELD_NUMBER: _ClassVar[int] - release: WorkflowRelease - clusters: _containers.RepeatedCompositeFieldContainer[Cluster] - def __init__(self, release: _Optional[_Union[WorkflowRelease, _Mapping]] = ..., clusters: _Optional[_Iterable[_Union[Cluster, _Mapping]]] = ...) -> None: ... diff --git a/tilebox-workflows/tilebox/workflows/workflows/v1/workflows_pb2_grpc.py b/tilebox-workflows/tilebox/workflows/workflows/v1/workflows_pb2_grpc.py deleted file mode 100644 index 0696724..0000000 --- a/tilebox-workflows/tilebox/workflows/workflows/v1/workflows_pb2_grpc.py +++ /dev/null @@ -1,682 +0,0 @@ -# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! -"""Client and server classes corresponding to protobuf-defined services.""" -import grpc - -from tilebox.workflows.workflows.v1 import workflows_pb2 as workflows_dot_v1_dot_workflows__pb2 - - -class WorkflowsServiceStub: - """A service for managing workflows. - """ - - def __init__(self, channel): - """Constructor. - - Args: - channel: A grpc.Channel. - """ - self.CreateCluster = channel.unary_unary( - '/workflows.v1.WorkflowsService/CreateCluster', - request_serializer=workflows_dot_v1_dot_workflows__pb2.CreateClusterRequest.SerializeToString, - response_deserializer=workflows_dot_v1_dot_workflows__pb2.Cluster.FromString, - _registered_method=True) - self.GetCluster = channel.unary_unary( - '/workflows.v1.WorkflowsService/GetCluster', - request_serializer=workflows_dot_v1_dot_workflows__pb2.GetClusterRequest.SerializeToString, - response_deserializer=workflows_dot_v1_dot_workflows__pb2.Cluster.FromString, - _registered_method=True) - self.UpdateCluster = channel.unary_unary( - '/workflows.v1.WorkflowsService/UpdateCluster', - request_serializer=workflows_dot_v1_dot_workflows__pb2.UpdateClusterRequest.SerializeToString, - response_deserializer=workflows_dot_v1_dot_workflows__pb2.Cluster.FromString, - _registered_method=True) - self.DeleteCluster = channel.unary_unary( - '/workflows.v1.WorkflowsService/DeleteCluster', - request_serializer=workflows_dot_v1_dot_workflows__pb2.DeleteClusterRequest.SerializeToString, - response_deserializer=workflows_dot_v1_dot_workflows__pb2.DeleteClusterResponse.FromString, - _registered_method=True) - self.ListClusters = channel.unary_unary( - '/workflows.v1.WorkflowsService/ListClusters', - request_serializer=workflows_dot_v1_dot_workflows__pb2.ListClustersRequest.SerializeToString, - response_deserializer=workflows_dot_v1_dot_workflows__pb2.ListClustersResponse.FromString, - _registered_method=True) - self.CreateWorkflow = channel.unary_unary( - '/workflows.v1.WorkflowsService/CreateWorkflow', - request_serializer=workflows_dot_v1_dot_workflows__pb2.CreateWorkflowRequest.SerializeToString, - response_deserializer=workflows_dot_v1_dot_workflows__pb2.Workflow.FromString, - _registered_method=True) - self.ListWorkflows = channel.unary_unary( - '/workflows.v1.WorkflowsService/ListWorkflows', - request_serializer=workflows_dot_v1_dot_workflows__pb2.ListWorkflowsRequest.SerializeToString, - response_deserializer=workflows_dot_v1_dot_workflows__pb2.ListWorkflowsResponse.FromString, - _registered_method=True) - self.ListPublicWorkflows = channel.unary_unary( - '/workflows.v1.WorkflowsService/ListPublicWorkflows', - request_serializer=workflows_dot_v1_dot_workflows__pb2.ListPublicWorkflowsRequest.SerializeToString, - response_deserializer=workflows_dot_v1_dot_workflows__pb2.ListPublicWorkflowsResponse.FromString, - _registered_method=True) - self.GetWorkflow = channel.unary_unary( - '/workflows.v1.WorkflowsService/GetWorkflow', - request_serializer=workflows_dot_v1_dot_workflows__pb2.GetWorkflowRequest.SerializeToString, - response_deserializer=workflows_dot_v1_dot_workflows__pb2.Workflow.FromString, - _registered_method=True) - self.UpdateWorkflow = channel.unary_unary( - '/workflows.v1.WorkflowsService/UpdateWorkflow', - request_serializer=workflows_dot_v1_dot_workflows__pb2.UpdateWorkflowRequest.SerializeToString, - response_deserializer=workflows_dot_v1_dot_workflows__pb2.Workflow.FromString, - _registered_method=True) - self.DeleteWorkflow = channel.unary_unary( - '/workflows.v1.WorkflowsService/DeleteWorkflow', - request_serializer=workflows_dot_v1_dot_workflows__pb2.DeleteWorkflowRequest.SerializeToString, - response_deserializer=workflows_dot_v1_dot_workflows__pb2.DeleteWorkflowResponse.FromString, - _registered_method=True) - self.PublishWorkflowRelease = channel.unary_unary( - '/workflows.v1.WorkflowsService/PublishWorkflowRelease', - request_serializer=workflows_dot_v1_dot_workflows__pb2.PublishWorkflowReleaseRequest.SerializeToString, - response_deserializer=workflows_dot_v1_dot_workflows__pb2.WorkflowRelease.FromString, - _registered_method=True) - self.UnpublishWorkflowRelease = channel.unary_unary( - '/workflows.v1.WorkflowsService/UnpublishWorkflowRelease', - request_serializer=workflows_dot_v1_dot_workflows__pb2.UnpublishWorkflowReleaseRequest.SerializeToString, - response_deserializer=workflows_dot_v1_dot_workflows__pb2.UnpublishWorkflowReleaseResponse.FromString, - _registered_method=True) - self.DeployWorkflowRelease = channel.unary_unary( - '/workflows.v1.WorkflowsService/DeployWorkflowRelease', - request_serializer=workflows_dot_v1_dot_workflows__pb2.DeployWorkflowReleaseRequest.SerializeToString, - response_deserializer=workflows_dot_v1_dot_workflows__pb2.DeployWorkflowReleaseResponse.FromString, - _registered_method=True) - self.UndeployWorkflowRelease = channel.unary_unary( - '/workflows.v1.WorkflowsService/UndeployWorkflowRelease', - request_serializer=workflows_dot_v1_dot_workflows__pb2.UndeployWorkflowReleaseRequest.SerializeToString, - response_deserializer=workflows_dot_v1_dot_workflows__pb2.UndeployWorkflowReleaseResponse.FromString, - _registered_method=True) - - -class WorkflowsServiceServicer: - """A service for managing workflows. - """ - - def CreateCluster(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def GetCluster(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def UpdateCluster(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def DeleteCluster(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def ListClusters(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def CreateWorkflow(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def ListWorkflows(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def ListPublicWorkflows(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def GetWorkflow(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def UpdateWorkflow(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def DeleteWorkflow(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def PublishWorkflowRelease(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def UnpublishWorkflowRelease(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def DeployWorkflowRelease(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def UndeployWorkflowRelease(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - -def add_WorkflowsServiceServicer_to_server(servicer, server): - rpc_method_handlers = { - 'CreateCluster': grpc.unary_unary_rpc_method_handler( - servicer.CreateCluster, - request_deserializer=workflows_dot_v1_dot_workflows__pb2.CreateClusterRequest.FromString, - response_serializer=workflows_dot_v1_dot_workflows__pb2.Cluster.SerializeToString, - ), - 'GetCluster': grpc.unary_unary_rpc_method_handler( - servicer.GetCluster, - request_deserializer=workflows_dot_v1_dot_workflows__pb2.GetClusterRequest.FromString, - response_serializer=workflows_dot_v1_dot_workflows__pb2.Cluster.SerializeToString, - ), - 'UpdateCluster': grpc.unary_unary_rpc_method_handler( - servicer.UpdateCluster, - request_deserializer=workflows_dot_v1_dot_workflows__pb2.UpdateClusterRequest.FromString, - response_serializer=workflows_dot_v1_dot_workflows__pb2.Cluster.SerializeToString, - ), - 'DeleteCluster': grpc.unary_unary_rpc_method_handler( - servicer.DeleteCluster, - request_deserializer=workflows_dot_v1_dot_workflows__pb2.DeleteClusterRequest.FromString, - response_serializer=workflows_dot_v1_dot_workflows__pb2.DeleteClusterResponse.SerializeToString, - ), - 'ListClusters': grpc.unary_unary_rpc_method_handler( - servicer.ListClusters, - request_deserializer=workflows_dot_v1_dot_workflows__pb2.ListClustersRequest.FromString, - response_serializer=workflows_dot_v1_dot_workflows__pb2.ListClustersResponse.SerializeToString, - ), - 'CreateWorkflow': grpc.unary_unary_rpc_method_handler( - servicer.CreateWorkflow, - request_deserializer=workflows_dot_v1_dot_workflows__pb2.CreateWorkflowRequest.FromString, - response_serializer=workflows_dot_v1_dot_workflows__pb2.Workflow.SerializeToString, - ), - 'ListWorkflows': grpc.unary_unary_rpc_method_handler( - servicer.ListWorkflows, - request_deserializer=workflows_dot_v1_dot_workflows__pb2.ListWorkflowsRequest.FromString, - response_serializer=workflows_dot_v1_dot_workflows__pb2.ListWorkflowsResponse.SerializeToString, - ), - 'ListPublicWorkflows': grpc.unary_unary_rpc_method_handler( - servicer.ListPublicWorkflows, - request_deserializer=workflows_dot_v1_dot_workflows__pb2.ListPublicWorkflowsRequest.FromString, - response_serializer=workflows_dot_v1_dot_workflows__pb2.ListPublicWorkflowsResponse.SerializeToString, - ), - 'GetWorkflow': grpc.unary_unary_rpc_method_handler( - servicer.GetWorkflow, - request_deserializer=workflows_dot_v1_dot_workflows__pb2.GetWorkflowRequest.FromString, - response_serializer=workflows_dot_v1_dot_workflows__pb2.Workflow.SerializeToString, - ), - 'UpdateWorkflow': grpc.unary_unary_rpc_method_handler( - servicer.UpdateWorkflow, - request_deserializer=workflows_dot_v1_dot_workflows__pb2.UpdateWorkflowRequest.FromString, - response_serializer=workflows_dot_v1_dot_workflows__pb2.Workflow.SerializeToString, - ), - 'DeleteWorkflow': grpc.unary_unary_rpc_method_handler( - servicer.DeleteWorkflow, - request_deserializer=workflows_dot_v1_dot_workflows__pb2.DeleteWorkflowRequest.FromString, - response_serializer=workflows_dot_v1_dot_workflows__pb2.DeleteWorkflowResponse.SerializeToString, - ), - 'PublishWorkflowRelease': grpc.unary_unary_rpc_method_handler( - servicer.PublishWorkflowRelease, - request_deserializer=workflows_dot_v1_dot_workflows__pb2.PublishWorkflowReleaseRequest.FromString, - response_serializer=workflows_dot_v1_dot_workflows__pb2.WorkflowRelease.SerializeToString, - ), - 'UnpublishWorkflowRelease': grpc.unary_unary_rpc_method_handler( - servicer.UnpublishWorkflowRelease, - request_deserializer=workflows_dot_v1_dot_workflows__pb2.UnpublishWorkflowReleaseRequest.FromString, - response_serializer=workflows_dot_v1_dot_workflows__pb2.UnpublishWorkflowReleaseResponse.SerializeToString, - ), - 'DeployWorkflowRelease': grpc.unary_unary_rpc_method_handler( - servicer.DeployWorkflowRelease, - request_deserializer=workflows_dot_v1_dot_workflows__pb2.DeployWorkflowReleaseRequest.FromString, - response_serializer=workflows_dot_v1_dot_workflows__pb2.DeployWorkflowReleaseResponse.SerializeToString, - ), - 'UndeployWorkflowRelease': grpc.unary_unary_rpc_method_handler( - servicer.UndeployWorkflowRelease, - request_deserializer=workflows_dot_v1_dot_workflows__pb2.UndeployWorkflowReleaseRequest.FromString, - response_serializer=workflows_dot_v1_dot_workflows__pb2.UndeployWorkflowReleaseResponse.SerializeToString, - ), - } - generic_handler = grpc.method_handlers_generic_handler( - 'workflows.v1.WorkflowsService', rpc_method_handlers) - server.add_generic_rpc_handlers((generic_handler,)) - server.add_registered_method_handlers('workflows.v1.WorkflowsService', rpc_method_handlers) - - - # This class is part of an EXPERIMENTAL API. -class WorkflowsService: - """A service for managing workflows. - """ - - @staticmethod - def CreateCluster(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary( - request, - target, - '/workflows.v1.WorkflowsService/CreateCluster', - workflows_dot_v1_dot_workflows__pb2.CreateClusterRequest.SerializeToString, - workflows_dot_v1_dot_workflows__pb2.Cluster.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) - - @staticmethod - def GetCluster(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary( - request, - target, - '/workflows.v1.WorkflowsService/GetCluster', - workflows_dot_v1_dot_workflows__pb2.GetClusterRequest.SerializeToString, - workflows_dot_v1_dot_workflows__pb2.Cluster.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) - - @staticmethod - def UpdateCluster(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary( - request, - target, - '/workflows.v1.WorkflowsService/UpdateCluster', - workflows_dot_v1_dot_workflows__pb2.UpdateClusterRequest.SerializeToString, - workflows_dot_v1_dot_workflows__pb2.Cluster.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) - - @staticmethod - def DeleteCluster(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary( - request, - target, - '/workflows.v1.WorkflowsService/DeleteCluster', - workflows_dot_v1_dot_workflows__pb2.DeleteClusterRequest.SerializeToString, - workflows_dot_v1_dot_workflows__pb2.DeleteClusterResponse.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) - - @staticmethod - def ListClusters(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary( - request, - target, - '/workflows.v1.WorkflowsService/ListClusters', - workflows_dot_v1_dot_workflows__pb2.ListClustersRequest.SerializeToString, - workflows_dot_v1_dot_workflows__pb2.ListClustersResponse.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) - - @staticmethod - def CreateWorkflow(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary( - request, - target, - '/workflows.v1.WorkflowsService/CreateWorkflow', - workflows_dot_v1_dot_workflows__pb2.CreateWorkflowRequest.SerializeToString, - workflows_dot_v1_dot_workflows__pb2.Workflow.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) - - @staticmethod - def ListWorkflows(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary( - request, - target, - '/workflows.v1.WorkflowsService/ListWorkflows', - workflows_dot_v1_dot_workflows__pb2.ListWorkflowsRequest.SerializeToString, - workflows_dot_v1_dot_workflows__pb2.ListWorkflowsResponse.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) - - @staticmethod - def ListPublicWorkflows(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary( - request, - target, - '/workflows.v1.WorkflowsService/ListPublicWorkflows', - workflows_dot_v1_dot_workflows__pb2.ListPublicWorkflowsRequest.SerializeToString, - workflows_dot_v1_dot_workflows__pb2.ListPublicWorkflowsResponse.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) - - @staticmethod - def GetWorkflow(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary( - request, - target, - '/workflows.v1.WorkflowsService/GetWorkflow', - workflows_dot_v1_dot_workflows__pb2.GetWorkflowRequest.SerializeToString, - workflows_dot_v1_dot_workflows__pb2.Workflow.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) - - @staticmethod - def UpdateWorkflow(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary( - request, - target, - '/workflows.v1.WorkflowsService/UpdateWorkflow', - workflows_dot_v1_dot_workflows__pb2.UpdateWorkflowRequest.SerializeToString, - workflows_dot_v1_dot_workflows__pb2.Workflow.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) - - @staticmethod - def DeleteWorkflow(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary( - request, - target, - '/workflows.v1.WorkflowsService/DeleteWorkflow', - workflows_dot_v1_dot_workflows__pb2.DeleteWorkflowRequest.SerializeToString, - workflows_dot_v1_dot_workflows__pb2.DeleteWorkflowResponse.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) - - @staticmethod - def PublishWorkflowRelease(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary( - request, - target, - '/workflows.v1.WorkflowsService/PublishWorkflowRelease', - workflows_dot_v1_dot_workflows__pb2.PublishWorkflowReleaseRequest.SerializeToString, - workflows_dot_v1_dot_workflows__pb2.WorkflowRelease.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) - - @staticmethod - def UnpublishWorkflowRelease(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary( - request, - target, - '/workflows.v1.WorkflowsService/UnpublishWorkflowRelease', - workflows_dot_v1_dot_workflows__pb2.UnpublishWorkflowReleaseRequest.SerializeToString, - workflows_dot_v1_dot_workflows__pb2.UnpublishWorkflowReleaseResponse.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) - - @staticmethod - def DeployWorkflowRelease(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary( - request, - target, - '/workflows.v1.WorkflowsService/DeployWorkflowRelease', - workflows_dot_v1_dot_workflows__pb2.DeployWorkflowReleaseRequest.SerializeToString, - workflows_dot_v1_dot_workflows__pb2.DeployWorkflowReleaseResponse.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) - - @staticmethod - def UndeployWorkflowRelease(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary( - request, - target, - '/workflows.v1.WorkflowsService/UndeployWorkflowRelease', - workflows_dot_v1_dot_workflows__pb2.UndeployWorkflowReleaseRequest.SerializeToString, - workflows_dot_v1_dot_workflows__pb2.UndeployWorkflowReleaseResponse.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) diff --git a/tools/generate-protobuf.sh b/tools/generate-protobuf.sh new file mode 100755 index 0000000..c4a7b86 --- /dev/null +++ b/tools/generate-protobuf.sh @@ -0,0 +1,23 @@ +#!/bin/sh +set -eu + +cd "$(dirname "$0")/.." +generated=$(mktemp -d) +trap 'rm -rf "$generated"' EXIT HUP INT TERM + +buf generate --template buf.gen.yaml --output "$generated" +# Strip upstream trailing whitespace, including docstrings, before replacing files. +uv run --no-sync ruff check --isolated --select W291 --fix --unsafe-fixes --ignore-noqa "$generated" + +for mapping in \ + datasets/v1:tilebox-datasets/tilebox/datasets/v1 \ + datasets/stac:tilebox-datasets/tilebox/datasets/stac \ + tilebox/v1:tilebox-core/_tilebox/proto/tilebox/v1 \ + workflows/v1:tilebox-workflows/tilebox/workflows/v1 \ + opentelemetry:tilebox-workflows/tilebox/workflows/_otel/opentelemetry +do + source=${mapping%%:*} + destination=${mapping#*:} + rm -rf "$destination" + mv "$generated/$source" "$destination" +done diff --git a/tools/generate_protobuf.py b/tools/generate_protobuf.py deleted file mode 100755 index 638b56b..0000000 --- a/tools/generate_protobuf.py +++ /dev/null @@ -1,73 +0,0 @@ -#!/usr/bin/env python - -""" -A small utility script to generate protobuf files. - -This will put them in the right place, and automatically fix the imports as well - -Usage (from repo root): -uv run generate-protobuf -""" - -import subprocess -import sys -from pathlib import Path - - -def main() -> None: - clients_repo = Path(__file__).parent.parent - - if len(sys.argv) == 2: # manual arg parsing, don't want any dependencies for this simple script - clients_repo = Path(sys.argv[1]) - elif len(sys.argv) != 1: - print("Usage: uv run generate-protobuf ") # noqa: T201 - sys.exit(1) - - # The Buf templates deliberately exclude buf.validate: these clients do not perform - # client-side validation, and validation-blind messages avoid global descriptor conflicts. - print("Running buf generate") # noqa: T201 - subprocess.run(["buf", "generate", "--template", "buf.gen.datasets.yaml"], check=True) # noqa: S607 - subprocess.run(["buf", "generate", "--template", "buf.gen.datasets-bufpy.yaml"], check=True) # noqa: S607 - subprocess.run(["buf", "generate", "--template", "buf.gen.workflows.yaml"], check=True) # noqa: S607 - - package_mapping = { - "from datasets.v1 import": "from tilebox.datasets.datasets.v1 import", - "import datasets.v1.": "import tilebox.datasets.datasets.v1.", - "from datasets.stac.v1 import": "from tilebox.datasets.datasets.stac.v1 import", - "import datasets.stac.v1.": "import tilebox.datasets.datasets.stac.v1.", - "from tilebox.v1 import": "from tilebox.datasets.tilebox.v1 import", - "import tilebox.v1.": "import tilebox.datasets.tilebox.v1.", - "from workflows.v1 import": "from tilebox.workflows.workflows.v1 import", - "import workflows.v1.": "import tilebox.workflows.workflows.v1.", - } - - folders = ( - clients_repo / "tilebox-datasets" / "tilebox" / "datasets" / "datasets" / "v1", - clients_repo / "tilebox-datasets" / "tilebox" / "datasets" / "datasets" / "stac" / "v1", - clients_repo / "tilebox-datasets" / "tilebox" / "datasets" / "tilebox" / "v1", - clients_repo / "tilebox-workflows" / "tilebox" / "workflows" / "workflows" / "v1", - ) - - for folder in folders: - for pattern in ("*.py", "*.pyi"): - for py_file in folder.rglob(pattern): - fix_imports(py_file, package_mapping) - - -def fix_imports(file_path: Path, package_mapping: dict[str, str]) -> None: - """Fix imports in a single Python file.""" - content = file_path.read_text() - original_content = content - - for old_import, new_import in package_mapping.items(): - content = content.replace(old_import, new_import) - - content = content.strip() + "\n" # end of file -> new line fix - - if content != original_content: - file_path.write_text(content) - print(f"Fixed imports in: {file_path}") # noqa: T201 - - -if __name__ == "__main__": - main() diff --git a/uv.lock b/uv.lock index f5c40a5..4c2fab3 100644 --- a/uv.lock +++ b/uv.lock @@ -15,6 +15,7 @@ resolution-markers = [ [manifest] members = [ + "tilebox-core", "tilebox-datasets", "tilebox-grpc", "tilebox-python", @@ -528,6 +529,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/fc/ad/d07d7862a62ffa6d79d68074d14823243dd235a77c45262acbf6adeb28bf/charset_normalizer-3.5.2-py3-none-any.whl", hash = "sha256:b6b751274acb69d77b3323d6b7dbaa3c7fdfc1eb829b7eb61d262f32e1af9685", size = 68872, upload-time = "2026-09-30T04:39:21.828Z" }, ] +[[package]] +name = "click" +version = "8.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c7/0e/7fa0ef50764b67090eca4114772a2abf8b6148198475e54c660b97caeee6/click-8.5.0.tar.gz", hash = "sha256:ba0d2089de75ea0310e2dde03160e6ca10009947fb95a182f9b54021bb272e34", size = 382235, upload-time = "2026-08-26T13:33:14.56Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/58/50/6c0d534c5f134586a8e1ba4e330569e32f057e33372ae556463212fb4cd3/click-8.5.0-py3-none-any.whl", hash = "sha256:255bc9599cf7748b4b1a446ccc735421bd08a2ae529a8b88597d3de5664ee360", size = 125251, upload-time = "2026-08-26T13:33:12.928Z" }, +] + [[package]] name = "colorama" version = "0.4.6" @@ -548,15 +558,15 @@ wheels = [ [[package]] name = "connectrpc" -version = "0.10.1" +version = "0.12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "protobuf" }, + { name = "protobuf-py" }, { name = "pyqwest" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/93/11/adb3ab104282202000b1c98213c70c7068d76917d9f17c83b818e3d212c1/connectrpc-0.10.1.tar.gz", hash = "sha256:eaf093a4f0d1b9c854c71839083ed5242a551100e99cf52e3cc5e35663f2a5ea", size = 47043, upload-time = "2026-05-29T02:19:57.2Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b8/43/025430769a6cc82a1e1c76cd9760639f6bf9b05577f90e38a0bb57b41b39/connectrpc-0.12.1.tar.gz", hash = "sha256:da7ca41c6cd8d9ce640ad86ae325a71cd83dfdd1e43748976315cb4b0546fb69", size = 50556, upload-time = "2026-09-04T15:23:31.327Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/cf/67/49a6a2d1d0ee04b4d96410d12c751a44437a9bdf4991d7b88fa5c231c0d2/connectrpc-0.10.1-py3-none-any.whl", hash = "sha256:6e965625bbc4b185532bd98e4919528bea6d1c24619b6dba416d0dfafc10e344", size = 64120, upload-time = "2026-05-29T02:19:55.991Z" }, + { url = "https://files.pythonhosted.org/packages/0e/f8/b244edb73395c5a74bfeab13ace9e6ccda89d31fb5e493ca8a8c96106eae/connectrpc-0.12.1-py3-none-any.whl", hash = "sha256:ba85ff49b5b7e0f1b16d07b35a05e39ce92c1a7116a7ed1a271c76393323cd07", size = 70058, upload-time = "2026-09-04T15:23:28.686Z" }, ] [[package]] @@ -925,81 +935,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/65/b9/6b29500a1c581ff4d77fd83c6568d068bee06f1b139fb6eb0a4f2d4bce8a/googleapis_common_protos-1.75.5-py3-none-any.whl", hash = "sha256:d7285525c23039db98f2463e6d5a4f9b958b94d497f03a844ece3259c4e72d5d", size = 307737, upload-time = "2026-09-29T19:25:48.735Z" }, ] -[[package]] -name = "grpcio" -version = "1.84.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/3f/4f/4435c0aae54657258d9cfcba78598f3d9e5fe4c82ff18d78558567b90faf/grpcio-1.84.0.tar.gz", hash = "sha256:19aaf172fc2edbefccce3f6e92c5150975dbe56c45744e9e87cf72ebdf85bfbe", size = 13493876, upload-time = "2026-09-14T06:59:33.291Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2d/b9/46146728b3f4a5c7e34c17d0ab724d58b5456b116e76dc77d3ef4e79b135/grpcio-1.84.0-cp311-cp311-linux_armv7l.whl", hash = "sha256:4aaeceeb7fa7d824c322d1ec3208c8495c88478a927295553235435fc49043ad", size = 6454572, upload-time = "2026-09-14T06:57:14.651Z" }, - { url = "https://files.pythonhosted.org/packages/e3/63/5d668b4102637410d700153fd12d6a798e3ff8308bd9dcbaeae93f191060/grpcio-1.84.0-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:06619ba1515e5ee69fb2a514e95dd8be05ce74cb3928d5b34f87f87c86fe3c27", size = 12359529, upload-time = "2026-09-14T06:57:17.202Z" }, - { url = "https://files.pythonhosted.org/packages/18/2a/52e29c02047a493f15a78c0502bde4d3fab7c19c7813944d367cd501811c/grpcio-1.84.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:158c1c11cfb61b4849c3caf4d52de6f5ecd376e14446feb4a90dc95a90d616f5", size = 7029927, upload-time = "2026-09-14T06:57:19.767Z" }, - { url = "https://files.pythonhosted.org/packages/0a/11/9962b313553647abb091943e0721e4a1662ecc63cdfe930abf00abcce47a/grpcio-1.84.0-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:a9383401d9f116f98cacd4eba6c505a6edb80ba65badfc8e8ed8ae64983bcc44", size = 7782268, upload-time = "2026-09-14T06:57:22.381Z" }, - { url = "https://files.pythonhosted.org/packages/e2/b7/14a9413cb7d4b2e782b4f79c81a918610caedf55138ab5916f5fdd4b002f/grpcio-1.84.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:bd8ea8eb3817b226057cc1c0e7ec4b378dcda52043b972b6ff12b1152178967d", size = 7187959, upload-time = "2026-09-14T06:57:24.686Z" }, - { url = "https://files.pythonhosted.org/packages/ee/3b/6cc8e6aed8f23be40f52af341e5d4595ec3ec8d7572271a692b5c1212178/grpcio-1.84.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:756ea5c2da00fa65c930284892d2a9706828704ca3ba40b4c51c4834eb39fcfd", size = 7737554, upload-time = "2026-09-14T06:57:27.5Z" }, - { url = "https://files.pythonhosted.org/packages/3c/7e/6f61002a01802ca9675e1b3599c9b0f9f3cf168ded94ebacc02199309f88/grpcio-1.84.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:28d2609691da93051e998495108bbddd2a9f7a561253bae94828d81290f30c15", size = 8792681, upload-time = "2026-09-14T06:57:29.731Z" }, - { url = "https://files.pythonhosted.org/packages/eb/84/8bec1ae7e6732a9b435a394ddfdfffde46c2620ae0109823f7cce1a54455/grpcio-1.84.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:27b8b36200a9fbee6e120246f4a8a41657549107ef19fb2c819c4b2fd524f39a", size = 8145493, upload-time = "2026-09-14T06:57:32.672Z" }, - { url = "https://files.pythonhosted.org/packages/59/84/c8c7bd210d657288f18af06522f150f61e81ea14fd3c7c135beed697c5fd/grpcio-1.84.0-cp311-cp311-win32.whl", hash = "sha256:465eef3d17e59ad22a556fc0138f7c7c799df426734344daec42c797d49fda99", size = 4495596, upload-time = "2026-09-14T06:57:34.799Z" }, - { url = "https://files.pythonhosted.org/packages/da/1e/da99356b3b573af357d059753a47fba54f1ca1a9c0e4deccd0210cb7f4ba/grpcio-1.84.0-cp311-cp311-win_amd64.whl", hash = "sha256:f9a456bdbed52a01c9ab8423bdebab04a5363c78676edc55ab9b58bd13bdf9e1", size = 5259900, upload-time = "2026-09-14T06:57:37.067Z" }, - { url = "https://files.pythonhosted.org/packages/0a/c1/4c9a2e0e6b0aaf02781404cad2f79211f989f2c827cf672a4a48d1604d3e/grpcio-1.84.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:b5c6f20d657ae09ae4e30d9d3a21edd13f1219d58cc6f999b9d1bb63be9c1baa", size = 6415756, upload-time = "2026-09-14T06:57:39.345Z" }, - { url = "https://files.pythonhosted.org/packages/b1/57/131e7007bdee9acb77a8dbe8a16fa9fef75f88c1695242d8ee0993ac2d3d/grpcio-1.84.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:406583b4e8fb2282ebd392e12b963e601c1f82e07125a8c2cb5b144e7e024796", size = 12339195, upload-time = "2026-09-14T06:57:42.373Z" }, - { url = "https://files.pythonhosted.org/packages/db/d1/a7b7cda98fcab9b3d2916204a872d87371158a7a34e41768f524584fb64d/grpcio-1.84.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fbdbcd06986ede3ce584083b1dc2afe6808e8943e5cf50ad11183c03aceda25a", size = 6984468, upload-time = "2026-09-14T06:57:45.035Z" }, - { url = "https://files.pythonhosted.org/packages/19/81/c5be83e3ac9416f73c4c51fe1ea9c41a0c42fc3509e3505faa46f5046abe/grpcio-1.84.0-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:23e6e8e8a75cff88e0a793bfd3becea03a13e2763ae90c1ff573bc19ca5b429a", size = 7749432, upload-time = "2026-09-14T06:57:47.395Z" }, - { url = "https://files.pythonhosted.org/packages/a0/bf/258cd7c0a7ed92745dc93c31666d462d05b702807a689744bd49fb833bde/grpcio-1.84.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b44f0a0fc7bc6677d38cc80bca1a32814ce6c8f200fb8b3c1a61c9d77eaefbf3", size = 7156115, upload-time = "2026-09-14T06:57:49.657Z" }, - { url = "https://files.pythonhosted.org/packages/2b/4b/7f829418dbfcf91b875e55e2973f1059a95decb4f081313416317ef04ec1/grpcio-1.84.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:210e4c32f907045eb8158273e60c6ab69a3947697df6245dbda381f26c59485b", size = 7708010, upload-time = "2026-09-14T06:57:52.496Z" }, - { url = "https://files.pythonhosted.org/packages/34/f0/9932e2fec6a04205f8bf3f8f4d2020479dcdac88feb6f93822ed31bf0eba/grpcio-1.84.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a71d24f40b0cc6798feaa978c7411dc1135b7018e9fc0442db611c139bf58344", size = 8759980, upload-time = "2026-09-14T06:57:55.312Z" }, - { url = "https://files.pythonhosted.org/packages/2c/5c/b67407c6dbc480dfc0715f6eccdb1061e7c88d85f9a330a241d357a538c5/grpcio-1.84.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f6c972474ce691aca74e58d17625450cef153dc4760364cadeb167983ea6d589", size = 8124904, upload-time = "2026-09-14T06:57:58.569Z" }, - { url = "https://files.pythonhosted.org/packages/02/37/2bfdae2df8dfcfc0df619b628e0c7153ce703adae827243f44720322ccc1/grpcio-1.84.0-cp312-cp312-win32.whl", hash = "sha256:0d532ade4486dad9b302ffa4d4683d67561051c26d17c4023322845e9fa10140", size = 4478915, upload-time = "2026-09-14T06:58:00.714Z" }, - { url = "https://files.pythonhosted.org/packages/85/2c/309268b7b39f6deb2342f634841e105623a0b67982e8b10ec516782ff1c6/grpcio-1.84.0-cp312-cp312-win_amd64.whl", hash = "sha256:49717e857899f4136d7657bf5aded61ac479110a075438290923a4d86af7cd02", size = 5253534, upload-time = "2026-09-14T06:58:03.336Z" }, - { url = "https://files.pythonhosted.org/packages/5d/51/40f99701adb01d4e5316a2aaf13838da1a24d5c879cd8c95156d7c364454/grpcio-1.84.0-cp313-cp313-linux_armv7l.whl", hash = "sha256:209414080da8c20af94df1395b635da52dd57b5edc9e917e1deca0dc1c4bb55e", size = 6427619, upload-time = "2026-09-14T06:58:06.025Z" }, - { url = "https://files.pythonhosted.org/packages/c5/4b/ed8e22a1237e6b2be6ef4f221d074a5b0e0dd8a0da8c944c04aea731f0eb/grpcio-1.84.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:e41c3993eee896c617dbd8a505085d28b6e84a0445ed9a1f40f95808473cf678", size = 12336549, upload-time = "2026-09-14T06:58:08.583Z" }, - { url = "https://files.pythonhosted.org/packages/d3/50/00165b05cd73f45996748ea67ce9e55d08936f2fea94a7fd8541cc2d0e54/grpcio-1.84.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fff5ef3fe1bba7d6147e5f19e01e5e122ac2c076486887ddcb8d42e663400fbe", size = 6989458, upload-time = "2026-09-14T06:58:11.884Z" }, - { url = "https://files.pythonhosted.org/packages/26/38/d0486230e684d916f97429a53041db88410e662a38f2a8d09e2d90375840/grpcio-1.84.0-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:b8c62888c3e49debf37ad9773e3c02f77b0c1e811f8fb0962f2b6c3bbab5b97a", size = 7757778, upload-time = "2026-09-14T06:58:14.849Z" }, - { url = "https://files.pythonhosted.org/packages/da/56/548a643decb059ca244499c675ae2c13a15f523ba94592c2774bd80a13c1/grpcio-1.84.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:986e9751d416d7a6eaa2fecdac38da63153d63a4b340ba7d624889c490451500", size = 7159572, upload-time = "2026-09-14T06:58:17.87Z" }, - { url = "https://files.pythonhosted.org/packages/db/f5/42caac81a79ec680f1f7a8eaf7ca90d2f93936ce0c3a073141ba96757f77/grpcio-1.84.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5933a052946873d01a42119a05420d669bdca436aeba2d1851988ccb12b421c0", size = 7710547, upload-time = "2026-09-14T06:58:20.607Z" }, - { url = "https://files.pythonhosted.org/packages/57/a4/828ad990b2410fee0a55cc73aa1bf98eb5b911c54847374ef4f24b9e877b/grpcio-1.84.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:e094dd21f077af8194923fc263cad872eaa1802bb0156fd7e5ae18e99cd86715", size = 8761519, upload-time = "2026-09-14T06:58:23.875Z" }, - { url = "https://files.pythonhosted.org/packages/d5/a5/1f91af098919eaf5d80d5a61126ad9fae074e5190c25a3014ce1d8d0d890/grpcio-1.84.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:08735e3d08d24ab3132cf87e2e5dea8746cabcc7d676c2b0b7362f195feef9d9", size = 8121424, upload-time = "2026-09-14T06:58:27.006Z" }, - { url = "https://files.pythonhosted.org/packages/8c/8f/77fd4a7a913b636785479922349c4cb98d94d05d15652e556b3ca0df6663/grpcio-1.84.0-cp313-cp313-win32.whl", hash = "sha256:70bb4ce8be0c5606bec259cbd7152374470396413b7863a658a08c849e6b29ff", size = 4477974, upload-time = "2026-09-14T06:58:29.528Z" }, - { url = "https://files.pythonhosted.org/packages/d0/9a/1fa59ddbfc8898e5518d1447e46f771f387f0ed6132ad531395338e51a5c/grpcio-1.84.0-cp313-cp313-win_amd64.whl", hash = "sha256:b61692f0069b3eee2fc8a3a1b7f6c044df9e03fede6ce69b3ca832e1c39f26c5", size = 5255326, upload-time = "2026-09-14T06:58:31.781Z" }, - { url = "https://files.pythonhosted.org/packages/26/6f/e25ca89ca5b0b7b95464c907a5c21a77c0ac8c4ee1dca164c4dd8f153ddb/grpcio-1.84.0-cp314-cp314-linux_armv7l.whl", hash = "sha256:026d757df86c5b7a41de8200b9a2cda454aaa5004cb0c7e3374c66eb82f61499", size = 6428207, upload-time = "2026-09-14T06:58:34.401Z" }, - { url = "https://files.pythonhosted.org/packages/cd/b4/6b76b429f3f9b901cdbc306c81364d708bc957f847a05cbd1046cd2d05d8/grpcio-1.84.0-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:3de427b05f244ba2c2a9bdc67e7a6731c8340811524ecc4435466549f8af1d17", size = 12342420, upload-time = "2026-09-14T06:58:37.416Z" }, - { url = "https://files.pythonhosted.org/packages/af/64/ac86d638ba7f73bee0dccb608ba551d4f63adf75151f00d2c43e46d3979e/grpcio-1.84.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e90e3bdf7b5eac005fef631adae9cafde16f922def207b80a7c46b253c18ad20", size = 6998396, upload-time = "2026-09-14T06:58:40.535Z" }, - { url = "https://files.pythonhosted.org/packages/4a/65/fa12e9ec9d7ebf8cc3e81428fa9e1ca0d30d22d546ce2baa4c64bc917cbc/grpcio-1.84.0-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:e88d304f094f4937bc27ec6a435e218a084168f11ec630c8d5d39b431d08d81d", size = 7757538, upload-time = "2026-09-14T06:58:43.297Z" }, - { url = "https://files.pythonhosted.org/packages/21/d7/94240c7fae121ff1f116dcf04a3b7ee0216a06832c704310363f72638d4c/grpcio-1.84.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:57dc36a5ab0e676f5f6e171de2917fd0aef73f32a9aaf23956bfe19997a30bd1", size = 7161480, upload-time = "2026-09-14T06:58:45.939Z" }, - { url = "https://files.pythonhosted.org/packages/23/c9/7033e95d4b344969818b09185721c7608b47fc2498d97b5e4eec4995dbf3/grpcio-1.84.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:5deda5b4bf62769eb98c119cca43d40e1231e34846b19db5cdea821d446a2253", size = 7720191, upload-time = "2026-09-14T06:58:48.308Z" }, - { url = "https://files.pythonhosted.org/packages/95/22/b45df2deba81d55069076859480bae7109c9eec02bce5515c799530cc2aa/grpcio-1.84.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:9bab4cf571653a8afffb83ce21aa27b51dfe629b526b7b6adec35491fe1fc2ea", size = 8762792, upload-time = "2026-09-14T06:58:51.068Z" }, - { url = "https://files.pythonhosted.org/packages/de/c4/3e1c3d6155c16b8737cc31d5b477d6cf1fc7cdd10d58320cf0ec9b446f42/grpcio-1.84.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c5559b492007dc09b4de9b95dab05f0b5e53547aad230cf07e46c7dd017a3be5", size = 8123299, upload-time = "2026-09-14T06:58:54.332Z" }, - { url = "https://files.pythonhosted.org/packages/56/fe/f4864de5b815e5ba18858771f99381a398fac14117f89ef5291ed43d3c4e/grpcio-1.84.0-cp314-cp314-win32.whl", hash = "sha256:2c024da73b296f040b8360e60bd73a659b230093684a438da0e1260f34cc724e", size = 4562560, upload-time = "2026-09-14T06:58:56.894Z" }, - { url = "https://files.pythonhosted.org/packages/44/03/640811d4d8c84f5e603995c5a9bab725223aa472cad9ca4286c3bbf1c3e3/grpcio-1.84.0-cp314-cp314-win_amd64.whl", hash = "sha256:800b7e00d92553313c0463c200087930aa78678ec1d528193aeb50906f55989b", size = 5394092, upload-time = "2026-09-14T06:58:59.61Z" }, - { url = "https://files.pythonhosted.org/packages/4a/1a/9e3d2c9f005f680f03308fa894b1db91d4ab3f0fe65ff630c69561e91e95/grpcio-1.84.0-cp315-cp315-linux_armv7l.whl", hash = "sha256:47ecf0d9b81d981f07b61bd89eced9d2582f5eaacc3aaa36ad27f81aef70a27f", size = 6428252, upload-time = "2026-09-14T06:59:02.597Z" }, - { url = "https://files.pythonhosted.org/packages/77/34/0bc9f52ebf091311651eeab3a452fb557985604a3088cb5406f4d6df85d3/grpcio-1.84.0-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:61386101ecaa096b694d0dd278caf99a56aeec78440cc17e918eef0b50f2d567", size = 12359488, upload-time = "2026-09-14T06:59:05.646Z" }, - { url = "https://files.pythonhosted.org/packages/93/0e/c31052712f241cb6ecae9c226fabd519b7f8c64a7a40bac27e9ca0405b78/grpcio-1.84.0-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f6d178ba6dc8e82976c184b65fddde172d054c17237993a3e083efe4f134d55b", size = 7019339, upload-time = "2026-09-14T06:59:08.76Z" }, - { url = "https://files.pythonhosted.org/packages/55/b9/b9b33ea4f1eb4cad28833cade604febf357385b5ebb0c9c7562d020e167a/grpcio-1.84.0-cp315-cp315-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:15bb76489e337fc492685c9758e2fd4d4ab516b901ad830dc5a91987decf00be", size = 7107974, upload-time = "2026-09-14T06:59:11.568Z" }, - { url = "https://files.pythonhosted.org/packages/0e/9e/799d4c45db91bbdcd8c54b3982932dbcf3d059f7ce67dca3e8540faa1ece/grpcio-1.84.0-cp315-cp315-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:82da34ae4f639c73ac46e521e00c0a49bf86f717b9fb1f405f133e98731e38dc", size = 7200036, upload-time = "2026-09-14T06:59:14.401Z" }, - { url = "https://files.pythonhosted.org/packages/45/dc/dcfdd13ada41aff9098f0c2c6f260eb7debbc88b84b7e5fcbd085165427d/grpcio-1.84.0-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:9b73836ba0e16fcbb57c31cf6cbc2907c8d8c790b83679df454b74bd15e0be04", size = 7742281, upload-time = "2026-09-14T06:59:17.348Z" }, - { url = "https://files.pythonhosted.org/packages/55/31/75eab2ec77b80804bc5e21cec99b57598e726fca6484cd3e8920a97639d5/grpcio-1.84.0-cp315-cp315-musllinux_1_2_i686.whl", hash = "sha256:42959bd50dd660ffc3f2a9bec15a6da4f9aaa0dda555d59ff2d2e80b908456a8", size = 8113629, upload-time = "2026-09-14T06:59:20.584Z" }, - { url = "https://files.pythonhosted.org/packages/34/f0/fdcf6bdc1df9ca11679a1187bef8e6b81df31a2baae69497e17344f05ea3/grpcio-1.84.0-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:659728f20fc7a0933ed7b1945435e31014b97ab8a5a7edcbaa70da4794aeb191", size = 8152972, upload-time = "2026-09-14T06:59:24.523Z" }, - { url = "https://files.pythonhosted.org/packages/5c/cf/6720e720bfa80fcb1ace873f66724eb3c8b03bba2fa078a30c12cab3212e/grpcio-1.84.0-cp315-cp315-win32.whl", hash = "sha256:edb6f87fc60ff438557291501b3e16c7a77c3b01a52d782cf276dccc7c5dd89c", size = 4561981, upload-time = "2026-09-14T06:59:27.275Z" }, - { url = "https://files.pythonhosted.org/packages/7f/b9/69d8a709df225bc2e06e028e9465166b174c24b3da07cc72d9a5ddc63194/grpcio-1.84.0-cp315-cp315-win_amd64.whl", hash = "sha256:4119efa6519871719ad81f33bc95ab87857dcb1c5801f30a6e592f2c41164169", size = 5394757, upload-time = "2026-09-14T06:59:30.118Z" }, -] - -[[package]] -name = "grpcio-status" -version = "1.84.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "googleapis-common-protos" }, - { name = "grpcio" }, - { name = "protobuf" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/52/45/f80309cdb6a7dbf8f65e2082dd2ddc9797ba7180516a73c54d966ba632c4/grpcio_status-1.84.0.tar.gz", hash = "sha256:5caf28ba7184b81f618b5f7f094859fd2541bf429d2189bbbcd715c9c2cdcee2", size = 14015, upload-time = "2026-09-14T07:10:29.402Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/71/c4/3a77e4273e866b1b0c412afd80882e95941a37170032b5109d847c501124/grpcio_status-1.84.0-py3-none-any.whl", hash = "sha256:0c182ca0d6e60acbfd0e14499cf39a155e4827a1c3fd9f7638e49af15a74c30a", size = 14639, upload-time = "2026-09-14T07:10:15.175Z" }, -] - [[package]] name = "h11" version = "0.16.0" @@ -2143,15 +2078,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/41/d9/bb6811e438d3c74d26cb5d72e6343606e0a7a6283fee87521189f4ba1191/prek-0.5.5-py3-none-win_arm64.whl", hash = "sha256:3db3191d2509830c75438e20956a47c2259867d18a4106064ed75088b968a0c6", size = 5517355, upload-time = "2026-10-05T06:08:57.193Z" }, ] -[[package]] -name = "promise" -version = "2.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "six" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/cf/9c/fb5d48abfe5d791cd496e4242ebcf87a4bb2e0c3dcd6e0ae68c11426a528/promise-2.3.tar.gz", hash = "sha256:dfd18337c523ba4b6a58801c164c1904a9d4d1b1747c7d5dbf45b693a49d93d0", size = 19534, upload-time = "2019-12-18T07:31:43.07Z" } - [[package]] name = "prompt-toolkit" version = "3.0.53" @@ -2936,23 +2862,17 @@ wheels = [ ] [[package]] -name = "tilebox-datasets" -source = { editable = "tilebox-datasets" } +name = "tilebox-core" +source = { editable = "tilebox-core" } dependencies = [ - { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, - { name = "numpy", version = "2.5.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, - { name = "pandas" }, - { name = "promise" }, + { name = "anyio" }, + { name = "connectrpc" }, { name = "protobuf-py" }, - { name = "shapely" }, - { name = "tilebox-grpc" }, - { name = "tqdm" }, - { name = "xarray" }, + { name = "pyqwest" }, ] [package.dev-dependencies] dev = [ - { name = "hypothesis" }, { name = "pytest" }, { name = "pytest-asyncio" }, { name = "pytest-cov" }, @@ -2960,38 +2880,36 @@ dev = [ [package.metadata] requires-dist = [ - { name = "numpy", specifier = ">=1.25" }, - { name = "pandas", specifier = ">=2.2.2" }, - { name = "promise", specifier = ">=2.3" }, - { name = "protobuf-py", specifier = ">=0.2.0" }, - { name = "shapely", specifier = ">=2.0.6" }, - { name = "tilebox-grpc", editable = "tilebox-grpc" }, - { name = "tqdm", specifier = ">=4.65" }, - { name = "xarray", specifier = ">=2024.6" }, + { name = "anyio", specifier = ">=4" }, + { name = "connectrpc", specifier = ">=0.12.1,<0.13" }, + { name = "protobuf-py", specifier = ">=0.6.0,<0.7" }, + { name = "pyqwest", specifier = ">=0.7.0" }, ] [package.metadata.requires-dev] dev = [ - { name = "hypothesis", specifier = ">=6.112.1" }, { name = "pytest", specifier = ">=8.3.2" }, { name = "pytest-asyncio", specifier = ">=0.24.0" }, { name = "pytest-cov", specifier = ">=5.0.0" }, ] [[package]] -name = "tilebox-grpc" -source = { editable = "tilebox-grpc" } +name = "tilebox-datasets" +source = { editable = "tilebox-datasets" } dependencies = [ - { name = "anyio" }, - { name = "connectrpc" }, - { name = "grpcio" }, { name = "lz4" }, - { name = "protobuf" }, - { name = "pyqwest" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "numpy", version = "2.5.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "pandas" }, + { name = "shapely" }, + { name = "tilebox-core" }, + { name = "tqdm" }, + { name = "xarray" }, ] [package.dev-dependencies] dev = [ + { name = "hypothesis" }, { name = "pytest" }, { name = "pytest-asyncio" }, { name = "pytest-cov" }, @@ -2999,27 +2917,38 @@ dev = [ [package.metadata] requires-dist = [ - { name = "anyio", specifier = ">=4" }, - { name = "connectrpc", specifier = ">=0.10.1,<0.11.0" }, - { name = "grpcio", specifier = ">=1.84.0" }, { name = "lz4", specifier = ">=4" }, - { name = "protobuf", specifier = ">=6.31.0" }, - { name = "pyqwest", specifier = ">=0.7.0" }, + { name = "numpy", specifier = ">=1.25" }, + { name = "pandas", specifier = ">=2.2.2" }, + { name = "shapely", specifier = ">=2.0.6" }, + { name = "tilebox-core", editable = "tilebox-core" }, + { name = "tqdm", specifier = ">=4.65" }, + { name = "xarray", specifier = ">=2024.6" }, ] [package.metadata.requires-dev] dev = [ + { name = "hypothesis", specifier = ">=6.112.1" }, { name = "pytest", specifier = ">=8.3.2" }, { name = "pytest-asyncio", specifier = ">=0.24.0" }, { name = "pytest-cov", specifier = ">=5.0.0" }, ] +[[package]] +name = "tilebox-grpc" +source = { editable = "tilebox-grpc" } +dependencies = [ + { name = "tilebox-core" }, +] + +[package.metadata] +requires-dist = [{ name = "tilebox-core", editable = "tilebox-core" }] + [[package]] name = "tilebox-python" source = { editable = "." } dependencies = [ { name = "tilebox-datasets" }, - { name = "tilebox-grpc" }, { name = "tilebox-storage" }, { name = "tilebox-workflows" }, ] @@ -3040,7 +2969,6 @@ dev = [ [package.metadata] requires-dist = [ { name = "tilebox-datasets", editable = "tilebox-datasets" }, - { name = "tilebox-grpc", editable = "tilebox-grpc" }, { name = "tilebox-storage", editable = "tilebox-storage" }, { name = "tilebox-workflows", editable = "tilebox-workflows" }, ] @@ -3070,6 +2998,7 @@ dependencies = [ { name = "pyproj", version = "3.7.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, { name = "pyproj", version = "3.8.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, { name = "tenacity" }, + { name = "tilebox-core" }, { name = "tilebox-datasets" }, ] @@ -3091,6 +3020,7 @@ requires-dist = [ { name = "obstore", specifier = ">=0.11.1" }, { name = "pyproj", specifier = ">=3.4" }, { name = "tenacity", specifier = ">=8" }, + { name = "tilebox-core", editable = "tilebox-core" }, { name = "tilebox-datasets", editable = "tilebox-datasets" }, ] @@ -3110,7 +3040,6 @@ dependencies = [ { name = "azure-identity" }, { name = "boto3" }, { name = "google-auth", extra = ["requests"] }, - { name = "grpcio" }, { name = "msgspec" }, { name = "obstore" }, { name = "opentelemetry-api" }, @@ -3119,8 +3048,8 @@ dependencies = [ { name = "opentelemetry-proto" }, { name = "opentelemetry-sdk" }, { name = "tenacity" }, - { name = "tilebox-datasets" }, - { name = "tilebox-grpc" }, + { name = "tilebox-core" }, + { name = "uvicorn" }, ] [package.optional-dependencies] @@ -3139,7 +3068,6 @@ notebook = [ dev = [ { name = "boto3-stubs", extra = ["s3"] }, { name = "google-cloud-storage" }, - { name = "grpcio-status" }, { name = "hypothesis" }, { name = "ipywidgets" }, { name = "moto" }, @@ -3147,6 +3075,7 @@ dev = [ { name = "pytest" }, { name = "pytest-asyncio" }, { name = "pytest-cov" }, + { name = "tilebox-datasets" }, ] [package.metadata] @@ -3155,7 +3084,6 @@ requires-dist = [ { name = "azure-identity", specifier = ">=1.23" }, { name = "boto3", specifier = ">=1.40.2" }, { name = "google-auth", extras = ["requests"], specifier = ">=2.29" }, - { name = "grpcio", specifier = ">=1.84.0" }, { name = "ipywidgets", marker = "extra == 'notebook'", specifier = ">=8.1.7" }, { name = "msgspec", specifier = ">=0.19" }, { name = "obstore", specifier = ">=0.8.2" }, @@ -3168,8 +3096,8 @@ requires-dist = [ { name = "pyproj", marker = "extra == 'geospatial'", specifier = ">=3.4" }, { name = "shapely", marker = "extra == 'geospatial'", specifier = ">=2" }, { name = "tenacity", specifier = ">=8" }, - { name = "tilebox-datasets", editable = "tilebox-datasets" }, - { name = "tilebox-grpc", editable = "tilebox-grpc" }, + { name = "tilebox-core", editable = "tilebox-core" }, + { name = "uvicorn", specifier = ">=0.35" }, ] provides-extras = ["geospatial", "notebook"] @@ -3177,7 +3105,6 @@ provides-extras = ["geospatial", "notebook"] dev = [ { name = "boto3-stubs", extras = ["s3"], specifier = ">=1.40.2" }, { name = "google-cloud-storage", specifier = ">=3.2.0" }, - { name = "grpcio-status", specifier = ">=1.78.0" }, { name = "hypothesis", specifier = ">=6.112.1" }, { name = "ipywidgets", specifier = ">=8.1.7" }, { name = "moto", specifier = ">=5" }, @@ -3185,6 +3112,7 @@ dev = [ { name = "pytest", specifier = ">=8.4" }, { name = "pytest-asyncio", specifier = ">=1.4.0" }, { name = "pytest-cov", specifier = ">=5.0.0" }, + { name = "tilebox-datasets", editable = "tilebox-datasets" }, ] [[package]] @@ -3346,6 +3274,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d7/bc/c2a384f5bc7b98a48fcb58ded963bd0862f753f8442572ce76037a753e7b/urllib3_future-2.25.902-py3-none-any.whl", hash = "sha256:88e6f3c19f553931e78f84f011a34220f827c971e6cf06b3a075eaa788c2bf7d", size = 862229, upload-time = "2026-10-04T08:50:56.575Z" }, ] +[[package]] +name = "uvicorn" +version = "0.54.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/da/34/30e9280707135d2cfc589dfff3cb796bd07a3aeb1a3e415ba09dd89d7bb4/uvicorn-0.54.0.tar.gz", hash = "sha256:a2e33cbfaa0306f8e6b0c13e0cb89d7d7a2da3e62b90c66e18c33d9807b28620", size = 112283, upload-time = "2026-09-25T06:52:37.601Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/38/0c/b54a4fdd7f90a3af8b02ebc9ce6712c2c208b7926a2f7bad95c33ebbe943/uvicorn-0.54.0-py3-none-any.whl", hash = "sha256:505bdb0f318731d45f1f712071fc781a8981f6847a31c902c9f5e652d4f67faf", size = 87427, upload-time = "2026-09-25T06:52:35.829Z" }, +] + [[package]] name = "wassima" version = "2.1.4" From 108011ef71c81e1fb0971ce73357106f35fe28bf Mon Sep 17 00:00:00 2001 From: Lukas Bindreiter Date: Tue, 27 Oct 2026 14:48:35 +0100 Subject: [PATCH 2/4] Remove google storage cache compatibilty bridge --- CHANGELOG.md | 4 +- pyproject.toml | 1 - tilebox-workflows/pyproject.toml | 1 - tilebox-workflows/tests/test_cache.py | 2 +- .../tests/test_google_cache_compatibility.py | 96 -------------- tilebox-workflows/tilebox/workflows/cache.py | 74 +---------- uv.lock | 117 ------------------ 7 files changed, 9 insertions(+), 286 deletions(-) delete mode 100644 tilebox-workflows/tests/test_google_cache_compatibility.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 62fb9c5..f936c15 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,13 +9,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed -- `tilebox-core`: Own shared Connect RPC infrastructure and protobuf-py messages, public +- `tilebox-core`: New package to own shared Connect RPC infrastructure and protobuf-py messages, public `tilebox.query` interval/pagination types, and `tilebox.errors` exceptions with a common `TileboxError` base. - `tilebox-grpc`: Retain an empty compatibility package depending on `tilebox-core>=0.67.0` for one release. - `tilebox-datasets`, `tilebox-workflows`, and `tilebox-storage`: Depend explicitly on `tilebox-core`; datasets retains its scientific and LZ4 dependencies. - `tilebox-workflows`: Remove the runtime dependency on datasets. Existing public datasets imports of shared intervals, pagination, and conversions remain compatible. Task interval serialization is unchanged. +- `tilebox-workflows`: `GoogleStorageCache` now accepts only bucket names and uses obstore with google-auth. + Remove support for Google Storage SDK `Bucket` objects. - Parse ordinary Python datetimes without pandas. Strings and scientific datetime values still use pandas; workflows-only users need to install pandas explicitly for those inputs. Dataset installations are unchanged. - Raise `tilebox.errors.RequestCanceledError` for remote request cancellation instead of `KeyboardInterrupt`. diff --git a/pyproject.toml b/pyproject.toml index 688ef3c..83aa77d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,7 +25,6 @@ dev = [ "ruff", "ty", "prek", - "types-protobuf>=6.30", "junitparser>=3.2.0", ] diff --git a/tilebox-workflows/pyproject.toml b/tilebox-workflows/pyproject.toml index 7db025a..1d696cd 100644 --- a/tilebox-workflows/pyproject.toml +++ b/tilebox-workflows/pyproject.toml @@ -49,7 +49,6 @@ geospatial = [ dev = [ "tilebox-datasets>=0.67.0", # cross-SDK serialization and compatibility tests "boto3-stubs[s3]>=1.40.2", - "google-cloud-storage>=3.2.0", "hypothesis>=6.112.1", "ipywidgets>=8.1.7", "odc-geo>=0.5", diff --git a/tilebox-workflows/tests/test_cache.py b/tilebox-workflows/tests/test_cache.py index 958b615..a8b05fe 100644 --- a/tilebox-workflows/tests/test_cache.py +++ b/tilebox-workflows/tests/test_cache.py @@ -173,7 +173,7 @@ def test_google_storage_credentials_and_groups() -> None: first = cache.group("folder") assert_type(cache.bucket, str) assert_type(first.bucket, str) - assert_type(first, GoogleStorageCache[str]) + assert_type(first, GoogleStorageCache) first["one"] = b"first" cache.group("folder2")["two"] = b"second" assert list(first) == ["one"] diff --git a/tilebox-workflows/tests/test_google_cache_compatibility.py b/tilebox-workflows/tests/test_google_cache_compatibility.py deleted file mode 100644 index 2c95132..0000000 --- a/tilebox-workflows/tests/test_google_cache_compatibility.py +++ /dev/null @@ -1,96 +0,0 @@ -from collections.abc import Iterator -from typing import assert_type -from unittest.mock import MagicMock, patch -from urllib.parse import parse_qs, urlsplit - -import pytest -from google.api_core.exceptions import Forbidden, NotFound, ServiceUnavailable -from google.auth.credentials import AnonymousCredentials -from google.cloud.storage import Blob, Bucket, Client -from google.oauth2.credentials import Credentials -from requests import Response - -from tilebox.workflows.cache import GoogleStorageCache - - -@pytest.fixture(params=[False, True], ids=["authenticated", "anonymous"]) -def google_bucket(request: pytest.FixtureRequest) -> Iterator[Bucket]: - credentials = AnonymousCredentials() if request.param else Credentials(token="configured-token") # noqa: S106 - client = Client( - project="configured-project", - credentials=credentials, - _http=MagicMock(), - client_options={"api_endpoint": "https://custom.example.test"}, - ) - with ( - patch("google.auth.default", side_effect=AssertionError("Bucket credentials must not be replaced by ADC")), - patch("obstore.store.GCSStore", side_effect=AssertionError("Bucket configuration must stay with its client")), - ): - yield client.bucket("configured-bucket", user_project="billing-project") - - -def test_google_bucket_read_preserves_client(google_bucket: Bucket) -> None: - # Bucket reads and nested groups retain credentials, custom transport, endpoint, and requester-pays billing. - credentials = google_bucket.client._credentials - transport = google_bucket.client._http - response = Response() - response.status_code = 200 - response._content = b"configured client content" - response._content_consumed = True - response.raw = MagicMock(headers={}) - response.headers["content-length"] = str(len(response.content)) - transport.request.return_value = response - root = GoogleStorageCache(google_bucket) - cache = root.group("folder").group("nested") - assert_type(root.bucket, Bucket) - assert_type(cache.bucket, Bucket) - assert_type(cache, GoogleStorageCache[Bucket]) - assert isinstance(cache, GoogleStorageCache) - assert cache.bucket is root.bucket is google_bucket - assert cache["file.txt"] == b"configured client content" - assert google_bucket.client._credentials is credentials - # Newer Google clients may also fetch bucket metadata in a background thread. - downloads = [call for call in transport.request.call_args_list if call.args and "/download/" in call.args[1]] - assert len(downloads) == 1 - method, url = downloads[0].args - assert method == "GET" - parsed = urlsplit(url) - assert parsed.netloc == "custom.example.test" - assert parsed.path.endswith("/configured-bucket/o/jobs%2Ffolder%2Fnested%2Ffile.txt") - assert parse_qs(parsed.query)["userProject"] == ["billing-project"] - - -@pytest.mark.parametrize("prefix", ["jobs", ""]) -def test_google_bucket_operations(google_bucket: Bucket, prefix: str) -> None: - # Bucket caches preserve writes, membership, deletion, and delimiter-based listing for each group. - cache = GoogleStorageCache(google_bucket, prefix).group("folder") - path = f"{prefix}/folder" if prefix else "folder" - with ( - patch.object(Blob, "upload_from_file", autospec=True) as upload, - patch.object(Blob, "exists", autospec=True, return_value=True) as exists, - patch.object(Blob, "delete", autospec=True) as delete, - patch.object(google_bucket, "list_blobs", return_value=[Blob(f"{path}/one", google_bucket)]) as listing, - ): - cache["one"] = b"saved value" - blob, stream = upload.call_args.args - assert blob.bucket is google_bucket - assert blob.name == f"{path}/one" - assert stream.getvalue() == b"saved value" - assert "one" in cache - assert exists.call_args.args[0].name == f"{path}/one" - assert list(cache) == ["one"] - listing.assert_called_once_with(prefix=f"{path}/", delimiter="/") - del cache["one"] - assert delete.call_args.args[0].name == f"{path}/one" - - -@pytest.mark.parametrize("error_type", [NotFound, Forbidden, ServiceUnavailable]) -def test_google_bucket_read_errors(google_bucket: Bucket, error_type: type[Exception]) -> None: - # Only missing objects become KeyError; configured-client permission and backend errors propagate unchanged. - error = error_type("read failed") - with patch.object(Blob, "download_as_bytes", side_effect=error): - expected = KeyError if error_type is NotFound else error_type - with pytest.raises(expected) as raised: - GoogleStorageCache(google_bucket).group("nested")["file"] - if error_type is not NotFound: - assert raised.value is error diff --git a/tilebox-workflows/tilebox/workflows/cache.py b/tilebox-workflows/tilebox/workflows/cache.py index f15f37a..a949248 100644 --- a/tilebox-workflows/tilebox/workflows/cache.py +++ b/tilebox-workflows/tilebox/workflows/cache.py @@ -1,12 +1,11 @@ import contextlib from abc import ABC, abstractmethod -from collections.abc import Iterable, Iterator +from collections.abc import Iterator from copy import copy -from io import BytesIO from pathlib import Path from pathlib import PurePosixPath as ObjectPath from threading import RLock -from typing import TYPE_CHECKING, Any, Generic, Protocol, Self, TypeVar +from typing import TYPE_CHECKING, Any, Self if TYPE_CHECKING: from obstore.store import ObjectStore @@ -259,36 +258,15 @@ def group(self, key: str) -> "LocalFileSystemCache": return LocalFileSystemCache(self.root / key) -class _GoogleBlob(Protocol): - @property - def name(self) -> str: ... - def exists(self) -> bool: ... - def upload_from_file(self, stream: BytesIO, /) -> None: ... - def download_as_bytes(self) -> bytes: ... - def delete(self) -> None: ... - - -class _GoogleBucket(Protocol): - def blob(self, name: str, /) -> _GoogleBlob: ... - def list_blobs(self, *, prefix: str, delimiter: str) -> Iterable[_GoogleBlob]: ... - - -_BucketT = TypeVar("_BucketT", bound=str | _GoogleBucket) - - -class GoogleStorageCache(ObstoreCache, Generic[_BucketT]): - def __init__(self, bucket: _BucketT, prefix: str | ObjectPath = "jobs") -> None: +class GoogleStorageCache(ObstoreCache): + def __init__(self, bucket: str, prefix: str | ObjectPath = "jobs") -> None: """A cache implementation that stores data in Google Cloud Storage. Args: - bucket: A bucket name or Google SDK Bucket. Names use obstore and Google Auth; - Bucket objects keep their existing client and its configuration. + bucket: The bucket name. Uses obstore with Google application default credentials. prefix: A path prefix to append to all objects stored in the cache. Defaults to "jobs". """ self.bucket = bucket - if not isinstance(bucket, str): - self.prefix = ObjectPath(prefix) - return import google.auth # noqa: PLC0415 from obstore.auth.google import GoogleCredentialProvider # noqa: PLC0415 @@ -298,48 +276,6 @@ def __init__(self, bucket: _BucketT, prefix: str | ObjectPath = "jobs") -> None: store = GCSStore(bucket, credential_provider=GoogleCredentialProvider(credentials=credentials)) super().__init__(store, prefix) - def __contains__(self, key: str) -> bool: - if isinstance(self.bucket, str): - return super().__contains__(key) - return self.bucket.blob(str(self.prefix / key)).exists() - - def __setitem__(self, key: str, value: bytes) -> None: - if isinstance(self.bucket, str): - super().__setitem__(key, value) - else: - self.bucket.blob(str(self.prefix / key)).upload_from_file(BytesIO(value)) - - def __getitem__(self, key: str) -> bytes: - if isinstance(self.bucket, str): - return super().__getitem__(key) - - from google.api_core.exceptions import NotFound # noqa: PLC0415 - - try: - return self.bucket.blob(str(self.prefix / key)).download_as_bytes() - except NotFound: - raise KeyError(f"{key} is not cached!") from None - - def __delitem__(self, key: str) -> None: - if isinstance(self.bucket, str): - super().__delitem__(key) - return - - from google.api_core.exceptions import NotFound # noqa: PLC0415 - - try: - self.bucket.blob(str(self.prefix / key)).delete() - except NotFound: - raise KeyError(f"{key} is not cached!") from None - - def __iter__(self) -> Iterator[str]: - if isinstance(self.bucket, str): - yield from super().__iter__() - return - prefix = "" if self.prefix == ObjectPath(".") else str(self.prefix) + "/" - for blob in self.bucket.list_blobs(prefix=prefix, delimiter="/"): - yield str(ObjectPath(blob.name).relative_to(self.prefix)) - def group(self, key: str) -> Self: group = copy(self) group.prefix = self.prefix / key diff --git a/uv.lock b/uv.lock index 4c2fab3..da69b78 100644 --- a/uv.lock +++ b/uv.lock @@ -813,23 +813,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl", hash = "sha256:760643d3452b4d777d295bb167ccc74c64a81df23fb5e08eff250c425a4b2017", size = 28317, upload-time = "2025-09-01T09:48:08.5Z" }, ] -[[package]] -name = "google-api-core" -version = "2.40.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "google-auth" }, - { name = "googleapis-common-protos" }, - { name = "opentelemetry-api" }, - { name = "proto-plus" }, - { name = "protobuf" }, - { name = "requests" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/23/f7/0fb8c3618c783ad49da1aaf97f93a4bb9e4ed522135516b44d129a7f85bb/google_api_core-2.40.0.tar.gz", hash = "sha256:ebee7d1b138b5362beecec260e6e8988ac97346562c7382ccb6f0ad8435c599f", size = 258617, upload-time = "2026-09-29T19:25:54.611Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/44/e4/35f849ef989a801fa4f32ada19cef898fc32913c6b93bc2a6a4f95d2df9d/google_api_core-2.40.0-py3-none-any.whl", hash = "sha256:4b9e0a80024c269ae173136d5439f4ed284651d6e5e2773ba5c694f481f9c0f4", size = 214233, upload-time = "2026-09-29T19:25:29.006Z" }, -] - [[package]] name = "google-auth" version = "2.59.1" @@ -848,81 +831,6 @@ requests = [ { name = "requests" }, ] -[[package]] -name = "google-cloud-core" -version = "2.8.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "google-api-core" }, - { name = "google-auth" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/55/71/d6081acadf55d4233271c39860f0d140ef61fbdb4bceb2075e9c2905d947/google_cloud_core-2.8.0.tar.gz", hash = "sha256:365f8e4518ae81c8101b8dea5fc1c32a960badedb8b511f19db2843cbbd285d2", size = 36605, upload-time = "2026-09-29T19:25:59.275Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/12/ed/1b09640a565e5d34d4e517463f65a67a11f9dcdeb6b6e8220f2d06aaf489/google_cloud_core-2.8.0-py3-none-any.whl", hash = "sha256:e235b0952f7ffe7b9c71a4cf96b506d9cfb557e22557c412f0df9b7068b5d007", size = 31048, upload-time = "2026-09-29T19:25:35.361Z" }, -] - -[[package]] -name = "google-cloud-storage" -version = "3.16.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "google-api-core" }, - { name = "google-auth" }, - { name = "google-cloud-core" }, - { name = "google-crc32c" }, - { name = "google-resumable-media" }, - { name = "requests" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/ae/54/d487cd2bcf2c94befce79baa677c54fa863f88c221e5481a4066ab08c33e/google_cloud_storage-3.16.0.tar.gz", hash = "sha256:3f75cf8ad345d68ef33f0c69db8a8b3f34f160b1524e75d73890602b7b8a4d6e", size = 17351062, upload-time = "2026-10-01T18:18:20.765Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/47/bf/1a0f5f7fc1d4af8b95fc1da72d3cfe84ac709ef4dcabfd2182ef9cfe3a7a/google_cloud_storage-3.16.0-py3-none-any.whl", hash = "sha256:59e54ff1f7f8a7253c01f593f79d253728c122a8901b83f68fe7464a4131539f", size = 343457, upload-time = "2026-10-01T18:12:33.141Z" }, -] - -[[package]] -name = "google-crc32c" -version = "1.9.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/fa/25/9cb0c1c31c45b893eb8f11ae70b3f4309432d59b5acaebca5dbe791729a4/google_crc32c-1.9.0.tar.gz", hash = "sha256:7b8c84c3d159ab6817fe3f74e6e6cef099c3f95dcec3abc0d8afb1404642efbe", size = 14857, upload-time = "2026-09-24T21:39:32.067Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/0e/55/a2f07f15e624f0de79359b1a6c1deb59ec5061bd3b38744b3b2849400662/google_crc32c-1.9.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:457d0d9a4718fd52b1494eac5c200ad25beeadbdc91843d550a003910838589f", size = 31958, upload-time = "2026-09-24T21:19:00.994Z" }, - { url = "https://files.pythonhosted.org/packages/f8/b3/923743597b774bbcf12a7c3e00e48d745e15fd616ad7489a40a63fff8f2f/google_crc32c-1.9.0-cp311-cp311-macosx_12_0_x86_64.whl", hash = "sha256:ccfe40021fd6afe23361175cf7551e3cef5fd34dc1ebe319f14993a83579e0eb", size = 31805, upload-time = "2026-09-24T21:22:25.019Z" }, - { url = "https://files.pythonhosted.org/packages/df/a6/4d0352fe889663e0d81cea7fc664ec9158727384de4a44ab10e9967a7682/google_crc32c-1.9.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fbef61a3794e011c65fb4396a196cf123a7f474fe5a443db8e5dd7d751b9e6d4", size = 38119, upload-time = "2026-09-24T21:38:06.634Z" }, - { url = "https://files.pythonhosted.org/packages/aa/e3/26685384e4b66ff0928d9566ef6110a7df76029175a1842329d7e3515f10/google_crc32c-1.9.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:86764b99e7a607830d93cb5b75e0ec3ff6cb06d3c274624418473cee701900d4", size = 36677, upload-time = "2026-09-24T21:38:08.082Z" }, - { url = "https://files.pythonhosted.org/packages/cb/ce/4e90102e84880e97d3cf935f2672ecd29191bdeacf57f01740f92debda00/google_crc32c-1.9.0-cp311-cp311-win_amd64.whl", hash = "sha256:43a2dc26f9be213fbe0b4fc4a1088c5d45cbfcb3247420ccc820f0fc3edeea86", size = 35096, upload-time = "2026-09-24T21:39:28.201Z" }, - { url = "https://files.pythonhosted.org/packages/e4/5d/0730e1b3a14d054d1466f2fec88dadf978509c749a3d96d8b069cc56d38a/google_crc32c-1.9.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:53fdafef58e230d0c946ab5f8446d123d9f548230a73b29c8b41c9546f268bc1", size = 31961, upload-time = "2026-09-24T21:19:01.724Z" }, - { url = "https://files.pythonhosted.org/packages/dd/32/d085abaf2fd907121975b92245bb3480fb8be40c37d03f9d6c41857f84c3/google_crc32c-1.9.0-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:8b91f41645b15a720357183fa5716682ada441873e3c462c15f9714be36f146b", size = 31804, upload-time = "2026-09-24T21:22:25.81Z" }, - { url = "https://files.pythonhosted.org/packages/94/78/dd1935432337e5da7af391a6fc9f161c1c8e9b9002a402b9190135fe1b59/google_crc32c-1.9.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:16865b477d7941712cb0e0aad8ad4815e984fb5fc16d3fdaef7d986e26e53c95", size = 37775, upload-time = "2026-09-24T21:38:09.249Z" }, - { url = "https://files.pythonhosted.org/packages/9e/43/9db03635bb10188d93dcbab9baa2a8670a0da4e868b4370cdbd98d65fed8/google_crc32c-1.9.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3abb18297d9ef0ab120531838be0e6d68c9fa876570e11c229c48f2edac23ce7", size = 36347, upload-time = "2026-09-24T21:38:10.141Z" }, - { url = "https://files.pythonhosted.org/packages/cf/eb/94dee516c846bd9382c3f566d8f8e5fb9e90599e45afeb697f9fc2533528/google_crc32c-1.9.0-cp312-cp312-win_amd64.whl", hash = "sha256:fb63a8d7fa2e95dcff1ca16af2f4d88b526fa5ff72d1696285884ac2d49b6963", size = 35097, upload-time = "2026-09-24T21:39:28.934Z" }, - { url = "https://files.pythonhosted.org/packages/3f/34/cb484e8b6174f130f8c6dc79c733a9dd8869b410ad6511fb6104c46b973a/google_crc32c-1.9.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:f1dc17d987ddcc5eba12a7ce48f0eb93141dea236b170c1101151396edf2f0cf", size = 31960, upload-time = "2026-09-24T21:19:02.454Z" }, - { url = "https://files.pythonhosted.org/packages/af/25/3e8e567bd48448e225ea27318ccf2b94e05124e7b8b97b13eaec9e127199/google_crc32c-1.9.0-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:f894a2877650b56201d26a012a257b76d54a68834dc3913a93830ca8a047b075", size = 31803, upload-time = "2026-09-24T21:22:27.008Z" }, - { url = "https://files.pythonhosted.org/packages/f0/18/bee0dd59ae622482dc6463636c79e4bde7c954d061c859c9256362c9931a/google_crc32c-1.9.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:4488f1553a9ab7e86cdedc833374a7e904031803b995dc0bd0be48c271fa6556", size = 37776, upload-time = "2026-09-24T21:38:11.056Z" }, - { url = "https://files.pythonhosted.org/packages/fd/b6/e76e80fed5f2558273c7839e622f98095c9b36c719c7147e38e3c055cb70/google_crc32c-1.9.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0568b17ed90ac596f29400d99e243fd0cc6276766183def888d1bf8d1dc13827", size = 36350, upload-time = "2026-09-24T21:38:12.138Z" }, - { url = "https://files.pythonhosted.org/packages/87/34/165542bfa99dfef91a76471cc48cce74b8ff4e295722896087ab2b8e8611/google_crc32c-1.9.0-cp313-cp313-win_amd64.whl", hash = "sha256:8583ec21d56b565d68ab2963cc7e21b3b271247c29b04286068255ef65f221bd", size = 35090, upload-time = "2026-09-24T21:39:29.764Z" }, - { url = "https://files.pythonhosted.org/packages/8f/eb/43ea41f4061a1cad87b2b6559c98e960e45bf551fe66f83d833b98aaf0c9/google_crc32c-1.9.0-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:6a3b2c8a343c570ed8100a7627c20badfd92c6caa2067093a86be45af27f5b1b", size = 31963, upload-time = "2026-09-24T21:19:03.208Z" }, - { url = "https://files.pythonhosted.org/packages/45/d2/a968c0c29ccd2b0c980ff4f9e3f7035cee28c23a1c57541825cc8221858c/google_crc32c-1.9.0-cp314-cp314-macosx_12_0_x86_64.whl", hash = "sha256:13179f7e3282617923e957b8e54b8f9c3968030f48640a9f47fd7c5c38c4a215", size = 31805, upload-time = "2026-09-24T21:22:27.917Z" }, - { url = "https://files.pythonhosted.org/packages/03/73/388e493d6c3e252e37165d22efe5a1361f872a24425391b999822861b23a/google_crc32c-1.9.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:265233aff33d835f5b909584fe36ab29647b598c271b661a300001099109e53e", size = 37925, upload-time = "2026-09-24T21:38:13.32Z" }, - { url = "https://files.pythonhosted.org/packages/98/36/190d32caa363ef25d685f422ed1bbf93ff1140fb22fd4d90f24cec209977/google_crc32c-1.9.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:dee799544cae42a42b17a88e38b59cf2c271051dc001da2117a8ff240ffa0548", size = 36495, upload-time = "2026-09-24T21:38:14.211Z" }, - { url = "https://files.pythonhosted.org/packages/d3/fd/81cefea6adae7bd92abb23d4567d199f6485a20ec0a305ca5fa04c52b9c5/google_crc32c-1.9.0-cp314-cp314-win_amd64.whl", hash = "sha256:af73200fa9791ccd380f3598235dba8d82b8af0905df045b3dc60b59836e8ddd", size = 35535, upload-time = "2026-09-24T21:39:30.52Z" }, - { url = "https://files.pythonhosted.org/packages/c5/18/19d4f17f3f33f8fdffcb3e1e69219d6f7ec2c359c160867b04dac1d0a64d/google_crc32c-1.9.0-cp315-cp315-macosx_12_0_arm64.whl", hash = "sha256:e6e8be8a94436079cb5340f6d495d9d7ba30124d8b952703994c739c7c06e236", size = 31977, upload-time = "2026-09-24T21:19:03.976Z" }, - { url = "https://files.pythonhosted.org/packages/81/b4/8010372c4b46f2ee2352dfdb630c397570cd85522a315df024ad2f9459aa/google_crc32c-1.9.0-cp315-cp315-macosx_12_0_x86_64.whl", hash = "sha256:f2b64641bca27497b986b9d87883014035aa904cb4fa333407c6752b3afee9ba", size = 31809, upload-time = "2026-09-24T21:22:29.1Z" }, - { url = "https://files.pythonhosted.org/packages/c5/f8/7e33845d6b90ce1cf37cfabf25cb859277c7d3533ef1b6b1e1ca58581549/google_crc32c-1.9.0-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f97c3806dcea41c29c04965347b0e12481561b75e0045dc7a4f69d75dec5d9b1", size = 38359, upload-time = "2026-09-24T21:38:14.983Z" }, - { url = "https://files.pythonhosted.org/packages/36/ff/556b2423f449a7515af6b8222a4d7833cbe09ff3e8d2f0b80471f5f6d02e/google_crc32c-1.9.0-cp315-cp315-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0abe7e202c25909869c35672ab0f2fe748a7acf276eb78577332a7c38999740f", size = 36858, upload-time = "2026-09-24T21:38:15.799Z" }, - { url = "https://files.pythonhosted.org/packages/40/71/4733f1b7c921d04a2bb9b9916cf66498bf7ad0860a06289413830da83192/google_crc32c-1.9.0-cp315-cp315-win_amd64.whl", hash = "sha256:5695c8b9327e040b2aba12c6659b0acb5995314ef0af0192da66e662e011103b", size = 35534, upload-time = "2026-09-24T21:39:31.337Z" }, -] - -[[package]] -name = "google-resumable-media" -version = "2.11.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "google-crc32c" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/cf/64/df6a482d5aa39d7f7be186d892377d605cae6c88525fd851d456e8bbe9c9/google_resumable_media-2.11.0.tar.gz", hash = "sha256:febd83686752799661b4de575f0b993c5c25c349a5362556fc4d7be164056a37", size = 2164914, upload-time = "2026-09-29T19:26:13.546Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5e/2e/4f0a152f2e576e496f31ba1c3c62ed174a8878d06008916a7edc58b1bb28/google_resumable_media-2.11.0-py3-none-any.whl", hash = "sha256:f43d15e6a7f818f762eaead0f369c551f8275a4179c9d6225d0d259f49b87b5d", size = 81515, upload-time = "2026-09-29T19:25:47.31Z" }, -] - [[package]] name = "googleapis-common-protos" version = "1.75.5" @@ -2090,18 +1998,6 @@ wheels = [ { 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.29.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "protobuf" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/46/70/783e33ffbb4466cc154a94f79b869b92a451e2bd45605054e68ff68b7af6/proto_plus-1.29.0.tar.gz", hash = "sha256:cfb4e62ad7e13dd18f346cabbda00cab39930d36a05791fd81ddb074d6ee884f", size = 58592, upload-time = "2026-09-29T19:26:15.963Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b1/05/a3ef5b1161498e7b5a2a60288a9cf41b9149e94555423a43ec536737ac1a/proto_plus-1.29.0-py3-none-any.whl", hash = "sha256:8acd070469a7aaf43f440b022ef9757c8cac1a9f866e933f59ae98669ddc6c8b", size = 50786, upload-time = "2026-09-29T19:25:50.409Z" }, -] - [[package]] name = "protobuf" version = "7.36.2" @@ -2963,7 +2859,6 @@ dev = [ { name = "pyarrow" }, { name = "ruff" }, { name = "ty" }, - { name = "types-protobuf" }, ] [package.metadata] @@ -2983,7 +2878,6 @@ dev = [ { name = "pyarrow", specifier = ">=17.0.0" }, { name = "ruff" }, { name = "ty" }, - { name = "types-protobuf", specifier = ">=6.30" }, ] [[package]] @@ -3067,7 +2961,6 @@ notebook = [ [package.dev-dependencies] dev = [ { name = "boto3-stubs", extra = ["s3"] }, - { name = "google-cloud-storage" }, { name = "hypothesis" }, { name = "ipywidgets" }, { name = "moto" }, @@ -3104,7 +2997,6 @@ provides-extras = ["geospatial", "notebook"] [package.metadata.requires-dev] dev = [ { name = "boto3-stubs", extras = ["s3"], specifier = ">=1.40.2" }, - { name = "google-cloud-storage", specifier = ">=3.2.0" }, { name = "hypothesis", specifier = ">=6.112.1" }, { name = "ipywidgets", specifier = ">=8.1.7" }, { name = "moto", specifier = ">=5" }, @@ -3215,15 +3107,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/6d/1c/e6e91852b72566b1ab9aca504712857dfb89324c12092a5bd61d42d34427/ty-0.0.84-py3-none-win_arm64.whl", hash = "sha256:6c9d3718c4a0d318bc9c92af9b2aa85c3294e7dff54cf1d69ad79568f56516da", size = 13656453, upload-time = "2026-09-24T13:16:21.51Z" }, ] -[[package]] -name = "types-protobuf" -version = "7.35.1.20260906" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e1/6c/e3e5b3e10bc328126a39637c138f9ebfd734bf14342b9f3540039b4ab995/types_protobuf-7.35.1.20260906.tar.gz", hash = "sha256:efd1a3862d4c967dad5512ef8d56b1530ac84f182c41735b94004756518c4998", size = 69895, upload-time = "2026-09-06T06:35:28.308Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/44/4e/f63e826c68f77ef875506d72f225918800346545ee99847bc28f3394f18d/types_protobuf-7.35.1.20260906-py3-none-any.whl", hash = "sha256:5155e48569e0dabff303fdf578db96cd31ea9a4a63b18018a4ceac6b0ae17462", size = 86419, upload-time = "2026-09-06T06:35:27.247Z" }, -] - [[package]] name = "types-s3transfer" version = "0.16.0" From ba4ef94df42ffa32159e7e93e1733fd0833ea890 Mon Sep 17 00:00:00 2001 From: Lukas Bindreiter Date: Tue, 27 Oct 2026 14:51:45 +0100 Subject: [PATCH 3/4] Update deps --- prek.toml | 2 +- uv.lock | 1905 +++++++++++++++++++++++++++-------------------------- 2 files changed, 967 insertions(+), 940 deletions(-) diff --git a/prek.toml b/prek.toml index fa6e725..eed06cd 100644 --- a/prek.toml +++ b/prek.toml @@ -19,7 +19,7 @@ hooks = [ [[repos]] repo = "https://github.com/charliermarsh/ruff-pre-commit" -rev = "v0.16.10" +rev = "v0.17.3" hooks = [ { id = "ruff-check", diff --git a/uv.lock b/uv.lock index da69b78..5d0bcd2 100644 --- a/uv.lock +++ b/uv.lock @@ -78,7 +78,7 @@ dependencies = [ { name = "async-tiff" }, { name = "defusedxml" }, { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, - { name = "numpy", version = "2.5.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "numpy", version = "2.5.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, { name = "pyproj", version = "3.7.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, { name = "pyproj", version = "3.8.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, ] @@ -137,7 +137,7 @@ wheels = [ [[package]] name = "azure-identity" -version = "1.26.0" +version = "1.27.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "azure-core" }, @@ -146,37 +146,37 @@ dependencies = [ { name = "msal-extensions" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e6/87/e88098a024d74b7434de5b110c261da024fe5d6c139f1847f6f8a2b76678/azure_identity-1.26.0.tar.gz", hash = "sha256:61b4538b87df10d2d6d44b77cd5ea28a8929fd4929d58b45bf3f230f7daba02e", size = 310151, upload-time = "2026-10-01T14:17:47.825Z" } +sdist = { url = "https://files.pythonhosted.org/packages/cf/33/0b881d0093d64c965bb20fcf71b806c7097881b544a2a55d519cdcf3bab9/azure_identity-1.27.0.tar.gz", hash = "sha256:6a4c50e25e8e9a47b324a95ad024897e56cb3c6241e0094e93b126a2bf71edc5", size = 331360, upload-time = "2026-10-26T21:17:39.095Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b2/d5/ae45ce8009d0d015304b206c7c4a6cbab02c03f8f07eedefb32b099ccee3/azure_identity-1.26.0-py3-none-any.whl", hash = "sha256:3c60d9682b3ac01507388d474cb5a5ffbe54152a96b6cdcaa7013da9315cf186", size = 195958, upload-time = "2026-10-01T14:17:49.705Z" }, + { url = "https://files.pythonhosted.org/packages/29/1e/6a2fe486576f41884a120ef7b2c3774f334f9143402fb73650c67db94ddd/azure_identity-1.27.0-py3-none-any.whl", hash = "sha256:92fd2a93c0d2f777dac6c04e5fd7d5734f6643582b983544ef66b4575844e46d", size = 203474, upload-time = "2026-10-26T21:17:40.92Z" }, ] [[package]] name = "boto3" -version = "1.43.108" +version = "1.43.119" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "botocore" }, { name = "jmespath" }, { name = "s3transfer" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/48/59/fb93b6ebd9ad43eb9a58c7a6da51a0fe24ab0c04bc4d534a0bfc5eba7f59/boto3-1.43.108.tar.gz", hash = "sha256:03341f089158368acf83e921aca98b706095322ca52bc4c039a616940aa5ad41", size = 112682, upload-time = "2026-10-02T19:32:25.211Z" } +sdist = { url = "https://files.pythonhosted.org/packages/02/1b/97f8ec6ce00ae49455797b8af9fde522624ecf80bc73c937596b558b1853/boto3-1.43.119.tar.gz", hash = "sha256:e12c3b1d81713e14bd6947b7412d9d0ea49b320642d795ded1d64d6483ca6f49", size = 112659, upload-time = "2026-10-26T19:25:58.76Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/74/e4/7e88c40e9f61888e12dac0de41a5fddc2bcd1c3992d9b28e17d915cce0df/boto3-1.43.108-py3-none-any.whl", hash = "sha256:19e9da95ef0c494e27052049a42137550e66730509bb613e76eaa30ddf9a7170", size = 140045, upload-time = "2026-10-02T19:32:23.826Z" }, + { url = "https://files.pythonhosted.org/packages/3a/d7/6c82dff652082a4bab907b7a5833933a362f12400f14bd7a9935a330145e/boto3-1.43.119-py3-none-any.whl", hash = "sha256:d1718edae48c3241d289c0ece8a515399a7242c406e35328b9685c4d24039186", size = 140045, upload-time = "2026-10-26T19:25:57.128Z" }, ] [[package]] name = "boto3-stubs" -version = "1.43.108" +version = "1.43.119" 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/db/2c/b28a8cc8e4beea290fdac2323ebbdf3521ca6570cb1ab93f862d8d219738/boto3_stubs-1.43.108.tar.gz", hash = "sha256:a1ed29b4c7957568e6261fc36d4c59025c004997a229e8f01940c205f2128e4a", size = 105637, upload-time = "2026-10-02T21:05:54.272Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c8/89/460a01ee879d0eaf34f4e1b9a8d7f1da17ec972bd738122bbd616d3ea947/boto3_stubs-1.43.119.tar.gz", hash = "sha256:5734e5c36496d6a41c9ffa247fae83ece5e14b1da33222861482974f5f67e48f", size = 105535, upload-time = "2026-10-26T21:50:20.435Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/57/5b/e212cbe3fc5a49d684410a195060d7152416e00d9aa24ed4d5fa4a22f94a/boto3_stubs-1.43.108-py3-none-any.whl", hash = "sha256:40f02e30f6e455ccb658dcdeb5756ce23bde767f425ab6d7f57a9ffad75a848c", size = 72039, upload-time = "2026-10-02T21:05:51.118Z" }, + { url = "https://files.pythonhosted.org/packages/6d/0e/df9807a63ad9a37ec5921f8950a6fd46ecc3c556b6e91efb4a53b7009b84/boto3_stubs-1.43.119-py3-none-any.whl", hash = "sha256:12d6a2cff9439798ac20e7b069cca92a79334898727d6ed1c3100ef44ce63cb4", size = 72013, upload-time = "2026-10-26T21:50:16.162Z" }, ] [package.optional-dependencies] @@ -186,25 +186,25 @@ s3 = [ [[package]] name = "botocore" -version = "1.43.108" +version = "1.43.119" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "jmespath" }, { name = "python-dateutil" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/61/16/6b4477f433da2c11193802f538330ce080076c2f38d817ad437ed3cd1465/botocore-1.43.108.tar.gz", hash = "sha256:ee4f75cf3bdbb0da7912e089950e8112f692016539d939312c771499958e6cfd", size = 16312879, upload-time = "2026-10-02T19:32:20.797Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a8/ab/8e6d1d97c086c127f725cdeffae3b44d92ca3ca40741fb90f245f11887d5/botocore-1.43.119.tar.gz", hash = "sha256:ed11934be6e6d398ac21b7fe03778c9584368eeff8ba5371581f19bb47db0b6a", size = 16457548, upload-time = "2026-10-26T19:25:52.703Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7a/0b/4670b7e23914b5cc357be5ca45fae503b57d98ecff4b3eabea70412809fd/botocore-1.43.108-py3-none-any.whl", hash = "sha256:ab9d16c6b4350aaa54ed28202dfa2998b2d735fdbf3247eb60af469d8d48a5b8", size = 16008194, upload-time = "2026-10-02T19:32:15.086Z" }, + { url = "https://files.pythonhosted.org/packages/3f/54/04e5950c7f2d22d78f22db00ca119b257b7cd2024328b6116ebaeb9c14eb/botocore-1.43.119-py3-none-any.whl", hash = "sha256:6026aec97914a4fa116915fd3b05af899912eff51b93fb8dff95aa228a3e761b", size = 16154402, upload-time = "2026-10-26T19:25:48.301Z" }, ] [[package]] name = "botocore-stubs" -version = "1.43.67" +version = "1.43.119" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/3f/45/53d662227dc4787b2c854445ee7eb4751cb5d74cfb5c686a6ecbe1f94c17/botocore_stubs-1.43.67.tar.gz", hash = "sha256:853e74014a1f557055c4ffae5fb38d7c65c7c0520e1aab366cac41d5428f419d", size = 42846, upload-time = "2026-08-08T14:57:53.412Z" } +sdist = { url = "https://files.pythonhosted.org/packages/90/ef/96cde812b9cb005849a34ce4c1ff5410b76d28ab3ccd896e6ec12fe8b606/botocore_stubs-1.43.119.tar.gz", hash = "sha256:6fa626afaf9a017ca023c38f9e1a03a9ea17d1d00367f09927f1a7b4b55e811c", size = 42957, upload-time = "2026-10-26T23:41:56.154Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/4e/5e/bdbf19967898a032292da65a47d6e25b2eee55865db4e687f861d80b5602/botocore_stubs-1.43.67-py3-none-any.whl", hash = "sha256:c51262bac3341c1cda71f05fa01141fffd3990d7a92c7960e3b755c1bc830373", size = 67244, upload-time = "2026-08-08T14:57:52.01Z" }, + { url = "https://files.pythonhosted.org/packages/c5/be/03f83efdafe720fe73d6d67339243aff7565f5bc53d24e0393b6859b24ca/botocore_stubs-1.43.119-py3-none-any.whl", hash = "sha256:b627edbb9ee698382b9175f4b8304b2c8ad7b34d61d8003e7b8f73ef987c5a5c", size = 67247, upload-time = "2026-10-26T23:41:54.644Z" }, ] [[package]] @@ -223,65 +223,65 @@ wheels = [ [[package]] name = "cachetools" -version = "7.2.0" +version = "7.2.6" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/29/2c/3f18755527b03ca9ff6be724bd5370cb777c76a87f17301377cf04a4729b/cachetools-7.2.0.tar.gz", hash = "sha256:bcac1a1b8da6909994a2957238a57b8140dab7c5c5c69a43669654fe87a33c1d", size = 41129, upload-time = "2026-09-16T20:48:27.209Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f2/0f/e02be8857e0fc519a12337568f0bf4b0aad4f3a95d7303e9e9d9fbcca8c2/cachetools-7.2.6.tar.gz", hash = "sha256:1096513bdeed468ba505df3d123e4578a5a22c955f4a9ba3912fd71f3f95363a", size = 46662, upload-time = "2026-10-26T21:46:43.818Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/cc/bb/1c6e7a89b11da19f137e4ef5a5c5fe47a5bb686449ba343944ccf8fb34b2/cachetools-7.2.0-py3-none-any.whl", hash = "sha256:3045213f186b89fdd95d94441354c4bd87c570b7a38d8c3fd1d9dd37f6dc90d8", size = 16918, upload-time = "2026-09-16T20:48:25.575Z" }, + { url = "https://files.pythonhosted.org/packages/53/98/c8201d54b354857a3050a52751ca482eb143c404b387ab2c41a0f981b610/cachetools-7.2.6-py3-none-any.whl", hash = "sha256:8722e08fc36ae0838a9a0a2ab9935554720de3343a62dad7aae7c595854c8623", size = 19523, upload-time = "2026-10-26T21:46:42.277Z" }, ] [[package]] name = "caio" -version = "0.12.9" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/56/51/bd8b64bf700f5b1a956a60bb62276b79a094e8cd0ddc60b1b61c3edd496f/caio-0.12.9.tar.gz", hash = "sha256:99e99419b44ab5511f7468c6a452887dd125b8e4042672a7589f0cf01d254ea8", size = 84084, upload-time = "2026-09-26T09:51:49.433Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ee/90/648c2d37a805528371934f649cb1070477b20d06b4bda8a1cef3ec45f2dc/caio-0.12.9-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4edc95d8dc49f91fc9e870174860729fd088f9529464a7a6a1deda8a14d59a4f", size = 48876, upload-time = "2026-09-26T09:50:56.322Z" }, - { url = "https://files.pythonhosted.org/packages/24/93/1951d8cf56fdfbb4f28c4073f5a732b7863a01c2ad6a7574792726b9ed00/caio-0.12.9-cp311-cp311-manylinux_2_34_aarch64.whl", hash = "sha256:14483697a27aefd265decb4b595e70814d284f46bc352c75eb9e9dae94a7b398", size = 162790, upload-time = "2026-09-26T09:50:57.835Z" }, - { url = "https://files.pythonhosted.org/packages/76/a3/2878af4bfaca5a25dc573afaf18d32dd32612fbde7ec5a3e51d69786b11f/caio-0.12.9-cp311-cp311-manylinux_2_34_x86_64.whl", hash = "sha256:e10e3be34fca464cc5e27d3011e284fd0cde0cc488cda4742e765c8e9291647d", size = 159007, upload-time = "2026-09-26T09:50:59.091Z" }, - { url = "https://files.pythonhosted.org/packages/97/c6/21be4805d108c21c89c205d48331d8d509116998ef39a80cbc6b40e5af62/caio-0.12.9-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:25255807af48386c50a2b186eaa582ffd5347d67c4f1cb3aa1bb79b36ace7af9", size = 160314, upload-time = "2026-09-26T09:51:00.54Z" }, - { url = "https://files.pythonhosted.org/packages/77/65/1cd722b6f288ebe04d6fd85c8a5595f2b350b3070a15390566970e2eb7cd/caio-0.12.9-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:477d66e16948845d0f5ec6535e84c0c9ab7420b2f78f81bcf7b0f81d18a34ffb", size = 158682, upload-time = "2026-09-26T09:51:01.908Z" }, - { url = "https://files.pythonhosted.org/packages/08/36/4cf8f5a6fd0ea7234107e5ab38ca5e0058beae7477c24768a6ab51b79e0a/caio-0.12.9-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:554c6ee883a0f1e95236d85b8e7d8187b212668855ac3a858acb80f5e2e82e82", size = 49041, upload-time = "2026-09-26T09:51:03.37Z" }, - { url = "https://files.pythonhosted.org/packages/94/31/3b6e9d644f9337ac6823f25b3cce838569d4b27ac9a3f9a371369f3d15d5/caio-0.12.9-cp312-cp312-manylinux_2_34_aarch64.whl", hash = "sha256:6e72fb0ddd369f712a4ad229ee0f1d7df6852e9b59b36fe8a46210b4c9ea8e82", size = 164686, upload-time = "2026-09-26T09:51:04.88Z" }, - { url = "https://files.pythonhosted.org/packages/71/f7/1894b1ca1fae9317b8b8203f999107e927ca9badafbe0f7a294952fe00cb/caio-0.12.9-cp312-cp312-manylinux_2_34_x86_64.whl", hash = "sha256:f0698976f84dd40024204f0f77cf59ed7446e989575c8ad8cb64cd4b3e2871f3", size = 162375, upload-time = "2026-09-26T09:51:06.448Z" }, - { url = "https://files.pythonhosted.org/packages/21/ce/040af704ea23a6f4383685d05068ec59301e3bb12cc2ca7c664d16a82691/caio-0.12.9-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a06afa038f76324f439595b7a3242abf090a80b03006ed3d7dbbb9c031baf8a3", size = 162189, upload-time = "2026-09-26T09:51:07.862Z" }, - { url = "https://files.pythonhosted.org/packages/41/b1/d94003593fee5b76d725e5cc51e902439ee3491c6bd4331b232b2ebf12aa/caio-0.12.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ed343077d8178c003b1596f40b1f46955be963a2871a1f85519be823113b24b0", size = 161976, upload-time = "2026-09-26T09:51:09.178Z" }, - { url = "https://files.pythonhosted.org/packages/e8/bf/ed21d62204e4feece79985f45d2ebce6949c9e3d320e9f9e9638e9dc980a/caio-0.12.9-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ed23f6ad6897fae4c02e3e8d236aa3cc18e766a7c5bb0352ea5e9fb26c5d3335", size = 49024, upload-time = "2026-09-26T09:51:10.472Z" }, - { url = "https://files.pythonhosted.org/packages/cc/c6/fea92dd83cdf28ee20d403a910e194fa5e1a97215a00e5bbbeb699ac582a/caio-0.12.9-cp313-cp313-manylinux_2_34_aarch64.whl", hash = "sha256:c327977b8174337c1aaff27da17ac249d176ef8ea6e2dbf70b49cdd8038d57d3", size = 164835, upload-time = "2026-09-26T09:51:12.213Z" }, - { url = "https://files.pythonhosted.org/packages/ca/0f/8ec37d3d6b47b8c6bd6c68a1d2d9b5de1ea05df59b601575b7003c9439e2/caio-0.12.9-cp313-cp313-manylinux_2_34_x86_64.whl", hash = "sha256:f6ffb3d448016d20d8c40c53864d81bdf7463157969de65841eeb370519dbf78", size = 162567, upload-time = "2026-09-26T09:51:13.559Z" }, - { url = "https://files.pythonhosted.org/packages/ec/5f/b3258150ea0e87f032859df825dd0ec8e9f3a6c798addc6463cf1e4a2298/caio-0.12.9-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a11421fb4ac591e6fea5512d9a8ad1b488e7b28cf610ede973bbfb4be5177454", size = 162299, upload-time = "2026-09-26T09:51:14.977Z" }, - { url = "https://files.pythonhosted.org/packages/f8/6e/5712cbf5168fdb4c65c44b0435daeeac7c00609cdc1fd3414812691fbb68/caio-0.12.9-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c3f6dc05486ce4e1027f1d2da4d84c1d6bb815886e76f34a9228d370d86a5537", size = 162155, upload-time = "2026-09-26T09:51:16.395Z" }, - { url = "https://files.pythonhosted.org/packages/3a/6a/5a08b26fae45320c8d6f3b42e6225c5316b39f6cea811ba3f890315b4641/caio-0.12.9-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5c43eaebf220aeb9a388d309a5352e82562a7b9b2ea0102502204413641cfed7", size = 42777, upload-time = "2026-09-26T09:51:17.929Z" }, - { url = "https://files.pythonhosted.org/packages/44/fd/d84318ae7704b8c427584becaacfc3888fca8ec3fca25904ef479f57fa2a/caio-0.12.9-cp314-cp314-macosx_26_0_arm64.whl", hash = "sha256:bc63db6b4a54b2f1c519424acfb2b7198664e4ef8427d397215a81c0bed9e9e6", size = 42008, upload-time = "2026-09-26T09:51:19.15Z" }, - { url = "https://files.pythonhosted.org/packages/d1/f3/34487be50fbdc4cc809bcbe82eac565376fdfa7c675ca02feca2f96cbc6c/caio-0.12.9-cp314-cp314-manylinux_2_34_aarch64.whl", hash = "sha256:83718f0ba9ff56de9c3ce7a61b463466fbb064be4f087230064abac5d08b8100", size = 165343, upload-time = "2026-09-26T09:51:20.394Z" }, - { url = "https://files.pythonhosted.org/packages/a3/f5/3baf870c5775c1bf2399d971d213d34a30750475010070a595df7f536f7d/caio-0.12.9-cp314-cp314-manylinux_2_34_x86_64.whl", hash = "sha256:4a69de19ef8780ea67f5fffa6fed95af32ed4c036e338a361307314306ac816c", size = 162599, upload-time = "2026-09-26T09:51:21.814Z" }, - { url = "https://files.pythonhosted.org/packages/93/1d/fbc0005d9aa44204f6d106707a3261c19e402ec64381217998df270cbbe5/caio-0.12.9-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d3d3664c757d59d330381666683cdfa287c5bfff90819868e98ef0bb8c2d2382", size = 162959, upload-time = "2026-09-26T09:51:23.489Z" }, - { url = "https://files.pythonhosted.org/packages/3a/6d/d6274d9a4d637d484314456222c898988ec03c1895d3b9ec2fcefe3c24cd/caio-0.12.9-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2e152e1a1970d49b6056c1c94547801fcc6b41fc12185db6628ed58ec7785b04", size = 162234, upload-time = "2026-09-26T09:51:24.977Z" }, - { url = "https://files.pythonhosted.org/packages/9e/fc/228e74a8408714cc2e274250055412c1abd02cce62371820ac9da99a2a1d/caio-0.12.9-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:32755af4459fed70d10a5e8c396de3ccb57e02365c7d818937a9794884b172da", size = 42780, upload-time = "2026-09-26T09:51:26.261Z" }, - { url = "https://files.pythonhosted.org/packages/47/20/d9d7ee48d7a3cc3211cb841aa78712676416099025c60cb9e316c321d100/caio-0.12.9-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:de4458707370b9f13de2ead07e6719305624b4f0aad665ce6cb2f1452eb4965d", size = 186873, upload-time = "2026-09-26T09:51:27.538Z" }, - { url = "https://files.pythonhosted.org/packages/0e/a9/6fc366300809c09916ee9212711ee470ed5103d5d4895a843633a2ca1ad8/caio-0.12.9-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:0a571981c8724f69c34ed4c7619585d552d27fe38bad0daf970dca3e14d6922b", size = 182783, upload-time = "2026-09-26T09:51:28.993Z" }, - { url = "https://files.pythonhosted.org/packages/65/5d/900bb797d8e51d06b5f1aa691dc1f7bf08a01e6a67397c08f8b4c090e01b/caio-0.12.9-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:1c043b15a19e33c0b18b1937493be6730c3b08acc33af6899f669ac8957a3e46", size = 185071, upload-time = "2026-09-26T09:51:30.424Z" }, - { url = "https://files.pythonhosted.org/packages/df/df/94e65a3e77c5cb1f7ce19714084fc1f19f86fa0a1db43c63edc39d6b38d9/caio-0.12.9-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:dada2e1ca5481e5c11201d269ac8e009ce86d34478f4426e9739470b0c2a1030", size = 182583, upload-time = "2026-09-26T09:51:31.921Z" }, - { url = "https://files.pythonhosted.org/packages/c8/36/3ae6214f37413e1451b9fba72a05f5f9d64f9020db5445e2d73fa403e53e/caio-0.12.9-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:53d3febc3e46707786a023b28bd21213ac78332db46fae7df2a03c0cc208976d", size = 42019, upload-time = "2026-09-26T09:51:33.226Z" }, - { url = "https://files.pythonhosted.org/packages/c1/0d/9b6cc05b09f66dae9cbb1633ca3c2598496239d636b95e3690db3b2c27e7/caio-0.12.9-cp315-cp315-manylinux_2_34_aarch64.whl", hash = "sha256:6634c57de5883819e0fb423094fe5cb480e81b8f5f46f601eb143e2c762f4c14", size = 166496, upload-time = "2026-09-26T09:51:34.558Z" }, - { url = "https://files.pythonhosted.org/packages/f2/63/ebb69add3f15b4345323f778295b27988b653b191df54060c19a66ea44d5/caio-0.12.9-cp315-cp315-manylinux_2_34_x86_64.whl", hash = "sha256:aa0fe6b459ef45d9d1fe82e22d5d849dcdb07e3a87f56444d1ff799249a08576", size = 163522, upload-time = "2026-09-26T09:51:36Z" }, - { url = "https://files.pythonhosted.org/packages/01/60/aec76058124f8a5b1979351cdfc83757a752c6456c36a6bdd256b743360f/caio-0.12.9-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:7fee93281b220488a5e6949ac197a52c2f1e877c6e32aec5ce658e9add3eba60", size = 164136, upload-time = "2026-09-26T09:51:37.383Z" }, - { url = "https://files.pythonhosted.org/packages/a4/86/bd5b6553fa6bab7976bf60087c2a73a8546c5bf5499e805d51ea0c33016c/caio-0.12.9-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:6c28c4789a9c6d8e8b36cac15f85727bd8e4d312c98fc5aa4fceaa4631669786", size = 163131, upload-time = "2026-09-26T09:51:38.868Z" }, - { url = "https://files.pythonhosted.org/packages/29/fa/2842996fdb0a906325c57bc8adf205be4556e33c7ad0d04eec80077ddc3a/caio-0.12.9-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:e600822c774fdb434e43163c7fc0d4dcfaa6540ebbf77e82e5f0dac071b27f8f", size = 42775, upload-time = "2026-09-26T09:51:40.587Z" }, - { url = "https://files.pythonhosted.org/packages/f5/9e/080587a73689f5f0de33f8be75cbb2e35c822d50d7f957150bdcef38dd92/caio-0.12.9-cp315-cp315t-manylinux_2_34_aarch64.whl", hash = "sha256:cc30e0c458d2d6785e72bb5117086ffce61b5c058bee5637c5aea69042db5e86", size = 187497, upload-time = "2026-09-26T09:51:42.074Z" }, - { url = "https://files.pythonhosted.org/packages/49/80/950a557f05c492416e3d5b4ec40d7b6c330567b311141aafe69ecb86a063/caio-0.12.9-cp315-cp315t-manylinux_2_34_x86_64.whl", hash = "sha256:7490517a72f4ad01b39311ff8e909c3cab77f12dddd370ba6deb035313038468", size = 183529, upload-time = "2026-09-26T09:51:43.515Z" }, - { url = "https://files.pythonhosted.org/packages/c9/35/44b405e601c9662223b01b56a38e16920d9633a8897a223399efa791d0b3/caio-0.12.9-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:b30f3f36e45afb0814fc828cbb10ef9dd91043ff0b0a14ffe1cd8a348b461179", size = 185758, upload-time = "2026-09-26T09:51:44.98Z" }, - { url = "https://files.pythonhosted.org/packages/fb/8f/2b08f5e117e3663396cfd0d935cdb9218ad3c374d10911ec278fc0c6aee5/caio-0.12.9-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:b17b9aca6360f72bf07f1062bd60d8849f3fd5c1cb77f43923d8dec7fdbbb5df", size = 183425, upload-time = "2026-09-26T09:51:46.402Z" }, - { url = "https://files.pythonhosted.org/packages/c0/99/96888ad510c9adf42bfbad9ba139831b58055fd61e1301f6268e9bb490b8/caio-0.12.9-py3-none-any.whl", hash = "sha256:bf12d4f014b2a33e642ed7905b5787656ede2fc24be21f7c086826ff0d32cec3", size = 26154, upload-time = "2026-09-26T09:51:48.071Z" }, +version = "0.12.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/66/66/a36d1367bd5ef70b6b97b2fb502f682106555e9e49e229f35c0a49d154d5/caio-0.12.10.tar.gz", hash = "sha256:5cecc44c04d5c6291d98b61bdba999f49a7e58dff2d782b4bb0642dc6f7ecf58", size = 84118, upload-time = "2026-10-22T21:35:06.294Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/45/ba/f545a34076181400dd0ad2b9883c4a5eeae85d142daf0f5ac2e3b718604f/caio-0.12.10-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f09366a7eaaa23f7dbf19c14802a3ba57b72e9efec463b89a8ff77bcbe3c8c09", size = 48895, upload-time = "2026-10-22T21:34:20.962Z" }, + { url = "https://files.pythonhosted.org/packages/28/e3/fc6c49b78d4168510f183ca502d9f0529b9b26db185e871d5331f3c57d40/caio-0.12.10-cp311-cp311-manylinux_2_34_aarch64.whl", hash = "sha256:9f5add2fea913a7dfc0e196b76c8d16c059b04a6c1651ae0118e86be11ecd9ad", size = 162811, upload-time = "2026-10-22T21:34:22.023Z" }, + { url = "https://files.pythonhosted.org/packages/5e/fa/29d14e56ab9aa18cbacce84cfa2d29c61befce876d24bb818412524c2c64/caio-0.12.10-cp311-cp311-manylinux_2_34_x86_64.whl", hash = "sha256:2d7f65d34a27a0969db7fb810a6beeebea13327a37f7dae3a39da64fd2534b6e", size = 159023, upload-time = "2026-10-22T21:34:23.296Z" }, + { url = "https://files.pythonhosted.org/packages/43/58/5f87b9a03218bdd8abe84f32e815712569ae765f4c635975c563eb0d2eb2/caio-0.12.10-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ca2fd82729aba943e190aefffd9dc96d49a1a401d466ea9743e6f54fa20f0e2f", size = 160341, upload-time = "2026-10-22T21:34:24.512Z" }, + { url = "https://files.pythonhosted.org/packages/27/fb/96bd3812166292f395a1e8fc217f75aac01eca476b95e13c0b1ed46480e3/caio-0.12.10-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:2786535292cbd7cf1f13fb13d94f7173e71cd353bec4cb2e3de13d94b7615a79", size = 158710, upload-time = "2026-10-22T21:34:25.755Z" }, + { url = "https://files.pythonhosted.org/packages/c5/ba/689a44c2f0d8ea68dfe2d721699fa81601dcc83f238102df22feb31697a0/caio-0.12.10-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:c396c9a850681bf51dcc9943468260f6fa7861498f84b5f3b9413e9d3283cbec", size = 49062, upload-time = "2026-10-22T21:34:27.004Z" }, + { url = "https://files.pythonhosted.org/packages/db/20/21fd0dbf1b2b0211f3a451587b6d63d3ad99385487323f2b9062d6e42ff8/caio-0.12.10-cp312-cp312-manylinux_2_34_aarch64.whl", hash = "sha256:93bb3a3f0bd684ac6b559c33a84817395c4d2c75d98dae2b7c40c57acce83737", size = 164716, upload-time = "2026-10-22T21:34:28.119Z" }, + { url = "https://files.pythonhosted.org/packages/95/00/8900d474b5e3285635865c9d5bdc71ad822f7932a7f90cd800fc837c1e67/caio-0.12.10-cp312-cp312-manylinux_2_34_x86_64.whl", hash = "sha256:70d5032f3e0b2dbeceb12d418951415e936cff7f33132ac9f7759937ff1916f3", size = 162411, upload-time = "2026-10-22T21:34:29.402Z" }, + { url = "https://files.pythonhosted.org/packages/6e/2c/0704c708eb255f2995fc9830a969e883338c3de204e1eac726ec337a7e9c/caio-0.12.10-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:347ff5321a23675ed3b2396bbc6b7220ec5531303395c6b0135ed6cda04277cd", size = 162211, upload-time = "2026-10-22T21:34:30.617Z" }, + { url = "https://files.pythonhosted.org/packages/67/52/5b9ccb8205c3d3d870540f0ffea07977b68a9b018c91c43fae0bdb780954/caio-0.12.10-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:bd3cb22b0c5ca195b35c2b7d57892c23cc13d969f24c44fea876af2cfee02e7a", size = 161998, upload-time = "2026-10-22T21:34:31.934Z" }, + { url = "https://files.pythonhosted.org/packages/84/3e/98ddc781ae6d76407b74f33136f838e444df41a1a43d80373da01da47bbb/caio-0.12.10-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:6b8e0724415ae86889df59af2d44fd6a531851ebc62c9cfdb1cc8235c929524d", size = 49054, upload-time = "2026-10-22T21:34:33.252Z" }, + { url = "https://files.pythonhosted.org/packages/8b/d6/9851c6b24674ef72054c7b05569bd444f1358955c19ba5b5f7e15fa64be6/caio-0.12.10-cp313-cp313-manylinux_2_34_aarch64.whl", hash = "sha256:4e72602d4c348d105c9e46dd300b85df3fd3e857e2068370847f5e29879181b5", size = 164861, upload-time = "2026-10-22T21:34:34.472Z" }, + { url = "https://files.pythonhosted.org/packages/85/9a/1f6eac05eb23284c834c08c17cc5f39fafe3754f1d2df94bcf5a2cb93179/caio-0.12.10-cp313-cp313-manylinux_2_34_x86_64.whl", hash = "sha256:1d8b6e9275d8f9608b8ecb4b97189e7f1d0b8ed948fb552eb06518d6342a1e53", size = 162592, upload-time = "2026-10-22T21:34:35.566Z" }, + { url = "https://files.pythonhosted.org/packages/1a/be/4bb0538aa4c953c4681c7b309a9cb452eb91640e6b62f250b464f91dfad4/caio-0.12.10-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cff7a239334b36c4dc82a0ab69aa3244188e5c9e2c30a242d81e4502520f2b1b", size = 162324, upload-time = "2026-10-22T21:34:36.977Z" }, + { url = "https://files.pythonhosted.org/packages/0a/9a/290190821dbc29fccb0ad96e51a7f573394df621da547cb13701de685805/caio-0.12.10-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4a9645aeed1cdeeff34ca78442e6b274c0f801876a6bf0991724d92b7d639655", size = 162171, upload-time = "2026-10-22T21:34:38.173Z" }, + { url = "https://files.pythonhosted.org/packages/39/9d/2a2177a43901f69ef120fb0dfe4a7dc6938e57a086da3ecdeacc12a05635/caio-0.12.10-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:edda39ac24b4cec74486a06afe7499fc9ddb2c3d5cb98494b2c9fdf95ed7aecd", size = 42798, upload-time = "2026-10-22T21:34:39.444Z" }, + { url = "https://files.pythonhosted.org/packages/b9/0a/5e404a09363c71dfb5f7c20234ee31dc9ad75c7072dc868b6cc5be6f9af3/caio-0.12.10-cp314-cp314-macosx_26_0_arm64.whl", hash = "sha256:3b9c6e6355267f2eccd23e1a17ec2f78dce02ab00b10276999ff7a4615512952", size = 42033, upload-time = "2026-10-22T21:34:40.448Z" }, + { url = "https://files.pythonhosted.org/packages/06/94/41549be5a09a0441f1c1313d5eeb8b60b08034c89cd2c0b5cfeb6a9b9111/caio-0.12.10-cp314-cp314-manylinux_2_34_aarch64.whl", hash = "sha256:a2b897245d5e6593a39ea9a52c7b372efc41e5a746c46a2985bdf94622f273fe", size = 165369, upload-time = "2026-10-22T21:34:41.513Z" }, + { url = "https://files.pythonhosted.org/packages/28/47/f272ce0eed9700749934823086434a94d9bea51555724188d8ee660a7fd4/caio-0.12.10-cp314-cp314-manylinux_2_34_x86_64.whl", hash = "sha256:40e84b8679f44bee5c2b7cc7093719e15616b6752560809896dc15a8667cd69f", size = 162620, upload-time = "2026-10-22T21:34:42.671Z" }, + { url = "https://files.pythonhosted.org/packages/65/ae/644e7e72a81c4d8ce31dac9865b2a6b38fdc4c56a1418cc2d4d55591be20/caio-0.12.10-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ccd4efddafd48018d30cd560f7c393d177fe01365ee0dcf0bc88528f220732ed", size = 162986, upload-time = "2026-10-22T21:34:43.801Z" }, + { url = "https://files.pythonhosted.org/packages/63/2a/557a90ae71f8411a2cfa0238ad70bddb60178133b7eb41e3fe8e8c854235/caio-0.12.10-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2138670dd6cab64bd29cfcbd6a7259c0c21646c4dc86a4ec12de907210a2aba9", size = 162258, upload-time = "2026-10-22T21:34:45.271Z" }, + { url = "https://files.pythonhosted.org/packages/78/1a/1ffea8df50383d5bc468e94d2b45d1c458f965b80bb12ad28891d0a55266/caio-0.12.10-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:79a4e0000dd92b1e4e7842fca99fdaa5fd0259dd33a9892741e7126e74656c49", size = 42801, upload-time = "2026-10-22T21:34:46.485Z" }, + { url = "https://files.pythonhosted.org/packages/f0/59/43da115afd82281312166711ceff5b7ca08d53f79a8955d2e22ac9968eca/caio-0.12.10-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:6fff1f609e54a2e007b6c5c0933f4782ecbf8b6f9144e0930e2dcd2e7440c637", size = 186884, upload-time = "2026-10-22T21:34:47.611Z" }, + { url = "https://files.pythonhosted.org/packages/fa/5d/708aed113a2ae829a265b5ce030bab52ff0e41339cc6047f29bb0b15dfdf/caio-0.12.10-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:925b938bcdf09d1c3df2b6ddd534004b616451ef1810e47f6a63257365376b25", size = 182799, upload-time = "2026-10-22T21:34:48.86Z" }, + { url = "https://files.pythonhosted.org/packages/a1/c3/8de2c1c89548902a389406560e67930333984194217d5bcef6129ff7f18a/caio-0.12.10-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:1ebcac2a14e045aa0a4f63824264a4a845c6b1ac9074d5d673c52ef564a1ada6", size = 185097, upload-time = "2026-10-22T21:34:50.035Z" }, + { url = "https://files.pythonhosted.org/packages/94/43/98997e24fa52f1e6a2a808e446693702773830a863a036a5e727024e7aa0/caio-0.12.10-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:39e91aab7d0a5f0cac5930562d751e9938ceef1c7ae18477a5689a4e437eada1", size = 182593, upload-time = "2026-10-22T21:34:51.256Z" }, + { url = "https://files.pythonhosted.org/packages/c3/17/78046176bc3572430d03bab1b9e82966bc5b9a28d27c41e6764a20144044/caio-0.12.10-cp315-cp315-macosx_26_0_arm64.whl", hash = "sha256:c0b52bd6fd006d2d07713025f0e1cad61ebd12342f86bfa47e2eff239fbdd01f", size = 42039, upload-time = "2026-10-22T21:34:52.646Z" }, + { url = "https://files.pythonhosted.org/packages/ea/ed/14a90cfe02f14813674a1cc91ff2fad9b0d1bb98260be09b987f579baa28/caio-0.12.10-cp315-cp315-manylinux_2_34_aarch64.whl", hash = "sha256:b7a42ef7379e36e99cc6990aa62465919a3be3fb1f049a40e5aef29fbbd859e1", size = 166510, upload-time = "2026-10-22T21:34:53.703Z" }, + { url = "https://files.pythonhosted.org/packages/c2/b1/9fbeafd6aae00cc0dae96d190cc4fe6e23d20ecd4f26e970878611761fdb/caio-0.12.10-cp315-cp315-manylinux_2_34_x86_64.whl", hash = "sha256:89fe0e3c849a07aba1f76012a50eaf4fb2eaed0398c01bd43912a1bd0f4ab5eb", size = 163501, upload-time = "2026-10-22T21:34:54.988Z" }, + { url = "https://files.pythonhosted.org/packages/34/81/2342f17ab96a7b5d3666cc7dccea59728c4d421e7c954e2937cd817c3be5/caio-0.12.10-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:21b04f6722884e5fb19690743772875b34306e5ba68d9ecabf7454193bb5caab", size = 164139, upload-time = "2026-10-22T21:34:56.201Z" }, + { url = "https://files.pythonhosted.org/packages/17/b3/e15cfdc902a13d885fa16481c7d01ad04de7b50e0195b9dd5e219ce07614/caio-0.12.10-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:a04ac9769fd8eddc1c0ca6308f0f6e668a86cf8d577770c11d85d0aeb584fcf4", size = 163137, upload-time = "2026-10-22T21:34:57.402Z" }, + { url = "https://files.pythonhosted.org/packages/49/ad/7fda5be8557afa8833ea65ebb29feaeeb96cb2b3373fe3220fe1e6899260/caio-0.12.10-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:c8fc80a37f08bba4c2f108277b6eabba564f74963a1a6cab01e50f9ae4434681", size = 42800, upload-time = "2026-10-22T21:34:58.494Z" }, + { url = "https://files.pythonhosted.org/packages/0d/e0/e2692eeb6841614f60ec099eac3c9b10a00dac74adf7321f442a2291ef8d/caio-0.12.10-cp315-cp315t-manylinux_2_34_aarch64.whl", hash = "sha256:3e3799d09b08401e83c30c671f6fa1e1227f6346a3b1336f24cb94c5a8a82b64", size = 187471, upload-time = "2026-10-22T21:34:59.585Z" }, + { url = "https://files.pythonhosted.org/packages/cc/a8/2c3a0cd781732057ab6a8023ef0fe14017b29ef75259295df8f585e3dfba/caio-0.12.10-cp315-cp315t-manylinux_2_34_x86_64.whl", hash = "sha256:ea2e0ea832d36c1e50ab730ba304153f34eb7dc2300b1285fc7cdfae1f1f4075", size = 183526, upload-time = "2026-10-22T21:35:00.764Z" }, + { url = "https://files.pythonhosted.org/packages/62/e2/9f32db16441875afaa2a8a27d9734c2c8421f259163d7d814528160fa6c8/caio-0.12.10-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:8d1071f8cb5d23c8aeccffe36c50a17d32257cddbf913b7e9b6fb524ba42d7a8", size = 185740, upload-time = "2026-10-22T21:35:02.059Z" }, + { url = "https://files.pythonhosted.org/packages/0d/7d/17836b477a1e57a648b90f16a27ebd367d686a02e5b2a4e72b53ae5ad7fe/caio-0.12.10-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:2a54ddadb7db4eab50bfc8b34f36fcf6763e9007f86b92f7cd03f07b542e891e", size = 183396, upload-time = "2026-10-22T21:35:03.492Z" }, + { url = "https://files.pythonhosted.org/packages/5c/c3/1c44005ec4d4c3f7c51b707308efe7836ee16c466f4ff2e2a6f2089eceee/caio-0.12.10-py3-none-any.whl", hash = "sha256:b34a5483b987d207348ebbf86921bc42f08abcde833b91e0caa8760c7daa2f03", size = 26166, upload-time = "2026-10-22T21:35:04.842Z" }, ] [[package]] name = "certifi" -version = "2026.7.22" +version = "2026.10.23" source = { registry = "https://pypi.org/simple" } -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" } +sdist = { url = "https://files.pythonhosted.org/packages/fd/11/b524cbd9bbdaca58608e4cf4053fd463417549e1959fa8d3aa18319f9555/certifi-2026.10.23.tar.gz", hash = "sha256:a9895e6e2758b371e0545e321a3a8fd1fcfd9829ada8824a936dfb16aaaf36cd", size = 136225, upload-time = "2026-10-23T11:36:16.478Z" } wheels = [ - { 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" }, + { url = "https://files.pythonhosted.org/packages/32/d1/a3652fe2c19134d0d15922177821d863cbd7e842d53a21bbe7e8e3a1b5ac/certifi-2026.10.23-py3-none-any.whl", hash = "sha256:e9b54de76e530bb6cf0ccd036787103acb58fd42ba930ea8bf948ddbaa2f61d1", size = 135082, upload-time = "2026-10-23T11:36:14.763Z" }, ] [[package]] @@ -571,121 +571,128 @@ wheels = [ [[package]] name = "coverage" -version = "7.16.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/2f/55/d1eaf3e73781174340a00dc1ba2aee8a65f82fadb18e2797b192b6b3925b/coverage-7.16.2.tar.gz", hash = "sha256:ca64d9f1f384f151b9511bec01126072acd2f313439f8ed015a22d8790aab6fa", size = 971999, upload-time = "2026-09-27T12:29:01.118Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/58/fa/ce3baf63d85b730398d92a7162f486f3a5e4e2cc3382a02488b3943725ba/coverage-7.16.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:732d950e51f3ba4fb6209c73250f3e8924fefca42953ee04a9e65d8c02414d7d", size = 223797, upload-time = "2026-09-27T12:25:54.756Z" }, - { url = "https://files.pythonhosted.org/packages/7a/57/9ba29c2aac7f756d479f03d45762120060f0f988788001001bf36e0e6fca/coverage-7.16.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5dca0bb66b4c3d624ba047887bf70270030c150692d543cb501293dc38a9f4b5", size = 224303, upload-time = "2026-09-27T12:25:56.214Z" }, - { url = "https://files.pythonhosted.org/packages/5d/7b/0d6d60906dca7d28cc1e3fce12a9861801c4fbb6cbf220ad78cd059c9467/coverage-7.16.2-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:af2a2a8c7c74de0559e0c368d94c8def9e16c58faaee33a0bf081057c4227e3b", size = 254715, upload-time = "2026-09-27T12:25:57.755Z" }, - { url = "https://files.pythonhosted.org/packages/cd/b8/9198b865679379fb165c689c64f6e11105ef380f6bd1c7673e83f73d9f5c/coverage-7.16.2-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:db5f8394e17f877a625b257f2ba0ce8e728a499c2c1579ad66220272cd3df510", size = 256627, upload-time = "2026-09-27T12:25:59.131Z" }, - { url = "https://files.pythonhosted.org/packages/98/79/9521462cb6072fe394701bc8974b74afd576c9c9355156c7844e1a86a42b/coverage-7.16.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5b3146d2317c75f70df2509066d979dadd941f7021cdf9b5db4bcd8568258e25", size = 258724, upload-time = "2026-09-27T12:26:00.691Z" }, - { url = "https://files.pythonhosted.org/packages/a6/76/8d7d5d633db9fe0f3182fedc731bf09f9bcf2366055735152504ad614677/coverage-7.16.2-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9e1d0ced76318bab499693ff25f64faa343415187cb2e4d7befdfdd391a1cf6a", size = 260692, upload-time = "2026-09-27T12:26:02.083Z" }, - { url = "https://files.pythonhosted.org/packages/4e/a7/76cb09c89ba46d74d37428bf93251fc14fb0bbe9e05cc2a5ef61773d318a/coverage-7.16.2-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:af98ad5ed9d6daaca956201e00bb429a7eb2b080426686f70a20353e0f9839f5", size = 254800, upload-time = "2026-09-27T12:26:03.369Z" }, - { url = "https://files.pythonhosted.org/packages/72/b6/2351c1979aaeb5b4a8091a75b90ca997ad60de36e181ddba267cf61dac97/coverage-7.16.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d56e4d21c56d2046447733f8b118409597db48c01efe898ee9ac24e858ec2d6", size = 256423, upload-time = "2026-09-27T12:26:04.751Z" }, - { url = "https://files.pythonhosted.org/packages/0f/f4/ad9a4f8b5cb2d494fa9452b546fe742ed2f9d3847cc14c05e36279a3e649/coverage-7.16.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:1d5d0e3b660506fb84f995814e3118a21efdc0c8eb80127da1be627d90093c17", size = 254487, upload-time = "2026-09-27T12:26:06.082Z" }, - { url = "https://files.pythonhosted.org/packages/6c/1f/a520470472f3e8b01169bf42162b1470c9ba992230432f62ca36269bf3a0/coverage-7.16.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:17228fbca0f22976f797be94e975dcd237799c657d49551c7de1e0654d1202e9", size = 258259, upload-time = "2026-09-27T12:26:07.513Z" }, - { url = "https://files.pythonhosted.org/packages/09/d2/ff26d5938274745855fa61cfcba0245c88ccc10d98d2cbd96064f16cd5a7/coverage-7.16.2-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:bc0b0ac781d489304b741269857f1f8338b7a26b1b89c06c0344658001ec0035", size = 254216, upload-time = "2026-09-27T12:26:08.982Z" }, - { url = "https://files.pythonhosted.org/packages/a4/1d/5d832d3b06785d9f53267e4f2724a9f60c312eee6ebed9063a461d0d3b45/coverage-7.16.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bf1bd822ec4e387ed245bed0d71151582cf7be9e5309bc4145eefe36083d5878", size = 255125, upload-time = "2026-09-27T12:26:10.35Z" }, - { url = "https://files.pythonhosted.org/packages/55/4d/1d33edbc2fcf7d99e384e393e712aa5a2ebbbd8409825357815982207976/coverage-7.16.2-cp311-cp311-win32.whl", hash = "sha256:7ed238d227e23cc300c3d464babdaf9f6ddc740aa1b15a77ae96136e6a7c4516", size = 225898, upload-time = "2026-09-27T12:26:11.7Z" }, - { url = "https://files.pythonhosted.org/packages/6f/7c/676df4882118756c4f8f560c954eddb93e166d84dda8c5f0b6a829689bde/coverage-7.16.2-cp311-cp311-win_amd64.whl", hash = "sha256:a90700f743e29aa3d75a6ff5f01953176a889c00e526194bc4d281731b88d99d", size = 226369, upload-time = "2026-09-27T12:26:13.375Z" }, - { url = "https://files.pythonhosted.org/packages/7a/0e/a457f4a461b3c5610d845137fdd45fa465e011a64c25af440518ab1f4e41/coverage-7.16.2-cp311-cp311-win_arm64.whl", hash = "sha256:a336eec40e3520d369b8a6cdabb4f596e69a8b42927ca074aa1452fed943238a", size = 226344, upload-time = "2026-09-27T12:26:15.127Z" }, - { url = "https://files.pythonhosted.org/packages/5e/2c/f8296c63c5d542f3d21aed685e56b7031a419037d155bb3382fc0940d249/coverage-7.16.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:218d742afca2b5ad5ca759e93eddedfbcc6eadf8322f080dcefc40b7bd4e2d48", size = 223969, upload-time = "2026-09-27T12:26:16.753Z" }, - { url = "https://files.pythonhosted.org/packages/90/23/6f3dcb1423a0d43216e402ea1746e4a7c7c44f38896b97dd573790f56a40/coverage-7.16.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a9a638be322a8d76a41cdb17781c7f82aaee6a66493d8ffb7e2c09ee22423d99", size = 224328, upload-time = "2026-09-27T12:26:18.15Z" }, - { url = "https://files.pythonhosted.org/packages/ac/7d/8f3b6dc920e3fc6732f7678785a2091db439f186afbec30dbf2214d9b1f7/coverage-7.16.2-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:724bd0f1e81856b35e59fc98cf7b4e544a3cb662e4e0864dca73d4326ee9d808", size = 255832, upload-time = "2026-09-27T12:26:19.799Z" }, - { url = "https://files.pythonhosted.org/packages/d1/36/6c45f15be4eca4ac1062c6a55a323286494c99726a7e58951fe85967ac08/coverage-7.16.2-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:5375ebd99038021b35e99dc88255022912c06565d316212f4a576e4b08d30f5d", size = 258571, upload-time = "2026-09-27T12:26:21.199Z" }, - { url = "https://files.pythonhosted.org/packages/34/fb/b54cbeba3ad89082c2e441278681859e538322cc34b84b2af7ebff00080f/coverage-7.16.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7a076277ca9f5750cc230f0f578ebd2620cec60255b25707361699fef6fb465c", size = 259680, upload-time = "2026-09-27T12:26:22.822Z" }, - { url = "https://files.pythonhosted.org/packages/6e/a2/0dc65ec3d61930e1e4c2e371763b15eb4290896eb343a12d5d3091308116/coverage-7.16.2-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:58d4a54c6ea672afef66d49be922a2c69826c5ae1a42a9cd94f0c9c2bacdf800", size = 261945, upload-time = "2026-09-27T12:26:24.336Z" }, - { url = "https://files.pythonhosted.org/packages/d6/93/5fad7a61f2c14e08e98946fc31c1c7ffc1195061bf3fdc351db3be77a863/coverage-7.16.2-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0dcbcfcc059117284c603ff8cb61a65872512882f84a8cf0339241f7f7c2f148", size = 256191, upload-time = "2026-09-27T12:26:25.89Z" }, - { url = "https://files.pythonhosted.org/packages/2d/47/74e5de9227b939ece9f64e729645ddc4296bea10dbfa98721c1333c8be2e/coverage-7.16.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:afdf43b72ef3876c1fe66423b91466e37877c9e81e8cec70542b7e8525b9d1b7", size = 257610, upload-time = "2026-09-27T12:26:27.35Z" }, - { url = "https://files.pythonhosted.org/packages/13/fe/2cf28d40b43645d1b72388fe3ee7f7c747533a6a9557bb8c24a7ae74fe1a/coverage-7.16.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:9acc7f7ec4a1b5f89bd929fde5b8a714f6fafdc6cc18725413d510aa082b47ad", size = 255757, upload-time = "2026-09-27T12:26:28.949Z" }, - { url = "https://files.pythonhosted.org/packages/d7/3d/7c149fd99fc8bbc39c80db5e688d1d39fd040be2ecb78b8335a51a55b9c0/coverage-7.16.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:80d3f7b48d43ee8fc5e8707a8adb43d743a5a1a85256c25a24f9d6d0e2238fa6", size = 259822, upload-time = "2026-09-27T12:26:30.515Z" }, - { url = "https://files.pythonhosted.org/packages/e6/3f/b283fce09d5995e227bd8e513358dd7471bedc0f78abc85a925ebdb0a2f6/coverage-7.16.2-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:126d1af8804d7224421fe991ff65d3ce649081560df7a98b1a5ffff07f9923bd", size = 255325, upload-time = "2026-09-27T12:26:32.037Z" }, - { url = "https://files.pythonhosted.org/packages/bf/91/f3325edf0c4223fb1fe1532b8dbef2a1d2f729459a9a7d1a44d073bae534/coverage-7.16.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c19cd6d025c1673f22afcd22c7df8a662d779e05d8e3fa6820c22afb895b0206", size = 257191, upload-time = "2026-09-27T12:26:33.525Z" }, - { url = "https://files.pythonhosted.org/packages/c4/89/21eb5e83ecf2eed523c4eb3d65ae513cd082c8fd1b6deb34c4cb6c332f97/coverage-7.16.2-cp312-cp312-win32.whl", hash = "sha256:152877cdc8a07264882cfcd503ba56a3ef6cba56a70e8c70f6eb8ffd7384789a", size = 226034, upload-time = "2026-09-27T12:26:35.021Z" }, - { url = "https://files.pythonhosted.org/packages/db/de/e3ad6d864c0833624b4f1f9b53f9e58e116c945e5e965c3f1e172c5e84cd/coverage-7.16.2-cp312-cp312-win_amd64.whl", hash = "sha256:e6c52d3307824ff93b39efd99e4185d557db40bd841452abfb32e5d9151ca162", size = 226569, upload-time = "2026-09-27T12:26:36.604Z" }, - { url = "https://files.pythonhosted.org/packages/3e/c1/bccc58ebe5489cc70628f635c1932fd371f5d7da850dbcf960f95f4c4afc/coverage-7.16.2-cp312-cp312-win_arm64.whl", hash = "sha256:a678c0b6b22086ec2427359d22e37445d4a792f5fdbbc744112c7dade65cad02", size = 226372, upload-time = "2026-09-27T12:26:38.406Z" }, - { url = "https://files.pythonhosted.org/packages/f0/f6/8eb4f220ef24f84fb27d852d4f9bf83e0c73ec1a4a08dd9a87e3f4529739/coverage-7.16.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1a37c6e478cf687e1aa30a593d19c92c02fad9d122b51ab73f51b8dc7a0c0fc9", size = 223993, upload-time = "2026-09-27T12:26:40.164Z" }, - { url = "https://files.pythonhosted.org/packages/40/23/d4bbaf0c154e0b0c2b5264890dbf6ef098dcb50ec8f2469be9490d191660/coverage-7.16.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0993d0e90858c03943d3cb152e068a20dd4707924deec84dd2230261baae3b1b", size = 224368, upload-time = "2026-09-27T12:26:41.762Z" }, - { url = "https://files.pythonhosted.org/packages/7f/48/fc1e88fd571ec5cb38150b7f89f7696ca1bdf9920e01432febb69774cc85/coverage-7.16.2-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:bb2fc905bbf4e6b7f40806ea79e31515abf6349594cdf0adf27c4215f0463204", size = 255358, upload-time = "2026-09-27T12:26:43.442Z" }, - { url = "https://files.pythonhosted.org/packages/1d/56/6785397d07c29c8e70fbb9a07e97d062b43c21ffc5f12385917847f09f63/coverage-7.16.2-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4358b9c8c0125b460407f3017c6cce8156e904b32772c5630d27112f52bdbfe5", size = 257955, upload-time = "2026-09-27T12:26:45.725Z" }, - { url = "https://files.pythonhosted.org/packages/27/3b/c8cdd07721e5f99abd81cea970d971997f99bf158c0b85f51bd284179c8b/coverage-7.16.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1f15254427c9b33eedac4f198eaf9e356eb4f6214551afb43da6194a2c088ad7", size = 259183, upload-time = "2026-09-27T12:26:47.208Z" }, - { url = "https://files.pythonhosted.org/packages/9b/11/606b192fe43d32574ec6238549d48de588fdcc18485682a5ec0a8ac357f2/coverage-7.16.2-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9a75a4704ff640e46170042eec1f984385a121227c505d5a16ad8e495f452541", size = 261325, upload-time = "2026-09-27T12:26:49.084Z" }, - { url = "https://files.pythonhosted.org/packages/67/90/eea481f8b0305ceeb33f081a5f47e298391dbd1b589de0c4b3b3aa50d3f2/coverage-7.16.2-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:14253fc7bb15749b849795a06f5d3b6d8bc3fb8a4b5ddc341faf7a89dce205fc", size = 255531, upload-time = "2026-09-27T12:26:50.509Z" }, - { url = "https://files.pythonhosted.org/packages/6b/be/dedbf9aea1457b120c27ac10b8fc2a357f37fa2b54c3e7286d42980a0a2a/coverage-7.16.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:921415102a90637fcc2e3f169f61dad7699ecf690e8639fc21b813acbedc0967", size = 257329, upload-time = "2026-09-27T12:26:52.005Z" }, - { url = "https://files.pythonhosted.org/packages/fa/cb/b25c19d5bb2bd0f2e4e27fe8e2ffcae80c7a91ae181c0dc749ed60e9b1a4/coverage-7.16.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cce2bc991293f15cc4084ca116827b5900c5f34e1a54dfe83f10ab5c43162eb7", size = 255294, upload-time = "2026-09-27T12:26:53.634Z" }, - { url = "https://files.pythonhosted.org/packages/5f/a2/892c5c5f4ad44b7b2ca009aee705191f3f268f15052244f2f9e3539b2e35/coverage-7.16.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:e1fa594c887365b69745f25a416806e61085dd07b94c9eae68a6e20730629b23", size = 259444, upload-time = "2026-09-27T12:26:55.243Z" }, - { url = "https://files.pythonhosted.org/packages/ed/99/a562537deba0a3e370182ae71c149be796c39d8087365f17a09188f27145/coverage-7.16.2-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:11e597173af1dc33d5f8a7332ada544199269a223af1ee1770ddd5e245ad0fe8", size = 255111, upload-time = "2026-09-27T12:26:56.851Z" }, - { url = "https://files.pythonhosted.org/packages/2d/20/854ec68641a9b3362ff068a32dfa41637299761617ef253791dbade6fc76/coverage-7.16.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:3e7f99698ba3a7d13988bdd984b7ebf13af4dbe2166dc8502eef90d77603b0a4", size = 256882, upload-time = "2026-09-27T12:26:58.41Z" }, - { url = "https://files.pythonhosted.org/packages/db/0d/748e4518b0ac0f9ff2687c248a6e5f8c0737306e709372632a2556f84443/coverage-7.16.2-cp313-cp313-win32.whl", hash = "sha256:f80bd9f9633eafc73d0a913ba2645c96ba58bba1befc30590f7c0fbfde59d865", size = 226045, upload-time = "2026-09-27T12:26:59.983Z" }, - { url = "https://files.pythonhosted.org/packages/31/fa/6e46edba66a183fe4d99d4bb52c173287e9b8dddabe0888d24cb8210e580/coverage-7.16.2-cp313-cp313-win_amd64.whl", hash = "sha256:8be099e979fc42559328a21828281b4578304191ae46ed4e80a407048a82eee6", size = 226581, upload-time = "2026-09-27T12:27:01.494Z" }, - { url = "https://files.pythonhosted.org/packages/1b/d9/9ef6845367600b336ff75d000444a0d32497d6972c833141bd39356abf68/coverage-7.16.2-cp313-cp313-win_arm64.whl", hash = "sha256:28ff850182a67d117990fa2ce5ea1032836d8c9630dae867e8bdd3bff4533b79", size = 226409, upload-time = "2026-09-27T12:27:03.116Z" }, - { url = "https://files.pythonhosted.org/packages/59/4c/577fc0803dab4155dcf808faffbdd7b159256781c0874a8586e17b81b149/coverage-7.16.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:4ee546b9e4872ffa194bf07ac87bfa1202ebb824d0795dc1ef22f175545ca90a", size = 224138, upload-time = "2026-09-27T12:27:05.141Z" }, - { url = "https://files.pythonhosted.org/packages/75/9e/e3785ba3ecba2bd11efc74bfe2801ca4b78c4480b15a375648d809a59da3/coverage-7.16.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a2fac6895eb299a2e52d7bbb8fb3903502b9da8d3f5309ceb16ec40c646b58ee", size = 224445, upload-time = "2026-09-27T12:27:06.805Z" }, - { url = "https://files.pythonhosted.org/packages/f0/d0/963ff22d3fd27117da3b8cc442f5bdc91196f783321e1a8ff0ec43476772/coverage-7.16.2-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:57ff3783f99d75a1e81dd56a9737eb5665e6736a5d93258ba596b6dcad8fd05b", size = 256112, upload-time = "2026-09-27T12:27:08.43Z" }, - { url = "https://files.pythonhosted.org/packages/a8/d4/a306940c81c6ae759e82fff27d20b7fdc6896e422b821f51313cce212b6c/coverage-7.16.2-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:35f37886699cb9abd29958247d718628d5bc6f39e623dff66a09e546c42a7e03", size = 258727, upload-time = "2026-09-27T12:27:09.927Z" }, - { url = "https://files.pythonhosted.org/packages/b9/a3/d3d99d93b02517087aa05bc0cf2d04d372956b849e5443e059079901429b/coverage-7.16.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0fd7a86fdda7cb6d616d178654bd0ad6bc0f3f33c2e478aa598500a1a9e34eda", size = 259909, upload-time = "2026-09-27T12:27:11.55Z" }, - { url = "https://files.pythonhosted.org/packages/08/44/39dd599181726758dd185ae4dc0c0ab3aeabf7ca70e68e145060feeaaa16/coverage-7.16.2-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ac0f3b379c94acc2f7dce5f5f0b24d44fa1cc6a509717ef83dfee07450c2117c", size = 262481, upload-time = "2026-09-27T12:27:13.17Z" }, - { url = "https://files.pythonhosted.org/packages/99/e8/91ee43f6ded411460c359d7e1aebde4d6fd8f00a2e5394182d9d212eb23c/coverage-7.16.2-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7d0732c83746bc24123c581a85d9dd96b70ddb538c9076020aa1a041790361e9", size = 256012, upload-time = "2026-09-27T12:27:14.91Z" }, - { url = "https://files.pythonhosted.org/packages/11/8c/e9499ddc33197bd7eabcb1118ca81756fc874457b324e2b479a4804b2ad2/coverage-7.16.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7b451c68218c150f616bc9649783ec8de76a59792c759b43aa0c9c0466a465e4", size = 257898, upload-time = "2026-09-27T12:27:16.588Z" }, - { url = "https://files.pythonhosted.org/packages/5f/6e/c081cb5991a0afba99f9c4ad6c74a5fce9513a38ddc64e3e6680c6fed9af/coverage-7.16.2-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:a56ac4fa5a75c7e182e8f62600cfb4aff43c5ed7356a034f3557659c3bec1d90", size = 255938, upload-time = "2026-09-27T12:27:18.19Z" }, - { url = "https://files.pythonhosted.org/packages/b2/42/1c3d819e8f9b6eb01c2fe90874d67a8882adb9507e0bbb09361ed131ea89/coverage-7.16.2-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:4cc4f73aa3fabc36e32046d6cd2971405948d8a903636508a3d3b2f9128b3a95", size = 260368, upload-time = "2026-09-27T12:27:19.903Z" }, - { url = "https://files.pythonhosted.org/packages/19/4f/d70eac07901fd587b6ab05e659b52afe13959992aa5113bf6cce059cc572/coverage-7.16.2-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:723dcdab91357159b722935b500ee8abc0a66c8c432e1e9fabf4cc7598952de8", size = 255620, upload-time = "2026-09-27T12:27:21.621Z" }, - { url = "https://files.pythonhosted.org/packages/34/5e/6d87af88317d3d9a9b18a9ca1bc1673eb516917f296e579d0d4a55cb3490/coverage-7.16.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5397e21a90dde0e9c6896b77ded8f0be26b66f8b22b33aed41f6043ed95d55e6", size = 257521, upload-time = "2026-09-27T12:27:23.358Z" }, - { url = "https://files.pythonhosted.org/packages/79/bb/90c2641170d2fa1a6757b3f8450ba2740197317b0ddd749e9604b914e886/coverage-7.16.2-cp314-cp314-win32.whl", hash = "sha256:848893e1d361448c113dc2f0913503522a6f7be231d0e38333d2a22d9698a011", size = 226248, upload-time = "2026-09-27T12:27:25.153Z" }, - { url = "https://files.pythonhosted.org/packages/30/08/d8d0478bb02c8eb0ae20a496fc80c40fcf4d3450bd184300d682ba2d28a6/coverage-7.16.2-cp314-cp314-win_amd64.whl", hash = "sha256:5a27b731c171e43dc8b5f32b76a5051dde2ec9b9366c87028f08a7088ebc2c7b", size = 226732, upload-time = "2026-09-27T12:27:26.907Z" }, - { url = "https://files.pythonhosted.org/packages/32/3f/0001da22155b0a8ce063ec0f7e64ecbe17b373f306e7a74435f6d6accb72/coverage-7.16.2-cp314-cp314-win_arm64.whl", hash = "sha256:1c569a9fd25505f1cd6bea90588818f90373ce90e2632e2cacf19ddbd6e14fdb", size = 226645, upload-time = "2026-09-27T12:27:28.588Z" }, - { url = "https://files.pythonhosted.org/packages/d7/85/6d8813aff9b8b8586691a9d33c43c5604f7227622574da7cdc3d91a86861/coverage-7.16.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:d93db87adb6b1c1b408dce4763314b55d76a9f589e96783a84ac9e7689e48bdf", size = 224871, upload-time = "2026-09-27T12:27:30.32Z" }, - { url = "https://files.pythonhosted.org/packages/5c/70/444f3a4981ac2cda40fdcf4cc9b56a4e1a33c222abeb33e51ed3e3eb2a6b/coverage-7.16.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:aa62c85046473959c13ba9edca9dc90a77d5c1095b1ba313556314d77fe5b036", size = 225123, upload-time = "2026-09-27T12:27:32.33Z" }, - { url = "https://files.pythonhosted.org/packages/d0/c1/980681cd7b33eb66ac835044116ef0a92e11fcc7bdd866cc89d10b1130b9/coverage-7.16.2-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:db76506aa5416081f3e8974ae0f7965c58ada0bb0ef7339ac86099588dbb20d3", size = 265049, upload-time = "2026-09-27T12:27:34.085Z" }, - { url = "https://files.pythonhosted.org/packages/b2/e3/87679875c33bb2191f0f05544a1cc9adcc940fe0c35443a10f2df753dde5/coverage-7.16.2-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:a0f2285329dac10ab08f79cb11f5692c497018e6c7c511f95e6fd63a70b8f831", size = 267717, upload-time = "2026-09-27T12:27:36.025Z" }, - { url = "https://files.pythonhosted.org/packages/76/64/5d372776d6eb523d4e93bafba2253f96984e3b18261c4cc56a50863c6d0d/coverage-7.16.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:382d3346d56b0eec1b793d53a4c88799c8053f516aa3a8d7c44315696954bacf", size = 270047, upload-time = "2026-09-27T12:27:37.96Z" }, - { url = "https://files.pythonhosted.org/packages/be/c1/44082ff0cbf9f97d0043f57970a71204097ec7ba606361a9fd2065393669/coverage-7.16.2-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:648352b94507179d82637292e7ae8802508d95f78e2f00a705a50b6c48011681", size = 271360, upload-time = "2026-09-27T12:27:39.766Z" }, - { url = "https://files.pythonhosted.org/packages/b8/17/9a215efe25b5e0ecc87c89dbe525c4a87d14d87c8c0c7316ef140a5f6f3e/coverage-7.16.2-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fb2bde05838fffae1a1bf75e5d411a6cac3e4e9bb97e6640fed8cd47888b33f0", size = 264736, upload-time = "2026-09-27T12:27:42.072Z" }, - { url = "https://files.pythonhosted.org/packages/a2/da/7f0a31af8e448107d4d32844bd684757f51ea907bc0c68c8fd537b2123ff/coverage-7.16.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:6a75180829efb8ae62b4aded25be6ddca1c888d138d2d82e21d93bfbd88f41cb", size = 267600, upload-time = "2026-09-27T12:27:43.85Z" }, - { url = "https://files.pythonhosted.org/packages/dd/a4/3bfecbd3366b775bacdcb3330394d356cf384b5d8f5b2146ac4b14b252b5/coverage-7.16.2-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:99704f73721e23859112072d522076e11c31744fc96b5652e5dd2018aa4359f7", size = 264767, upload-time = "2026-09-27T12:27:45.768Z" }, - { url = "https://files.pythonhosted.org/packages/b8/3f/5d62163732d87e4a0c4710a0eab30f0fd6a2d480112abe2029f014fe8c9d/coverage-7.16.2-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:29309ccc86b7f33df7db12813c299f215bbbc470ed6292d0bedd63ffae1ebf64", size = 269026, upload-time = "2026-09-27T12:27:47.787Z" }, - { url = "https://files.pythonhosted.org/packages/49/4d/8e4579f225426535085a9be371cc75e3b026d058d679b80affbdfb4c3ef0/coverage-7.16.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:30c1b65d529e46569899fadca59e4a87c1faf2886923f1307ba61e654d4f3c20", size = 264147, upload-time = "2026-09-27T12:27:49.681Z" }, - { url = "https://files.pythonhosted.org/packages/d1/36/ef1f77e2c3f7bb03c2b13b9a2006f88700fdd75535ef158d70049f425c1c/coverage-7.16.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:dcf4bc2aab4e16b1c4c0c2005918f23a7dd5d7821ddae82caed9e3342dc2fcce", size = 266376, upload-time = "2026-09-27T12:27:51.551Z" }, - { url = "https://files.pythonhosted.org/packages/be/79/0cb2bf4428830dec971c718c2c841a039c084415c99e67281f5a72841aab/coverage-7.16.2-cp314-cp314t-win32.whl", hash = "sha256:a9cd3de0a5bfe7b0e21ee10e1a14e3d61bf52efc88217ab1d95d6ace6970bd46", size = 226585, upload-time = "2026-09-27T12:27:53.945Z" }, - { url = "https://files.pythonhosted.org/packages/3c/f9/da17121c16667fd84998e972200ae226a41540f6ea4795776c6d99e8976f/coverage-7.16.2-cp314-cp314t-win_amd64.whl", hash = "sha256:611a44e5229a59d7483ce830160e1a0e85f700562c7a5651c7c63fb8f4eb528c", size = 227378, upload-time = "2026-09-27T12:27:55.778Z" }, - { url = "https://files.pythonhosted.org/packages/74/89/01179c62d1b7e6e33bd5001566b02d7f778cf33d3ec1e81e94ca170c517f/coverage-7.16.2-cp314-cp314t-win_arm64.whl", hash = "sha256:22957cef43ce038641de78ba995de7568d2d6a37c6ddbf7fa0fd7d1ae2344d91", size = 227064, upload-time = "2026-09-27T12:27:57.496Z" }, - { url = "https://files.pythonhosted.org/packages/4c/57/52935003c3f627ba6e5203d7179aad32448c10899663a30336aba8e81a2c/coverage-7.16.2-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:414c26dfdb96aac2d570a54e03008f001e32eb2d413705365503648c6bd361d8", size = 224140, upload-time = "2026-09-27T12:27:59.343Z" }, - { url = "https://files.pythonhosted.org/packages/31/38/df472520f3e626524d7e2fc9d6da0afe7895a2f1489d36b48af8ca40bb41/coverage-7.16.2-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:00d3eb96e9988c45f50cccd1f1496571ac5c1f91386ac02c4d55516eeda19a24", size = 224449, upload-time = "2026-09-27T12:28:01.299Z" }, - { url = "https://files.pythonhosted.org/packages/0c/aa/3be084d5b82e63ccdad4ed751e4acbae294673573e30481d29f8b7402eec/coverage-7.16.2-cp315-cp315-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:4dbbd1155ca46e6e0b6b89d204428c56ef6a459af21333f365d135a2820e5a09", size = 256060, upload-time = "2026-09-27T12:28:03.185Z" }, - { url = "https://files.pythonhosted.org/packages/de/29/48fca82a7ebf7ff7b2e35019cc9537e7f65e4d2aa1215cc5a8792c989251/coverage-7.16.2-cp315-cp315-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8fc15cc8d0d06e873c00ef18e1372d605f9aaf3de27d8c24e50782e75bc8b843", size = 259106, upload-time = "2026-09-27T12:28:05.15Z" }, - { url = "https://files.pythonhosted.org/packages/06/3d/b2d5986f2dd53fe201aa1be2e4ab204fa1aed5101e67c0dbbb419b850aee/coverage-7.16.2-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c6afdd69218202bc1758c9a14b86b8cf1084f37ed2ca143e567a103772b16d1", size = 260674, upload-time = "2026-09-27T12:28:06.868Z" }, - { url = "https://files.pythonhosted.org/packages/ce/7e/b50160be3506ead12e6480d14279af7f0f17627694300a2d1fd2c42d2ff5/coverage-7.16.2-cp315-cp315-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:aba5c63b7afdc749cc9eae943d5b868cba2b261a176378fa1c5a30bc8bc89982", size = 263139, upload-time = "2026-09-27T12:28:08.771Z" }, - { url = "https://files.pythonhosted.org/packages/14/5e/7c805ac9a32606de1399bd7e9bd375aa2f973dc61b12680d9e6403c2e891/coverage-7.16.2-cp315-cp315-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9174f0af24e5eff248b9dbfe76ec5275a3d19d37edbc2810543f12cf97347a34", size = 256591, upload-time = "2026-09-27T12:28:10.842Z" }, - { url = "https://files.pythonhosted.org/packages/ab/9e/76f1ed129a2daf658a3ea17122824cf2e3b91fea0460d8d3664fc5a61018/coverage-7.16.2-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:80e9fdb4c3d926b6ba721d4bf7435bdb869c3527ae7803290361d0ab73db13b6", size = 258708, upload-time = "2026-09-27T12:28:12.962Z" }, - { url = "https://files.pythonhosted.org/packages/5a/b7/8d62e75f48b527619239a65294f842d4b7fd02a0839d43ae1de80184e2df/coverage-7.16.2-cp315-cp315-musllinux_1_2_i686.whl", hash = "sha256:7b3bce4a0d05401d70b7d0d5ca783e686bc9d30e81dbd7d980d532609bf809e4", size = 256562, upload-time = "2026-09-27T12:28:14.934Z" }, - { url = "https://files.pythonhosted.org/packages/b8/8d/0a15f95c3afb78e947c52644786ba4bc9de259905687dd720d5e6fae2e76/coverage-7.16.2-cp315-cp315-musllinux_1_2_ppc64le.whl", hash = "sha256:44f21e407b278efdfc1ee5e481e00518bd1d500310a30a5fbf2bcbedfef4aaf0", size = 261077, upload-time = "2026-09-27T12:28:17.215Z" }, - { url = "https://files.pythonhosted.org/packages/25/00/88389987305a47d732866c07c8a500000ab574df9505e3114ac69c8d027f/coverage-7.16.2-cp315-cp315-musllinux_1_2_riscv64.whl", hash = "sha256:59c3926585e1cd1f2190f4b2ac9014de1bbeaf0d5d0587b0dc6b0aa90d17896a", size = 256034, upload-time = "2026-09-27T12:28:19.08Z" }, - { url = "https://files.pythonhosted.org/packages/92/02/34d079d4952ad461bde037d353f9a6e037a7edc45fe0f9ee8781ff73f028/coverage-7.16.2-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:066429634299e14dd2d511e1e85f8f9cecc500781f6b41907c0dd6f1baea7e63", size = 258034, upload-time = "2026-09-27T12:28:21.242Z" }, - { url = "https://files.pythonhosted.org/packages/f6/d8/3e59a62879285b464ec1b10fd824fbc1af9ce66e842cd39974f80a0becc4/coverage-7.16.2-cp315-cp315-win32.whl", hash = "sha256:893ea9cf86cb8d2546812ac93d973aaf2ee1fb45110a873b014214fd23e3725e", size = 226252, upload-time = "2026-09-27T12:28:23.102Z" }, - { url = "https://files.pythonhosted.org/packages/f4/e1/128026e1b2836e9ad6b219207ba9edf1c5e0088a7869e23088aee7fbbe7a/coverage-7.16.2-cp315-cp315-win_amd64.whl", hash = "sha256:01c6908bc613b420c26c818fe948e1b97dfd041a53c98b01c63bd8321f5c9aae", size = 226728, upload-time = "2026-09-27T12:28:25.21Z" }, - { url = "https://files.pythonhosted.org/packages/a8/f4/c9fa8e7cf525ca7748ac52b0ee89331d13fe09808e45c679830708782e90/coverage-7.16.2-cp315-cp315-win_arm64.whl", hash = "sha256:967d72c835d7a8cf0af99ec813a2d06e3db6df706402f1fe85b31b437645f495", size = 226648, upload-time = "2026-09-27T12:28:27.136Z" }, - { url = "https://files.pythonhosted.org/packages/a2/13/e96b045447a856666f36f9c653e2a80bdaa732aaaf72412b19aa2c26a473/coverage-7.16.2-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:98d9c97f51b334b0adce7b964442a9af33c1a00c6ac856984cc5dc8d18f81c75", size = 224858, upload-time = "2026-09-27T12:28:29.169Z" }, - { url = "https://files.pythonhosted.org/packages/23/90/087f6ad1bd3df059632ca3407a4e6552ed1053ee35354de0a771acf35423/coverage-7.16.2-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:3e861f1071dcc2fec1e88bef0920f6b1eaa66a143555b4f8ab79ba2b0f30ef55", size = 225139, upload-time = "2026-09-27T12:28:31.131Z" }, - { url = "https://files.pythonhosted.org/packages/7e/8e/285dcef0184358044e7cbcd810a1bdc9566bc620f54702d605477155df4a/coverage-7.16.2-cp315-cp315t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:fb9d92ecfe2d5b494367c67f7446f8b75b68d8d0c8cf3bc3e6997478be25d9e2", size = 265090, upload-time = "2026-09-27T12:28:33.04Z" }, - { url = "https://files.pythonhosted.org/packages/06/b2/cc83f3a6e5789a4e89059c69555bc641c2efcde568405a1c06fc702951ab/coverage-7.16.2-cp315-cp315t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:eb57acff4a74246ae513c142d4b36e18c389c3aed8661914a53f7cd0071031b2", size = 268254, upload-time = "2026-09-27T12:28:35.135Z" }, - { url = "https://files.pythonhosted.org/packages/ac/41/f548c19530f5d66ac6e3c92bbcbc49da7261de3a458b9f3e54a3efb1a0b2/coverage-7.16.2-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:444889f7f66b74e4455c0a97e0e166dd41177f1dca8c0239a47cff25e05ba7e1", size = 270695, upload-time = "2026-09-27T12:28:36.959Z" }, - { url = "https://files.pythonhosted.org/packages/94/61/4dc27cf82ef96434d2874110ad0cc10ea4621025705dc5049862bd3bd181/coverage-7.16.2-cp315-cp315t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a740ea6f083c6db7b926534d159508f80ba275ab35e722522de0d18d0f56e55f", size = 271853, upload-time = "2026-09-27T12:28:38.821Z" }, - { url = "https://files.pythonhosted.org/packages/38/29/bf8072b1b8bd5f2de8b21460a404460b1a2b97e80a9464c78ec0271f6199/coverage-7.16.2-cp315-cp315t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8e209591f7c41ae4a9171335cf6156afda0b21de73b02f73f5aa95b2d5fbb08d", size = 265644, upload-time = "2026-09-27T12:28:40.815Z" }, - { url = "https://files.pythonhosted.org/packages/7c/2f/0aecb8721be5cdeb8afd9d6d9f6b463f074e4d8d37f00f4c42442522709f/coverage-7.16.2-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:396bb16e04ce04efbb3df91456ae4e3da918e69ecdf67fb711b0a0fdf35ccce0", size = 268530, upload-time = "2026-09-27T12:28:42.725Z" }, - { url = "https://files.pythonhosted.org/packages/ab/0b/92b4b7628268ee711249958e68fc0328779bd3d9a7ab4715379465aedb84/coverage-7.16.2-cp315-cp315t-musllinux_1_2_i686.whl", hash = "sha256:9cdf19874e0d247f32f03609200370343c3c7aa260b191d8c2bb251d36198283", size = 265154, upload-time = "2026-09-27T12:28:44.684Z" }, - { url = "https://files.pythonhosted.org/packages/7b/d9/41c95c1ab29b3dcd357cd1227181d1c98185632aca41ce670ce671b23a43/coverage-7.16.2-cp315-cp315t-musllinux_1_2_ppc64le.whl", hash = "sha256:fd3d72233eb8b48acc94fa57d44e2d32ce8e7abed02882ccb6d855ccc4ed33ec", size = 269799, upload-time = "2026-09-27T12:28:46.672Z" }, - { url = "https://files.pythonhosted.org/packages/80/07/ebeb259aa5362b033a137b86d7274ff4b109d59be8cc9913889b783bf75a/coverage-7.16.2-cp315-cp315t-musllinux_1_2_riscv64.whl", hash = "sha256:bb4ffe96aa663cee727659db5a2afeb38c95f8677b747d447b90d6d4874ea2c5", size = 265196, upload-time = "2026-09-27T12:28:48.996Z" }, - { url = "https://files.pythonhosted.org/packages/b2/18/8437620f90d023680a072eee02f968055f3658bbfb7d386d0ea34cfb7f30/coverage-7.16.2-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:dba2edfb054f6d4a08df9d1637c39a5aa3865bca6617c13c86be21e45658a59c", size = 267266, upload-time = "2026-09-27T12:28:51.361Z" }, - { url = "https://files.pythonhosted.org/packages/28/6c/f08e8ee4293e6434035424180bef4d45e028e8ecc006c61bf9453e74405e/coverage-7.16.2-cp315-cp315t-win32.whl", hash = "sha256:251aed777c47c77aba047096d4542889db089227655711dfc2b9c54ef0e15e35", size = 226583, upload-time = "2026-09-27T12:28:53.33Z" }, - { url = "https://files.pythonhosted.org/packages/f7/fd/3f939c2847f4a72c20cff8b1ac33da78ea91a2d38d9b43336e60db719103/coverage-7.16.2-cp315-cp315t-win_amd64.whl", hash = "sha256:2aca0bdfa9e91621d5b09d815357bf63def4fc0e9cb66da67bf2cf93f3b1a6f5", size = 227374, upload-time = "2026-09-27T12:28:55.158Z" }, - { url = "https://files.pythonhosted.org/packages/5a/35/b98cdc354c952402132e675a87f2cc3227fb68f959c84aaa491fbe15933d/coverage-7.16.2-cp315-cp315t-win_arm64.whl", hash = "sha256:b88841e654f09732804809e435b3e005a929ffd9998b872b7b213957b8759cb8", size = 227064, upload-time = "2026-09-27T12:28:57.075Z" }, - { url = "https://files.pythonhosted.org/packages/3f/0c/7a64e1ac90541a8edf50daef0914848011fb057a5bf55284a4811e21939a/coverage-7.16.2-py3-none-any.whl", hash = "sha256:11d28e9123a9156cb405d8d27b44256c9a58fb5decc2073a8f17862057e3aa0f", size = 215754, upload-time = "2026-09-27T12:28:59.075Z" }, -] - -[package.optional-dependencies] -toml = [ - { name = "tomli", marker = "python_full_version <= '3.11'" }, +version = "7.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d2/79/e78bd8f75a9b8e32233ce92d4607f0a21ce88a9156cace2884e7175a82f5/coverage-7.17.0.tar.gz", hash = "sha256:cc010a0a7fbdd9bb8bdfd0d3f04a5151275860213cbd7e2b876b29778489c712", size = 991021, upload-time = "2026-10-22T16:06:50.009Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/75/25/79a66272edd250031aeee6db87d500a0bf76390d0d5f8c2f431fe354715f/coverage-7.17.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:99a7b390cbccc6bf029ca4da3f0f607c218bb4085e9cd6b58a90d14ca12993c3", size = 225837, upload-time = "2026-10-22T16:03:55.622Z" }, + { url = "https://files.pythonhosted.org/packages/52/a6/f5f6fefb2fc1305363927edad1d0a60ce6ef80d6c1a74a79e825f0f670eb/coverage-7.17.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f72640e02a470b57b15cd655820133f6892760209283a363a60e2df932d5f0fe", size = 226257, upload-time = "2026-10-22T16:03:57.164Z" }, + { url = "https://files.pythonhosted.org/packages/28/61/72889bac8eb44c51f8043edb227df551dfdca1fc4819a6c6d67b3f69427b/coverage-7.17.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:2dc9233f39149b5a4f4aba17a4b6bc5cca22e52cfc4b06fdb07d014439b8b2ea", size = 258282, upload-time = "2026-10-22T16:03:58.521Z" }, + { url = "https://files.pythonhosted.org/packages/f4/e4/03e4a5644df9b73de0d44d1b82acd18b06c42b5abc3f1433be8363fa983d/coverage-7.17.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8e8113d3852e9377421a61de1917df27b3abb25404a4ecf69f9e64612c4345b5", size = 260506, upload-time = "2026-10-22T16:04:00.116Z" }, + { url = "https://files.pythonhosted.org/packages/6f/4e/949022286df7e59e8633a3a7e6635e0e6b53af7d57074cb0bf8f500373fb/coverage-7.17.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c86a39539d10d666bb819c16c94d7edfc1aa2e0531b937f7c549b83d15177c6e", size = 262600, upload-time = "2026-10-22T16:04:01.49Z" }, + { url = "https://files.pythonhosted.org/packages/27/cf/7d67f60df081006315699b7edb9402c1b47142caf1383f4e56ae8ff1a501/coverage-7.17.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:559f748446ee799803686b5784e3deb54c70d655ac386a860e06b11672504ba7", size = 264609, upload-time = "2026-10-22T16:04:03.286Z" }, + { url = "https://files.pythonhosted.org/packages/e1/e0/9293d94afbc099990a085e88229a51c79b00828aba0f2adda32dc4cc16d1/coverage-7.17.0-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:34d65c9e8e257f48a654908005046fbf1bc48d9540ae3776ad08c0acd010e10c", size = 258382, upload-time = "2026-10-22T16:04:04.804Z" }, + { url = "https://files.pythonhosted.org/packages/24/c8/bcd28efedf262c074dae76f61098739eb02b15f3f716f80cc86f9885b8a9/coverage-7.17.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5b7fa0997b8eba7378ac66f31ec7f70164490353ab79b9667d37c0d365e50177", size = 260299, upload-time = "2026-10-22T16:04:06.291Z" }, + { url = "https://files.pythonhosted.org/packages/b8/dc/503f55e5e2200933403c90d619d9f779b5060ddd338b1bdc63cd235592cb/coverage-7.17.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:6ae0453a3390e67f7bfbe34417bbdf5b4ebfc21db23fb446a0a41115eeeca6ca", size = 258314, upload-time = "2026-10-22T16:04:07.729Z" }, + { url = "https://files.pythonhosted.org/packages/bb/b1/f1a57c36077cc516240636f7eff0d1427939ed795f096dd2c7769213a56c/coverage-7.17.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:96151262832f3c230ebae23b2d6911ad549e1738465946c97004913ff06112ba", size = 262239, upload-time = "2026-10-22T16:04:09.116Z" }, + { url = "https://files.pythonhosted.org/packages/a8/4f/c2e59d4a132936c7c207b9b69ab99c0f27b14c3c93266e9922aa3b562b37/coverage-7.17.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:cd7397de6cc782f00fe5ac1d75e9b306bcfa49c8b6b8dd6ed1f7709393ad40b3", size = 257841, upload-time = "2026-10-22T16:04:10.509Z" }, + { url = "https://files.pythonhosted.org/packages/05/04/50742c0ee8b6c85826ff11737c93ed957fea4c9acf4b4b99804bdac1dd84/coverage-7.17.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9d1fdf82c6fea57b5e9dada509f9ce9638c8334664e2c98c7f6f1ccf3d143ad2", size = 259039, upload-time = "2026-10-22T16:04:11.916Z" }, + { url = "https://files.pythonhosted.org/packages/8e/cd/83ad4bbbfcb6d045a33c14cdca313f6c9275737c67f3b7300222e3239a37/coverage-7.17.0-cp311-cp311-win32.whl", hash = "sha256:38c86e5d74bbfd88a393fa3f91257c1225c63aa6db6d5918de3e49ab04f91c6e", size = 227687, upload-time = "2026-10-22T16:04:13.537Z" }, + { url = "https://files.pythonhosted.org/packages/ca/c8/dcc5a7effa79707eb8a61e699b141cff7c9b46036771274cfb48ea7b765f/coverage-7.17.0-cp311-cp311-win_amd64.whl", hash = "sha256:2ed3097c6817bc1a7fc8ae0a6131db74aa1150d863cccc308237ff362d3226b2", size = 228290, upload-time = "2026-10-22T16:04:14.899Z" }, + { url = "https://files.pythonhosted.org/packages/d1/58/a8553e0dcd38d8136df63964638d2b588e0a4a916bff1bca996244fda693/coverage-7.17.0-cp311-cp311-win_arm64.whl", hash = "sha256:6052e878b628abcd38fa5f2b64abebc686eed4e2267da42434017a9c6d24f241", size = 228199, upload-time = "2026-10-22T16:04:16.351Z" }, + { url = "https://files.pythonhosted.org/packages/0a/25/989fd3b495c4274ac0cf54cb6664326415e60183a7f8a73dd9bbb0b11e34/coverage-7.17.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0cfdc938ca3319d873d1f1252e60620a39aee4ebf9dbd7cc58238109ebda9c51", size = 225938, upload-time = "2026-10-22T16:04:18.113Z" }, + { url = "https://files.pythonhosted.org/packages/db/cb/14726426bf3241657facbf0f7df7babd8f7d3fcf8e4609b7929f9dceecbc/coverage-7.17.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d523da3c2e4bca49417768bcdb1a443c5b359dc44b8b93a3102e118fce912c40", size = 226166, upload-time = "2026-10-22T16:04:19.524Z" }, + { url = "https://files.pythonhosted.org/packages/8e/97/f95ed32cd3cfbe261a3598e694cb770cefc7b2d13ff013418d6f70a44dde/coverage-7.17.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:adbf3450c0f3f2b2854e282a8758f2b3eddf787eeff24ec9c9bc2cbc38328cc1", size = 258462, upload-time = "2026-10-22T16:04:21.079Z" }, + { url = "https://files.pythonhosted.org/packages/91/ac/eb4c31f741509a9ef48cf6f28385e98e9de0785599282252eee252ff8af7/coverage-7.17.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:85932227e9dd38e62cd29c17fefdfecfb919eead6af1e3259522d03cf85de983", size = 261789, upload-time = "2026-10-22T16:04:23.037Z" }, + { url = "https://files.pythonhosted.org/packages/e8/3e/d9cd006d1a3cc5deab07beb29b86cb4a10dd84d66b330820363fb8d310d6/coverage-7.17.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:30f5477aac9c9e4a0a641c55f8f8fddd7f3bd91176c912c50f40ffced96b4034", size = 262322, upload-time = "2026-10-22T16:04:24.94Z" }, + { url = "https://files.pythonhosted.org/packages/c0/3a/4c872c6ae0762b09cdb8e856891bb3f584848cb60b6dda2bacb85fe2f3f0/coverage-7.17.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0b8bb651a4406fb6a983a905309fec1783443c65e1771b41e92740a0f4a1176b", size = 264600, upload-time = "2026-10-22T16:04:26.444Z" }, + { url = "https://files.pythonhosted.org/packages/df/98/1d41a2373fa98b423bcd70adb91d75e38eafb417955426dbb920b10016f3/coverage-7.17.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0edeb6594f080c8de1263cccb239cb316ef77aeb959e2e0f38f2953fcc72a49e", size = 258678, upload-time = "2026-10-22T16:04:27.832Z" }, + { url = "https://files.pythonhosted.org/packages/92/6a/fe04b5e63e76506daa63d90bdfb00a53767fe5df18546b437d6226918d6b/coverage-7.17.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:da94ec0484e84fce5c7bf8d0f54b97767a90d992653a129b35634699d516e0a5", size = 260210, upload-time = "2026-10-22T16:04:29.275Z" }, + { url = "https://files.pythonhosted.org/packages/4f/c3/5c5a78dc19dd4799be9c6ec022316fb25ababaeacfaf0f52f24c56a57b43/coverage-7.17.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a25cc5e8c23a590815c6a7aa43624234d25cb23b21b484adb8a0daed59860db5", size = 258375, upload-time = "2026-10-22T16:04:30.647Z" }, + { url = "https://files.pythonhosted.org/packages/ed/e7/4c1d8eaff4d756fefd15d58c4953a78ab656782fd0e32c5a3465dba61aa0/coverage-7.17.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:db645c81cf4d6bcb392ea1894aad65d55d1588a8472c834b25ca575da3029a56", size = 262422, upload-time = "2026-10-22T16:04:32.053Z" }, + { url = "https://files.pythonhosted.org/packages/a6/2a/0b17c3e2e7b6ff1a5d92eb58915c0d1026668928f961d14dac73cf1b6096/coverage-7.17.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:6a80a11b90c4a03df1502304cf24ff953255b6b5d076ef79066ecf88baaff31f", size = 257875, upload-time = "2026-10-22T16:04:33.824Z" }, + { url = "https://files.pythonhosted.org/packages/d3/2d/eb0cbde0535342df0d17a667163030a3afb67e5eb9e3ad94af9540041584/coverage-7.17.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e4eaf11d6b0f2147f46cfbf06fe7edcca6f095d0120da00ddc2458995ee869fc", size = 260361, upload-time = "2026-10-22T16:04:35.661Z" }, + { url = "https://files.pythonhosted.org/packages/2e/47/1be2a6db7eb655d0a1d899217c3f2469f9121cbbeddac35c1c8173b1482e/coverage-7.17.0-cp312-cp312-win32.whl", hash = "sha256:912a29f338798541d795e2e25903c6fe7a99dee70a40fe4706a9a124649b523e", size = 227747, upload-time = "2026-10-22T16:04:37.102Z" }, + { url = "https://files.pythonhosted.org/packages/11/c5/201c894c57de81875d8b8cc527ba222aa350e8cfb183eb3f6df45d06ad8e/coverage-7.17.0-cp312-cp312-win_amd64.whl", hash = "sha256:88a1a9fb0075b83389dd9b403bb3fce976031b3c2aa0e05e3e57143cf639de69", size = 228325, upload-time = "2026-10-22T16:04:38.989Z" }, + { url = "https://files.pythonhosted.org/packages/0b/5d/196095339b19758cf83f507fb598bc42820ae124745feb3084662729e17a/coverage-7.17.0-cp312-cp312-win_arm64.whl", hash = "sha256:2c680e838ff9e68d491158625ca2c1ddb17d78d791bba96a31f8b839b4990438", size = 228131, upload-time = "2026-10-22T16:04:40.654Z" }, + { url = "https://files.pythonhosted.org/packages/54/e6/1f01afbc9f4fa8ca5e1fa6fdf537a5c58b5765d1b4167ee284cddb9acc72/coverage-7.17.0-cp313-cp313-android_24_arm64_v8a.whl", hash = "sha256:84538c040f4e6214f663459d89372ce4e6b2bbdd9d397ef0dca7b3ecd56b8eb2", size = 227080, upload-time = "2026-10-22T16:04:42.325Z" }, + { url = "https://files.pythonhosted.org/packages/92/7a/bdb7e6260c4a211389aeac9b89dc31662ebe122153ad0ecfd156897912df/coverage-7.17.0-cp313-cp313-android_24_x86_64.whl", hash = "sha256:6abfb6a4cf1cb1b67bc8fccdde177e2d2b781d0d34449e7078156547025140e5", size = 227152, upload-time = "2026-10-22T16:04:43.737Z" }, + { url = "https://files.pythonhosted.org/packages/37/4e/276e7d5b3cfb124871bdb544c27b004a8b4e270e6bf8e7414149c54b5e5b/coverage-7.17.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:ef65e96f0fc1a7c8b7644c7011d47a8228555e7888ac4d6246f715a7f3d7c398", size = 225397, upload-time = "2026-10-22T16:04:45.292Z" }, + { url = "https://files.pythonhosted.org/packages/b3/7d/dac073db12f3c131598a6693253aded581e4d7d622a89864f11bfb0ca2f5/coverage-7.17.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:55939ea4c097fda2edabf80fac5e49bef15597b950a9f29fc56292a895245feb", size = 225705, upload-time = "2026-10-22T16:04:46.603Z" }, + { url = "https://files.pythonhosted.org/packages/00/45/877b0a9248e921080ae91d115c13192d17e308f5aee86bcc7f7bc2a54ed1/coverage-7.17.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:bfb6f868af552c0ceb2d19220b8cbcc2be096ca26163ac30e682a13041433429", size = 225828, upload-time = "2026-10-22T16:04:48.065Z" }, + { url = "https://files.pythonhosted.org/packages/4f/f5/2c48d47abec0e4590b302920334be5ee3854f96bbd22524feabb8d92b220/coverage-7.17.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8d823d66afd59566030539c49c7a6acaf3083996682be2dda760c63555842094", size = 226088, upload-time = "2026-10-22T16:04:49.513Z" }, + { url = "https://files.pythonhosted.org/packages/a8/a0/ec0442f3f372c7fd742f49b3022edd392f79e37a0048e1fa333f56d01ec3/coverage-7.17.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:50678474e714c684b82c108e63fbb8f1bcf1266d8293a49980f1d08526f093b5", size = 257257, upload-time = "2026-10-22T16:04:50.982Z" }, + { url = "https://files.pythonhosted.org/packages/92/e2/eb0927ba93809f76eee69fa1e47999825fc7dbbabc3e0e1a2744f2677acb/coverage-7.17.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:045c1eeb19fb4eae949850a761102fffb3a5f638b97f977912f95843e54f6669", size = 260165, upload-time = "2026-10-22T16:04:52.409Z" }, + { url = "https://files.pythonhosted.org/packages/9a/a2/8d4c6055efb32099b15c9cc33d007b8aabebc4832f372a3274de5ba0afc5/coverage-7.17.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dfb12891a773cbc2132349c77c690a045a882fe3f98b4bda50dfabb01893f9bd", size = 261098, upload-time = "2026-10-22T16:04:53.763Z" }, + { url = "https://files.pythonhosted.org/packages/2c/1e/93412288688a7b03f152a734c8ef086e395108a0a6a746985d3967e220f2/coverage-7.17.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3067608b4429629f640c1b23d2f69b05d2fd45e00ebb6ee9e8fad220a6c11722", size = 263164, upload-time = "2026-10-22T16:04:55.386Z" }, + { url = "https://files.pythonhosted.org/packages/3c/20/7eb8d805c4f1a212245cef20181dfbf39535357985073d49730ee9cf08d4/coverage-7.17.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a1762e85cbbe3b5ff9c6dcbcc1b89d5c72320094292b7b9572bf882c85e428b7", size = 257287, upload-time = "2026-10-22T16:04:56.976Z" }, + { url = "https://files.pythonhosted.org/packages/1a/c1/1df29630b4ed17e135c76390afcc6bd273c3dc5ba2a21a76813a600fe98c/coverage-7.17.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:62bb77987d3f72a0aaa77c567ee7f9806bc2e748c8f46fafbe1fdb90dd148398", size = 259256, upload-time = "2026-10-22T16:04:58.545Z" }, + { url = "https://files.pythonhosted.org/packages/a1/79/2365d1008d28aafe468a450a24c6c63363de794b5140223d67685cd0e205/coverage-7.17.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:6cc31f27240d4e080437e3b8229d92f1d809c9a4f233607385373b73974e183e", size = 257178, upload-time = "2026-10-22T16:05:00.103Z" }, + { url = "https://files.pythonhosted.org/packages/c0/9f/f002952d295d772688dffce0d991e3822795b1e3aad158073712a5a5e86e/coverage-7.17.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:61cde5222f1933d476647c5a25ed1a8678b7703098cb5a15f4a0aa39967d8c28", size = 261275, upload-time = "2026-10-22T16:05:01.462Z" }, + { url = "https://files.pythonhosted.org/packages/40/fe/f9e15fbb426be97cd679ee5d359151e046f103bcd76b5092c318c26ed596/coverage-7.17.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:11f5f7fb2d97a0fe865f5e7bad4eb0619b957637f676bc088d13844adf26ba95", size = 257029, upload-time = "2026-10-22T16:05:02.8Z" }, + { url = "https://files.pythonhosted.org/packages/c2/4a/3c398f38694387334ff4c9f71afc8e15b4fa7db7bc0bea4d5b6032eb0df0/coverage-7.17.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6e09612b1b82c9df1d2b6c85dee88d5aad3e73d77a8ec0b16ac6a7322cbdf5d9", size = 259109, upload-time = "2026-10-22T16:05:04.241Z" }, + { url = "https://files.pythonhosted.org/packages/bd/a9/908a3cf1a2335aec1c788682a06c2f742a62a4f987033f080de1c5fd1997/coverage-7.17.0-cp313-cp313-win32.whl", hash = "sha256:bd3e9eb493631ff1ca34bf5efac25b5c66724f837bf6182c4fc771c55946423d", size = 227691, upload-time = "2026-10-22T16:05:05.636Z" }, + { url = "https://files.pythonhosted.org/packages/13/f7/8cdd7bfd8a63b3e6b133fc395ecfc7b1cb6f561926e7e2ad050de85b83e7/coverage-7.17.0-cp313-cp313-win_amd64.whl", hash = "sha256:f1fc9a7cea899c51d8a53e66c2f082a3d9e461ece32418d3ef0a0b90c32bfd3d", size = 228418, upload-time = "2026-10-22T16:05:07.057Z" }, + { url = "https://files.pythonhosted.org/packages/09/cf/0b3fc57f735cd3831ca13d0be5312a5477d5934066683c5d34018cab7541/coverage-7.17.0-cp313-cp313-win_arm64.whl", hash = "sha256:e65a1e61a68493fbfbe44d8bec71b3231d81b9917f62d3dafe0e105072147e83", size = 228142, upload-time = "2026-10-22T16:05:08.435Z" }, + { url = "https://files.pythonhosted.org/packages/4d/e1/722462fe79912ebebc101497bd16c42413cde2dbae771017f1ec0acb84c2/coverage-7.17.0-cp314-cp314-android_24_arm64_v8a.whl", hash = "sha256:dc5936491e306f2f6f095bd5fba593de11eb0375cee787ca1f245c3a333a523a", size = 227197, upload-time = "2026-10-22T16:05:09.846Z" }, + { url = "https://files.pythonhosted.org/packages/b7/74/826b9c09f2ebfea803bccb391d935688a25309bff8e819fc446bc629c108/coverage-7.17.0-cp314-cp314-android_24_x86_64.whl", hash = "sha256:968c5fb6e0c83cab752d79a70209648ce4d90cae815c60fa229b59e264601062", size = 227314, upload-time = "2026-10-22T16:05:11.422Z" }, + { url = "https://files.pythonhosted.org/packages/6d/19/c8514d7f5e0659a96ff2bc7377119dca6d420e2761c5fc69ae37618734f8/coverage-7.17.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:12cb8a20ad904a5811e6660d829a586b216e5254b9b34b76dde8a4d65d827dc6", size = 225477, upload-time = "2026-10-22T16:05:12.761Z" }, + { url = "https://files.pythonhosted.org/packages/81/74/ef9433ce599f07d8633b4ce6e6d6921505c4ef3f7f9006f8eb66e2c278cf/coverage-7.17.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:bb5ca66937c55bc85d1a5e9c5900a3e4693a8edf2280a9b3672b7b2661a84b95", size = 225787, upload-time = "2026-10-22T16:05:14.394Z" }, + { url = "https://files.pythonhosted.org/packages/d8/66/26e58981940e8b3944c6a9941bec5c7f50840e7009a046712e3f3bcd9e6a/coverage-7.17.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:5cc15faaf318ee5040d835a62c75bc6f122d837274f3b5efd47bf9fbd4ac5b85", size = 226019, upload-time = "2026-10-22T16:05:15.798Z" }, + { url = "https://files.pythonhosted.org/packages/e9/63/fe175310815cc8357b83b773c9b4c20943c269f217dd825a37c4b4bfe324/coverage-7.17.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:7d6d9d396c8b7bcaf76d774a10140b218111484e54c73a5f2a1b75655381748e", size = 226169, upload-time = "2026-10-22T16:05:17.188Z" }, + { url = "https://files.pythonhosted.org/packages/2c/b0/ac84761f5b4bd226696b0ec4606903d2ef7208abda0aec06c14a4218def4/coverage-7.17.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:42e6f170d1ac04b7eb3363310a6c41cdfd82c1fe6bf9ffbc4a86f380bdf091c7", size = 258092, upload-time = "2026-10-22T16:05:18.691Z" }, + { url = "https://files.pythonhosted.org/packages/e9/58/d8bdd65a661913bc12eaaa3e59742b2cb8b94f2e7f78e62184f0a58ca493/coverage-7.17.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:acdb5f3c96046efff9dc6c572a6acb2b8d0da09372d7b9a9ed7249cf7d17434f", size = 261096, upload-time = "2026-10-22T16:05:20.089Z" }, + { url = "https://files.pythonhosted.org/packages/7b/15/5d82e2f0a75ea149e0a6412e137e1d610538772fdfd7199737db7fe234da/coverage-7.17.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3393209f6894713161ee82a9b0e673e58237e0f9733c6638e7a6c0c620ef0b33", size = 262080, upload-time = "2026-10-22T16:05:21.568Z" }, + { url = "https://files.pythonhosted.org/packages/0f/cc/e3ebfd295d1dd47c7bc84a17d73f150465a37ab388dd68fe82b495dc79a0/coverage-7.17.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e9494d9e99389d39e6a96227749af898d8924ff591cb63944f8a98e68582bf0e", size = 264558, upload-time = "2026-10-22T16:05:22.906Z" }, + { url = "https://files.pythonhosted.org/packages/e0/9d/e2adff76d6544bbc6df7366d74967923c2de37ba889a006bfb875bf5857b/coverage-7.17.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:032d4c2db4c677eb71d43f2f8b80bf64e9346f7a3f7b6ebb13f1782f3cc6c274", size = 257855, upload-time = "2026-10-22T16:05:24.343Z" }, + { url = "https://files.pythonhosted.org/packages/d9/fd/ecd1e9cddf1c36b5d03c438568b1f53e63db84bb3656539d71b454484c7d/coverage-7.17.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:4343a9378b964eb3cb80fa90406385de91c7e982a769ca9314d848f93bc0d3f2", size = 259933, upload-time = "2026-10-22T16:05:25.79Z" }, + { url = "https://files.pythonhosted.org/packages/40/b6/e17a19dccdbcad494c64691f71c1dba3e55c30625bc29ffbb88ac5a0aa8b/coverage-7.17.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:b609df3d6d900464207532c5170a65c6ede8d2e0b91f151f90008a866395cd76", size = 257972, upload-time = "2026-10-22T16:05:27.185Z" }, + { url = "https://files.pythonhosted.org/packages/6a/0d/37dac534709a3fa43a937c8b0604839bb44b8670b83d143a4b78fec20d1e/coverage-7.17.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:a01ef92caeedfa4f2ad171a3a8e6fece4565de871d8bca428151bf9929f41048", size = 262271, upload-time = "2026-10-22T16:05:28.619Z" }, + { url = "https://files.pythonhosted.org/packages/cb/17/7c05186925b4d64a46c271dfd944e3f0c1ad89db9095c31a6df157a75df2/coverage-7.17.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:a3914e2c98c34b94c26d2add3d2a61ccfd3534fc4eaa879ee290bdbb5184ba6c", size = 257604, upload-time = "2026-10-22T16:05:30.07Z" }, + { url = "https://files.pythonhosted.org/packages/95/65/c241cdee66a0ff86c68f6d37328e735d10e3d30cd6ff66f356039eb688d2/coverage-7.17.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e8a72b26dc5c9a9422e45472b1d67ba5caf273c524c8668cc04b23062663755e", size = 259714, upload-time = "2026-10-22T16:05:31.402Z" }, + { url = "https://files.pythonhosted.org/packages/db/09/e93233fe2405574ce0a31ed201c0cba3a860e085db5d2828bf12b7dc0790/coverage-7.17.0-cp314-cp314-win32.whl", hash = "sha256:03baf0688e14a5a44ffd1eefa8928dfc92b14565e521174e072b31b971aa1657", size = 227914, upload-time = "2026-10-22T16:05:32.78Z" }, + { url = "https://files.pythonhosted.org/packages/d7/77/04979eb217edb94b94afabb4bcc179f60c746f68d3fd0b12688ba9195730/coverage-7.17.0-cp314-cp314-win_amd64.whl", hash = "sha256:3a4459b0f389dd1e51aac3dbfc4ca35b30cae7dc99caa3c0a086d1997a985709", size = 228543, upload-time = "2026-10-22T16:05:34.118Z" }, + { url = "https://files.pythonhosted.org/packages/a0/38/155f2f2c6769f8c43eca8a5e581443faa0908828650090b97103b4a41376/coverage-7.17.0-cp314-cp314-win_arm64.whl", hash = "sha256:a32ee5831b15ba239bef5d272ae586ca8acea4a661b5f2941f5a0d380e518b23", size = 228396, upload-time = "2026-10-22T16:05:35.951Z" }, + { url = "https://files.pythonhosted.org/packages/52/55/d6751572d9445485c4c5a51ec6ae11caa5c11b7ef2448cdccc884f7f54d9/coverage-7.17.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:9c083e253e96fdeeaf48133fe836cac86ace2db215d235700e7f7532420a200c", size = 226486, upload-time = "2026-10-22T16:05:37.363Z" }, + { url = "https://files.pythonhosted.org/packages/2f/a8/82d37760e537bfdb9fdee285fa1e1b45bc153293d23e2331c7befedd1990/coverage-7.17.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b88fb4d4d7608cbcebdc7ff7dbffc2115e3b81d6c33a79147cd79d54fcb080f4", size = 226861, upload-time = "2026-10-22T16:05:38.777Z" }, + { url = "https://files.pythonhosted.org/packages/24/96/70ec00a9db6d7bdd3f914eba12057360769f5cb78a98456e71fe1937deb5/coverage-7.17.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:2bf3ee38a40c0570c316b9e7507c472469586906a20f289705898528c31aaea8", size = 266849, upload-time = "2026-10-22T16:05:40.285Z" }, + { url = "https://files.pythonhosted.org/packages/77/d8/d99be3bfbb208512f039c80b31e404138a86678fc929a17acd5b7135a6ae/coverage-7.17.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7c570293f001ea91598b90e90cb9b98fddee833e90bbede432cbd218331ccd8e", size = 269402, upload-time = "2026-10-22T16:05:41.585Z" }, + { url = "https://files.pythonhosted.org/packages/82/50/6cbe8b5a5060edbd21ce8109c4e900f8b7656e8b72ede6dc4d82dc0b8bfb/coverage-7.17.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:791f9ee09814dd30d9562b4f4eb037a371485ec1c830b53999d2320d20c4e764", size = 272025, upload-time = "2026-10-22T16:05:42.921Z" }, + { url = "https://files.pythonhosted.org/packages/bd/05/d3633add01e5518dfdacfdbd1be41cb5c581412dd8bccd663edf6ef7fec8/coverage-7.17.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c7c5672ecf1b497de7a0b37743bef40e3b58e3207e9fe2ed6682cb0420836a88", size = 273515, upload-time = "2026-10-22T16:05:44.324Z" }, + { url = "https://files.pythonhosted.org/packages/57/d9/b83387780271bf9ea9f96a3aeb01c1c302c4494a927923c4fa70cd170a3f/coverage-7.17.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:337f87e62e1c5f43c8198d879f677c342d152f84d3f157add12558853cbb8eaf", size = 266725, upload-time = "2026-10-22T16:05:45.713Z" }, + { url = "https://files.pythonhosted.org/packages/e4/be/8e80b29764ae3f4d70d44952eee3f3927eb36897490fe799e400adb7f5da/coverage-7.17.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c8a58baa8eb6154c6645eadcc19b6c089deab78c24a612a83206f2a8a5992a7c", size = 269312, upload-time = "2026-10-22T16:05:47.054Z" }, + { url = "https://files.pythonhosted.org/packages/b8/c9/e702f588fe8d83ca844f133270ad0a49bf650c8cf260551ff6f11a6b6bbf/coverage-7.17.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:fb5745048ee20f796b0e3ab61831049e749acff188563e2af32f58ede957ac1b", size = 266740, upload-time = "2026-10-22T16:05:48.343Z" }, + { url = "https://files.pythonhosted.org/packages/8a/11/ee13ad14b714d75ef07c490b6e43969574a8b8b8c6d67bc741fcbacbf9c3/coverage-7.17.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:7b5abcae74750302ac4bc77351eaee87c450e4abd3b6225eacde3858e260f8b2", size = 271036, upload-time = "2026-10-22T16:05:49.727Z" }, + { url = "https://files.pythonhosted.org/packages/e2/bc/854e8912c11c54aaf416e74602000589c6efbbe1f251dc90202fdabddcb6/coverage-7.17.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:4e153f0bd6509dbf837fd585f489b3328bfd98f2a718a0b15ae63032148b285c", size = 266158, upload-time = "2026-10-22T16:05:51.141Z" }, + { url = "https://files.pythonhosted.org/packages/a8/dd/96432830ddf63bc42654de54eee77a0cd6949cc65769bda06f353323f23a/coverage-7.17.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e42a835271922fe7bf98c7a05c78d4032f4e52bf5b07fafa1eadfdfc2f0ad22f", size = 268088, upload-time = "2026-10-22T16:05:52.92Z" }, + { url = "https://files.pythonhosted.org/packages/ce/ee/230c6ec065a6629d41cc89323ceb54d37e2650c104f70fc108f4ee7c5051/coverage-7.17.0-cp314-cp314t-win32.whl", hash = "sha256:5900055351ec89fdffb34b27898815106529ded29a9496464c1ebd6313fd40fa", size = 228229, upload-time = "2026-10-22T16:05:54.255Z" }, + { url = "https://files.pythonhosted.org/packages/f0/1d/23eb6aadcb3c00764ff2961c0ff55f5dfa2543856d932b949bbc04fa68e9/coverage-7.17.0-cp314-cp314t-win_amd64.whl", hash = "sha256:5ee9c8bd13a59e301dc2aefe7aa8384633406808de051b1982775667094eb673", size = 229154, upload-time = "2026-10-22T16:05:55.682Z" }, + { url = "https://files.pythonhosted.org/packages/3e/f1/7df117e8e016b7a3ee459e833b3456f3610505afefd071f8727702d98439/coverage-7.17.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a9cb9a0fb58e6fff5442969af36094ecd6976f4907f179e33b27e15355d8f74", size = 228726, upload-time = "2026-10-22T16:05:57.179Z" }, + { url = "https://files.pythonhosted.org/packages/9f/a1/f9c24911755f91e6f451ca2e2c1804fbcbe9fecbc3310e341bbbbfa1d85b/coverage-7.17.0-cp315-cp315-android_24_arm64_v8a.whl", hash = "sha256:adabc1079ba844b096799ba8638d8df434aca1613f95bb7d5791af320cf05559", size = 227196, upload-time = "2026-10-22T16:05:58.543Z" }, + { url = "https://files.pythonhosted.org/packages/b0/a5/2b665b40c626e6bb40e5e8e23d91170b48b5f2f57186f46b07f2fac42386/coverage-7.17.0-cp315-cp315-android_24_x86_64.whl", hash = "sha256:6b4593d2871c4c801656dd5861e759f8f5156febd6b6061b02f6a72d64618b7b", size = 227315, upload-time = "2026-10-22T16:06:00.409Z" }, + { url = "https://files.pythonhosted.org/packages/3d/8c/22b518d61b930b8a4d62905d046c76a03675ef19b466462011dfb16439a3/coverage-7.17.0-cp315-cp315-ios_13_0_arm64_iphoneos.whl", hash = "sha256:c2c48a36bb970b7265b196032bfd4237c0b373f2b25ef7012d7e905b5b35c19c", size = 225490, upload-time = "2026-10-22T16:06:01.743Z" }, + { url = "https://files.pythonhosted.org/packages/08/5b/d3b141455944e030cd4987f055f8c84a44a1a0b28d81ae93389f44051691/coverage-7.17.0-cp315-cp315-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:f8bc2cd4c6aa58be1ca243fb8cddb6c3c42031fe4403991ad9cf2b3baed03d9f", size = 225797, upload-time = "2026-10-22T16:06:03.423Z" }, + { url = "https://files.pythonhosted.org/packages/03/24/aab2156983f7022361352640c583d08f0238e4cd7463eb921172001b4cfe/coverage-7.17.0-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:cbf0b83ce927de6053279ba61342d9f8892afde1385fcba9144cd05d4023d109", size = 226025, upload-time = "2026-10-22T16:06:04.76Z" }, + { url = "https://files.pythonhosted.org/packages/87/db/94697b9ccf4ec94cdad07bf94e2a21b9702247cb212d2d5f18d2b9c3d0de/coverage-7.17.0-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:66efc0fb99cee34626fc17d3adac9c3cd816bfdad563bb1e983c3ef432f566d4", size = 226193, upload-time = "2026-10-22T16:06:06.167Z" }, + { url = "https://files.pythonhosted.org/packages/a0/fd/db1c4ade4219831d590fff5aa5b7d9ceaf84586e8e60da6fff71b6182251/coverage-7.17.0-cp315-cp315-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:58a304efdf2253fb2ea03d45d0a04cc174a92a0cd5279c28c5bb893ce7410968", size = 258046, upload-time = "2026-10-22T16:06:07.51Z" }, + { url = "https://files.pythonhosted.org/packages/95/ee/84ed138990ed752abd73db4adffc1842d6152017d8e21809cc109a413fda/coverage-7.17.0-cp315-cp315-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:aed06129a9f530a948928260d7d2d81c47261355b0127e3c782251b38958463c", size = 261735, upload-time = "2026-10-22T16:06:08.861Z" }, + { url = "https://files.pythonhosted.org/packages/64/ad/c1e4367bbd1e7d562d6bc979c82b542f2040544e95a8cd995b694caf0e1a/coverage-7.17.0-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f6cdb47a9be2d3313829bed1d296086287682a3ee901ff987b3432b45705b771", size = 262819, upload-time = "2026-10-22T16:06:10.49Z" }, + { url = "https://files.pythonhosted.org/packages/b5/3a/e8e8509b0940403eab15d86f70022790a01fce3e971a161919c544953f7a/coverage-7.17.0-cp315-cp315-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:804fbaf81d09b34e2f8b1159d01e211a50713d743d32fd9b2ca6cf0ae507a66f", size = 265194, upload-time = "2026-10-22T16:06:11.847Z" }, + { url = "https://files.pythonhosted.org/packages/1f/77/d64bbdbb734b1059ebcde2b5df567f142a9948079e51af9b0abf8c9ec17b/coverage-7.17.0-cp315-cp315-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b2afebb96da9a1bbca142f8cc12320c14e5c215e0a49cded4f191f5a31506a28", size = 258410, upload-time = "2026-10-22T16:06:13.234Z" }, + { url = "https://files.pythonhosted.org/packages/e2/29/7cf81f08a254532c074eb52cfe6b12bb753d08f2997269c9fac8de359541/coverage-7.17.0-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:bd04fd23206be9ad3de13b0b39313094fafa66a1f6baf70f5471c004a9fac47d", size = 260697, upload-time = "2026-10-22T16:06:14.595Z" }, + { url = "https://files.pythonhosted.org/packages/7f/56/bacea6dee2d5ccb31ab0cfffcee87ca7efa80ff048840209c8013aeaf02e/coverage-7.17.0-cp315-cp315-musllinux_1_2_i686.whl", hash = "sha256:5ebfbc87836529a31d25c2dec1262c38eea852ec1c46c0f690a515a7339d9c5b", size = 258649, upload-time = "2026-10-22T16:06:16.265Z" }, + { url = "https://files.pythonhosted.org/packages/a4/97/fd8a6a63740f9ee177c53b59e2f4d0752369bb0fd154086a1e69aa1fb255/coverage-7.17.0-cp315-cp315-musllinux_1_2_ppc64le.whl", hash = "sha256:a59c3b470ac3b2432af3598a996affbeb10236cd899a005c1c4e517fcff8b592", size = 262989, upload-time = "2026-10-22T16:06:17.85Z" }, + { url = "https://files.pythonhosted.org/packages/c1/37/7cd3d3520d45db3788456452b65446182c08f2a7c276f40c9dd11966be02/coverage-7.17.0-cp315-cp315-musllinux_1_2_riscv64.whl", hash = "sha256:7fef12cec4b96f59e09761cbd4fc9bdf4788062d1ae435b920241fc16c8a9549", size = 257955, upload-time = "2026-10-22T16:06:19.563Z" }, + { url = "https://files.pythonhosted.org/packages/c1/b9/80343a55e07d854927a915cc21a52e350ae1db9ead33f0248507b960785f/coverage-7.17.0-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:a14e8d69464da9cbe1dc29e7116dbd37296d1d51a695327c478e250349085089", size = 260512, upload-time = "2026-10-22T16:06:21.055Z" }, + { url = "https://files.pythonhosted.org/packages/f3/32/e4c816f0cb3efdc6392bb276ff88359729f9dec605bdd3c1c7a44776f759/coverage-7.17.0-cp315-cp315-win32.whl", hash = "sha256:9a66074231799d8884d267234d49f60c90b43a945dfe069646b72f3058cd4e67", size = 227913, upload-time = "2026-10-22T16:06:22.407Z" }, + { url = "https://files.pythonhosted.org/packages/43/14/283120142ac281834808584249b89957de997c28573ddd65ae106b622121/coverage-7.17.0-cp315-cp315-win_amd64.whl", hash = "sha256:79cf71e5f9a48877ccfed5baa2c72f45af4feeaa596249e23eb15b473134830e", size = 228532, upload-time = "2026-10-22T16:06:23.756Z" }, + { url = "https://files.pythonhosted.org/packages/b9/bc/83dbc6c3bd58042afaefcb0cfd7d7a2e3076989a2f3d9bc6e6edac819784/coverage-7.17.0-cp315-cp315-win_arm64.whl", hash = "sha256:09e563b49c81cd0a33734e926d7a4a6ef1778d74304cde2dcc3aff8b30ea1036", size = 228393, upload-time = "2026-10-22T16:06:25.163Z" }, + { url = "https://files.pythonhosted.org/packages/00/ed/9463d1cb1766a2570343a372c2871c7fb4d561e5051f029399f4d57ce947/coverage-7.17.0-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:a5e2e0625dfc789af0b2e10acfc106b352f314fffbd19e6392a7961c8cbe5d9a", size = 226481, upload-time = "2026-10-22T16:06:26.563Z" }, + { url = "https://files.pythonhosted.org/packages/ae/59/6536c7fa87be13236b4184fc395f14d18f622a5b8ef7448a6bfdf5f24ec0/coverage-7.17.0-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:194562ab1eff0d85aee8a17ce4c5c0bc1ebf70a629a7145257123156ad100fd6", size = 226876, upload-time = "2026-10-22T16:06:27.921Z" }, + { url = "https://files.pythonhosted.org/packages/b0/bc/4ffae6038f78382c9ab6a4969cb5fc01d50b083d119851085807629abe88/coverage-7.17.0-cp315-cp315t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:85682ca89bc4601125dcdff23fa70e6274e13c9ad502fba845e6c274ab2c6569", size = 267241, upload-time = "2026-10-22T16:06:29.356Z" }, + { url = "https://files.pythonhosted.org/packages/c0/60/cae4b708b4217d33ebf66b9c57f8910a257346b3eaf054a78d73605ddc4c/coverage-7.17.0-cp315-cp315t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0d0fac604a962ff546beb038cba9a5a09d88e6e3f01612fb46f7bd4fe5dc8dca", size = 270079, upload-time = "2026-10-22T16:06:30.736Z" }, + { url = "https://files.pythonhosted.org/packages/a9/84/1225ab04e55a6a2e483e903a5eb0f8f081cb5325df833594534aa288d91c/coverage-7.17.0-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:72edd4f9483ae30cd1ba4f230687840ef7b58a16afb1c9e81131fd6d64655f36", size = 272775, upload-time = "2026-10-22T16:06:32.247Z" }, + { url = "https://files.pythonhosted.org/packages/94/ad/567f8dd9246a553aa8753732c41f0e0654e98e9a617d6282e64eae0e6aa8/coverage-7.17.0-cp315-cp315t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:91e487ad07c511da77b574820141a7c2da09bc2a4b4090b5be2424295d100697", size = 274110, upload-time = "2026-10-22T16:06:33.859Z" }, + { url = "https://files.pythonhosted.org/packages/66/5f/e0ee76895df40601e4aedffb6b8e71ea48e276a629fb4488b92cffffca14/coverage-7.17.0-cp315-cp315t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1cabb31531002fef5197de5df3c84730aef01e750e25154b9813a2583435aba2", size = 267775, upload-time = "2026-10-22T16:06:35.193Z" }, + { url = "https://files.pythonhosted.org/packages/19/6d/d19c6e45e940e5b9ba697b7be9a8e63cef73d90bdea45b670a59df466717/coverage-7.17.0-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:c9ac886242bbd50fb35a2242ba79555a99891e657da33f98b7ca968e3488a350", size = 270268, upload-time = "2026-10-22T16:06:36.597Z" }, + { url = "https://files.pythonhosted.org/packages/27/a3/b2731bb7aef2db800b330603675c3fa58a6497a05d5b5f8625f3b6285f68/coverage-7.17.0-cp315-cp315t-musllinux_1_2_i686.whl", hash = "sha256:d45cdc495671fc99268ff3653e0da4244972e9fc0e1fc3ea9de18781ad28963a", size = 267298, upload-time = "2026-10-22T16:06:37.973Z" }, + { url = "https://files.pythonhosted.org/packages/50/75/d0c90a81b6b6607933109914dc8b0ef0a5f4cacf9482ca868ae773ee5d0b/coverage-7.17.0-cp315-cp315t-musllinux_1_2_ppc64le.whl", hash = "sha256:12961f738d717a046d4bf3f8d1727eb3ebe2ed5478ff96cd6233d4dfc39323d3", size = 271899, upload-time = "2026-10-22T16:06:39.389Z" }, + { url = "https://files.pythonhosted.org/packages/64/1e/f038f3a2614e9ef839a40a184713aca40b8ed79e626ab5cdc388771045df/coverage-7.17.0-cp315-cp315t-musllinux_1_2_riscv64.whl", hash = "sha256:1ee0a982b0e1230b3edf768a7dd022bd2377303763895327ac62deb5efacee03", size = 267380, upload-time = "2026-10-22T16:06:40.97Z" }, + { url = "https://files.pythonhosted.org/packages/c9/30/8d636e639b2d8b1934128573402a7caf66c8cfbc794fa02061d84f115c34/coverage-7.17.0-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:460679f53893081edb94eeed011d26be37228ba649b853fd2aed7e4394fb1f35", size = 268993, upload-time = "2026-10-22T16:06:42.361Z" }, + { url = "https://files.pythonhosted.org/packages/73/7a/219f6b391cb0636242d06c390a8add5af91afa3e92afd0380dbedd34f487/coverage-7.17.0-cp315-cp315t-win32.whl", hash = "sha256:600e121e412cd6bdb52ec105b95c6a04da0c9710d1534f6e64a42db667907802", size = 228216, upload-time = "2026-10-22T16:06:43.714Z" }, + { url = "https://files.pythonhosted.org/packages/f0/3f/514d294319c5eac11494394545de209079f33993e14526c220eab29b2a96/coverage-7.17.0-cp315-cp315t-win_amd64.whl", hash = "sha256:c3e11b81faafb1f063248735c3e2c5c4183fc1db2c7f46782170dd7760dffcbc", size = 229146, upload-time = "2026-10-22T16:06:45.262Z" }, + { url = "https://files.pythonhosted.org/packages/f6/82/c0d8bb0a67df838fc7bc6f4060ffc404fd1154fd18e624d83e6abc41aeef/coverage-7.17.0-cp315-cp315t-win_arm64.whl", hash = "sha256:d472ee1c64868a917a47f02ffb4ac8c22b477f7589d57a3eb0c58b1ad4f0137c", size = 228716, upload-time = "2026-10-22T16:06:47.097Z" }, + { url = "https://files.pythonhosted.org/packages/c9/17/48032919343afa2906112d458aceda2043afdb24a2ee8eb4c60904d8ddf1/coverage-7.17.0-py3-none-any.whl", hash = "sha256:bac487dce0d2aff28ddf1a533a916f771416eee0e493960f611e49037b878e9d", size = 217383, upload-time = "2026-10-22T16:06:48.471Z" }, ] [[package]] @@ -806,24 +813,24 @@ wheels = [ [[package]] name = "executing" -version = "2.2.1" +version = "2.3.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/cc/28/c14e053b6762b1044f34a13aab6859bbf40456d37d23aa286ac24cfd9a5d/executing-2.2.1.tar.gz", hash = "sha256:3632cc370565f6648cc328b32435bd120a1e4ebb20c77e3fdde9a13cd1e533c4", size = 1129488, upload-time = "2025-09-01T09:48:10.866Z" } +sdist = { url = "https://files.pythonhosted.org/packages/52/92/8a611141e42930c0f194f7504063ab87dfe970cf1a5af28e7703df448734/executing-2.3.0.tar.gz", hash = "sha256:15919cb5d667e5cb4e099511971d00d659573fff2dd5c4e6cd8b71636c7858d2", size = 1338003, upload-time = "2026-10-10T14:06:04.64Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl", hash = "sha256:760643d3452b4d777d295bb167ccc74c64a81df23fb5e08eff250c425a4b2017", size = 28317, upload-time = "2025-09-01T09:48:08.5Z" }, + { url = "https://files.pythonhosted.org/packages/4d/dd/8bc67e7d5ffc1d88aa5af511189dfb76dc4e1e5b808fab186146ef2663e5/executing-2.3.0-py3-none-any.whl", hash = "sha256:736e859c9f8701f11fcf516856f26f562e04776387824b43a35a1dfe21c84122", size = 29393, upload-time = "2026-10-10T14:06:02.777Z" }, ] [[package]] name = "google-auth" -version = "2.59.1" +version = "2.62.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cryptography" }, { name = "pyasn1-modules" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/81/e3/9f752a968e487fbc10b2c2a1dd561b62489f923972911009577bb88fe7bb/google_auth-2.59.1.tar.gz", hash = "sha256:ce50fc533ac02f489a2b183a0c156672c376ecb2091b1127bc7efba2975fff27", size = 384298, upload-time = "2026-09-30T17:26:25.738Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f5/b9/4b2528f30114b106e3c3a7298ba38d85e682fd1f83fe7c4a5b77c7677082/google_auth-2.62.0.tar.gz", hash = "sha256:0bef0ce54bdf9ce226c5d66e4264413bd918141c31bbe49fb52eac882f513d69", size = 398180, upload-time = "2026-10-12T19:20:48.328Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/03/38/311ac322f46ecd789f1342bb7adb0a14b940cf54b5db0e92d4f449aa6cf8/google_auth-2.59.1-py3-none-any.whl", hash = "sha256:89c3f931683a482ac97e61df7eb9da5e08a91703f3c752b2377d72cfb7d69e6a", size = 263519, upload-time = "2026-09-30T17:26:24.017Z" }, + { url = "https://files.pythonhosted.org/packages/eb/a3/87fe9b7203ec2e56ea6a57c75e6d632eaf9487c7f7978cdad1d801bee0af/google_auth-2.62.0-py3-none-any.whl", hash = "sha256:4ff4319aeb4ad128409759d397a9fcafad126d0031d241cc0dd6b9a00b43e3f3", size = 267911, upload-time = "2026-10-12T19:20:46.355Z" }, ] [package.optional-dependencies] @@ -833,14 +840,14 @@ requests = [ [[package]] name = "googleapis-common-protos" -version = "1.75.5" +version = "1.76.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "protobuf" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/8d/2b/6ce81972d5c8cab9705fddce3153be63222d9e12fd96f8baba5038a744dd/googleapis_common_protos-1.75.5.tar.gz", hash = "sha256:c7a866fc34ed29a3b10af627a4b9b1dc2433313ca6e959f0ae4feb132047ed72", size = 156513, upload-time = "2026-09-29T19:26:14.863Z" } +sdist = { url = "https://files.pythonhosted.org/packages/62/b6/bc095b23a3e0d978e75c8c18cabe4c11f791837410670e4486f0d539bfba/googleapis_common_protos-1.76.0.tar.gz", hash = "sha256:71db0e44a1d4c2dd024ebad44ca67129cc1e2892e4861df466764749d5ef8642", size = 156250, upload-time = "2026-10-22T23:43:36.263Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/65/b9/6b29500a1c581ff4d77fd83c6568d068bee06f1b139fb6eb0a4f2d4bce8a/googleapis_common_protos-1.75.5-py3-none-any.whl", hash = "sha256:d7285525c23039db98f2463e6d5a4f9b958b94d497f03a844ece3259c4e72d5d", size = 307737, upload-time = "2026-09-29T19:25:48.735Z" }, + { url = "https://files.pythonhosted.org/packages/05/6d/78dc67a9e779e594b49bf62604034d3a6aa428a55afc729cafda4b7333e1/googleapis_common_protos-1.76.0-py3-none-any.whl", hash = "sha256:59e13344a9f3651aa201a03e2bee815a3bf43a4baf1f9ff8a9b62a840ce25fac", size = 307707, upload-time = "2026-10-22T23:43:10.445Z" }, ] [[package]] @@ -854,90 +861,95 @@ wheels = [ [[package]] name = "hypothesis" -version = "6.168.4" +version = "6.170.12" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "sortedcontainers" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/64/25/a512e7e1acf630ef9147319ca7a0c93344a61061d91db46543af364b40db/hypothesis-6.168.4.tar.gz", hash = "sha256:fb72038b41d026878f484491ed9c2670e89daee898d276b92129da2cfe4a3846", size = 511160, upload-time = "2026-10-05T04:23:26.006Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e9/2a/cb9375a693a1e66559e3548e32edb3cc3a0d63a1fe16a45e48c8ff35820d/hypothesis-6.168.4-cp310-abi3-macosx_10_12_x86_64.whl", hash = "sha256:397c2ec80b847fd4f50e249ba6ab0c9ac7aa0e578136b718a060cddc0bbf00eb", size = 791569, upload-time = "2026-10-05T04:14:20.342Z" }, - { url = "https://files.pythonhosted.org/packages/63/88/987dc44d8ee04e5025b6d1221d2c2db3ab6768fbd4a20df45ce16dc09ef5/hypothesis-6.168.4-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:7c5935004f9b46135fba7a7d376c345714a6968b0a936e6f631af560a66a594b", size = 787268, upload-time = "2026-10-05T04:23:13.637Z" }, - { url = "https://files.pythonhosted.org/packages/b8/06/123b3d1b4367ad3a30c5d3ebe4e3ab5b849892c7b0ccc5cad8a198bca2e6/hypothesis-6.168.4-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3639efaffa11ad30ff79aaf5793b39c17135cb52f4211d27051c5c63d4bd13a4", size = 1115227, upload-time = "2026-10-05T04:23:15.62Z" }, - { url = "https://files.pythonhosted.org/packages/f9/70/cc1f7029d0773ee02fca9ffea42045dd45df3f88aceb245bf8299183cebf/hypothesis-6.168.4-cp310-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:460ba530803157649965b93f9954cbfbe302d3ed9432f7537a618de18320a0ff", size = 1145140, upload-time = "2026-10-05T04:13:53.491Z" }, - { url = "https://files.pythonhosted.org/packages/56/37/8d84dd014df3406f9a1f0dbb5b889a9dc05dddb0e0307e07be91ab9d310b/hypothesis-6.168.4-cp310-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7cad5cfc81df8bd02f34c6f9d15afdabc63d095c956694ad8223863c4a6ba918", size = 1139592, upload-time = "2026-10-05T04:23:11.538Z" }, - { url = "https://files.pythonhosted.org/packages/f1/33/5f09e705d8da235ccd23cb3eac4996786023e734a67d51b6fda41541000e/hypothesis-6.168.4-cp310-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:040d304cf8219c6317bffb4d4a2d57a87dd788d8a177ca4866e3193a20a44367", size = 1192168, upload-time = "2026-10-05T04:23:01.277Z" }, - { url = "https://files.pythonhosted.org/packages/50/0f/d755a9270a8d5cb09aa298b94b146c6c1308180e7fba1397be19b14c6418/hypothesis-6.168.4-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:946b60d3ca6726d6791bb7fcd43d25568fd0ac370945a5efb46b034b2cac19e0", size = 1157265, upload-time = "2026-10-05T04:23:17.517Z" }, - { url = "https://files.pythonhosted.org/packages/5d/77/177b64d1f71112d965bd98bbb2287d82c68696d3e1c332469a501b47749a/hypothesis-6.168.4-cp310-abi3-manylinux_2_31_riscv64.whl", hash = "sha256:9f8852fdd3dee584f950fdd172f5e437724a3becf884a6ea21ccaec1060d107f", size = 1113782, upload-time = "2026-10-05T04:13:56.593Z" }, - { url = "https://files.pythonhosted.org/packages/89/3c/f18cd13760dce2124924d75f5b543df89988530a548524d8becdfd09b0e0/hypothesis-6.168.4-cp310-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a8ba1b97877f99c15ff3699c9405fde49ccae827da58d087e80413082e1b2594", size = 1152694, upload-time = "2026-10-05T04:14:17.321Z" }, - { url = "https://files.pythonhosted.org/packages/dd/ce/f899c7df84c31185a7bbc4c4994e7c30bb79a93f9341d6afb8929fdc5cc1/hypothesis-6.168.4-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9117df3e17768daa9b693d0e83b589e047f501d0e5bc8c112b45db6d963ef4fc", size = 1290097, upload-time = "2026-10-05T04:13:30.794Z" }, - { url = "https://files.pythonhosted.org/packages/0f/aa/89502f3a5a9295f85308d6191e230195537e5d2f623b985cd7f6ed7fcbe6/hypothesis-6.168.4-cp310-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:6bee48f368e50155a1230045eaddefd4c3a6e58f6fa9da7e490b7108085a78d6", size = 1418506, upload-time = "2026-10-05T04:23:21.888Z" }, - { url = "https://files.pythonhosted.org/packages/8e/a3/4c5e2b6e2354f1a3739eab55c7dea779fa26778273aba985612ef6aae629/hypothesis-6.168.4-cp310-abi3-musllinux_1_2_i686.whl", hash = "sha256:a3d935059ed949220e429fbccfcb31368c12b43112b2c034d3236ee2ac6b65a7", size = 1372155, upload-time = "2026-10-05T04:13:48.928Z" }, - { url = "https://files.pythonhosted.org/packages/87/2b/b163a87a8c6bce333dc900022fa8e3bbb03b21ba733faef23ffe88e4c820/hypothesis-6.168.4-cp310-abi3-musllinux_1_2_ppc64le.whl", hash = "sha256:14dfa375391975480eee5762e0d52d4919c20512d1d6689e48ff29406d79206b", size = 1268987, upload-time = "2026-10-05T04:14:03.814Z" }, - { url = "https://files.pythonhosted.org/packages/c1/d6/4db9eac8a26b78bc83d7572048c8946fb96951d4dfadb15f9dbfba612b36/hypothesis-6.168.4-cp310-abi3-musllinux_1_2_riscv64.whl", hash = "sha256:1d9fc7b6ddbefbe07a905b61b03784cbef44d9db0161afa9c15777b37da6a013", size = 1283929, upload-time = "2026-10-05T04:23:05.429Z" }, - { url = "https://files.pythonhosted.org/packages/b4/b5/20887c10586a21b54b21e3efdca5c3f705933c33a012f8be26ac219438d7/hypothesis-6.168.4-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a51d71b65e76d3cad9e370aa35e822ae30bf5a5dcf53622f8c9b82fc6c37972f", size = 1323857, upload-time = "2026-10-05T04:13:39.524Z" }, - { url = "https://files.pythonhosted.org/packages/f4/73/3dc86a8071bb97eefb9cc74d19b94eed4ce4c2918e0806eda6d523091e8b/hypothesis-6.168.4-cp310-abi3-win32.whl", hash = "sha256:0c63542aee2d1847499f8bf4214b95ff9a12f972b8102e63e181e4043cbb007e", size = 677935, upload-time = "2026-10-05T04:13:44.607Z" }, - { url = "https://files.pythonhosted.org/packages/13/5d/dedf7b7b5c058caa53740016465eff30b2954b14890a2423c7efcefcd69a/hypothesis-6.168.4-cp310-abi3-win_amd64.whl", hash = "sha256:5670d5dc6f998a3bef868b9d80721ee3aeb286b3e813f8e54f5a906d79cff25f", size = 684630, upload-time = "2026-10-05T04:22:52.675Z" }, - { url = "https://files.pythonhosted.org/packages/c7/7c/8c717c6fc5d7fab3e951b8ab6a71e7ecd1da0cf9c30967706853b1c33eac/hypothesis-6.168.4-cp310-abi3-win_arm64.whl", hash = "sha256:b3ece73b7a1defa3a524e0aa2e7d03bd12828894a7b44e4610f3446814fac49f", size = 683087, upload-time = "2026-10-05T04:14:38.842Z" }, - { url = "https://files.pythonhosted.org/packages/79/03/1ba19676b089dbfbe1a09113a42f50c396385dfb2292515bb92ecd205ce6/hypothesis-6.168.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:b7eb4da108cf584fe73b5f3341696dc287305312db1236f4df6b18374b4bf2a4", size = 792133, upload-time = "2026-10-05T04:23:07.387Z" }, - { url = "https://files.pythonhosted.org/packages/e9/5e/7ff020163688230f809041da00a9d20445e8112cafc3cc26e2903b72653b/hypothesis-6.168.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bf97817588222524a83bae2ea18d1ef016b4598afa5e113b88c48c3b4dd67743", size = 788063, upload-time = "2026-10-05T04:13:35.715Z" }, - { url = "https://files.pythonhosted.org/packages/21/d8/1e604df052e5df85adf69282f417fd87e3de05fcc12f3f9c2dea3f804d0f/hypothesis-6.168.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad94699869748f3aab46cd713282f40dc90cdec050100c26ca730ef69bea863c", size = 1115468, upload-time = "2026-10-05T04:14:53.266Z" }, - { url = "https://files.pythonhosted.org/packages/cb/6c/8a5d180fae5c719af9418f443465793b133758bfc3671998da87898583a1/hypothesis-6.168.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:73b4a72ad8a8ac5af01be4e6fa4e0fc6d319fafecc0f1050df9eaa7762c3ebe7", size = 1157516, upload-time = "2026-10-05T04:13:51.732Z" }, - { url = "https://files.pythonhosted.org/packages/5c/1d/359275050058d9230c8cfce0e09f3a8548fa467a346c8869d616ccb35bd6/hypothesis-6.168.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:83a463a9312838cbff4b9ea12d61c5795e304ca987e02d4d122f3037b52d9d14", size = 1290262, upload-time = "2026-10-05T04:13:21.295Z" }, - { url = "https://files.pythonhosted.org/packages/8d/47/134c1ca074a1ecd3b8a2cb2644f32036502ef1cb5a718739627ad905d123/hypothesis-6.168.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5b84cc9bb448005b2a98c6c82da831929f2eead8ec5ff127e13a75ead009eb7a", size = 1324266, upload-time = "2026-10-05T04:13:58.071Z" }, - { url = "https://files.pythonhosted.org/packages/67/9e/f5b940617586b72bad462962c64c5831c13a8e42c8d9d7983d3f8f153805/hypothesis-6.168.4-cp311-cp311-win_amd64.whl", hash = "sha256:f68b3d9e0e5a1309ced5847d6d925cbe1d418cdb0bda646bb67ae9966d160160", size = 684444, upload-time = "2026-10-05T04:14:11.212Z" }, - { url = "https://files.pythonhosted.org/packages/60/37/49f936402bb892a369480fabecc1f511739910cc8e04a4cabb7bd8562d94/hypothesis-6.168.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:4d2589861e66b97cbc8f0ed90d45fab39d89e4bb6b25334a865e0de801ac8d77", size = 793184, upload-time = "2026-10-05T04:14:14.034Z" }, - { url = "https://files.pythonhosted.org/packages/9c/bc/6e2cc2fb5d8a420a17c6d1b9859c5ce87a9998132502575780e4c76f5d67/hypothesis-6.168.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0b523ef433a752822b83a1d454ae1351de8345d47348d2cf5f2b301b599bbad4", size = 784820, upload-time = "2026-10-05T04:14:40.686Z" }, - { url = "https://files.pythonhosted.org/packages/75/d5/3afba0a41080043d4ab9d08e98b802389c869c773f4d5e120f357ef441f1/hypothesis-6.168.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aff1ebfdf21d4acd5805602b73292c5cac99d1c9a45b18121802711b7dcca9b6", size = 1113885, upload-time = "2026-10-05T04:13:43.353Z" }, - { url = "https://files.pythonhosted.org/packages/54/9b/21e4aec72e14c5c1448c2a667d272038a1f957d92761d6f329ad0a5ef456/hypothesis-6.168.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e7165b1ce545a261276087b3b761156376907866e4f2e5605ce7947f39e9b75", size = 1156707, upload-time = "2026-10-05T04:14:18.858Z" }, - { url = "https://files.pythonhosted.org/packages/f5/b7/c6d960dfd8999621bbaa6e12df89400e09edb4180cfaa2888589f3c7d305/hypothesis-6.168.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:96e5cad6c7ed2babf1bf41bdf67474f3da203e1b5e5d357d9fc20c5101a6c02f", size = 1288812, upload-time = "2026-10-05T04:14:15.443Z" }, - { url = "https://files.pythonhosted.org/packages/e4/d9/0d1fb7b33385cc150e0f33d027d088d01e38816a0ee85dbc6eef3912b07c/hypothesis-6.168.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0b127bd1b36b48f20bc4e3556ff1c17a19350181d5350c202d3d9f779c579461", size = 1323147, upload-time = "2026-10-05T04:13:26.621Z" }, - { url = "https://files.pythonhosted.org/packages/84/c5/09b0882a34d30ceb0660174f8e521f8b1a6a0d34339f969785181373c95d/hypothesis-6.168.4-cp312-cp312-win_amd64.whl", hash = "sha256:83b89884e2d5e349578a660d006714a677a738f6ed85993d7f40420df07f8ace", size = 682027, upload-time = "2026-10-05T04:14:54.996Z" }, - { url = "https://files.pythonhosted.org/packages/bd/2d/418f31084cb5b5c8f486e4b845a7b1a361d1858709aeec0b8493a31997b5/hypothesis-6.168.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:29ce52e7d59afeddca0856548f60e99003873d7289581efc8faa127b332c6653", size = 793103, upload-time = "2026-10-05T04:13:23.042Z" }, - { url = "https://files.pythonhosted.org/packages/2b/66/7437130b552edf8bc1da961c4bb985208d3533055bce1864f65d6d09fe20/hypothesis-6.168.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:296a1dfd1c3a3c223234da55509bdbcfd057eb8416a2e42775035f8e5c60bfe7", size = 784719, upload-time = "2026-10-05T04:13:40.713Z" }, - { url = "https://files.pythonhosted.org/packages/cd/39/dc6fbe4ae5222e916d98963745d48bab6d8483d2f21271357b198a4c589b/hypothesis-6.168.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:17fce531c5812f4a1eee8915e1d48e2ac9b7ee67a0715b35c346557b16b14749", size = 1113720, upload-time = "2026-10-05T04:14:33.612Z" }, - { url = "https://files.pythonhosted.org/packages/27/a5/9986299503b582413f6f3f04d96b972a0b36c630d6da512b4bbf57f47975/hypothesis-6.168.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50d43c132ece8712c50ba20a2103b27693bdb098d0b3d2b451c3bf6837476655", size = 1156586, upload-time = "2026-10-05T04:14:30.051Z" }, - { url = "https://files.pythonhosted.org/packages/d3/ed/d5bd511bc5c80d0d9c62e89420f6de71d943624c332b37deebdf82fcc5b4/hypothesis-6.168.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:10371810168054ceadaf34af164e8a2177050340b6309f32dd83a609ef12380d", size = 1288700, upload-time = "2026-10-05T04:13:38.272Z" }, - { url = "https://files.pythonhosted.org/packages/be/e9/722a57f8d376572f4693c2491e9b4b3d382f394daac26d1e36913efaad00/hypothesis-6.168.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:cdeddb81bb7342980758ac473fbc3a67048c4aef410bd3a7ec6ba592dc5a4207", size = 1323148, upload-time = "2026-10-05T04:14:08.367Z" }, - { url = "https://files.pythonhosted.org/packages/b9/27/10bd4a64d6b45fe658cf605435ebb486a6cc20463142776efdef60a78da7/hypothesis-6.168.4-cp313-cp313-win_amd64.whl", hash = "sha256:a0ae9080c9ca90c6a40fb98229729cbb5b97009c323da0c4b2032957b94a15db", size = 682050, upload-time = "2026-10-05T04:14:02.512Z" }, - { url = "https://files.pythonhosted.org/packages/02/a2/43207e078c1b64df70610726c31859facd61dcb4a94fe5648356840321ed/hypothesis-6.168.4-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:053f19ba197044d771e3486c7f4eca2ef0b709c76b748a24a540e3c52a056ac9", size = 793280, upload-time = "2026-10-05T04:14:58.667Z" }, - { url = "https://files.pythonhosted.org/packages/aa/a7/4b1aab246f7ffa69aaf97ffdaf1a55e41c6b24fffa80c55615685bb3c699/hypothesis-6.168.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:5df643acbe059e576778b579052d53a51e08be072d80b1bc207dff1519dcc73a", size = 784862, upload-time = "2026-10-05T04:13:29.752Z" }, - { url = "https://files.pythonhosted.org/packages/13/fd/59f9ab4d7f4a71a1840a32c92818b3403668a76d63e6840edb408922f364/hypothesis-6.168.4-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:525de469919b0480715411db8f0595fff808ad90d278e95a01464bba64f665a1", size = 1114298, upload-time = "2026-10-05T04:13:25.458Z" }, - { url = "https://files.pythonhosted.org/packages/cf/39/86278e204cdfa8cf43e7176bd7d4cf4ec180d556172faaf4f902258758b4/hypothesis-6.168.4-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9845ff08f422114883e2b85cceab16b65c4e6bc5a50667cd34b105959aa4fff1", size = 1156820, upload-time = "2026-10-05T04:13:33.383Z" }, - { url = "https://files.pythonhosted.org/packages/a8/6f/ab8089844e41d298237d7d87d487c6766c8acb2cca5cab59735361feb34b/hypothesis-6.168.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:8f5e16803b0ef3d45ec5f1821802b18fcddd1d67c6f355dcb3e1e3ffade23c1e", size = 1288879, upload-time = "2026-10-05T04:14:09.734Z" }, - { url = "https://files.pythonhosted.org/packages/d2/71/baff8b218ccae84de4bf62d52a7c0d63ff424f38d1765cb140386e9f3c69/hypothesis-6.168.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:db367ead803f356f140bb5765e479b26160e92b70f580d684b5966c2bf6b7ea3", size = 1323338, upload-time = "2026-10-05T04:14:23.51Z" }, - { url = "https://files.pythonhosted.org/packages/f1/91/f07b79f26696ae6e9f8e57a9e104ac350d7148e2b6ee694030e62524de09/hypothesis-6.168.4-cp314-cp314-pyemscripten_2026_0_wasm32.whl", hash = "sha256:289117c492a43972fd4fdfb69d4c65da8215a6b6ab8507db07364e216cb682b3", size = 624264, upload-time = "2026-10-05T04:13:47.351Z" }, - { url = "https://files.pythonhosted.org/packages/cb/28/31e377dc5e0527769372bbe7082b036e535b01dc986336537472d3b62400/hypothesis-6.168.4-cp314-cp314-win_amd64.whl", hash = "sha256:1ea459c1b523f97090fa6764ae6a3c7a0b275cb9e3661da1673b3476767a2151", size = 681915, upload-time = "2026-10-05T04:14:51.375Z" }, - { url = "https://files.pythonhosted.org/packages/28/7b/a42932e30feb8c5a4774e1669511b83977243f365e390900ec8ef9b446be/hypothesis-6.168.4-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:ce0e423f457b6a7916b5ded5321a7ffbdc888c95e644c9634f2801d70a4faaea", size = 791667, upload-time = "2026-10-05T04:14:36.996Z" }, - { url = "https://files.pythonhosted.org/packages/b4/a1/8a5d9866a6260344803eee47ee86fcc9be7a51c88aa3e8def19281f26145/hypothesis-6.168.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:209e63b25d0e002c1558567b030817de78873d9bf4a410d112965bd0182af9b3", size = 783311, upload-time = "2026-10-05T04:14:46.247Z" }, - { url = "https://files.pythonhosted.org/packages/2b/d4/02b0ee75e31dbfddb229600d0cf62e79390ee44130acc94e9f3b2ff1cbeb/hypothesis-6.168.4-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7a7ba9299a01d283feabb64ba7ec4321f3ac762df2ce784a665826ea49409952", size = 1112178, upload-time = "2026-10-05T04:22:50.691Z" }, - { url = "https://files.pythonhosted.org/packages/22/3e/16fcb5c55a91ece447496e52cbc0a25f27dfba994c619915222b458ecf39/hypothesis-6.168.4-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0453cca80239dfc0a747ee03e2ffcf462944cea9fc86c83d123b29ea51dfdfa1", size = 1155658, upload-time = "2026-10-05T04:23:03.501Z" }, - { url = "https://files.pythonhosted.org/packages/8d/8e/7b382149f9ded7bb1e80c2ee2146ecf24a76758df526ae59c1d646275fb5/hypothesis-6.168.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:d2b35de88b1b8a3e45300b6eda5286de04e6d68779f5094448ec3ed2fd685cfe", size = 1287060, upload-time = "2026-10-05T04:13:28.003Z" }, - { url = "https://files.pythonhosted.org/packages/9a/2c/5edf74c75893385e3cd5d8c74943b6ac3bbc6e8086c58cd22d0cb178e012/hypothesis-6.168.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:252845ab5d5e4c8d23bdfb2287d7b16c0a1df697f5da21cbea2c9ab87c53b27f", size = 1322207, upload-time = "2026-10-05T04:14:47.884Z" }, - { url = "https://files.pythonhosted.org/packages/68/3c/8fd40800218e19ca08b9b528fc71ce81654d3e5ec2e18c9bfb32c9e082da/hypothesis-6.168.4-cp314-cp314t-win_amd64.whl", hash = "sha256:50da9a9fa14684aa2164bd1dc5ee9b4a557fe140e499aa8ad8f96896798bbed8", size = 681632, upload-time = "2026-10-05T04:14:42.544Z" }, - { url = "https://files.pythonhosted.org/packages/74/08/5c94fd11a834a538672ded700116992b1fdf5eb375f3878bd18513c49ade/hypothesis-6.168.4-cp315-abi3.abi3t-macosx_10_12_x86_64.whl", hash = "sha256:579083aff67a2ba13cf84b5315e366a9dc018eb6f3171ce2a2125eb1d35e4902", size = 790983, upload-time = "2026-10-05T04:14:32.057Z" }, - { url = "https://files.pythonhosted.org/packages/13/48/676bfcbac2119adf799d29ff1f3ec3d085d727e1994660488ff316f06ab2/hypothesis-6.168.4-cp315-abi3.abi3t-macosx_11_0_arm64.whl", hash = "sha256:3e9b7ecfec9cac3f749374a2e8d4b9de46a24ed1de25b437b242e5e39a481965", size = 782968, upload-time = "2026-10-05T04:14:44.126Z" }, - { url = "https://files.pythonhosted.org/packages/6c/46/4548f8372d11f997f3cd6a53a4e045e8afe15f3ae301d071760875e1851b/hypothesis-6.168.4-cp315-abi3.abi3t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28f1f75d08e9dd07c2ee18c9119c9ecaf43ad23e12b369620d3e8f541e37e602", size = 1111425, upload-time = "2026-10-05T04:13:50.346Z" }, - { url = "https://files.pythonhosted.org/packages/68/c4/88d5d7522d29b97dca6d4124259f192c70a254a6c2e95cb9c8e4d9823690/hypothesis-6.168.4-cp315-abi3.abi3t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:02882475be65f5d25f60d9b3a70a113907d02cba520460608f032794054a39a5", size = 1142746, upload-time = "2026-10-05T04:14:28.322Z" }, - { url = "https://files.pythonhosted.org/packages/20/ee/6377758185d65cbf3e7e299a85f2bdf4316801afe80288ce4ff99583b6d5/hypothesis-6.168.4-cp315-abi3.abi3t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a98f95f6159e5a244b17552d34930272b92cb4ca3f2af9cf5ca4d540ee141076", size = 1135993, upload-time = "2026-10-05T04:22:54.949Z" }, - { url = "https://files.pythonhosted.org/packages/6a/01/4be86b63156dc7fd593e29bc6a3742b6583d9cbd03938d8180d625f442c4/hypothesis-6.168.4-cp315-abi3.abi3t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b0f6fe61bf35dbfb8698a217680a5d1504e778841ce5c9e703d8a0ba6acaeaff", size = 1189670, upload-time = "2026-10-05T04:13:59.449Z" }, - { url = "https://files.pythonhosted.org/packages/56/d3/fb1c960197bbb8aa6cc0f0155a110c1d45914a764dd9c9206ea1d337e546/hypothesis-6.168.4-cp315-abi3.abi3t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a57720cbc084d589abb4626eac048c41bb318e3941589f1c7813dc9f7ccdb705", size = 1154868, upload-time = "2026-10-05T04:19:01.547Z" }, - { url = "https://files.pythonhosted.org/packages/32/cb/0d38ec7f41f893ecc09dc20acd92ad7ef13c4a6d1590f7a5dec8600522a4/hypothesis-6.168.4-cp315-abi3.abi3t-manylinux_2_31_riscv64.whl", hash = "sha256:0b30d77fe614e904468f1a94553f011d62ac75c927529cad4f7b7fc962e5d731", size = 1109954, upload-time = "2026-10-05T04:14:05.223Z" }, - { url = "https://files.pythonhosted.org/packages/08/7a/83c0412e5e19848a73e4ed205971258d43ec54fd6ca8e837dbb01d06bd57/hypothesis-6.168.4-cp315-abi3.abi3t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a9b9f6fdf1f3e38eb49e6e17ac7ba116d2a5d3c2498f48bf7920774772b11d25", size = 1148507, upload-time = "2026-10-05T04:14:35.291Z" }, - { url = "https://files.pythonhosted.org/packages/8b/27/907c99a9965a47b8eb467b9b6cf3e2987dc2fef940bf9dc6f791854a9828/hypothesis-6.168.4-cp315-abi3.abi3t-musllinux_1_2_aarch64.whl", hash = "sha256:1b374eb3f2af2395186ffc078aae9aca1ef6f30e3b06afa4ab7c51860395b0c9", size = 1285881, upload-time = "2026-10-05T04:13:36.856Z" }, - { url = "https://files.pythonhosted.org/packages/c2/6e/617eea9d674300f717b121f943335ffc4bf970af4e382df6f62630919806/hypothesis-6.168.4-cp315-abi3.abi3t-musllinux_1_2_armv7l.whl", hash = "sha256:91f9b5fc94c23f280d461d53bfb67c02848e7dca3a242de8de932c5242edda35", size = 1415525, upload-time = "2026-10-05T04:13:45.863Z" }, - { url = "https://files.pythonhosted.org/packages/0a/57/42437d2af905e3bbd268830e667f0dfd38ba4f8a73bfb4fce513c52d849c/hypothesis-6.168.4-cp315-abi3.abi3t-musllinux_1_2_i686.whl", hash = "sha256:378913d16968b8a66f5544e10c1c472ef3fef8bd1929cd4c190f17e24d04742b", size = 1368652, upload-time = "2026-10-05T04:23:20.024Z" }, - { url = "https://files.pythonhosted.org/packages/16/0d/237c2af97b6d43d98a46a7a87698838acf8e286144ca691ea999f2243435/hypothesis-6.168.4-cp315-abi3.abi3t-musllinux_1_2_ppc64le.whl", hash = "sha256:39d3c73ebfa31789d564514d8016bba920136051a85a55e5cc27cc2417c1d4be", size = 1264576, upload-time = "2026-10-05T04:13:55.041Z" }, - { url = "https://files.pythonhosted.org/packages/e6/ef/495950d0ed3a45d06daf64058283b3b80fbbdadf6dfd9ab3150f8ea38dec/hypothesis-6.168.4-cp315-abi3.abi3t-musllinux_1_2_riscv64.whl", hash = "sha256:2b89a7a8a664b3bbf500e56e4c12bd489f254689571bc94f8bad09c4d2481de5", size = 1281796, upload-time = "2026-10-05T04:22:48.651Z" }, - { url = "https://files.pythonhosted.org/packages/53/31/002341df953f98d7e09c6bd88b0b8a53b3b41529dd2ae149ddd078b687af/hypothesis-6.168.4-cp315-abi3.abi3t-musllinux_1_2_x86_64.whl", hash = "sha256:89a6d31335a506466a243b3a25512ebd52614c49a6d9ecd4a406f8b74f8926e6", size = 1321522, upload-time = "2026-10-05T04:13:24.306Z" }, - { url = "https://files.pythonhosted.org/packages/a8/f8/3d2acaa8c69cf8ba28238491a24e62f46eabb2b0f1d3f58bc33e58b79550/hypothesis-6.168.4-cp315-abi3.abi3t-win32.whl", hash = "sha256:3f0beea465731d17b222dae8a9c8a478161d0ba2ac57cf90df1105dd2b6e450c", size = 675007, upload-time = "2026-10-05T04:22:59.138Z" }, - { url = "https://files.pythonhosted.org/packages/c4/a8/6cb52b53d1b88de7a18305372eb00e6be88650ae09c61edae9145bd7743d/hypothesis-6.168.4-cp315-abi3.abi3t-win_amd64.whl", hash = "sha256:d6b1a8f2b18377c63f5dc089ad3991be7ec16149b9743d6e269f1a457c1b8f47", size = 681351, upload-time = "2026-10-05T04:14:25.083Z" }, - { url = "https://files.pythonhosted.org/packages/69/82/14b58c670a1a64ab28d632a81a80d43d0767b66321470e51d92367b62760/hypothesis-6.168.4-cp315-abi3.abi3t-win_arm64.whl", hash = "sha256:4310ff1e1924805a44dbe30fb1f9c401c2634b3138d1e88ebd65f5eb6c683e2e", size = 679661, upload-time = "2026-10-05T04:14:21.728Z" }, - { url = "https://files.pythonhosted.org/packages/92/28/9dd04bd40a1ff641ac147bc5dc4aff2f290fa3d5f390db2f6c56a9f5322f/hypothesis-6.168.4-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:45bbf832cae82211cae53c01c19c33728b3521db1ec96e16c0a3557b5137783a", size = 792056, upload-time = "2026-10-05T04:22:56.756Z" }, - { url = "https://files.pythonhosted.org/packages/73/22/1b65a2bba2aa0a4805081487213c1b6d66587ad459fcea04f6fffab9023e/hypothesis-6.168.4-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:f867f9dac2cb90e5f486143124181e7bea3f24b043fbe4d0d9de79e743911a2f", size = 787348, upload-time = "2026-10-05T04:14:56.766Z" }, - { url = "https://files.pythonhosted.org/packages/18/d6/a0fbd666f36a8e63d85374c310da11058014d54ae88d77de13ebad7da401/hypothesis-6.168.4-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:85c0c9b1da200e954c804c916b05a5a6d62156cc1cc0b368692d91f931c43d0b", size = 1115149, upload-time = "2026-10-05T04:13:34.605Z" }, - { url = "https://files.pythonhosted.org/packages/88/bc/9ff42a0d6aecce3f7ae4f2460f23d6e96febc335cc1a7504d35a5d224891/hypothesis-6.168.4-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5deb912750c74131a2d94a5299309848c400439eb7aec6baea6b4a025cbc75f3", size = 1157846, upload-time = "2026-10-05T04:23:09.412Z" }, - { url = "https://files.pythonhosted.org/packages/5c/e7/c2689246e0ccd093e342207731fb8f2bba35e357e09893508e9d129b92f3/hypothesis-6.168.4-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:43935f1404f71e76c04ef7837cb717777cdf70104e6d7441f09d49befbecd44a", size = 685062, upload-time = "2026-10-05T04:13:42.098Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/11/02/d55560240dcb9f3e758213f0079b312271a6e832c5f84e767f7af6130898/hypothesis-6.170.12.tar.gz", hash = "sha256:92b353d21a010b50b638c730ce67cb631ca78a369b146bcc28b3488f83136e35", size = 521506, upload-time = "2026-10-26T17:12:08.305Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/eb/08/e460fc3c5fcbd8e7ceecd45a9503fdcc8780c3a74b12354bb239f023068e/hypothesis-6.170.12-cp311-abi3-macosx_10_12_x86_64.whl", hash = "sha256:8d601516e32bd822c5558b553d186e35cd1451d0a241403a2357aee0d8085278", size = 800088, upload-time = "2026-10-26T17:10:30.787Z" }, + { url = "https://files.pythonhosted.org/packages/d4/c4/60086043aa1688eeaff8236ca131bad1c6bd1b651806204efdf2f910d737/hypothesis-6.170.12-cp311-abi3-macosx_11_0_arm64.whl", hash = "sha256:b9129107c3d13cb778cb4ee438358677c9e20e916a824073fc601649a28f78fb", size = 795251, upload-time = "2026-10-26T17:10:32.15Z" }, + { url = "https://files.pythonhosted.org/packages/e7/f8/4211426f3d9b3a4315441792ef784109db87107a7670fc40954ec229a3d6/hypothesis-6.170.12-cp311-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:13a922a8498457d293f20b3682bca09eb27495d8b83d1620301a1331a90b97e1", size = 1124178, upload-time = "2026-10-26T17:10:27.046Z" }, + { url = "https://files.pythonhosted.org/packages/b6/a5/1ee9e9eac46a7bc7f6b0bcd516e64be4bd8ff2c9b59b7a7bb5dd666e6217/hypothesis-6.170.12-cp311-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bcb5a1fe6c0d0a1167ea6683e1adb4794b3ce3d9425f6f2dd8ee1341e6851398", size = 1154653, upload-time = "2026-10-26T17:11:00.946Z" }, + { url = "https://files.pythonhosted.org/packages/32/1a/fd539478f430cd096290bfa5f7ed714a7034bea82761fc3cb45448847466/hypothesis-6.170.12-cp311-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4a052212ada966f9d770b402a6f07c2f552e503242d8c186ba10f1de81cc79c6", size = 1148554, upload-time = "2026-10-26T17:10:24.438Z" }, + { url = "https://files.pythonhosted.org/packages/e0/c4/a5c0a87ba22d96cfa1a4f716f3485d2cb247a96fbfa6ebd042f2b87df771/hypothesis-6.170.12-cp311-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7f59d0530d25b96fbdcabf6d2eb10d740e001dd01ff37729b0bbcaedf67ea298", size = 1201308, upload-time = "2026-10-26T17:10:42.748Z" }, + { url = "https://files.pythonhosted.org/packages/12/81/4012aa8f70ce549ac1503078e196175527584e0c86197f625a7c5a7eb7a9/hypothesis-6.170.12-cp311-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e8f58c29512520a29c4fe90545f7f299cf7b42f018b9218e1c1ee1a3045df5bd", size = 1167564, upload-time = "2026-10-26T17:10:22.516Z" }, + { url = "https://files.pythonhosted.org/packages/ad/e2/72fe8caf0c5833d2546078cbef2e3f45d9f73e8ce5f38a9ef8fb424ba5da/hypothesis-6.170.12-cp311-abi3-manylinux_2_31_riscv64.whl", hash = "sha256:abde1babc40ecfdbd5ec7837fd47ff0799df27190ff2f097d88c4fa802741068", size = 1123683, upload-time = "2026-10-26T17:10:25.77Z" }, + { url = "https://files.pythonhosted.org/packages/aa/f8/222034c030457a28caf4ca52264088ff8b547f5c2bbaca2c26da1dafdd72/hypothesis-6.170.12-cp311-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d63321e5ec4b3c1b34298e2b44fe79bc607d230d4799a72d4a083856843e6b8c", size = 1161998, upload-time = "2026-10-26T17:11:55.968Z" }, + { url = "https://files.pythonhosted.org/packages/25/91/3eb747c2334c2f89cb5c7385d490c25602389e7ab19e2bc2b57940c75d11/hypothesis-6.170.12-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:98af0af451ed0c7ec21640490a5454ff57ac67ad5381db489effb2305c3c3165", size = 1299097, upload-time = "2026-10-26T17:11:20.238Z" }, + { url = "https://files.pythonhosted.org/packages/83/2c/c7104b8b3d0451be15a4fd4f04043fb0a73092a5f042b3fbe73e25a8bc14/hypothesis-6.170.12-cp311-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:0992ada76db76aace71f4514e66730f6c836b507c57f821e36b474f2a10fe7ab", size = 1427748, upload-time = "2026-10-26T17:11:25.643Z" }, + { url = "https://files.pythonhosted.org/packages/a7/c9/aa3538690ec226b66e8e4a22ce714aa8cce67da6a9496209654d22a3db18/hypothesis-6.170.12-cp311-abi3-musllinux_1_2_i686.whl", hash = "sha256:64a3b3faa02238e08d5dfe66ef8891571a511d4a92e5f45dbd41c6c277975689", size = 1381824, upload-time = "2026-10-26T17:10:46.797Z" }, + { url = "https://files.pythonhosted.org/packages/61/2d/ac7320c4f0b34fdd065e179f932aae6b0420b1811fb0df95e28511fdd5a0/hypothesis-6.170.12-cp311-abi3-musllinux_1_2_ppc64le.whl", hash = "sha256:0baceceb4a32310186c1cefacb28874f6298658f9486f7e90ca5f366fb9d3627", size = 1277780, upload-time = "2026-10-26T17:11:06.139Z" }, + { url = "https://files.pythonhosted.org/packages/fd/7b/c1aa4b06db40e8635d2444f43997a7c1d0e0f2dfc31264d590b1fb76d994/hypothesis-6.170.12-cp311-abi3-musllinux_1_2_riscv64.whl", hash = "sha256:e6ddbab42945eb706ad0c3c7ae05d8df1f1b63ca62e212959169ab8baf29bc9b", size = 1293539, upload-time = "2026-10-26T17:11:54.406Z" }, + { url = "https://files.pythonhosted.org/packages/15/3e/68e83242d21561aff4a0f1dbc7edc1341ec397163f2a7d5481e26f6465a6/hypothesis-6.170.12-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:f240b0f4a5c8d0b0399b854e76351d266d73692cc55c227f23ac2680b82891c2", size = 1333281, upload-time = "2026-10-26T17:12:01.444Z" }, + { url = "https://files.pythonhosted.org/packages/89/ac/0a024b48507e6db4378fc51b8f5ed9a5a0d2fe874a2de91cb44328e19802/hypothesis-6.170.12-cp311-abi3-win32.whl", hash = "sha256:cf96091375fa2e612cece3926b1cfc72763b6bd00c9a2052ee839d4cd7d3ef85", size = 687286, upload-time = "2026-10-26T17:11:46.263Z" }, + { url = "https://files.pythonhosted.org/packages/df/0d/cdf07a645b2fe1224e70a1080670fc793b296333af3f0caf73409729f294/hypothesis-6.170.12-cp311-abi3-win_amd64.whl", hash = "sha256:41e47d6c82e69138de731e39467d244a42e752bfa3d9462159e4c00d414fe7df", size = 693348, upload-time = "2026-10-26T17:11:38.561Z" }, + { url = "https://files.pythonhosted.org/packages/22/07/d5ff2b49491c287dca5c8c75537b4cdc901fe550970e9771996a7b9d9048/hypothesis-6.170.12-cp311-abi3-win_arm64.whl", hash = "sha256:c564cc9e1e1a5ca833e1f6620371e926f61817ed7d466cde9e190a029ae3eeaf", size = 691653, upload-time = "2026-10-26T17:10:50.59Z" }, + { url = "https://files.pythonhosted.org/packages/2c/de/b1509a931d9dc0f3cf5f2f2d4f25cc4856b8c0017a613170f68125b16fcc/hypothesis-6.170.12-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:92e4df5c3cb67779fdb3be9b3b1661fba9b99798bd8e7b9dd8f7cf6929dff43a", size = 800804, upload-time = "2026-10-26T17:10:38.171Z" }, + { url = "https://files.pythonhosted.org/packages/06/d0/426931e695cf68f18d03959f71d5d3a5048a2d6dba6e7a937911d83474b5/hypothesis-6.170.12-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2d3025b55670373756a30a27fcafeb73d86118009367af18bd573410e6666acb", size = 796479, upload-time = "2026-10-26T17:10:51.807Z" }, + { url = "https://files.pythonhosted.org/packages/4f/5d/aee62331f499b13792a0f44c70994cfda8f8070c1abb5d82c33888d7eba3/hypothesis-6.170.12-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8d51ecc561464fca7633c671cafca7a11f09f56aeb8c6a95c685a14c6558624a", size = 1125102, upload-time = "2026-10-26T17:11:42.597Z" }, + { url = "https://files.pythonhosted.org/packages/9f/a3/5577afe54770f65b7fca743f87fd8e5063f7d7b69feb9fd34f8325aae2d7/hypothesis-6.170.12-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a0e470fcc238db3b193b3f04483985b331117ed04a9abce19199032e870f3164", size = 1167862, upload-time = "2026-10-26T17:10:58.234Z" }, + { url = "https://files.pythonhosted.org/packages/65/a3/ae5490a6e15781dcd229074212dcd20fddaba9552039d9d3ba347f1be680/hypothesis-6.170.12-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:27136250ae1356c7de139e7dfb1a1949872fc6c4b73fd58ac70525ef66faf4d4", size = 1299836, upload-time = "2026-10-26T17:11:58.395Z" }, + { url = "https://files.pythonhosted.org/packages/5f/70/5c774c8399fe13050072c777e32d194e43f2ba58dac3746a6973b16b36f5/hypothesis-6.170.12-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:13c5656eaa6f2aedc597583bc4db7bf3f7c8dc2d293b41be9bd5c0cb979848d9", size = 1333844, upload-time = "2026-10-26T17:10:44.074Z" }, + { url = "https://files.pythonhosted.org/packages/47/ea/22f4298ac904c350152c543558f3af187a1bd04fe4ecbe07b953ddaeeccb/hypothesis-6.170.12-cp311-cp311-win_amd64.whl", hash = "sha256:447fe134533d93b045797d5e44cb5ddf3460deaa1494d88b524266c21c7bf0c9", size = 693464, upload-time = "2026-10-26T17:11:51.344Z" }, + { url = "https://files.pythonhosted.org/packages/da/f6/424ecd49ba7339329e937ea3f88860ca7a455797a7184886ccaf73b7269b/hypothesis-6.170.12-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:0d010e40387d8c3a5e2c8fde71459ce6f2bb5e8bba8914731780529b79a0b5b0", size = 801881, upload-time = "2026-10-26T17:11:59.754Z" }, + { url = "https://files.pythonhosted.org/packages/e2/05/e38e2de5486b03670360d47e1deb419917754fc9daf2aaee3ad185083d5a/hypothesis-6.170.12-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:94b05dd80631d60f672c7d87d4280be9efa31b3116d0d88e71b7d26951fd3f94", size = 793880, upload-time = "2026-10-26T17:12:03.87Z" }, + { url = "https://files.pythonhosted.org/packages/af/71/4d693faeabc67d9d000db253e092ef72dd5725aaca3fd897a0f6a484acf9/hypothesis-6.170.12-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1e674b6a7a7f2dee9bdb1da9b42c78edd971fea6758640997a9d8bb32cfddeb9", size = 1123538, upload-time = "2026-10-26T17:11:31.265Z" }, + { url = "https://files.pythonhosted.org/packages/70/ff/4f1eb1bb67283240aac366b77e2ebbf864e1fd4d93c3762399f499d200db/hypothesis-6.170.12-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51ccd8964291b6cba57709b6acf1cad2e9c4347e993198ef8ece5653cdafcbdc", size = 1167229, upload-time = "2026-10-26T17:11:50.062Z" }, + { url = "https://files.pythonhosted.org/packages/cf/19/4e40637c37285231e4cbd6291fbce5cf9da325b6c73af53d9004f7030155/hypothesis-6.170.12-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:19fd1565947111aa1bba31689585f5501eca4f660762069d7515e5e1d3a45b3b", size = 1298417, upload-time = "2026-10-26T17:10:49.337Z" }, + { url = "https://files.pythonhosted.org/packages/57/96/97aa8d7115a399e409c24b3f7642477de3521da797e078ccb94d64a128b4/hypothesis-6.170.12-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5007cb77462b0e1ae444e05684fe799906673942ab18255ad6144ba2f9b74bfb", size = 1332856, upload-time = "2026-10-26T17:10:40.414Z" }, + { url = "https://files.pythonhosted.org/packages/96/ba/11c464d8aed7777b99eec35d31c474ea64caaa68e4da342609aa1bad2868/hypothesis-6.170.12-cp312-cp312-win_amd64.whl", hash = "sha256:51c7553a5d5a368347dc2f063ac1bc6ad5b3385b883ca9e7009bbeb52e79eaa6", size = 690967, upload-time = "2026-10-26T17:12:04.926Z" }, + { url = "https://files.pythonhosted.org/packages/11/f0/283fcf6c9ead403928a24cd3d32098bfcaf154d8556411cb2822120cc001/hypothesis-6.170.12-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:492eb0d70d8bda18322a1dee3e503796997bcab7415afea01b111551f7591e9c", size = 801848, upload-time = "2026-10-26T17:11:57.215Z" }, + { url = "https://files.pythonhosted.org/packages/79/c5/9df47092d6e7fa638f40ad268eb87476eec95897db98f3c6d11cc9680a65/hypothesis-6.170.12-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f67a87338893d5c050372ab3a1e818e6c5bd267f302624b7c444c18657b7da39", size = 793875, upload-time = "2026-10-26T17:10:48.069Z" }, + { url = "https://files.pythonhosted.org/packages/4a/76/79b52b312af5f2d9758293324078eba186a3f082ecba2879d606087e998f/hypothesis-6.170.12-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cae68b4bcae8c748ac16c91a39b2e13643400aa1b1cccc1682ec7ffdc9c82a6f", size = 1123439, upload-time = "2026-10-26T17:11:40.937Z" }, + { url = "https://files.pythonhosted.org/packages/05/b1/1e41fc27605bf9d7c43a8e873745e2eeafea80985cf517b1fef1e624fd07/hypothesis-6.170.12-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00275564eab789af6dba53947194eeff0937ef9cd82866cd49b1160f604b6b58", size = 1167165, upload-time = "2026-10-26T17:11:43.866Z" }, + { url = "https://files.pythonhosted.org/packages/05/ee/99cf7e7b034ab806041c9f3ad506ee7cbb83381284684d2406da42812918/hypothesis-6.170.12-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ea6334c3d67021c0778d9db519249d58a6d1e07baebb42144d7f0b88d4db8cf8", size = 1298395, upload-time = "2026-10-26T17:10:45.339Z" }, + { url = "https://files.pythonhosted.org/packages/7b/20/94d3ec0b039f6f42265fa64b97145065e1e286dcab7e68c56581ac9852ac/hypothesis-6.170.12-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b8a29ec47aca3e3e7ad47ffca197368afbeed156f4c03db482213ff084b10a50", size = 1332887, upload-time = "2026-10-26T17:11:13.819Z" }, + { url = "https://files.pythonhosted.org/packages/06/26/e08bc6f179a542e69c36bd02d78a01edf2a2ee561e6f12b00ca038d29c0d/hypothesis-6.170.12-cp313-cp313-win_amd64.whl", hash = "sha256:b3c4fbf33cf0c96381a9f301c7f423d21164222411f56c2bb050a7e44fb6dc51", size = 690938, upload-time = "2026-10-26T17:11:36.285Z" }, + { url = "https://files.pythonhosted.org/packages/de/8a/bebaec693c114e312382f12e6d79efadb4a378044ceba67e0ac3ff962558/hypothesis-6.170.12-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:90a63aebc766fc574276519d2840b7c144768be2f15f409458567f652f206559", size = 802107, upload-time = "2026-10-26T17:11:24.406Z" }, + { url = "https://files.pythonhosted.org/packages/00/14/e988ad36da4df17b8ec9131f29f77fa2a1a385be06b0d7cd2a7c20a39d16/hypothesis-6.170.12-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a1982c1660cc215679f34580690cd440c878208544e4eaf697c2f04698767aab", size = 793991, upload-time = "2026-10-26T17:11:45.133Z" }, + { url = "https://files.pythonhosted.org/packages/c5/5c/89593bb4c43d09a899878ba20ed00fb0e27896146e0b511d2449a045246a/hypothesis-6.170.12-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5648bad764aab81f38dc8fb9b80942e5e325e19c7d7497312e9f7ef63540d48f", size = 1123961, upload-time = "2026-10-26T17:12:07.238Z" }, + { url = "https://files.pythonhosted.org/packages/2d/ed/4a6cec0a929079b3c2103dbc71d5846c74264453cca30b2d0da61ca83e6a/hypothesis-6.170.12-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1cb0a1042041ad3b03159c658214eb19d19ec9d0efcf03a84f4ed94de54f1630", size = 1167355, upload-time = "2026-10-26T17:11:27.146Z" }, + { url = "https://files.pythonhosted.org/packages/3b/86/e991b1e6cc88a0d2e648864819360c48d03769c31ceec2b2095970a63f7c/hypothesis-6.170.12-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:45eefe8622916a10bd5ac1639adde0552985896624eadf9d9fa807b5eaa60aaf", size = 1298917, upload-time = "2026-10-26T17:11:08.811Z" }, + { url = "https://files.pythonhosted.org/packages/e2/b4/a8c292a80eabfbf5201c0360a677176880f993e91c890db7582e579efeb1/hypothesis-6.170.12-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:503a07eb31b806c29d4731c5e9c842df74dfb184f183f82f682c19f658761a7a", size = 1333081, upload-time = "2026-10-26T17:10:56.94Z" }, + { url = "https://files.pythonhosted.org/packages/7f/52/434322320807b9585f3eb7e414e0bd92126548de0dbeb0dee0dd5f38b513/hypothesis-6.170.12-cp314-cp314-pyemscripten_2026_0_wasm32.whl", hash = "sha256:a8ac6f5e5a943fcac211e09e858aaa90033e10e44295977a32fdc3cd4f180245", size = 633703, upload-time = "2026-10-26T17:12:02.785Z" }, + { url = "https://files.pythonhosted.org/packages/4d/d2/4a2483c029adf6bdb78b237c5cbedf411df97e31fbbb81c8bf4c3ffe6769/hypothesis-6.170.12-cp314-cp314-win_amd64.whl", hash = "sha256:f5d839f0ba43101b479f1559c3b3c2870fa0eb92ad86bca82f247fd3a9d98866", size = 690674, upload-time = "2026-10-26T17:10:39.303Z" }, + { url = "https://files.pythonhosted.org/packages/80/68/f7357b87c6e0b6f9d044f8247d8532cf79c68c3692f64800e3833261b9c7/hypothesis-6.170.12-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:ee71dadea93346799b6f6825b62c75f67ddaae36ecfd96591fd1fff17f687f7b", size = 800195, upload-time = "2026-10-26T17:11:48.676Z" }, + { url = "https://files.pythonhosted.org/packages/a4/46/1c054440c5f2d562ae973db1d4ac6ece2a2aadf442b3887c0bba4716a57b/hypothesis-6.170.12-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:681dfd2e47d2a473a36b3075bd15126b476a40a4cd5a2e134c71f3fafb651628", size = 792428, upload-time = "2026-10-26T17:10:59.572Z" }, + { url = "https://files.pythonhosted.org/packages/20/8a/6ea8efa8fcef75f44a250bd121dfd49c6cbd4f12407d996d472ae6754bf0/hypothesis-6.170.12-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:87d50be85d98a05f177941fb21c2619279a0c6a289b139fe510126580c9ffa91", size = 1122389, upload-time = "2026-10-26T17:11:02.296Z" }, + { url = "https://files.pythonhosted.org/packages/33/ae/62be30dbd660e288f028da31fc8a8809a1c2230ec589a96a04f6c2303a05/hypothesis-6.170.12-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fb59d1a301e5ceb4b402026880c74ba1b65ba1c249dc169324681e6a74fb0d59", size = 1166268, upload-time = "2026-10-26T17:11:28.494Z" }, + { url = "https://files.pythonhosted.org/packages/00/8d/1aa266dbf967081bfcbea3ffad90f77f879e1294585bdc94b38ac56b7cbc/hypothesis-6.170.12-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:4d015bfcd7d317ab8752b05c4469278cda807be8aab4f2dbdbf6662df0fb6570", size = 1297161, upload-time = "2026-10-26T17:11:12.544Z" }, + { url = "https://files.pythonhosted.org/packages/a8/35/4626673a9a6ba0781e36a064ec03083c397702737db06dfb1f48af42a9a8/hypothesis-6.170.12-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:3a3471e28d40532099751e1be61526f8a36329c4f3eb67a132526c795ff717f9", size = 1332087, upload-time = "2026-10-26T17:11:35.055Z" }, + { url = "https://files.pythonhosted.org/packages/6f/71/eb1725a0159c0759ba33e08964c3ce61335e304811e2dd4f0935cfd3dedf/hypothesis-6.170.12-cp314-cp314t-win_amd64.whl", hash = "sha256:322548a87c3bb790854f8688ad2e8a652f696305ee2a182c85a3e0bf4a7b0498", size = 690804, upload-time = "2026-10-26T17:11:21.424Z" }, + { url = "https://files.pythonhosted.org/packages/64/cc/d9f44015a7b402f965ca80dcd4d3d5c179535235617baf1e306d5cea1b47/hypothesis-6.170.12-cp315-abi3.abi3t-macosx_10_12_x86_64.whl", hash = "sha256:9480e495c9bce6019971b5d70dbb737e3ba56d90c32d22930556c4c56bd6c528", size = 799824, upload-time = "2026-10-26T17:12:06.032Z" }, + { url = "https://files.pythonhosted.org/packages/cb/3e/5deca2e25b89e0ea4c8ede15a6e3ddf1c938671270c14add4a2491966fec/hypothesis-6.170.12-cp315-abi3.abi3t-macosx_11_0_arm64.whl", hash = "sha256:393f82b7f76141dac5082edba58287a7ee6cc255f986b7dd9dd5fc4c2bb12afa", size = 792273, upload-time = "2026-10-26T17:11:04.799Z" }, + { url = "https://files.pythonhosted.org/packages/57/be/9ec46e023726a54961af06521ebe6caeccfa9cd155e62feaf8cdc180286e/hypothesis-6.170.12-cp315-abi3.abi3t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d84e49b1db4f3fce90e5e8fba7a4e81961ebe522c5db8b60e3fe5fcd58512813", size = 1121164, upload-time = "2026-10-26T17:10:35.868Z" }, + { url = "https://files.pythonhosted.org/packages/8f/52/f42a0ae67c2d05e39f6170f7487f00650cab495ab604792cad7184ab5ff3/hypothesis-6.170.12-cp315-abi3.abi3t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0b9ac42c8e163c1da3938364f8338391f71e7c721b79ae5cb9b310c0bdb646f1", size = 1152061, upload-time = "2026-10-26T17:11:22.592Z" }, + { url = "https://files.pythonhosted.org/packages/78/a7/4699066529d2c70ffa2e4d9a8371363922a244fc6fcc29c0740075e62971/hypothesis-6.170.12-cp315-abi3.abi3t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9d1a12d62292568f4b37afd7ca0e05fc80e11e896e989d5f6ce7dad44efaff6d", size = 1145781, upload-time = "2026-10-26T17:11:37.362Z" }, + { url = "https://files.pythonhosted.org/packages/43/ab/1588dce65e5343364c0a5e4b0d4924aa415c446e65a6cb0a4e900bc081eb/hypothesis-6.170.12-cp315-abi3.abi3t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f14d172dab9bb05394d5409699c3817f3f759ca0313ee2a8933b7c190169c2df", size = 1199556, upload-time = "2026-10-26T17:11:03.52Z" }, + { url = "https://files.pythonhosted.org/packages/1e/d5/11d5810838ffa5ee40c4c1fdb4e00de9fa8ca7037f9e8d52ea5de739d2f4/hypothesis-6.170.12-cp315-abi3.abi3t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cb2a0cd5e3808077143290344646e6ed9e46fdf0c3777910f90763884de719e8", size = 1165475, upload-time = "2026-10-26T17:10:28.172Z" }, + { url = "https://files.pythonhosted.org/packages/bf/d6/751bb29605db0457bbdd2d497dbeb92874e348731ce4b7677ee8def67bc9/hypothesis-6.170.12-cp315-abi3.abi3t-manylinux_2_31_riscv64.whl", hash = "sha256:9db4f9d90a41e15938bbefd0b32dc93c589510ad1309f164e9436c11c1ad2ad0", size = 1119771, upload-time = "2026-10-26T17:10:29.561Z" }, + { url = "https://files.pythonhosted.org/packages/ea/25/c1eed293fb064ac3a128cf0b827199ba0487302fcd2f7dda73d692b4b757/hypothesis-6.170.12-cp315-abi3.abi3t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:10869687863bb79028025e5a4acf5d30d2da8b6d215f46f02c12d91647f14849", size = 1158253, upload-time = "2026-10-26T17:10:34.426Z" }, + { url = "https://files.pythonhosted.org/packages/26/5b/746b02f3b91bc11b77f7791a2da115afda4264e3ed8538a778abb2684d94/hypothesis-6.170.12-cp315-abi3.abi3t-musllinux_1_2_aarch64.whl", hash = "sha256:44463725ee0812b8226410294c8db69956ec726f22b42d133f5366fefa939cf9", size = 1295881, upload-time = "2026-10-26T17:10:33.242Z" }, + { url = "https://files.pythonhosted.org/packages/f0/75/d39b8caeb0f44e8e4e2cbda67dab372a5dded9cfc91daf8a7436633d1982/hypothesis-6.170.12-cp315-abi3.abi3t-musllinux_1_2_armv7l.whl", hash = "sha256:0d469c9cced28f7d7ed411dc41609b91ac19149947f7a68ac8404d576c433e85", size = 1425052, upload-time = "2026-10-26T17:11:39.715Z" }, + { url = "https://files.pythonhosted.org/packages/81/db/393ca7953415fbb9775824c7f8206e0ca004d664dddb2c5cdeea79de7e0c/hypothesis-6.170.12-cp315-abi3.abi3t-musllinux_1_2_i686.whl", hash = "sha256:02857858c8985fdd5652b56d4a5795e66416c80cb3dd5ea1e1d0ee496fc58cdd", size = 1378674, upload-time = "2026-10-26T17:11:11.101Z" }, + { url = "https://files.pythonhosted.org/packages/72/0b/7fe8b2f09c615886e454e418e0296d6b2756abd8866a83fdbcd8f912b9b3/hypothesis-6.170.12-cp315-abi3.abi3t-musllinux_1_2_ppc64le.whl", hash = "sha256:b5db8c5e15e3e0e3135d41d9b1c3ae361823d65d0b19adf2bb8cb09b21108607", size = 1274395, upload-time = "2026-10-26T17:11:15.151Z" }, + { url = "https://files.pythonhosted.org/packages/3d/21/fa36acad45a3a9b1da13c3e1eb9d11202dd483e665f5ae5722d42702f32a/hypothesis-6.170.12-cp315-abi3.abi3t-musllinux_1_2_riscv64.whl", hash = "sha256:67ef145399463542418c8c47d8f0ec31458ba07c86289bcd008e5081f5e2213d", size = 1291512, upload-time = "2026-10-26T17:11:07.508Z" }, + { url = "https://files.pythonhosted.org/packages/d7/fd/2bb225a5196fcfae0c474f5c405e5772011c6601d6ed0eddffa2a3ae6faa/hypothesis-6.170.12-cp315-abi3.abi3t-musllinux_1_2_x86_64.whl", hash = "sha256:9d77f02bd604b764398acb493203dc2f45b7634cafa3bdd6b18d502ed3a1bfe0", size = 1331449, upload-time = "2026-10-26T17:11:17.753Z" }, + { url = "https://files.pythonhosted.org/packages/ae/72/01d3b1834116952263a01e8da4b1634f83ac0b38904c805f8f732fef553b/hypothesis-6.170.12-cp315-abi3.abi3t-win32.whl", hash = "sha256:2484674ee787451b050dd9bf80105786cb34899c1e5a75195f2c8feaad87c47d", size = 684613, upload-time = "2026-10-26T17:11:52.637Z" }, + { url = "https://files.pythonhosted.org/packages/14/54/fecb01f6b5d7adf58b104fe2d8abfebd5d51a7afda702d20cc55c767a3e8/hypothesis-6.170.12-cp315-abi3.abi3t-win_amd64.whl", hash = "sha256:020c2be4b16a09787b2d5b532ad1a75e20a4443c8b220e59a981c45d488fa4d8", size = 690578, upload-time = "2026-10-26T17:11:47.512Z" }, + { url = "https://files.pythonhosted.org/packages/0c/ac/cf60b7e7901412b4d60e26be9373f9b9b9e4fdf5ffdd739aa3e43fdea4d9/hypothesis-6.170.12-cp315-abi3.abi3t-win_arm64.whl", hash = "sha256:a39811c29cac6bb35ef775db6a0e276578350e4768ea9a6669f70dfcc3626f6e", size = 688566, upload-time = "2026-10-26T17:11:09.934Z" }, + { url = "https://files.pythonhosted.org/packages/73/b2/907785e3c06c1c408b91222ef40fdc226ba7818e8f0fdd4e3dfa566fc3c1/hypothesis-6.170.12-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:145248c96c3af5262438ed356829f0f54c05ab4d1bc3ade476cfd6b6a40ea772", size = 800641, upload-time = "2026-10-26T17:10:37.057Z" }, + { url = "https://files.pythonhosted.org/packages/ab/76/7b8f74f49798aef9dd117a9a7d04048bf37342fbcc55bcf91b70c1cfbd58/hypothesis-6.170.12-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:ac592beb4129b83d89c619f97d6f0a388963211d8af17cc85e6c37d135cdfc4a", size = 795556, upload-time = "2026-10-26T17:10:53.127Z" }, + { url = "https://files.pythonhosted.org/packages/51/52/3ec388a5653fedd63b4673b69f4a31a9031adf2be80a186a130a81755369/hypothesis-6.170.12-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:04a5dfbcba89f2b5b42756404ebcd28f2a5b6bb9744c3e2a5a1b74dc9254d46e", size = 1124627, upload-time = "2026-10-26T17:11:33.795Z" }, + { url = "https://files.pythonhosted.org/packages/b8/d6/026e9fa82471cfc790e10a7d38be3cdf6da4a4a0a71a227cbcd41447f281/hypothesis-6.170.12-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:35963711f0f1013d65b3cec03b3f57c7dbdfe74043e0efb17ac22652a05b7904", size = 1168007, upload-time = "2026-10-26T17:10:54.376Z" }, + { url = "https://files.pythonhosted.org/packages/71/68/d716ce5c7d72959175daa90feb24b3e6023611db399a17e202774d4cd147/hypothesis-6.170.12-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:09986fa92076a03b083c5a52c930fea4b2c396a658c3e68b6d16a10f61105c96", size = 693936, upload-time = "2026-10-26T17:10:41.715Z" }, + { url = "https://files.pythonhosted.org/packages/45/fd/712d04bffaf9614ddaf9aef4ef355560dca122fe3408f668a1a929e42a2c/hypothesis-6.170.12-pp312-pypy312_pp73-macosx_10_12_x86_64.whl", hash = "sha256:8414611abd203b0fe3d11fb7877108c775416d454e29d3b0d72b5f8968549237", size = 800956, upload-time = "2026-10-26T17:11:16.549Z" }, + { url = "https://files.pythonhosted.org/packages/93/7c/568a6a7ce8c6a140def54fc5185eb070de946d16518a57f51bc4338f2b9a/hypothesis-6.170.12-pp312-pypy312_pp73-macosx_11_0_arm64.whl", hash = "sha256:8cd063d3620c4097505f4e697fcaded527a9565333ae3e4ef6a9b727f47b67e4", size = 792848, upload-time = "2026-10-26T17:10:55.658Z" }, + { url = "https://files.pythonhosted.org/packages/f0/3e/4fafe1a50b0df2d032b6142e13366a7d37ed0cab5d6af749c430fb538f0a/hypothesis-6.170.12-pp312-pypy312_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18ea9df1deec60fad073305438f050365a2ab8f5c7a577b5c8f96e5aaec620ad", size = 1122799, upload-time = "2026-10-26T17:11:19.011Z" }, + { url = "https://files.pythonhosted.org/packages/2f/24/b9b30baa2b6c3703162f8a5f49235d74df568ed941680edde70332e4fe42/hypothesis-6.170.12-pp312-pypy312_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:99efd12654a380a6d683b962f481969d08beb55bd2426424cd5178d3f9f12b23", size = 1167006, upload-time = "2026-10-26T17:11:29.968Z" }, + { url = "https://files.pythonhosted.org/packages/ce/13/dcfda3cbfcbd0501aefe0ee6e46591671a19df3ba18d65fdfd4229c5e3b6/hypothesis-6.170.12-pp312-pypy312_pp73-win_amd64.whl", hash = "sha256:8259bf11eed5379c8bc69c7fd998baa0e122350ab3e5b3432d5ef2e22e11d8e3", size = 692109, upload-time = "2026-10-26T17:11:32.645Z" }, ] [[package]] @@ -951,11 +963,11 @@ wheels = [ [[package]] name = "iniconfig" -version = "2.3.0" +version = "2.3.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } +sdist = { url = "https://files.pythonhosted.org/packages/01/e1/2069291243c926a2ff1cd706c7f3eeb9b62144bf60f77c9fb9ff2fb26bd3/iniconfig-2.3.1.tar.gz", hash = "sha256:67f4b9c50da0dedf52af349e7749a80a9057a5031199791b906c3bb3ae878960", size = 21209, upload-time = "2026-10-06T22:48:38.076Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, + { url = "https://files.pythonhosted.org/packages/56/43/4ca9e49d27a1fcf6bece6f6aec0ea46bb9112489b93d4b688fb415457bdb/iniconfig-2.3.1-py3-none-any.whl", hash = "sha256:9121e2c1fdb355232495be3194c8dfe87ccc2d5dee45947b78e68f499790d7a7", size = 7552, upload-time = "2026-10-06T22:48:36.959Z" }, ] [[package]] @@ -1010,14 +1022,14 @@ wheels = [ [[package]] name = "jedi" -version = "0.20.0" +version = "0.20.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "parso" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/46/b7/a3635f6a2d7cf5b5dd98064fc1d5fbbafcb25477bcea204a3a92145d158b/jedi-0.20.0.tar.gz", hash = "sha256:c3f4ccbd276696f4b19c54618d4fb18f9fc24b0aef02acf704b23f487daa1011", size = 3119416, upload-time = "2026-05-01T23:38:47.814Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a3/a4/4692ecd7fa700ac1066b1c1f6cadef60aa2e01e5e364710235137d823e0a/jedi-0.20.1.tar.gz", hash = "sha256:2f71208c3f9c1bca057c0e90d3f272aba44ace88fc4067d7587e9e069331b7e5", size = 3121738, upload-time = "2026-10-12T17:42:24.361Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9a/93/242e2eab5fe682ffcb8b0084bde703a41d51e17ee0f3a31ff0d9d813620a/jedi-0.20.0-py2.py3-none-any.whl", hash = "sha256:7bdd9c2634f56713299976f4cbd59cb3fa92165cc5e05ea811fb253480728b67", size = 4884812, upload-time = "2026-05-01T23:38:43.919Z" }, + { url = "https://files.pythonhosted.org/packages/be/c7/71eb0d51cdcbb63b857c931aa4e9218b078b5d9f8ecd383b234f28fe7b0f/jedi-0.20.1-py2.py3-none-any.whl", hash = "sha256:0fb16d86c4a4c73c37ba518c77419975e30fcc620658a8d14fbb5720cdd34142", size = 4885980, upload-time = "2026-10-12T17:42:21.585Z" }, ] [[package]] @@ -1318,7 +1330,7 @@ wheels = [ [[package]] name = "moto" -version = "5.2.3" +version = "5.2.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "boto3" }, @@ -1329,9 +1341,9 @@ dependencies = [ { name = "werkzeug" }, { name = "xmltodict" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6c/0f/1682c01ca0608c25526afb150246a3c9c1f609caccbd39758de4850e31bc/moto-5.2.3.tar.gz", hash = "sha256:a9e95c3218b6eda18e74571f1ced11cb1bc3151467d562c1da6037b9d19832cb", size = 8785514, upload-time = "2026-08-22T18:46:00.199Z" } +sdist = { url = "https://files.pythonhosted.org/packages/17/27/671bc2fbff0f86a8fcd6882ee56de69b5f80f71ba089eb663d10eca28726/moto-5.2.4.tar.gz", hash = "sha256:1a467004562034a09717c3f1ed533337a81ead573ed5d2d40cad648b5ec17e00", size = 9228741, upload-time = "2026-10-11T18:41:16.538Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/58/80/ee7ace7a49bd525455497fa2ff198111877d6f93b908b657adeb2cf057a4/moto-5.2.3-py3-none-any.whl", hash = "sha256:5ec31b3cdbb383a19d46030157bd82aaaa7a9cc5f564200f995663d347bd4728", size = 6780972, upload-time = "2026-08-22T18:45:55.719Z" }, + { url = "https://files.pythonhosted.org/packages/6d/00/5729790afc2ee0ac52567c2388452918dfabb383d3afbf613f9136ee5ee2/moto-5.2.4-py3-none-any.whl", hash = "sha256:b75cf0a0063315bab6a4c3606f475ee118f3c329c8d5477a2447e699bdf13155", size = 7195856, upload-time = "2026-10-11T18:41:12.892Z" }, ] [[package]] @@ -1551,7 +1563,7 @@ wheels = [ [[package]] name = "numpy" -version = "2.5.3" +version = "2.5.4" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.14' and sys_platform == 'win32'", @@ -1561,73 +1573,73 @@ resolution-markers = [ "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'emscripten'", "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", ] -sdist = { url = "https://files.pythonhosted.org/packages/13/01/11703282db468b85f6f7b8c7f22d058de5970d5c7e60a3a8aaa313c3de36/numpy-2.5.3.tar.gz", hash = "sha256:df2d5874ff183595a4ba404edd04f6bd9b5505c1d7708573f6a6c17489a67563", size = 20791231, upload-time = "2026-09-06T16:27:47.073Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d6/50/8fdbb16af64895706a45f06a4068e29db732ec180f3c1375f14123359138/numpy-2.5.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:cb189f09db39283b26bfd061ec16189e14f71c6755207f72a0f7540867afe5b9", size = 16994982, upload-time = "2026-09-06T16:24:29.244Z" }, - { url = "https://files.pythonhosted.org/packages/60/39/789131c1188c078dcb3a1692e72e1e050c68b88ffe72c9ccaac9bcd7a9cd/numpy-2.5.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f59a878c33d6b88122d80d239bb3b845d58708750b0cb06a09aebb9b18ec696c", size = 12009327, upload-time = "2026-09-06T16:24:32.491Z" }, - { url = "https://files.pythonhosted.org/packages/9c/59/a312e95696e5f601914dd8b6dd844692ba61670807417e24b68e337b5c70/numpy-2.5.3-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:a72f874bc9e10e4b8f80426fb49716d5141f64442a0c8418065093ec8017fbb0", size = 5445405, upload-time = "2026-09-06T16:24:35.071Z" }, - { url = "https://files.pythonhosted.org/packages/30/d0/5623a1707ed4fe16e3909fe3cf5ee3da004ae677ad23d83bbf3adf1a6faf/numpy-2.5.3-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:fc36dc566135b5eceec4cf89758fcb719266a019ef07dae1754ae7c9f617ef3e", size = 6783213, upload-time = "2026-09-06T16:24:37.253Z" }, - { url = "https://files.pythonhosted.org/packages/f1/32/84146fc020ad3c25f805f70ab60da46fe3c540a21369754a7e4369754b6f/numpy-2.5.3-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:76c2c1e6bfa5c84adc6434dfbf013aa92096a7985221762c8f11fedfd20fff58", size = 15687872, upload-time = "2026-09-06T16:24:39.751Z" }, - { url = "https://files.pythonhosted.org/packages/65/af/aa78d1a88805456e212b65461354cd943197fb9acecc4c90fd12295123a3/numpy-2.5.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b7e18c623bb5c95acb3b3328861272816ba199fb531921c5d6d0b675f1fde9e3", size = 16717410, upload-time = "2026-09-06T16:24:42.745Z" }, - { url = "https://files.pythonhosted.org/packages/3b/24/faa79d865e69a97ba17473b23a1b74094b2259c03e820c70297293b9ea49/numpy-2.5.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4f8929ee6c96bfbd7b4ed2032e0c03af86fe1826740ab61ddabf9072d06e57ff", size = 17040975, upload-time = "2026-09-06T16:24:45.961Z" }, - { url = "https://files.pythonhosted.org/packages/62/4a/8877e629445a7176297dffcaf9c485faa96a95d81728a62521ad55bd4c0f/numpy-2.5.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b5d93cf48f687479941d12b69c873ad2cc76bbd487f0091c2200636497f34034", size = 18476479, upload-time = "2026-09-06T16:24:49.35Z" }, - { url = "https://files.pythonhosted.org/packages/c8/db/35e1c2d38b04cbd5b731f9d71495e055e813197669d22b612f11748d2ff9/numpy-2.5.3-cp312-cp312-win32.whl", hash = "sha256:bf63afbe037eb5d2fe87fbcc7778e61da53ebaf21d938a4515aa73b62532a5d4", size = 6133378, upload-time = "2026-09-06T16:24:51.915Z" }, - { url = "https://files.pythonhosted.org/packages/3c/a1/accf6d4f0c80c5d9ba9735d6b1550e444180599f34dec69ca01360f717ad/numpy-2.5.3-cp312-cp312-win_amd64.whl", hash = "sha256:0a59a421a32580a009e8a1751345bf829631b990dc1794b80514ab722b435def", size = 12567828, upload-time = "2026-09-06T16:24:54.255Z" }, - { url = "https://files.pythonhosted.org/packages/22/43/1764aff32e4652526ae2f71fa8b3efd8d25c8a3d6926914454e47138ed1e/numpy-2.5.3-cp312-cp312-win_arm64.whl", hash = "sha256:ccb32e0525d29e8b0572eb84c9a57af0e7a4e615726927506f55063c62414034", size = 10485432, upload-time = "2026-09-06T16:24:57.278Z" }, - { url = "https://files.pythonhosted.org/packages/79/e5/8fb89cd46d14e35699d13bf943a5f5f441ecee8667120a1f6105ab89e349/numpy-2.5.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:66a78fe4556c60aceda5916f9eacd638b18e9e681016ec302dcb4682d6d4d034", size = 16991061, upload-time = "2026-09-06T16:25:00.411Z" }, - { url = "https://files.pythonhosted.org/packages/2f/06/9dc9e48b5e5e941c8b10350c5ff2d721da42a20517d911d15544246775ff/numpy-2.5.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:92f30e89b8ee0ecf363033576c422b2f58fed6a80bed0aa48dff6d14c654663e", size = 12003676, upload-time = "2026-09-06T16:25:03.475Z" }, - { url = "https://files.pythonhosted.org/packages/ab/2a/98282aa5b8f58b1157d440bb6282eed47e3632a5de53a714fbab17e659fe/numpy-2.5.3-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:f9a2353b37a1a9e78fd82b27ad7e2a32a2d036604d18f02b05e3136c62ca3b09", size = 5439695, upload-time = "2026-09-06T16:25:05.978Z" }, - { url = "https://files.pythonhosted.org/packages/a1/f9/b6533d777be9d6ffd29dc1be0867e563e6e8cc9a220ff1b716adc317f060/numpy-2.5.3-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:ccbc4665079665c3cf3bab4db9f6b095370cd6437d66be549b6c2a1fd19e1958", size = 6779395, upload-time = "2026-09-06T16:25:08.599Z" }, - { url = "https://files.pythonhosted.org/packages/73/85/735720d04ec197c5dcfacdfc9922667c7f1f5f496a279b7ba4d7c74c4cc7/numpy-2.5.3-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c76d5dde9f445058f83d0c02af00557a4db91de9a9a57c0df87d1535001d654b", size = 15681750, upload-time = "2026-09-06T16:25:11.173Z" }, - { url = "https://files.pythonhosted.org/packages/3a/1b/3b16a9bc514a440a7a0883684111dcb1ef1aee960af2ca95da8fc775f124/numpy-2.5.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a5fa86b80fd24bcd1aff83ad23be44ea323de3f787be8f8b15d4a65621e25321", size = 16708577, upload-time = "2026-09-06T16:25:14.171Z" }, - { url = "https://files.pythonhosted.org/packages/69/c4/386f397831b07328b639c96c5b62719346cf4baf07c68d927239752b1534/numpy-2.5.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bd4cb9ad3c7889b9b3fe0a9a9fb5d2ed26f9879bff2608d9f01aed147a20d231", size = 17042047, upload-time = "2026-09-06T16:25:17.582Z" }, - { url = "https://files.pythonhosted.org/packages/5f/3e/a700ecbf36e85ae8328fd3b0e12eeddc22ed6358a64cb2bd913e0d195d65/numpy-2.5.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1302b90c0e52281681b2975adfe8a860cb7b12216a27b4b0b4207c44bf7bccf0", size = 18465724, upload-time = "2026-09-06T16:25:20.949Z" }, - { url = "https://files.pythonhosted.org/packages/41/ee/38e785e88a4045f6ad1d1f2808dcdfafdca48c760260c0587bf171e29fc9/numpy-2.5.3-cp313-cp313-win32.whl", hash = "sha256:1c80eabb4035ecf4ca9cd49cde8a9fdd69a729e63e6474887d1523ade7aa277f", size = 6129003, upload-time = "2026-09-06T16:25:23.664Z" }, - { url = "https://files.pythonhosted.org/packages/f3/ec/100f2b1794ede74a9b3d7ec6b9736927f56713414c1dfe19ab6c383494bf/numpy-2.5.3-cp313-cp313-win_amd64.whl", hash = "sha256:71cad2b2a7451ab79d8f5e71b453485b6775963d5cf794179144a7463fe6e8ec", size = 12560965, upload-time = "2026-09-06T16:25:26.602Z" }, - { url = "https://files.pythonhosted.org/packages/80/b1/7dc825ca94c12acebbce4c37caa5e198695eb31424bc579679f32b1bb49d/numpy-2.5.3-cp313-cp313-win_arm64.whl", hash = "sha256:8e4dd766076855b5ff7ea52fa5f07ce26286726e0f8bff446b7739d02e6ea204", size = 10482343, upload-time = "2026-09-06T16:25:29.772Z" }, - { url = "https://files.pythonhosted.org/packages/70/78/cf416f15dc29375a229d9dfebf8db6e313f291580b39fa1a568b6052bb07/numpy-2.5.3-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:350ba9783ce969cf9f7ce6e6a9a58e1a6e2a19ca025b7ee448c4db727706212a", size = 16998686, upload-time = "2026-09-06T16:25:33.171Z" }, - { url = "https://files.pythonhosted.org/packages/9e/59/abcc2d8def4fd60eec7d87f92d27c13448ffd9ab14339bcc63a0d7a2fdea/numpy-2.5.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:012e66aca395d795496446e52aeeb5866312a5d4d3f27da270e5a0b43f70dc5c", size = 12013862, upload-time = "2026-09-06T16:25:36.748Z" }, - { url = "https://files.pythonhosted.org/packages/94/75/4640d2d6e4b64a049e48425a82728a41ef4adb61332d2cba68055774878b/numpy-2.5.3-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:adc1ada2662f8a5f960b8a10d9986897e7499ef07e06d4cfe7197f8cce923c07", size = 5449793, upload-time = "2026-09-06T16:25:39.476Z" }, - { url = "https://files.pythonhosted.org/packages/96/cd/625b57ae33d4ca560f32cc0b47b4a5922146d9beb998ddf773900d440a73/numpy-2.5.3-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:54a115e5a73b8fc44f0cebef486365a1894b5c9760685d4558b72b7c3eb846e0", size = 6785176, upload-time = "2026-09-06T16:25:42.069Z" }, - { url = "https://files.pythonhosted.org/packages/9c/72/12918652e7912ef9751e8694c88820fcd1908e0618cb23f5f3caa6004b7b/numpy-2.5.3-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:be5a8381859b6da607c84f4f7d6847725f1cf1853ef8a2c9e115b7d58bef47dc", size = 15703377, upload-time = "2026-09-06T16:25:45.135Z" }, - { url = "https://files.pythonhosted.org/packages/45/8f/9beacf79ca7c650688ad0baa80931adb988fe6e6e5d5903c23cc3dbd70eb/numpy-2.5.3-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b0521d0f4aebb6e06189451025fa17a913287b13c03d5fe05c017333b654ea5b", size = 16711928, upload-time = "2026-09-06T16:25:48.461Z" }, - { url = "https://files.pythonhosted.org/packages/09/8d/41d0a56e1ac4c87495c897a211b1368691b7237aadabec8b3b8f3a74d48f/numpy-2.5.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:9deb49575e5b0b94ed72c8a64ec4d033381adc27e9060ae842971f697ba96104", size = 17059507, upload-time = "2026-09-06T16:25:51.873Z" }, - { url = "https://files.pythonhosted.org/packages/08/1e/0dfbc5cc251d54e2af790f254d24ec38637fa97ec7d5d11de7ffed787098/numpy-2.5.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b00eefbcf0f292945c4b4dec2ae845389ef5bcdcd596e6e4328051db5b5ba694", size = 18471002, upload-time = "2026-09-06T16:25:55.233Z" }, - { url = "https://files.pythonhosted.org/packages/b5/2c/dfa40f6991f8185c8c30ffd023dfcbb11888e823cfab9557b920f3bb7bed/numpy-2.5.3-cp314-cp314-win32.whl", hash = "sha256:c2381f82999704f818e2c987a865050e285ec3621262c66d40f5a96c8f899f8e", size = 6180485, upload-time = "2026-09-06T16:25:58.157Z" }, - { url = "https://files.pythonhosted.org/packages/a4/73/d2c08231e4fde7e415501fd02c715d96e98599b2d8384445933944152984/numpy-2.5.3-cp314-cp314-win_amd64.whl", hash = "sha256:2c25dfa72943e4336ddb6b0ee4277b47a0c85bede0807530ec68103bf58e2c10", size = 12698179, upload-time = "2026-09-06T16:26:00.789Z" }, - { url = "https://files.pythonhosted.org/packages/5c/e9/dcdcc9b95cf5f49815055573aee1b11cfbf5299f38a180e437ded050810f/numpy-2.5.3-cp314-cp314-win_arm64.whl", hash = "sha256:15aa985ac73a8db02db7663381aa109510449d3819d37206caed27b33a65a8a6", size = 10769383, upload-time = "2026-09-06T16:26:04.011Z" }, - { url = "https://files.pythonhosted.org/packages/49/c4/af8bc08a7ef4e1529a7c0cf24969accce316b783999802089a581ec99272/numpy-2.5.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ac7bb1c52d445bd4f8f7f97fefe6abc3a084dc4d63df50d79b17fa2b78e89297", size = 12132668, upload-time = "2026-09-06T16:26:07.138Z" }, - { url = "https://files.pythonhosted.org/packages/c5/ae/0f15eb56d4ec5e13c1f7ff04ff407f997d1acbadb45d3e1f2e2645a8f43c/numpy-2.5.3-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:e6ab667ba76450084eb64013762c438ea76d9d29cc676dcd6c2e9892ba37f841", size = 5568580, upload-time = "2026-09-06T16:26:09.828Z" }, - { url = "https://files.pythonhosted.org/packages/23/fb/c72a8f25d4b6e96c354e7ab45ace3b27dc11e5d6a13b6c7d0cd6b08bf112/numpy-2.5.3-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:f7fabeb6cea87d65f3b926de33d03fb016cfdc29314c90974383b5582ae72891", size = 6882634, upload-time = "2026-09-06T16:26:12.524Z" }, - { url = "https://files.pythonhosted.org/packages/07/a9/968c90ed2ab15060c338e8137f1215b5a60756ae07328e0a60d1c6734df4/numpy-2.5.3-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1fb6f8fb9ff0b3a69f52c66ce397b0246583e9f28616231b0e32ca49259a5fa6", size = 15748923, upload-time = "2026-09-06T16:26:15.092Z" }, - { url = "https://files.pythonhosted.org/packages/59/08/9df04103947b95e3b6b1f2ed1a70521f325647a31b82da6a2aae3a485508/numpy-2.5.3-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:93e1f5447e2b1e479d7bd74701e84746b86450cff1fc368b132d195e2b8f8211", size = 16746748, upload-time = "2026-09-06T16:26:18.43Z" }, - { url = "https://files.pythonhosted.org/packages/41/a0/14c8d5fe5b53a334aabb653deb391c0fef49558f491880ea300ed6785224/numpy-2.5.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c00abe94c1a69d75d827dcf1c025b25c8a45d230b3bcd77a9020883a1b047653", size = 17111561, upload-time = "2026-09-06T16:26:22.113Z" }, - { url = "https://files.pythonhosted.org/packages/c4/a6/d7e96e42f01522e154c32489640f16dfc4f6181d165d05fc3bec8c2c4999/numpy-2.5.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:536f963710a4e63934d80ac0dc4f478804a83e9a84b6828018f25d09953ada33", size = 18513945, upload-time = "2026-09-06T16:26:25.401Z" }, - { url = "https://files.pythonhosted.org/packages/25/39/3453afb7119d0449ef11c886874120ff180e2c337760e0e2d88f70f1a945/numpy-2.5.3-cp314-cp314t-win32.whl", hash = "sha256:4c8a6d2ebce6305fd82fbefca827775437147052a976ee7c94b36a0c1b52ac6c", size = 6335421, upload-time = "2026-09-06T16:26:28.175Z" }, - { url = "https://files.pythonhosted.org/packages/99/01/22815d2b19a1a746b1d45205cffebb3fe511a18acb75fba6c88491fc9894/numpy-2.5.3-cp314-cp314t-win_amd64.whl", hash = "sha256:9a37475425b431b4d060f23b4f52cd2f3aef6bc7c654bd760adf0040eec9d435", size = 12896420, upload-time = "2026-09-06T16:26:31.265Z" }, - { url = "https://files.pythonhosted.org/packages/fa/ee/a7cbba67eeaff038dc29ca8b98a88396c8b0cc9c89d4924f4a27a5c9150b/numpy-2.5.3-cp314-cp314t-win_arm64.whl", hash = "sha256:2d8240cb4c16fd831074aa2b2cf9fc54664d826341d61c372245b96a74a49a9a", size = 10857177, upload-time = "2026-09-06T16:26:34.167Z" }, - { url = "https://files.pythonhosted.org/packages/45/56/78194492883ff5eec90423fe56a3a44b154da047d88a6307f629713c584f/numpy-2.5.3-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:a6391fafaba97500887132cd582abc6e19452b1ac775a47caa7b24490e152058", size = 16996531, upload-time = "2026-09-06T16:26:37.287Z" }, - { url = "https://files.pythonhosted.org/packages/11/39/dd55c0af90bbab564b09ae3b0aa60ec5c02b900fa4f1ba23440525c8b32d/numpy-2.5.3-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:09d5a423c71ad5feb5625844ad58050e35df43871004b52ac9c0ad44a56775be", size = 12012569, upload-time = "2026-09-06T16:26:40.707Z" }, - { url = "https://files.pythonhosted.org/packages/b6/51/04f67d32e4862b281b1cb84ceeaed3421189a84fb6fb51a391cd6d5009f7/numpy-2.5.3-cp315-cp315-macosx_14_0_arm64.whl", hash = "sha256:f9579f383d1bf9df80081e72760e84960a7fd4f88cf0c9e535a8597c9bb646f5", size = 5448498, upload-time = "2026-09-06T16:26:43.435Z" }, - { url = "https://files.pythonhosted.org/packages/a3/c9/25b4dc0dd1344ec26c7319e84fd4e9809d2b5628f4e12decd618036e5178/numpy-2.5.3-cp315-cp315-macosx_14_0_x86_64.whl", hash = "sha256:86bff898a431c0fb71f7610b75726e75a54d47b37edc9d537f48de63bb3c0b90", size = 6783026, upload-time = "2026-09-06T16:26:46.374Z" }, - { url = "https://files.pythonhosted.org/packages/fc/c7/29285be1e5232a6e7ee3268a33c85843f5a8ee93350c6465cddd66ebbf76/numpy-2.5.3-cp315-cp315-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1f3ed25271581281f2fccb1adcedfcde4c07362eec69189b50baf6f90e3ae159", size = 15697322, upload-time = "2026-09-06T16:26:49.415Z" }, - { url = "https://files.pythonhosted.org/packages/55/49/bbad5335fb4996a16881f853ff3e0ba582f01720e55c89b1c06b8fc42a90/numpy-2.5.3-cp315-cp315-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ffdc76bfcae6b255dff75202c5e7feaf95b40246bc0a17944facc1fecf9f79ab", size = 16708995, upload-time = "2026-09-06T16:26:53.127Z" }, - { url = "https://files.pythonhosted.org/packages/ef/e9/1df35483760b04a65ea44669f89dc64f30e5aca098b48ceb8b1310b0e0fe/numpy-2.5.3-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:116f96cadd935c6122e9228d676fe7ede19e741f5c8bb1c3cddbe0c51ccebea2", size = 17052508, upload-time = "2026-09-06T16:26:56.464Z" }, - { url = "https://files.pythonhosted.org/packages/b8/99/66e54da8265cc8be8a7382bf96edce17aaa2837d6f484432025932a3caa5/numpy-2.5.3-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:09ffa5d903faeaa5c4dd05009cf81c8bab9f2cb37c548b8d39b65b4cfa7c97f7", size = 18468224, upload-time = "2026-09-06T16:26:59.966Z" }, - { url = "https://files.pythonhosted.org/packages/01/bc/b5e90a91c115168d793dfd2ad9c69c438c2fe7a13a437e770bc5b078e732/numpy-2.5.3-cp315-cp315-win32.whl", hash = "sha256:e01c918ac3d48e18a927cf7b14a26a3e29ff2bdf2eacb976da0aecd6a43ed034", size = 6179919, upload-time = "2026-09-06T16:27:03.166Z" }, - { url = "https://files.pythonhosted.org/packages/37/ea/780748fd3985109075514ef8fc64cd25f943e40dde13a6d59141eb268fc8/numpy-2.5.3-cp315-cp315-win_amd64.whl", hash = "sha256:e931e4f499e0dc7ef29d269a8e5b35dd722e5d14be07df6240166ea7c6532fae", size = 12697656, upload-time = "2026-09-06T16:27:06.153Z" }, - { url = "https://files.pythonhosted.org/packages/b3/16/407be69a2a87c8cab64d95975a8977a426a29e138f07e276ec258f0fe4e5/numpy-2.5.3-cp315-cp315-win_arm64.whl", hash = "sha256:26e15e4aecd8617dfbaecb37d223e365d7b39411fba20454be2670a96aa74cb5", size = 10767601, upload-time = "2026-09-06T16:27:09.297Z" }, - { url = "https://files.pythonhosted.org/packages/44/bf/a97ffb01e41d50a32a9177aef942a4d0e389a3daf451d04e5f38ef6afb87/numpy-2.5.3-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:6cef4bb1706dfec49243c05d921eefb4e190d41e2528b30d8035ea1f36b4c24a", size = 17090092, upload-time = "2026-09-06T16:27:12.907Z" }, - { url = "https://files.pythonhosted.org/packages/d1/24/136c02f2c2af9a067a84d0c3aa10c99012c0476fa5066732fa4a4202557d/numpy-2.5.3-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:d1c89973648c85069c5046ad460f7b8a00218b29a2e42359ac8cc63e9ab94832", size = 12129429, upload-time = "2026-09-06T16:27:16.089Z" }, - { url = "https://files.pythonhosted.org/packages/fe/6c/b47582d6597789bf946d5efbeb6b9e56fd8bcbd5efc6fbf51dbe1ea31eb3/numpy-2.5.3-cp315-cp315t-macosx_14_0_arm64.whl", hash = "sha256:214045a5bf00113a146ab9ee9730c44501af6723cdf1f6830932f7b5ef2e7af0", size = 5565452, upload-time = "2026-09-06T16:27:19.868Z" }, - { url = "https://files.pythonhosted.org/packages/be/b4/ef3cc6da73774202d4deae16bb321fd8298a4e0561e3539f8c4be237d916/numpy-2.5.3-cp315-cp315t-macosx_14_0_x86_64.whl", hash = "sha256:8617bbfae4486cf99c9f899966699428d19da931d06ca94ad3da986c76e15997", size = 6876736, upload-time = "2026-09-06T16:27:22.232Z" }, - { url = "https://files.pythonhosted.org/packages/9e/24/e3813329498596cb842703dcacac1741612ed9fb9c4e6a3e0c7e2ebbc597/numpy-2.5.3-cp315-cp315t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:595d020938c84e320bcf40ad71089e108eac0d377cd018e14a8c094f39e98d85", size = 15745777, upload-time = "2026-09-06T16:27:25.181Z" }, - { url = "https://files.pythonhosted.org/packages/4a/9e/4e7a07fd0776dc2210cdacf2010be8665194d094defc10c419d7dea794cc/numpy-2.5.3-cp315-cp315t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6f24021b9f22bc6301c37b196974a92c1c18dccedb6fef3dd252e95f2d6adbe4", size = 16746949, upload-time = "2026-09-06T16:27:28.576Z" }, - { url = "https://files.pythonhosted.org/packages/91/db/01674c0e20335057813a00c2ebd546ed25bff9ed7914f9bced00f8c55d94/numpy-2.5.3-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:71b39d9f935b6ec0f8753e3e2afb51e3efba6f2e05b68b32a40754d24bcd4a3c", size = 17108994, upload-time = "2026-09-06T16:27:31.946Z" }, - { url = "https://files.pythonhosted.org/packages/45/7a/584c5e71f8d378e57cac0b033891ed65c683ef90573ba4854e8c28203db0/numpy-2.5.3-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:6b05c171afb3aa07adbd20abc00aea86fe375beb0fdb9ef780ec5b7f63bab1c0", size = 18512266, upload-time = "2026-09-06T16:27:35.196Z" }, - { url = "https://files.pythonhosted.org/packages/a1/d2/4e1014173aa3c55e6a756e0e567290743a6ab33a288460374d7ef6bcd239/numpy-2.5.3-cp315-cp315t-win32.whl", hash = "sha256:f54660b0eb6b0b9f36e7fe1cdfdff472028dd0d14acd9b9b65098efbad059469", size = 6330292, upload-time = "2026-09-06T16:27:38.149Z" }, - { url = "https://files.pythonhosted.org/packages/6c/b0/ff5658a58199b7bcaad87bf260eef6713d9d42cca4e028f935b4fc5fbac6/numpy-2.5.3-cp315-cp315t-win_amd64.whl", hash = "sha256:1aad64d99730d013cfc6debafed22783b4fc5a7f4b8bc744d2d8cf7dcc880551", size = 12884918, upload-time = "2026-09-06T16:27:40.965Z" }, - { url = "https://files.pythonhosted.org/packages/fb/0b/b12a2df5d1b774bd9007a6fdff9381145b6223d37f11afc9c37ab0efd9a1/numpy-2.5.3-cp315-cp315t-win_arm64.whl", hash = "sha256:befa1ae5bd6030b3f512b43ff3fa5290bbed6b84411a44244b14adf835f5b89d", size = 10850807, upload-time = "2026-09-06T16:27:43.868Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/95/b0/c7453d0b6e2073c3264468b106ee1563750cecc910965e67357e3698c83e/numpy-2.5.4.tar.gz", hash = "sha256:9a94cf751c9ad8ebaa835bcd3d40dacf8534ad086b88c38029b65123c7999d2a", size = 20866315, upload-time = "2026-10-10T20:05:31.422Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d0/97/ba2074e92b7befea137e77ea8471e768bbd87c339b7e8c9f5a931949f977/numpy-2.5.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c6342f54c67093cae5c0227eb0eb772fdb79f2a2c37a6eb278b9909ee06aa356", size = 17001609, upload-time = "2026-10-10T20:02:40.843Z" }, + { url = "https://files.pythonhosted.org/packages/ff/a9/bac826765e971d8e16e2064e9ac7525fd69b40ac17c905033a7f5442023f/numpy-2.5.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b11e8fda06a7d69f15ebf542660b74466c2e51094800c1fb794f47ad4faeef17", size = 12015718, upload-time = "2026-10-10T20:02:43.45Z" }, + { url = "https://files.pythonhosted.org/packages/31/2f/5ea3570fcb8ccd0882bea99436a513b2c85dad8f774a2057849130a8fb99/numpy-2.5.4-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:9cb18a327b49c5c337f972b03682f6a49855525faaf3c0d3e9c96cd0fd8880a8", size = 5451717, upload-time = "2026-10-10T20:02:46.169Z" }, + { url = "https://files.pythonhosted.org/packages/34/f2/b4fc1bafca03868220b5eaf729d2f21ebd7d7b151c0f9e144fe212bbca35/numpy-2.5.4-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:aec3fc4b32ff82421274f5d205c559c51c840c8df66a78efd7f3612dd005a26a", size = 6789926, upload-time = "2026-10-10T20:02:48.139Z" }, + { url = "https://files.pythonhosted.org/packages/dc/96/8319e2457ae4333c62c815c7006b869a4f60985c1e01024c2f8c6c040fe5/numpy-2.5.4-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fe4d21ab149f15e4e6043dfb0de87e6e5f34ac176cde83060e9802981fca2ac2", size = 15695312, upload-time = "2026-10-10T20:02:50.115Z" }, + { url = "https://files.pythonhosted.org/packages/43/a3/c799c62e19c337e6d3770b08e475887fb30ce8477d3c09efca6b2f0228a6/numpy-2.5.4-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fbde6962867ee75b48b0ee29b2b9372ec5d617799dbaf38e82dc0596f2f7738a", size = 16727283, upload-time = "2026-10-10T20:02:53.186Z" }, + { url = "https://files.pythonhosted.org/packages/39/6b/3604e53fb00314d0dc1b94ec9125a1484f649c0a17480b1f0f0c7a9d6250/numpy-2.5.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:381a7a3d2e65e64c0ec302795ab9dc12bb1e73f150904699c153716177eebdaf", size = 17047890, upload-time = "2026-10-10T20:02:56.038Z" }, + { url = "https://files.pythonhosted.org/packages/4a/7a/e8b58a5289a0d464c52885de47c35a935cdd70c03a4c3ab94a5126416dd0/numpy-2.5.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b89d0aaae2fe498c648f4c4795c084db535af5bd98ef942b2a3681fb74ce8645", size = 18485839, upload-time = "2026-10-10T20:02:59.018Z" }, + { url = "https://files.pythonhosted.org/packages/6f/c9/47094f597015009f310b8c900def59065ef1ff5a6fe7b51fc65ec58ec2c6/numpy-2.5.4-cp312-cp312-win32.whl", hash = "sha256:9968ab7e49b93ac6e1c3b2239732183152c9150f16308d30b66a372cffe3483c", size = 6138936, upload-time = "2026-10-10T20:03:01.626Z" }, + { url = "https://files.pythonhosted.org/packages/12/33/fefe62073dc8acfd0f2b9ed7c003af2f50aa61555e113e6db02b8f79f145/numpy-2.5.4-cp312-cp312-win_amd64.whl", hash = "sha256:a7b1b6353e36a7e50de2973a38d705c88ee93adcf120673cee7f45a4a3fa223a", size = 12573091, upload-time = "2026-10-10T20:03:04.349Z" }, + { url = "https://files.pythonhosted.org/packages/1a/07/161270b0c2eec56e4c905f6d6d22e1b836887b2cb189d3f5820aa588e9dd/numpy-2.5.4-cp312-cp312-win_arm64.whl", hash = "sha256:aa1cce2ff3f8d953de38b76bf44602caeb69f101430208f64a10067f7cb4b1d3", size = 10521630, upload-time = "2026-10-10T20:03:06.767Z" }, + { url = "https://files.pythonhosted.org/packages/67/14/1c3ee0118a8fce08565a5d8482631608426a33af10a01077fada5dc7c119/numpy-2.5.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2377da2dd3ba2c1200956acbab2a358c83b8e1f8531191672d1cd6ad83250d53", size = 16997729, upload-time = "2026-10-10T20:03:09.291Z" }, + { url = "https://files.pythonhosted.org/packages/83/8c/b0ea9477fb1f0d4484bbc5cba21678cc9969704d8d7f3f158d1db35f8e14/numpy-2.5.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7415db95818b39ec475a5eea54d9e3b6bc83e3912158e46da3438cdce399804d", size = 12009826, upload-time = "2026-10-10T20:03:11.946Z" }, + { url = "https://files.pythonhosted.org/packages/e2/84/6a3d75b3ba3dfe84ac0053450753d1e6d250a8bf80f66474cc46d1fb643f/numpy-2.5.4-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:6d6a71b9d9a97c03633aa12565ef2825ffa036cc1d99cfd50dacf0f128af4fe2", size = 5445803, upload-time = "2026-10-10T20:03:14.329Z" }, + { url = "https://files.pythonhosted.org/packages/61/18/bb993f267ca20b376e07092a16793a5b31ed3138751e9ba480011a14d742/numpy-2.5.4-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:d8200f16437b289a5bb927c6e184eccc3e8389bc0070fea4cd5b9e13c1757959", size = 6786220, upload-time = "2026-10-10T20:03:16.602Z" }, + { url = "https://files.pythonhosted.org/packages/db/b6/135bb0953b61dc21c6cafa14b424ae666944e4899cf140e00c2b322a1a45/numpy-2.5.4-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1c2e71b04c6cad90026e544501bbe0ab9290fa8a4d845e7e8c0d124fb429c988", size = 15689178, upload-time = "2026-10-10T20:03:18.721Z" }, + { url = "https://files.pythonhosted.org/packages/da/24/3bd070f3269dc609d8f26b2643f62ef91bb415841c0b294805aaf7fe06da/numpy-2.5.4-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6ffa07666f8da0eef81d149934a626d0d95fbd6838432a33e66245423a9062c0", size = 16718044, upload-time = "2026-10-10T20:03:21.386Z" }, + { url = "https://files.pythonhosted.org/packages/c7/8e/9d15bd356b0a019c965312b1a3c6a727cac4cae5bc40045fbc12ce4cff9c/numpy-2.5.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2fa3328f784fc8277fc48026f6cad516f5c561c5d8e2e39b3c9e0c8f23223b34", size = 17048364, upload-time = "2026-10-10T20:03:24.468Z" }, + { url = "https://files.pythonhosted.org/packages/dc/fe/9d5b560db964f15871885f2250795d15945f8699e17ef90c0c2ff4c875b2/numpy-2.5.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b86966fbe4ad7de710422175572bcdc75fdedadfb54bc6fab7deabccddd7780b", size = 18474904, upload-time = "2026-10-10T20:03:27.895Z" }, + { url = "https://files.pythonhosted.org/packages/e9/98/d27552990f1bd611ef3e7466adadc78312ea2df63b83aad47fdc3d3ca8df/numpy-2.5.4-cp313-cp313-win32.whl", hash = "sha256:5258bc06526964be5face2fc6f756857a3f24f21ec3e72ca131337a75b165d6c", size = 6134537, upload-time = "2026-10-10T20:03:30.511Z" }, + { url = "https://files.pythonhosted.org/packages/90/8c/140a40398a66b4471211be1affdb6ed24c486d581bd28d07b7f2fcb69540/numpy-2.5.4-cp313-cp313-win_amd64.whl", hash = "sha256:8b4d2fd2d34e5f8c9235ee787de5631a37a28402b15cb80814df973d2be54129", size = 12566113, upload-time = "2026-10-10T20:03:32.612Z" }, + { url = "https://files.pythonhosted.org/packages/34/52/01d205e5e8ccb27b2b0b141e801f22b830198c979111b0fa44771438d9a9/numpy-2.5.4-cp313-cp313-win_arm64.whl", hash = "sha256:bc39ac66a7a9a3fbd6134fda43136b60ffde99c8f4501e64e0d2b24da137babf", size = 10519523, upload-time = "2026-10-10T20:03:35.163Z" }, + { url = "https://files.pythonhosted.org/packages/99/ba/005cb5edd580d2f84d7ca3206b92dc17d4388e56e6f87ffe8f2762f83139/numpy-2.5.4-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:c668b2f0d651605b58892644b0e302c7157f7159544227758c896982ef384b18", size = 17005499, upload-time = "2026-10-10T20:03:37.961Z" }, + { url = "https://files.pythonhosted.org/packages/f3/49/fee7587c33ee35f7977f9051d7f2023d4e7246d62710c80f20c2361ea232/numpy-2.5.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ffa6ce09a1c6a08e9667dd9c97aa0b14184e8d18f2a14b78b2a2328c9147f076", size = 12019666, upload-time = "2026-10-10T20:03:40.606Z" }, + { url = "https://files.pythonhosted.org/packages/d5/b2/c6ce165acffceb15a82c07b9cc77d391f86b3f379ba62911908ae5d34b91/numpy-2.5.4-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:956555e0603a4d38019ae6925711cb9dc43195c076a928accf7ea5d50bddfe53", size = 5455617, upload-time = "2026-10-10T20:03:43.138Z" }, + { url = "https://files.pythonhosted.org/packages/77/7f/dd85ce260a669a89be06842cf355d7353a33e6cfbc590fb8ebb947d88dc9/numpy-2.5.4-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:2c2c4afffdeb7920e445028dd71eb932cac3e704792e964bc2a232426d4f1255", size = 6791932, upload-time = "2026-10-10T20:03:44.874Z" }, + { url = "https://files.pythonhosted.org/packages/63/d6/34b0a2b0741386a63025a65a2c09caaaaaad6d0ca95b66cd65c30dd7fcb5/numpy-2.5.4-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4054173604cd8658796053f1f3bc0befb68ec1c0762c57fdad61e199256a8617", size = 15710899, upload-time = "2026-10-10T20:03:46.839Z" }, + { url = "https://files.pythonhosted.org/packages/16/d5/928078d2b28f26829b138b4a6c3980045022fb409f570657a224ae60ef4e/numpy-2.5.4-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d549420b8858885cea8838a727842249218b9c1da24dd517e25c9c7a948310a3", size = 16721710, upload-time = "2026-10-10T20:03:49.489Z" }, + { url = "https://files.pythonhosted.org/packages/f9/cf/673fd1b8f4cd78eb6320e87ec4c90ac19c095644259e3749853a405c70f4/numpy-2.5.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:823874a507a84af050493b622affde94b6f7c3a0dc22cb2801381bc03b871c00", size = 17066182, upload-time = "2026-10-10T20:03:52.25Z" }, + { url = "https://files.pythonhosted.org/packages/f3/92/a77b5061b1b3e2643928c37976d79ee173e1b171ed158b7a3c61056b41bc/numpy-2.5.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:4e263278bfb5ee6409db8aedbc4cc32973b1b82bc1e8d3c668551d04d83a7e37", size = 18480315, upload-time = "2026-10-10T20:03:55.39Z" }, + { url = "https://files.pythonhosted.org/packages/bb/1d/1486ef3d3fb2279fd93c4c43c1bbbf1ca389a19816696684409f71babaab/numpy-2.5.4-cp314-cp314-win32.whl", hash = "sha256:cfd73180400042a7c532d30c5e287bdd03c59ff9ee1b4c0316af0539e29dfe23", size = 6185739, upload-time = "2026-10-10T20:03:58.186Z" }, + { url = "https://files.pythonhosted.org/packages/52/9a/e1e512ebc948d5b9dd33b08736760f0ebbed2848fd4eda1f553088a6dcee/numpy-2.5.4-cp314-cp314-win_amd64.whl", hash = "sha256:2ca144f15135b6212a5c47b1e2aeca6e412f102f95a2d5d88d8aec77eb255de3", size = 12703552, upload-time = "2026-10-10T20:04:00.28Z" }, + { url = "https://files.pythonhosted.org/packages/2c/05/de709a982d7bbcd688a3fad71f002e9ff80c2db39e03ee726609b610f1d1/numpy-2.5.4-cp314-cp314-win_arm64.whl", hash = "sha256:468397ba3c64427474706e5c9123fe266395496714dc684294eac75cd4930d1e", size = 10803901, upload-time = "2026-10-10T20:04:02.659Z" }, + { url = "https://files.pythonhosted.org/packages/13/34/083570ada3bb2a30fbe5d77c8c6fef9141144a15d33e6f793a67e9749ab8/numpy-2.5.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1ef3aa6d7e29bb13677323114280b05acc57607fa2300e66432d665d5418a162", size = 12138695, upload-time = "2026-10-10T20:04:05.012Z" }, + { url = "https://files.pythonhosted.org/packages/94/06/1f9c24db48eef0c2d1207e3b11fffb0478e39dfd8c1e1be7476936885eed/numpy-2.5.4-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:98b053943e5a0474ec0da309d2cb9d3f18ea57f8a2067c2ab7b5f763d1068380", size = 5574615, upload-time = "2026-10-10T20:04:07.316Z" }, + { url = "https://files.pythonhosted.org/packages/da/0f/593fba2e1560e949123bc7d2fc48b5893d56e58cd4bd5a273d2fbf60b220/numpy-2.5.4-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:b64a85f40e154983960a4167d4c1d57a50c7f109b3d3264a3a984154e90a8454", size = 6889383, upload-time = "2026-10-10T20:04:09.918Z" }, + { url = "https://files.pythonhosted.org/packages/eb/9f/b799dfdce4e05e80ed4bc815c71ff343a11533b2c0ffc221cae8538cda63/numpy-2.5.4-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a813ed7719bf45463c51779e6a98d0385fe905e48447526938a4b8337333d551", size = 15753763, upload-time = "2026-10-10T20:04:12.278Z" }, + { url = "https://files.pythonhosted.org/packages/34/88/16c5f12f86f5ad2817c4d103205131fc6c8acb3d1878af05a1a4f23ec859/numpy-2.5.4-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c9b80cdf5cedba0e90d93fa5f9a333c4d65bd545cd669b71bb97ce2b703c9d73", size = 16757212, upload-time = "2026-10-10T20:04:14.799Z" }, + { url = "https://files.pythonhosted.org/packages/ff/4f/a1fe40e18a898e6a5089f4f0d891f0a493eb0574d5b34458f0fbe5aa3e5c/numpy-2.5.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:2199ed071f460487c8db2c0e5c0b564494190edb4772fe80f9aad88b2604def5", size = 17116471, upload-time = "2026-10-10T20:04:17.58Z" }, + { url = "https://files.pythonhosted.org/packages/aa/46/e923a11c78e65c1722e7aaad817c06bd591324174b9d28ce5d31eee4d432/numpy-2.5.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:64f9c9878c1938476365e11ccfb6b770f3b9e5f045ccddc514235041e6959365", size = 18524063, upload-time = "2026-10-10T20:04:20.365Z" }, + { url = "https://files.pythonhosted.org/packages/5a/fa/84ab064514440c1f64a1b21088f2c82756defdd05e07c75ab233899565b2/numpy-2.5.4-cp314-cp314t-win32.whl", hash = "sha256:64d1c8ac28a4077cf987e0a71a7a0ef7e2df70722f07f0baa42dbb7eb6938647", size = 6340926, upload-time = "2026-10-10T20:04:22.865Z" }, + { url = "https://files.pythonhosted.org/packages/7e/7e/6cd886876f435b10685db9b9f7eeb70356f99e052116f4e5f11c5792c714/numpy-2.5.4-cp314-cp314t-win_amd64.whl", hash = "sha256:067374eb538c34c745436365cf7b0112595c1d326f21ce4ff340f61230239fbb", size = 12901584, upload-time = "2026-10-10T20:04:24.99Z" }, + { url = "https://files.pythonhosted.org/packages/38/1b/3c1684f6a06f7307f2335fca6e486cb162847fb97e91d65f8eb5cabad213/numpy-2.5.4-cp314-cp314t-win_arm64.whl", hash = "sha256:e94aef2c639da4a960ad0db8e06471208d8589974953d78b61d345b4eb99e394", size = 10891152, upload-time = "2026-10-10T20:04:27.52Z" }, + { url = "https://files.pythonhosted.org/packages/08/f4/3224deff3af2bef6bc0b175369698d8cb348f3d91d9bb0286cd5c9eae9e0/numpy-2.5.4-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:8dddfbee2e68d26d0d7d7d9cb247b1fd4409241cce32d815a11d97ec2cfde179", size = 17003231, upload-time = "2026-10-10T20:04:30.021Z" }, + { url = "https://files.pythonhosted.org/packages/be/75/fee0b8c6d94b44b2fdfae74f6a4ad5a138739589a8aebaec28ce4e713ed5/numpy-2.5.4-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:81e3420b27048b65eb14c3acf0c174a8cb0e023277716110347d2dcb26026dad", size = 12018300, upload-time = "2026-10-10T20:04:32.519Z" }, + { url = "https://files.pythonhosted.org/packages/47/c0/d0b335a499a04b65f532c3f034346ef390f81299060f928492dabc1e0272/numpy-2.5.4-cp315-cp315-macosx_14_0_arm64.whl", hash = "sha256:0b4724a19de67bea8cfc4970798efa78bcbbe2ac2613cfac16721a42d44de2a5", size = 5454250, upload-time = "2026-10-10T20:04:34.943Z" }, + { url = "https://files.pythonhosted.org/packages/5a/0e/461b3783c03d668052e6a21b01b673db6ffcb7831fd32d9aa5368c1cd426/numpy-2.5.4-cp315-cp315-macosx_14_0_x86_64.whl", hash = "sha256:2132418bf8dd124a427ca9e6a1daf9ee1a87185344c95119ceae868b99466da1", size = 6789644, upload-time = "2026-10-10T20:04:37.258Z" }, + { url = "https://files.pythonhosted.org/packages/b3/02/5dad269b02166965a7b4ca14adaddd75dbee0de42435bfecf561b84ba5a6/numpy-2.5.4-cp315-cp315-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:325518d4245b9e331387702aa58c2ce1dc4cdcbb41dfb4ccd5dcbc7e08db1266", size = 15704353, upload-time = "2026-10-10T20:04:39.616Z" }, + { url = "https://files.pythonhosted.org/packages/93/3a/01360c8036822ed9f7aa32189a77d1476567ec1e8e1383522389e4faac45/numpy-2.5.4-cp315-cp315-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:56733449d2544178beaa4545cee357370440cf056c197f9c7bfb19dbfdd0e86d", size = 16718648, upload-time = "2026-10-10T20:04:42.383Z" }, + { url = "https://files.pythonhosted.org/packages/7d/5c/b863a2c093c4d6f21a597fcaf24ead0835c09ab16a8312d5a5a8868af683/numpy-2.5.4-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:5ec3753760c1a6d8bb91200666e545c3a9728e6269dfb5d6ce02340996698aa3", size = 17059053, upload-time = "2026-10-10T20:04:44.976Z" }, + { url = "https://files.pythonhosted.org/packages/0a/60/ced4f57f9a1258a0af74f17cb0b0c2700b5c67cd6678823c803b263e4df3/numpy-2.5.4-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:b1185012870173de7ae33d370bd45b1cf5baee747ea4b97036b65f4e93016877", size = 18477406, upload-time = "2026-10-10T20:04:47.863Z" }, + { url = "https://files.pythonhosted.org/packages/f9/bd/0ef22dafaafcc7d4bb3ca26b8d2afbd55dedad8eaba99a8c864e1997456f/numpy-2.5.4-cp315-cp315-win32.whl", hash = "sha256:298eca75243f2cbbfdb460560b9fb2a1792a33cf2ab4286efd43d92e8d3df508", size = 6185133, upload-time = "2026-10-10T20:04:50.467Z" }, + { url = "https://files.pythonhosted.org/packages/50/bc/d2651b155ecc608a77e6f4d15495c11f14f19bb98f8bf0c5b0d38f86dda1/numpy-2.5.4-cp315-cp315-win_amd64.whl", hash = "sha256:332f3378fe077dd850e677ec01bdcc4f22368fb5d50ef10b2c79230b1bf5a592", size = 12703085, upload-time = "2026-10-10T20:04:52.63Z" }, + { url = "https://files.pythonhosted.org/packages/dc/d2/45e404f8abb26fb9eda12b94012936873e827b1be76f2ee7890be128312e/numpy-2.5.4-cp315-cp315-win_arm64.whl", hash = "sha256:d4cccbbc78717966f764cd3af4fb70276fa01fc7a2688af11c78901fa5c04f05", size = 10801451, upload-time = "2026-10-10T20:04:55.677Z" }, + { url = "https://files.pythonhosted.org/packages/c6/c3/2ae14e09cfdb67dc187a342e15308a21c15bf4d2071f8079e6aee5fe56dc/numpy-2.5.4-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:950ea81d57ef070665581b6e1b5f6a029306423cd1739c5b95fe78aa30db6b9d", size = 17097121, upload-time = "2026-10-10T20:04:58.403Z" }, + { url = "https://files.pythonhosted.org/packages/f5/cf/305ae624ef8a039414317224abe9ec9c2fe7ea3c2e1cf204d43ff6b2ffb9/numpy-2.5.4-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:c05ede731b03fb1b7591faca9389ade3267d2bddf1ad8882bb3f2cc5e101694f", size = 12135439, upload-time = "2026-10-10T20:05:01.65Z" }, + { url = "https://files.pythonhosted.org/packages/a9/a8/f75c63813aef95827bb2c0d13b12803016853056e8792c280058cdbfe783/numpy-2.5.4-cp315-cp315t-macosx_14_0_arm64.whl", hash = "sha256:5fbf7141bbfd63aea22f435c9062a032b9ea0082fe9845dad7f021d3f1234e71", size = 5571451, upload-time = "2026-10-10T20:05:04.135Z" }, + { url = "https://files.pythonhosted.org/packages/6f/0f/f17763f983868b5c49b4101ebd7e00760bd1769478a6bb6a8de6e085bbac/numpy-2.5.4-cp315-cp315t-macosx_14_0_x86_64.whl", hash = "sha256:3573cd22564692a5b899ec344e5d5b9cc4576f2985b96f22af3564ed54f2710f", size = 6883356, upload-time = "2026-10-10T20:05:06.249Z" }, + { url = "https://files.pythonhosted.org/packages/67/a7/8af04c5a79e047996cfa38854dcfbececdd0343a7c933a46fdd03ef6f5da/numpy-2.5.4-cp315-cp315t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6c109eac9cd439193678f69d70733c1108487546ca8eafc107b510ae10c1aecd", size = 15750991, upload-time = "2026-10-10T20:05:08.376Z" }, + { url = "https://files.pythonhosted.org/packages/57/7a/648254290d0c504faa8f2d07aa206660c728802c781a6f3fc68ab7cb5d71/numpy-2.5.4-cp315-cp315t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:80d6ef6e8620eb2c2b4c4caad50b5935d6db3cde2d51581b55dcc79e14016d1d", size = 16757675, upload-time = "2026-10-10T20:05:11.393Z" }, + { url = "https://files.pythonhosted.org/packages/b8/fe/4a8c3cdb0c70400cfe4c5bec42d3099a5673802a95064614b33e07b82aa1/numpy-2.5.4-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:77045a4b175bbf5316ec08003880804336c78f92281a1b72222b274ea85ec5ac", size = 17113846, upload-time = "2026-10-10T20:05:14.49Z" }, + { url = "https://files.pythonhosted.org/packages/1b/7e/619692bb67778702c0e9eb2d468568a7573f4e269386ea61aed01ee4e557/numpy-2.5.4-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:0f02a46e49cfb6c73bdb7aea1c0d3461dbae9aba613542b65f657cd3d17b9fab", size = 18522915, upload-time = "2026-10-10T20:05:17.33Z" }, + { url = "https://files.pythonhosted.org/packages/b7/b5/4da41c328788f575838f97a098fe8ca691ebc6f6fd73ad4a262ee40b184d/numpy-2.5.4-cp315-cp315t-win32.whl", hash = "sha256:ad62a416ddcf863bf44bba76fbf6b53366ab0692e294f51cae4b5fbe0d246788", size = 6335804, upload-time = "2026-10-10T20:05:19.921Z" }, + { url = "https://files.pythonhosted.org/packages/98/94/6482ddfa3d312490cb9358f375bf2ad56427dbea8769187158e94d653753/numpy-2.5.4-cp315-cp315t-win_amd64.whl", hash = "sha256:38f47be9f74ab870d2633b5456ae519c43758a8d1fd05342f0ce4ecc034396ee", size = 12890095, upload-time = "2026-10-10T20:05:21.875Z" }, + { url = "https://files.pythonhosted.org/packages/48/7f/c2d1b436b6e7cfebac140c2579a298344b85f2991a2ce5c3615cefb29400/numpy-2.5.4-cp315-cp315t-win_arm64.whl", hash = "sha256:7a14a461d9340f1b46b8648578aed9cdb8b3b018a8fac6c1dde2c9192a01a87f", size = 10883718, upload-time = "2026-10-10T20:05:28.547Z" }, ] [[package]] @@ -1644,51 +1656,63 @@ wheels = [ [[package]] name = "obstore" -version = "0.11.1" +version = "0.12.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions", marker = "python_full_version < '3.13'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a2/27/aa7549157a4a681157e315534ba5ab8f167f77662166e792a6e836938f46/obstore-0.11.1.tar.gz", hash = "sha256:a5afe8b99e3b20cdc9133be7a1b381259acf0d470029f6b2fc79c3f9947ad436", size = 130828, upload-time = "2026-08-21T23:57:27.837Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/0d/38/438f85772bfbcd2985845a5d00d1c910e98b15f587dae6cb1e435865eba9/obstore-0.11.1-cp311-abi3-macosx_10_12_x86_64.whl", hash = "sha256:d5c50b755d781efebe4f9c70ee6d00858e44d95f0229b8b5174358f861d9bd7c", size = 5400146, upload-time = "2026-08-21T23:56:29.168Z" }, - { url = "https://files.pythonhosted.org/packages/e7/62/edd2613649cb6b4400f5d125223ee094d2da1c4c93636453a5360c61b865/obstore-0.11.1-cp311-abi3-macosx_11_0_arm64.whl", hash = "sha256:093152daa5c32b70a032f231bbc6a7eff76dda4285eed5a81d272b4485032dab", size = 4596203, upload-time = "2026-08-21T23:56:30.7Z" }, - { url = "https://files.pythonhosted.org/packages/c1/52/09edd48251a26a65f786bf2def93994ffce501f3aea6724474d8de0a7f4f/obstore-0.11.1-cp311-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b97ee10456e65f166c030b5fbde8380ca508621e23b84efd77aad83afe99315a", size = 5003021, upload-time = "2026-08-21T23:56:32.085Z" }, - { url = "https://files.pythonhosted.org/packages/59/8f/89a28c75d46bbb0552d34a5e84c5bc512526d45f935321f00af2e6275a99/obstore-0.11.1-cp311-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:48983a143de69b11de49212caa79b5c39acbff7f592f9e85d156a0133a0e5796", size = 5240376, upload-time = "2026-08-21T23:56:33.658Z" }, - { url = "https://files.pythonhosted.org/packages/50/c3/b4620899003e2472bec7baf5d2bce12cf6f6f11d02d8f17e03e093717a53/obstore-0.11.1-cp311-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b9cb988e03ff963914cf2176aee293eaaff7dcf4efcb905ce6834264b4b0884", size = 5444946, upload-time = "2026-08-21T23:56:35.093Z" }, - { url = "https://files.pythonhosted.org/packages/bd/de/687685ab39ae4a70cc13ac252febfe62a387836a55e046f467c9fb231880/obstore-0.11.1-cp311-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:97850f68c8417f7167549bdb705c362c825ed470298c6b04faf52258c5e9234e", size = 5304065, upload-time = "2026-08-21T23:56:36.535Z" }, - { url = "https://files.pythonhosted.org/packages/b4/36/b962ad52031b5bfdd249923db6151ff2b665b08c017611db96838455f337/obstore-0.11.1-cp311-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2a9f3d66dbf3c073dd6b6033c0787ef14edf78d97bff95595d6f6979692da264", size = 5556770, upload-time = "2026-08-21T23:56:38.377Z" }, - { url = "https://files.pythonhosted.org/packages/e0/04/41a0f2917fcaa1e66b754fc98c92441d5a252ff9c619941ac086f9bdb4f6/obstore-0.11.1-cp311-abi3-manylinux_2_24_aarch64.whl", hash = "sha256:2ba3bceec4263b3a70eea873abedb82e03da9599f2326e80d6505cab0c10d401", size = 5337765, upload-time = "2026-08-21T23:56:40.279Z" }, - { url = "https://files.pythonhosted.org/packages/f9/39/0b618efe59ee4b58f16d2b9246674a70c390bebf665cd284b439bade9bd3/obstore-0.11.1-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:e956fa8a953b7eb8580658d68cdf37e410356032c17697a06e44d0e8c4084a0f", size = 5544012, upload-time = "2026-08-21T23:56:41.754Z" }, - { url = "https://files.pythonhosted.org/packages/57/3b/1018f6da240f3529d1cb9a836c79a91ce45349ba3cffcd6baa73296c7e6c/obstore-0.11.1-cp311-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:159a50d0f4cc53afe6f5c695313bcaf6e92ac81d7847692c8279153483272bfa", size = 5229851, upload-time = "2026-08-21T23:56:43.367Z" }, - { url = "https://files.pythonhosted.org/packages/35/2c/c2fe082816b255159373b15be3f28b2515d0eb954e248e2f65449c456a14/obstore-0.11.1-cp311-abi3-musllinux_1_2_i686.whl", hash = "sha256:128da07f3a1b9c70159e2b2be9e27f458a9d531d57b1856dd7c4ddb73b4c9937", size = 5361812, upload-time = "2026-08-21T23:56:44.871Z" }, - { url = "https://files.pythonhosted.org/packages/43/9c/656eb5cce818e7e3985d27f4bd403feb0b6e620494775a55a0943c8d2fff/obstore-0.11.1-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:67b8acfbbab960c2cb14c34294a96556caa67fcff15ee77c716d5c1bd1558606", size = 5790855, upload-time = "2026-08-21T23:56:46.391Z" }, - { url = "https://files.pythonhosted.org/packages/c1/5c/94ece1c902ff5cdbe1140997d1f720221a0d5defa902fdd93715297604f5/obstore-0.11.1-cp311-abi3-win_amd64.whl", hash = "sha256:e23ea15cebe5f5be5d11005043d7b5ff56848e39499681ef52f8227bd385bd51", size = 5308846, upload-time = "2026-08-21T23:56:47.774Z" }, - { url = "https://files.pythonhosted.org/packages/c0/af/235730a429e55777f15f9d7fef118a6ecf02f430ed4588deb4b7e13964aa/obstore-0.11.1-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:73284fc8a9804596baf4d80b55c0e71a6007487bfa28d6924acd85264d5be81f", size = 5424585, upload-time = "2026-08-21T23:56:49.532Z" }, - { url = "https://files.pythonhosted.org/packages/e3/3c/432ea70c061fc9401fe399391ed4f04dead4a4cf33ea7c9564ce0e299d24/obstore-0.11.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:8a3f93310422b153629af929d9e88d1fc826d5e32a456de4d3a1926a770ae09c", size = 4576791, upload-time = "2026-08-21T23:56:51.014Z" }, - { url = "https://files.pythonhosted.org/packages/43/c7/57dbdfa4ba5339b80fc755359bb232bdba7f82e92df6603ef383396b5f16/obstore-0.11.1-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d666690f0a53ae3df4be2c18af6820369a74c9c5b8960e8760f0f8c9a8f15b36", size = 4992141, upload-time = "2026-08-21T23:56:52.46Z" }, - { url = "https://files.pythonhosted.org/packages/37/4b/d25a4c129250364b0ca927fbfca5146744fb3bb4de60ae9b302300cbdfef/obstore-0.11.1-cp314-cp314t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0844ab75c8413c0af2d0fd2d2f6aa42d166809b6ac4be478151cd946ce7e5d69", size = 5217707, upload-time = "2026-08-21T23:56:53.946Z" }, - { url = "https://files.pythonhosted.org/packages/4b/07/0a925c886639d6edce37aafb6eba0c86820cbc9dda42e91ddd784dbe059d/obstore-0.11.1-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:307b5f9d64a7c00cd13371e376c05e3914216bd0818a19ff2ae05bc0135e01dc", size = 5429440, upload-time = "2026-08-21T23:56:55.507Z" }, - { url = "https://files.pythonhosted.org/packages/5f/c0/38d188a00b9f2de30df26802dd5bc2ecaefdabb882fde786cff800ccf50f/obstore-0.11.1-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6f89647953b4ea50bab3f66591f7f462cd454a5d2fbbeefd885716a2c54e13ce", size = 5308903, upload-time = "2026-08-21T23:56:56.924Z" }, - { url = "https://files.pythonhosted.org/packages/60/d3/815e5a7ca59f901b6d24fb01ad3af46de37dfdfd2971c019f914bdb7f65e/obstore-0.11.1-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50de3116af69b6f1669cb443cc200ccdc1523a790a41f00854331b48f716cf8f", size = 5547007, upload-time = "2026-08-21T23:56:58.508Z" }, - { url = "https://files.pythonhosted.org/packages/9c/a1/ec4d2291b7eb15e7ddccb2f0646ce560b8a539df4b0ab94d916e7a15a61c/obstore-0.11.1-cp314-cp314t-manylinux_2_24_aarch64.whl", hash = "sha256:eae71e1c5944ade976ce8cd1e780bed9b5c31d6d4f89cb7263e8dcff78f6cd8e", size = 5330062, upload-time = "2026-08-21T23:56:59.954Z" }, - { url = "https://files.pythonhosted.org/packages/0a/09/a4a1301f2474f6569bbc0f2d337108ff2225d0b3d285894ed9a6dc953438/obstore-0.11.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ff634b5edbbf76c56ae81397aa41500a66ad0aa48b1856dcc0cf31b159172dc0", size = 5539870, upload-time = "2026-08-21T23:57:01.485Z" }, - { url = "https://files.pythonhosted.org/packages/9e/8f/7dbbf935a07ff5375c33916307b77aef9d39bded35081530150822898010/obstore-0.11.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:cbe509350d66249fc9e65ece4e7b1855d650f18e477721887128452b95c44b24", size = 5222673, upload-time = "2026-08-21T23:57:03.023Z" }, - { url = "https://files.pythonhosted.org/packages/d5/89/4932b3dd7963a3a64fd532c30ccc836f0f4ede1a45bea9f12295299398e2/obstore-0.11.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:46122e585f48ab3f2e4fed51401a4e866279a3a3d1aee2372d598ab85ba2c539", size = 5335696, upload-time = "2026-08-21T23:57:04.439Z" }, - { url = "https://files.pythonhosted.org/packages/aa/ff/f3cf31ac4ebfa648dbd2c69774579fd5da4cf4b05fa1192405dafb6aeb38/obstore-0.11.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:7275e75228059b2b30772b2ba3e41562a4a92ebdd3a96619d300f98ef152119f", size = 5783641, upload-time = "2026-08-21T23:57:05.899Z" }, - { url = "https://files.pythonhosted.org/packages/51/31/b352db3450e700c2c51861d139fc92c3284f7e578ca306408b054f6b4a35/obstore-0.11.1-cp314-cp314t-win_amd64.whl", hash = "sha256:13bb0b6a40931ab2da93f93ad843865d483d95acec1bd586df79313daa5a50af", size = 5290060, upload-time = "2026-08-21T23:57:07.632Z" }, - { url = "https://files.pythonhosted.org/packages/4e/94/66f366c697ebf51201e8f0084bfd91f90c59cd3ba40306fa624add3fc8fd/obstore-0.11.1-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:68adc24a822536148a3c12be0b7cac091239b3f99c0589b16bcdba5a6ccf58e4", size = 5409081, upload-time = "2026-08-21T23:57:09.157Z" }, - { url = "https://files.pythonhosted.org/packages/d9/10/9ac2a43e3c25c386973b7e505d853b2ebaf13145a28214ec33d0d0cfe38b/obstore-0.11.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:ed098bea084f8d626d91facfebdc8fe49115b96a339465db6fea681635fc7440", size = 4604614, upload-time = "2026-08-21T23:57:10.61Z" }, - { url = "https://files.pythonhosted.org/packages/a6/f6/6fdc1fa1a5dec9bb11ac99974bcccd0dbb3541c999c402f34c0ff7f75f0a/obstore-0.11.1-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2d80248f106bf9a2860a4b21add7d14c95eaa5785c7fe11e56d28a4602c7a07c", size = 5009651, upload-time = "2026-08-21T23:57:12.217Z" }, - { url = "https://files.pythonhosted.org/packages/a7/a4/dfcfff02f7358a41316efe8e441d5726a6f9420e6ba0499b128633f0b851/obstore-0.11.1-pp311-pypy311_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3ad616bd597a453dcb225ef6bec2af0c71b24b22c314a4328bcc216f774d6b9a", size = 5248378, upload-time = "2026-08-21T23:57:13.715Z" }, - { url = "https://files.pythonhosted.org/packages/4e/ed/986f16d4e1d8204f13a37f849fd66c33d9203f0128d4ec56fc951b64db94/obstore-0.11.1-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8a399e7c816e8d7bfe5e992a04e25326bd0779822b65bfd3dbc943b8a62192dc", size = 5448683, upload-time = "2026-08-21T23:57:15.216Z" }, - { url = "https://files.pythonhosted.org/packages/a5/f4/e4d84c556d06e67184a790ba9c8d62073fd9f9d7b001bf12a84cd1b4bf57/obstore-0.11.1-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6680094928da4587dfd03be4e9c17ce7eaa1ec287f49519c75896819c0767247", size = 5310662, upload-time = "2026-08-21T23:57:16.951Z" }, - { url = "https://files.pythonhosted.org/packages/08/45/b4d665cdd42874d1454c124b710a8874c1c9be5b1268dc8dc74c753b4132/obstore-0.11.1-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b3ab26d158a096c750759981f75f279f6568d945b8fe640e0cebfdff8d601044", size = 5560244, upload-time = "2026-08-21T23:57:18.654Z" }, - { url = "https://files.pythonhosted.org/packages/9e/99/3e4c7b093f8eef1d46c4a9590a7d6a4955b05a8a14249854abebca75efdc/obstore-0.11.1-pp311-pypy311_pp73-manylinux_2_24_aarch64.whl", hash = "sha256:7b1b769fda200cbee559da2100586b3b7810bc9f207fe41b9a00fd749c997799", size = 5346961, upload-time = "2026-08-21T23:57:20.222Z" }, - { url = "https://files.pythonhosted.org/packages/f6/b6/52b9d69211721b55fe31af4c0b1da538c9db481d5065832ed12dd3939d4e/obstore-0.11.1-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:801f57c06df5d81eaff8ed553129cedadad12cf1d76fb7499fd6daa1d996c72b", size = 5557384, upload-time = "2026-08-21T23:57:21.765Z" }, - { url = "https://files.pythonhosted.org/packages/49/15/dd7c9a80f530a8e7ff8a1555e275d5316b2fe3b395771327492f02a81033/obstore-0.11.1-pp311-pypy311_pp73-musllinux_1_2_armv7l.whl", hash = "sha256:b895230ad67a7b9a2c7dfff7c7c9801570a9dfe71f7e383013f831d35b74ea2b", size = 5236324, upload-time = "2026-08-21T23:57:23.186Z" }, - { url = "https://files.pythonhosted.org/packages/c3/37/6196cbac2e5e2f4fb74b2226103d646ecf7089aa3b46a4edb1b3e3de40f5/obstore-0.11.1-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:955f2348bd17beb80f3f96bb52b119e61eee99c97d1f0b103aa61a0840b7c862", size = 5369644, upload-time = "2026-08-21T23:57:24.716Z" }, - { url = "https://files.pythonhosted.org/packages/09/77/a453c19c95121d62c5c734f355ab90592a070288cc84ee87b85ad076174e/obstore-0.11.1-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:3901f539d764cd2ec74c4e900dd461d89d765b2cacf06379ac7b7014e8d132ca", size = 5795546, upload-time = "2026-08-21T23:57:26.213Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/23/65/e14b7dba4ed71c3042fb9605fbfe3a8681bade1f5f90f18adbdc635b7c65/obstore-0.12.0.tar.gz", hash = "sha256:3f355f3333084172a8e8da2f62f929e2dd5d0909f6ba714a0b42cb1774a5be3c", size = 138191, upload-time = "2026-10-15T16:41:38.735Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bc/18/8c236dc6c93c57f3796afaa3a11c7ce943da2cf268bb97cfe65ebd6db9e8/obstore-0.12.0-cp311-abi3-macosx_10_12_x86_64.whl", hash = "sha256:6fddc673fee8ddade9d49bff32de4815afd30b28ba25cbdc29d87639149cf62a", size = 5417987, upload-time = "2026-10-15T16:39:52.308Z" }, + { url = "https://files.pythonhosted.org/packages/5b/a7/f8da19362e555e1b826509c6197ba2f98f8a700c4fa4dc0761c2401453f5/obstore-0.12.0-cp311-abi3-macosx_11_0_arm64.whl", hash = "sha256:656b9ae76280951f54503e883acb693f0fb4a3778d559733bdc4b996b2078715", size = 4615162, upload-time = "2026-10-15T16:39:54.81Z" }, + { url = "https://files.pythonhosted.org/packages/11/b6/e342f399a8c664a6f3ad61fb1a4a1f77e935d14bc7aaba3525b1a9bcc93d/obstore-0.12.0-cp311-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:76b942f2cadbdd35d1a37df8c56a51a00e8279feafb9b3fde4ab161b5ca7a114", size = 5025558, upload-time = "2026-10-15T16:39:56.61Z" }, + { url = "https://files.pythonhosted.org/packages/14/0c/4612546a8701c9d951920e0087be25d74865faed852731de69b2d9d63f51/obstore-0.12.0-cp311-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d87a362bfb078e015c2ab588ac38274db917c5563be2cd0306b51347db7d6edf", size = 5268378, upload-time = "2026-10-15T16:39:58.287Z" }, + { url = "https://files.pythonhosted.org/packages/fe/90/059ea17c30eef9f1025cd171d47360ea701ba8ca619302a75cf2a176bfdc/obstore-0.12.0-cp311-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a8d00b374ee368db3e8f537b4bab7c45bf6077d3f6c07ec93281f1cfd087271b", size = 5438991, upload-time = "2026-10-15T16:40:00.439Z" }, + { url = "https://files.pythonhosted.org/packages/fe/6a/fe36b87c170b7bafd6ec6162f923eed213a503e621bc699ee2fc296a0e93/obstore-0.12.0-cp311-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7789fda63972d2fac0107afe5aac8d362b4d248971217f26a29bb15ac21cbdf5", size = 5348697, upload-time = "2026-10-15T16:40:02.618Z" }, + { url = "https://files.pythonhosted.org/packages/70/3f/57e4173cbd61f1354d4f3f289a61a4271d8b3afa3a7f2efe519014697043/obstore-0.12.0-cp311-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa53318c8892c612a58d0ac6c1cf26d9f5b697ca880de4133e9bd4868e90e5be", size = 5575588, upload-time = "2026-10-15T16:40:04.29Z" }, + { url = "https://files.pythonhosted.org/packages/89/cb/ec80f278d834330b40834e34cf1332444fcdebe21c43f67e34b323ca2ece/obstore-0.12.0-cp311-abi3-manylinux_2_24_aarch64.whl", hash = "sha256:bf6b48324a6fec42088a9918c63ab064567d9544414aac086f65adc6c16ad2e1", size = 5340287, upload-time = "2026-10-15T16:40:06.166Z" }, + { url = "https://files.pythonhosted.org/packages/e4/01/bc007efe8a55ba4131a331fd47858d188ecaa3e23cf59bccb33bac2bd1c8/obstore-0.12.0-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9980becd715aa1cd7bd7ca13eeabae176e6713b6e39577622d964e31540a65e1", size = 5547826, upload-time = "2026-10-15T16:40:08.019Z" }, + { url = "https://files.pythonhosted.org/packages/3e/ae/57793c16df57064f062747d35a31167add5b68a7bfa1cf6f8ed929701b31/obstore-0.12.0-cp311-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:4b5fb3ef86cba98b280c6f02021274060787d5984ffe3791fdea9cdcea3f4bb6", size = 5249557, upload-time = "2026-10-15T16:40:10.148Z" }, + { url = "https://files.pythonhosted.org/packages/e5/37/878d4cc92c55114ee513bb77d4194ccbb99a87920e6345a1e972057383ec/obstore-0.12.0-cp311-abi3-musllinux_1_2_i686.whl", hash = "sha256:ac553dbe800be6b64cac1a4725c620ff5d474fd543deca90082dbd1fbfad2f1d", size = 5393052, upload-time = "2026-10-15T16:40:12.118Z" }, + { url = "https://files.pythonhosted.org/packages/4a/6d/a9e560f1232446cf1fc3cc1f03b5b7a9786e8312b6e543348d75a879e608/obstore-0.12.0-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:2956f243e459413f1f4bbd1195c721ca5a6c16d7de8bcd8241c2545e0bb87c18", size = 5809599, upload-time = "2026-10-15T16:40:14.398Z" }, + { url = "https://files.pythonhosted.org/packages/d4/1a/7586a93446b8ffff7bf9a1b41b1977ab125f4ef45e96d9b7b3f468961ffa/obstore-0.12.0-cp311-abi3-win_amd64.whl", hash = "sha256:7aef72fbe4631dd5462714309e55a3ceacfe15c5f31f518ddac41cc33a8cfcb0", size = 5341295, upload-time = "2026-10-15T16:40:16.498Z" }, + { url = "https://files.pythonhosted.org/packages/aa/97/8e68f15b93529939c6390bbbf59114e11017580629b8d14c4046dadb60d6/obstore-0.12.0-cp311-abi3-win_arm64.whl", hash = "sha256:e64494202759211cc42fa381d8959982473c9080a21aefcbf20121bdfb49a2cc", size = 5212128, upload-time = "2026-10-15T16:40:18.659Z" }, + { url = "https://files.pythonhosted.org/packages/4b/da/e51e62cb95c6c5531e49ea3aaf15df0e9fb34be0101bf4596d55afa6f820/obstore-0.12.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:728347d647edf981b7129899981a73ff0f0467225896d3747a6d4e993b7ee441", size = 5434598, upload-time = "2026-10-15T16:40:20.519Z" }, + { url = "https://files.pythonhosted.org/packages/e2/ba/1c2b8afedea56950785e43bcb0efb7e06e449ca147845e267e2e84fc317b/obstore-0.12.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2982abef7caa064dd412236d41e630a9b49dfb8265f62ca004059446624e9a73", size = 4584517, upload-time = "2026-10-15T16:40:22.326Z" }, + { url = "https://files.pythonhosted.org/packages/29/10/9607706283e00a4f3a8370a3d77486fd72e53e089734720054755846bf29/obstore-0.12.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fa8e979499741c57798d2daa048e4f42835ac9a8497f9255796db478e9a38d1b", size = 5419035, upload-time = "2026-10-15T16:40:24.283Z" }, + { url = "https://files.pythonhosted.org/packages/6f/bf/57f52ca4a48bdb469a84cf4f3334e60324a5dfe24f7bfb180e3a7b1a57ca/obstore-0.12.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:58a3da66f68a98c559011f0099f9f3961db27df84218576cc1f95f4a88b5b8a9", size = 5346814, upload-time = "2026-10-15T16:40:27.197Z" }, + { url = "https://files.pythonhosted.org/packages/26/ba/ecfc87519c17b2bba6c7275d754631b6ed1232f3f35e156b7a6749ba37cf/obstore-0.12.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78c1cd6498e52410f9663134c277b07234960ef2a78eb4988327c2d5d9345986", size = 5558323, upload-time = "2026-10-15T16:40:29.812Z" }, + { url = "https://files.pythonhosted.org/packages/56/49/4943918d58eff01789420f399484f867cef172822a7b77ec6087a3d4f6ab/obstore-0.12.0-cp314-cp314t-manylinux_2_24_aarch64.whl", hash = "sha256:a30dd09e62cb59f4c181f69b4e76780d6eaf0b3abb771fee3ac03f0e63109d1f", size = 5326762, upload-time = "2026-10-15T16:40:32.021Z" }, + { url = "https://files.pythonhosted.org/packages/dd/86/1eee013a94be33eea2ea618af4f3777b620dea7112e0c1b5356f082622b5/obstore-0.12.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:2622dc12100ca4b870a987ab9c0f4c3c1ca7122f14459bf66f834774790c9673", size = 5537435, upload-time = "2026-10-15T16:40:34.244Z" }, + { url = "https://files.pythonhosted.org/packages/45/b2/df7567ce2e689999f8ec455a4f825b83e6c4c3935213696a80050f4fe402/obstore-0.12.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f89ba827fe466d31cf7977525ce9ce7072e1395c98920b4846ae84a24c0b21e0", size = 5787179, upload-time = "2026-10-15T16:40:36.886Z" }, + { url = "https://files.pythonhosted.org/packages/17/09/f0ccf26d95193c7f0e75183b52926a4b30f5af69b1539f6f44b27520a43b/obstore-0.12.0-cp314-cp314t-win_amd64.whl", hash = "sha256:75dabd1c785cee1d1cdb7fed4906eebcaf7dbb3ed7faaa862b6f54af59444a22", size = 5319434, upload-time = "2026-10-15T16:40:38.857Z" }, + { url = "https://files.pythonhosted.org/packages/13/54/f80372e4773977c8f83f74ecd234c53273db32bb2f173aa7c49d5dbc805e/obstore-0.12.0-cp314-cp314t-win_arm64.whl", hash = "sha256:09f6f78e888a4b5a037b2c405456868102e3478a3fedcb3096afc51c26028f67", size = 5190455, upload-time = "2026-10-15T16:40:40.781Z" }, + { url = "https://files.pythonhosted.org/packages/ab/39/b80c381a82819f226219a588abdaed8bd30f27deb3afea99845a32c357f8/obstore-0.12.0-cp315-abi3.abi3t-macosx_10_12_x86_64.whl", hash = "sha256:0b8c8932d5234ec718e31ddcf9c4776c0cd8c694ea54baf555bffbbb8ca297e9", size = 5481244, upload-time = "2026-10-15T16:40:42.569Z" }, + { url = "https://files.pythonhosted.org/packages/89/4a/b07c3964c48e39bc8dc704254b47c850b75c22ba3ab4a392f77903ab6503/obstore-0.12.0-cp315-abi3.abi3t-macosx_11_0_arm64.whl", hash = "sha256:d35bf87c228466d3920e7f98ca1ba1471ca864c5701f3be38aa442eb51ed290f", size = 4628787, upload-time = "2026-10-15T16:40:44.506Z" }, + { url = "https://files.pythonhosted.org/packages/64/86/e190c535ed9d951dbbf28d716f1dc26a82f04ba597b59043f993b51a0ab2/obstore-0.12.0-cp315-abi3.abi3t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9a4c300fa1ab96f074df959e70cbdb37c0bb17d81d0b296b0d7fdd7a6fca4307", size = 5053980, upload-time = "2026-10-15T16:40:46.576Z" }, + { url = "https://files.pythonhosted.org/packages/6e/e3/5ea350a22070352859a9ccbc007061ffb9c3393c1c37de9cdbccbdc46eb9/obstore-0.12.0-cp315-abi3.abi3t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ac9909a21eea8ffb25dab9dad6491bac6b3234f93164cfa4b8638efb0fe248e6", size = 5282271, upload-time = "2026-10-15T16:40:48.615Z" }, + { url = "https://files.pythonhosted.org/packages/5e/8c/c3c87eeff228dea2dba146c20c0d94c3cd4a3c2d29027ed755bc2e8e1cea/obstore-0.12.0-cp315-abi3.abi3t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:000677a8bd10f2752cfea37a5f5f2a81414c0df07e9c5e3af4f1d1396e90250c", size = 5462575, upload-time = "2026-10-15T16:40:50.695Z" }, + { url = "https://files.pythonhosted.org/packages/2d/73/ffbda3894e7ee0ff42ae7e58e2c6007ee0433df261f62db31fabca7186dc/obstore-0.12.0-cp315-abi3.abi3t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3cf58ff29e0bffacfde8dd1858b57cc38f6a23e49f9357eb3c2ace9d741b3bbc", size = 5388817, upload-time = "2026-10-15T16:40:53.045Z" }, + { url = "https://files.pythonhosted.org/packages/e5/ce/cb0660726e2600d8fde9997cf5ddd90773379f5dd287a9411b83a7a6a29a/obstore-0.12.0-cp315-abi3.abi3t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:38acd564329c8732ea61a4c1f4dcb1ce34c4394371c42f4e075ee104268ddb7c", size = 5603974, upload-time = "2026-10-15T16:40:55.547Z" }, + { url = "https://files.pythonhosted.org/packages/4c/fa/6471d396c57d67ee09343ddc64a6723aa9bbe95039409981858ad708abea/obstore-0.12.0-cp315-abi3.abi3t-manylinux_2_24_aarch64.whl", hash = "sha256:c0c7d5d9735bbb06e0f824d2af64e0335ff63a18fac8cbd2f83943814779a2de", size = 5369797, upload-time = "2026-10-15T16:40:57.788Z" }, + { url = "https://files.pythonhosted.org/packages/12/81/0db46c02ffaf3a2db0a8db5a593e1d3a9c29bf7192bbb75fe6deaf79a7d8/obstore-0.12.0-cp315-abi3.abi3t-musllinux_1_2_aarch64.whl", hash = "sha256:cfdd2c7d04c9f81d3e110ba1e6690eddbeb9f9dfb932076624fad9076a970583", size = 5579688, upload-time = "2026-10-15T16:40:59.786Z" }, + { url = "https://files.pythonhosted.org/packages/4d/ef/8e606cf18bc8c9c0ce2523f27b9cc7888b329bd8a5bdd6ee5cd8d7a880bf/obstore-0.12.0-cp315-abi3.abi3t-musllinux_1_2_armv7l.whl", hash = "sha256:0fa81ce46e1d346c79e1a183a676d4511523079789c79edcf281b394c554303d", size = 5278268, upload-time = "2026-10-15T16:41:01.68Z" }, + { url = "https://files.pythonhosted.org/packages/e8/46/c967dac44c7c9cd2a07c75d32902b04a66422ff518002700aba5b8239198/obstore-0.12.0-cp315-abi3.abi3t-musllinux_1_2_i686.whl", hash = "sha256:bb944a435dff68147e462950119e9ab92392150cad91749c668cd10fc2bde919", size = 5402119, upload-time = "2026-10-15T16:41:03.497Z" }, + { url = "https://files.pythonhosted.org/packages/4e/53/c2261822b995c8dbc71f65e4a9914f6c63c1f15befb785d6d9b77037d13d/obstore-0.12.0-cp315-abi3.abi3t-musllinux_1_2_x86_64.whl", hash = "sha256:74bc425a675eeafc0ce1db29f38807126253ed377c0518569e61132341f7da7a", size = 5839610, upload-time = "2026-10-15T16:41:06Z" }, + { url = "https://files.pythonhosted.org/packages/cc/06/116c124a0fad9104f83f854296483c759ead70ba2229f1df2a445cf92a4d/obstore-0.12.0-cp315-abi3.abi3t-win_amd64.whl", hash = "sha256:690018140a0ca0e4346ec5afc2c20608555f5f5e447d0bd5ca8b99b6cb1ec198", size = 5362445, upload-time = "2026-10-15T16:41:08.488Z" }, + { url = "https://files.pythonhosted.org/packages/04/88/20151b32d7a0d1e15fd8f0b9a9c7439f67946f68071a2f63306b1e7fe946/obstore-0.12.0-cp315-abi3.abi3t-win_arm64.whl", hash = "sha256:3037ae42e0f036257a442d16ac49948a3759df22571213026d1c32d6045302dd", size = 5237094, upload-time = "2026-10-15T16:41:10.49Z" }, + { url = "https://files.pythonhosted.org/packages/5e/ff/3e417abec0bdfae2fe64b384b6cabe6417f33328fe239d155069f9924cdd/obstore-0.12.0-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:098c8e2a584dc991e33f3047143e7bb5c2bab1d3efc20f076fd1fdcda4fc92a5", size = 5020729, upload-time = "2026-10-15T16:41:12.931Z" }, + { url = "https://files.pythonhosted.org/packages/6a/60/0348ed2f2a00d1c7f380dfebdee049641f457e472a4afa328886db59c9ec/obstore-0.12.0-pp311-pypy311_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4d9cb87386e464a92c69ef6f3168ee2dbe21dbc0d738c7cda646e79de1e16a80", size = 5264660, upload-time = "2026-10-15T16:41:15.531Z" }, + { url = "https://files.pythonhosted.org/packages/d2/09/f724a30c06211dcb1e899149e095d00179a3cf6cff8de232735d941a823a/obstore-0.12.0-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dfbe400a02eea67104f5ae8fcad52fefe9571c042ee1257ce2059f0be1e47eaf", size = 5435452, upload-time = "2026-10-15T16:41:17.566Z" }, + { url = "https://files.pythonhosted.org/packages/16/e9/3563a37041b4186863b98343c53801b944d3d9a1ff8e8bca6f9e3ad3f7c9/obstore-0.12.0-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1642e69f80e95c0a2d40d87239228145d2a99cb7720a19343301ce1df0a963d0", size = 5345777, upload-time = "2026-10-15T16:41:19.983Z" }, + { url = "https://files.pythonhosted.org/packages/7b/ee/ff4f8179d03b59f952c26247b80e89f9cc660573dede6f38c7fc1a370fa3/obstore-0.12.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:25743269e0f2b49b2bdc4068a912fdd3fc811acdff79dd886429686364c12a27", size = 5570602, upload-time = "2026-10-15T16:41:22.024Z" }, + { url = "https://files.pythonhosted.org/packages/f8/7a/c0da11a5a8f0f6a4413747718dcaf5adc9d15da1375eaacc3eace5fdd31d/obstore-0.12.0-pp311-pypy311_pp73-manylinux_2_24_aarch64.whl", hash = "sha256:e6612a88cdde54db1aeea432fa68477edcc43423baa8c46fd19109fa4d8fea1e", size = 5338889, upload-time = "2026-10-15T16:41:24.231Z" }, + { url = "https://files.pythonhosted.org/packages/01/b5/ed40d03a77bfcaf3d65f260ea397fe1d50fe05e22fd560f944c15986c85b/obstore-0.12.0-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:3171dc1e0baac61725f7722219a92082ac65002af3be6dca0bda09f9049de624", size = 5547960, upload-time = "2026-10-15T16:41:26.279Z" }, + { url = "https://files.pythonhosted.org/packages/c9/62/9f316c412975f9a43f4ba8141fe53b72a35561e6825c7d3c282255153d2c/obstore-0.12.0-pp311-pypy311_pp73-musllinux_1_2_armv7l.whl", hash = "sha256:777678604e9658f7edfe7cbf0c0316953cc1511dd957a6f0a513866c4161ce43", size = 5246652, upload-time = "2026-10-15T16:41:28.582Z" }, + { url = "https://files.pythonhosted.org/packages/dc/3e/27ffabf8b3d7a9115f99b7374bccdcb5b5ae106897c3e5733e9c88ce174c/obstore-0.12.0-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:08e9009e4996d5a57e924324fbb7fb3d5d9a41dc1645e82d87268098f40c5bd9", size = 5389579, upload-time = "2026-10-15T16:41:30.615Z" }, + { url = "https://files.pythonhosted.org/packages/ba/5e/390c745f7e7e5db8db3e6cc9344b8abaf25baf14925469cf34e3b76fb02e/obstore-0.12.0-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:c45fa5a7200c2c1ef72cd195ce8c621de9d3cc819880c120b80610da2598d0aa", size = 5799158, upload-time = "2026-10-15T16:41:32.585Z" }, + { url = "https://files.pythonhosted.org/packages/25/bc/ac2409626548e681daa1d651bf70b79f5af49c7ab4ae5c0ea252809ef4d1/obstore-0.12.0-pp311-pypy311_pp80-macosx_10_12_x86_64.whl", hash = "sha256:3feb6b4cb17f8e0d4569730835c3628f0a5818ea63b42fbd04b7aec98549bd50", size = 5415927, upload-time = "2026-10-15T16:41:34.771Z" }, + { url = "https://files.pythonhosted.org/packages/8b/b3/88b609eeeacfec32ade070f2782570230a58a7f844cc1e3180e9aea39326/obstore-0.12.0-pp311-pypy311_pp80-macosx_11_0_arm64.whl", hash = "sha256:91d0635f8e714b3a3fccb3d4cc96e519decdd1eab4728876bcc6243e378e8363", size = 4613967, upload-time = "2026-10-15T16:41:36.827Z" }, ] [[package]] @@ -1699,7 +1723,7 @@ dependencies = [ { name = "affine" }, { name = "cachetools" }, { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, - { name = "numpy", version = "2.5.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "numpy", version = "2.5.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, { name = "pyproj", version = "3.7.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, { name = "pyproj", version = "3.8.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, { name = "shapely" }, @@ -1712,79 +1736,80 @@ wheels = [ [[package]] name = "opentelemetry-api" -version = "1.45.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/1f/dc/e12c1fe1ed8a7b7149777127b1a0e12ce5bd5a81d97408bedc2128c260f5/opentelemetry_api-1.45.0.tar.gz", hash = "sha256:711ede81773c8025c2c03dac0450bc89f3d30aea6eabcc815c570d4e35a963f7", size = 72115, upload-time = "2026-09-25T12:32:28.399Z" } +sdist = { url = "https://files.pythonhosted.org/packages/2e/02/6e0ae9cc61bd3169d401077b507b3ebc344745171e1051ab430be012dcd9/opentelemetry_api-1.45.1.tar.gz", hash = "sha256:aa38ed19bcc084ba42782a73255b3582283eced7ad6dddbd6695189e69adfb75", size = 72804, upload-time = "2026-10-06T17:32:58.133Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/44/b9/040d1a1c7836922828e6480cd2366bb8fe0ebf75b413d2bb51a9b0e7f78f/opentelemetry_api-1.45.0-py3-none-any.whl", hash = "sha256:80e068aba7cd56c8b58512d6a36f8d25cb1dfaa0c0a4cc1c938ccf9f362d9cb3", size = 60020, upload-time = "2026-09-25T12:32:03.192Z" }, + { url = "https://files.pythonhosted.org/packages/1e/41/f7dcf80b81ee8e71c1a2b59f14208bc723edbd89ed027a73b175abf6348e/opentelemetry_api-1.45.1-py3-none-any.whl", hash = "sha256:b31553efa588ae44bc306f863c785c5333a9ecc091248c6ee68b4b6c87fdedfb", size = 60256, upload-time = "2026-10-06T17:32:33.506Z" }, ] [[package]] name = "opentelemetry-exporter-http-transport" -version = "0.66b0" +version = "0.66b1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/5e/31/cbedb10e08c3c932b80f58edf055a16bb48a500c23c617b758fb3ec18f08/opentelemetry_exporter_http_transport-0.66b0.tar.gz", hash = "sha256:2c229b6593eaa22c86d9b8a15843dc23b406dbda00fb138339189aab07923b4e", size = 11694, upload-time = "2026-09-25T12:32:29.689Z" } +sdist = { url = "https://files.pythonhosted.org/packages/62/0c/e3ebdb4b507f66afcc905e6885a4946969bd75b45988492643356fbbdc63/opentelemetry_exporter_http_transport-0.66b1.tar.gz", hash = "sha256:443080203bf52586ce0b2ad901e8951c61833eab1aa539ae6f1f16fe9e8e7952", size = 11693, upload-time = "2026-10-06T17:32:59.65Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b1/65/34d7106eb45acda40f016c5b31b313ffbbf4744acd5f099f29eaf2958b4d/opentelemetry_exporter_http_transport-0.66b0-py3-none-any.whl", hash = "sha256:c82689928a11505a2a0a26a04afe0ce06d270d3db984bc16c1a80ffa902db00e", size = 12156, upload-time = "2026-09-25T12:32:05.621Z" }, + { url = "https://files.pythonhosted.org/packages/04/69/6af86ff66492b481c6a4c05dcfd68beb47ed8ba046440a26a2aac76b95c7/opentelemetry_exporter_http_transport-0.66b1-py3-none-any.whl", hash = "sha256:2f95404bdee7f9d2d529c7de56c7bd86d014d774d8fbf137810e0167f8a492bf", size = 12155, upload-time = "2026-10-06T17:32:35.454Z" }, ] [package.optional-dependencies] -urllib3 = [ - { name = "urllib3" }, +requests = [ + { name = "requests" }, ] [[package]] name = "opentelemetry-exporter-otlp-common" -version = "0.66b0" +version = "0.66b1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-sdk" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/68/09/01239cdfe8a414d46ed625b68b6da92666ffd16d93cc6dadf89404b4bd85/opentelemetry_exporter_otlp_common-0.66b0.tar.gz", hash = "sha256:362268ec6aa705e183776ff938539df1e8ce45bc5509d242538b1d40c26fe6a6", size = 14323, upload-time = "2026-09-25T12:32:31.481Z" } +sdist = { url = "https://files.pythonhosted.org/packages/cb/19/41de712173f43057e4532d42ece7d0c6d4210d353e5752433cb14987643f/opentelemetry_exporter_otlp_common-0.66b1.tar.gz", hash = "sha256:6b1403487a2185ac1feb45fd5546fdf8630ce71c36bcefaadf51e2130e9e23f9", size = 14325, upload-time = "2026-10-06T17:33:01.725Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c3/a9/b6a7a9d40ba99ed71b18e17310339024b086578f5f32162df1c1c0cf6270/opentelemetry_exporter_otlp_common-0.66b0-py3-none-any.whl", hash = "sha256:35d24c867310f4713a9738b0202b6bade77955418fccb9e2f7eea01b76263916", size = 12387, upload-time = "2026-09-25T12:32:08.438Z" }, + { url = "https://files.pythonhosted.org/packages/fc/39/8c23d67665c762aa51840fa06f86e902e8f6f1693bc8d7e3d98cd6e2f753/opentelemetry_exporter_otlp_common-0.66b1-py3-none-any.whl", hash = "sha256:00ff8592c3a7cb729ff3fdc7ffa12372c243bdf2163e80c180994d0c7bd83ee9", size = 12385, upload-time = "2026-10-06T17:32:38.177Z" }, ] [[package]] name = "opentelemetry-exporter-otlp-proto-common" -version = "1.45.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-proto" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e5/e0/ee3823dbdc10da15b5750becc37b61194dd7c55e3b56764ecbbe446f659a/opentelemetry_exporter_otlp_proto_common-1.45.0.tar.gz", hash = "sha256:36495115a0c6a7aa946cfda9d59b6ed4e917b6ab0f75cdaf66bc1b176ec1be1f", size = 18870, upload-time = "2026-09-25T12:32:34.141Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c1/8e/65e85e5137991a3c493b11682151d198638a5bc1dd4b4c5f67e013c57d7c/opentelemetry_exporter_otlp_proto_common-1.45.1.tar.gz", hash = "sha256:2e4adcc3a67bcf57804fc49514f0ef64974ca7590aa3491da389852b4a0628f6", size = 18873, upload-time = "2026-10-06T17:33:04.471Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f0/bf/3e21917368a81282ce9fc6d085a944dcaea6eed19de7d29cc979375db347/opentelemetry_exporter_otlp_proto_common-1.45.0-py3-none-any.whl", hash = "sha256:7e1410ae6d3ed301f7a74bec96467d519d3f37e52f9d95b309ae125e5a1af863", size = 15394, upload-time = "2026-09-25T12:32:12.663Z" }, + { url = "https://files.pythonhosted.org/packages/84/aa/92f225d353904e7f70b8b3e3c1b02db0cf56f744c2e83c581dc372e78873/opentelemetry_exporter_otlp_proto_common-1.45.1-py3-none-any.whl", hash = "sha256:2f446183ae7047b036226f1d846c41a834b0e8755ad13b51a51dd38952eb466c", size = 15393, upload-time = "2026-10-06T17:32:41.911Z" }, ] [[package]] name = "opentelemetry-exporter-otlp-proto-http" -version = "1.45.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "googleapis-common-protos" }, { name = "opentelemetry-api" }, - { name = "opentelemetry-exporter-http-transport", extra = ["urllib3"] }, + { name = "opentelemetry-exporter-http-transport", extra = ["requests"] }, { name = "opentelemetry-exporter-otlp-common" }, { name = "opentelemetry-exporter-otlp-proto-common" }, { name = "opentelemetry-proto" }, { name = "opentelemetry-sdk" }, + { name = "requests" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/94/78/a503801c1c8f80b1d8aad0e14106a7c57f39344001652a78a683f9a9089e/opentelemetry_exporter_otlp_proto_http-1.45.0.tar.gz", hash = "sha256:2f35496d96809f946f41b8805e6b93aec6c9b71b5fd759b75b6af4c084d992ae", size = 28818, upload-time = "2026-09-25T12:32:35.335Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1b/17/26487707ea4caa97b17e6e4b5fa72133a53512ffa2f5cf7a49ef284b29cb/opentelemetry_exporter_otlp_proto_http-1.45.1.tar.gz", hash = "sha256:45c218405ce3fd879596924b1874bf9a8f6880206d61065c5a912c8e5c297fb7", size = 28839, upload-time = "2026-10-06T17:33:05.713Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e7/18/875e0ee47e035f324e42eda9aab47d58381a4412181dca251d3504e24680/opentelemetry_exporter_otlp_proto_http-1.45.0-py3-none-any.whl", hash = "sha256:74385f99266dafdefff41f3da4b76f6dba79d237a9d4487e6ff1ee653c74a0a3", size = 22260, upload-time = "2026-09-25T12:32:14.658Z" }, + { url = "https://files.pythonhosted.org/packages/aa/1f/517eaa0187ba106a9da97160ce2add3a371812681dc440930b267f714e42/opentelemetry_exporter_otlp_proto_http-1.45.1-py3-none-any.whl", hash = "sha256:24a97cf3753c7fb52fad44a696e452ff371686339e2acf3309e2eda3d0230700", size = 22180, upload-time = "2026-10-06T17:32:43.946Z" }, ] [[package]] name = "opentelemetry-instrumentation" -version = "0.66b0" +version = "0.66b1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, @@ -1792,62 +1817,62 @@ dependencies = [ { name = "packaging" }, { name = "wrapt" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d1/97/e6959310cff3dbbce24ca0a60e5e12f773320d17480c779b038b55defd35/opentelemetry_instrumentation-0.66b0.tar.gz", hash = "sha256:316715ed7b33a12530c0ae3e8d4cdf26d70e1aad358d7ba256e5dea2d4ceed71", size = 43308, upload-time = "2026-09-25T12:35:46.504Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a5/03/89e47ff8d52a4f83b343e6eb9ef1698ff45357216e5b6b2b21e0da5c5c7d/opentelemetry_instrumentation-0.66b1.tar.gz", hash = "sha256:e79a510f7d87c72d95e964ddb42193a0d9a75668c027d980eab032ea1322a5ce", size = 43308, upload-time = "2026-10-06T17:36:10.703Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/4b/08/bf39ca469f178963715ff6fd7012eec0b438f472703b11a64a40cbb14f6f/opentelemetry_instrumentation-0.66b0-py3-none-any.whl", hash = "sha256:0b32e2fcfec1e4bd48511a3b77ce32bfb1c16dcd5479396b21f9086a4b06c0c9", size = 36900, upload-time = "2026-09-25T12:34:50.029Z" }, + { url = "https://files.pythonhosted.org/packages/da/b2/d1413681ff43e13ac9860df27e1226d3199ab0b97b352ceea41abcc660a5/opentelemetry_instrumentation-0.66b1-py3-none-any.whl", hash = "sha256:4c4aa14dc9a24a02325a9d4c42c4d0208dbb1374c2b1b8fe6c9392d59f3e1008", size = 36904, upload-time = "2026-10-06T17:35:11.663Z" }, ] [[package]] name = "opentelemetry-instrumentation-logging" -version = "0.66b0" +version = "0.66b1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, { name = "opentelemetry-instrumentation" }, { name = "opentelemetry-semantic-conventions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e0/f9/33a587a00c83e13d46a4570cb8ccb63129dfd803171a6ae173eb88cd9217/opentelemetry_instrumentation_logging-0.66b0.tar.gz", hash = "sha256:e3516fd5dbff7a93bb159e72802bb8f96ab92645c5bd81488d240b6f23a6df59", size = 20351, upload-time = "2026-09-25T12:36:05.072Z" } +sdist = { url = "https://files.pythonhosted.org/packages/28/ca/0fcf525f238f4ab9df3a6d0423fb057085ea39a3e79ac912fef3d5fa16b8/opentelemetry_instrumentation_logging-0.66b1.tar.gz", hash = "sha256:2250240ecb1fce36939f64eafd988832338684564fb4c7b401c9b9af0ee11d46", size = 20353, upload-time = "2026-10-06T17:36:29.546Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c3/93/c9ee99835a67711d2115e1cb9335e7f96736f6103b3368653fbd1d11399f/opentelemetry_instrumentation_logging-0.66b0-py3-none-any.whl", hash = "sha256:3b580f15f8b7874e58c835230571f1a46cc156399bedd77279d7e7510f76df43", size = 15928, upload-time = "2026-09-25T12:35:17.602Z" }, + { url = "https://files.pythonhosted.org/packages/2e/7b/76bea1e80787848a9ac9882a598cbf1e761f16cebdb399d0e675221e34a0/opentelemetry_instrumentation_logging-0.66b1-py3-none-any.whl", hash = "sha256:f13761110c52eecae9d7debedb0f88b46acc248c20437e97c2a5b3a2b4f96a02", size = 15928, upload-time = "2026-10-06T17:35:39.915Z" }, ] [[package]] name = "opentelemetry-proto" -version = "1.45.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "protobuf" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/72/28/67c38cfb7e2bdfdd0cde7dcdd0424aed0291fbd64aa4ea3e7e913734711a/opentelemetry_proto-1.45.0.tar.gz", hash = "sha256:96ee414f24bc3f61ea8e17dc56b4348d4049d73db3eb17c6b3edf75b5b403300", size = 46489, upload-time = "2026-09-25T12:32:41.409Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4b/7f/15f014fb195da6c2dbb6c71399b8e76824878718e94de6454038488eed28/opentelemetry_proto-1.45.1.tar.gz", hash = "sha256:79e0fb95e4616691a469439238aa9224d75779b3e108e895d1aa125ab29ca77c", size = 46488, upload-time = "2026-10-06T17:33:11.49Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6b/45/814d55714507c7ce9c9cd86a4f83ceb5a74ab0bffcc63ff493943f138de2/opentelemetry_proto-1.45.0-py3-none-any.whl", hash = "sha256:9731566359d7b8e1ee1e149d8e4ad6865bab965e657ad3387ec2784d16927666", size = 72486, upload-time = "2026-09-25T12:32:23.36Z" }, + { url = "https://files.pythonhosted.org/packages/ab/9a/42ec8180a769516ae757e893b69736826efceac7332553915b4528a91c6d/opentelemetry_proto-1.45.1-py3-none-any.whl", hash = "sha256:f38e2a8413053c180cd3d2637fbb279673ec2f6a6e09c995aafa2f452c52b46e", size = 72488, upload-time = "2026-10-06T17:32:53.057Z" }, ] [[package]] name = "opentelemetry-sdk" -version = "1.45.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, { name = "opentelemetry-semantic-conventions" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ac/ed/ad32d76cc86ebce601105d01d13f8a08d6abc1852eaf69d79cf199bc1ee7/opentelemetry_sdk-1.45.0.tar.gz", hash = "sha256:20caa5130505e386c67c3da1c76e446c842698ced54c76c6148679539aa97972", size = 218337, upload-time = "2026-09-25T12:32:43.591Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/79/7392e21a1c8f0c61d90b223e31c7e48cb9d452e91a6b820ad24cca5f23c4/opentelemetry_sdk-1.45.1.tar.gz", hash = "sha256:63d24a6ca645019a631e6a51999c73e93adcac1196ca640b8ae78a7cc4762bf3", size = 218324, upload-time = "2026-10-06T17:33:13.26Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8a/ae/46963201ce58d6b86f9e54087a39720a35ac9f57c3a9e440d275995c2b02/opentelemetry_sdk-1.45.0-py3-none-any.whl", hash = "sha256:5dc634c946546f61b757c5b1781f9fd1a10e96ffaf7357c797e508fe9c57e75e", size = 140132, upload-time = "2026-09-25T12:32:25.399Z" }, + { url = "https://files.pythonhosted.org/packages/95/3c/87c42b4bd6dd297536f04cd9383d212ac557ecd49f2cbdcd46da1c9ef5c8/opentelemetry_sdk-1.45.1-py3-none-any.whl", hash = "sha256:c604c11dc429810812348989115fa44bd558772a3d7442afc43d024f2c250ca4", size = 140063, upload-time = "2026-10-06T17:32:55.04Z" }, ] [[package]] name = "opentelemetry-semantic-conventions" -version = "0.66b0" +version = "0.66b1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2e/21/910f085c0b83b80e45c341c7859baef37c877046b2c44c88d5ff2d5db948/opentelemetry_semantic_conventions-0.66b0.tar.gz", hash = "sha256:97a77dce484c54861e7eeff7651fd8a806dd3c30e501dc316730215ec36890e6", size = 150247, upload-time = "2026-09-25T12:32:44.827Z" } +sdist = { url = "https://files.pythonhosted.org/packages/46/e4/dbbfb2a010c4db2224a5114638acede6fe563d33cc20fb1752cebcbe6298/opentelemetry_semantic_conventions-0.66b1.tar.gz", hash = "sha256:497ca63bf383723411e8eaf60c8779e9877633c936bb641080adab59d0eb6ec8", size = 150250, upload-time = "2026-10-06T17:33:14.073Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f4/93/2ad416d69c4ca8fd21ebc6894f23f4eda744dc6f43a14d7a9ffd7ed3abbd/opentelemetry_semantic_conventions-0.66b0-py3-none-any.whl", hash = "sha256:175b19dd98c4473f4f43a2b1df59186fd7b4a48cd3f77cbe03438b6d6fda230a", size = 206277, upload-time = "2026-09-25T12:32:26.484Z" }, + { url = "https://files.pythonhosted.org/packages/bc/14/67f8aa798857f8cf686f515bf93d9bb877ce952ddc8efae0fa25b45ce0d6/opentelemetry_semantic_conventions-0.66b1-py3-none-any.whl", hash = "sha256:d4cddeb4315490b35213f55e2bdc9ac54bb1e4d318927475bed62b35545e581b", size = 206279, upload-time = "2026-10-06T17:32:56.103Z" }, ] [[package]] @@ -1861,73 +1886,73 @@ wheels = [ [[package]] name = "pandas" -version = "3.0.6" +version = "3.1.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, - { name = "numpy", version = "2.5.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "numpy", version = "2.5.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, { name = "python-dateutil" }, { name = "tzdata", marker = "sys_platform == 'emscripten' or sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e2/17/d7b106e05bfa642e8694451e7d3d759c6a241c5386a5d962e4f66c047e06/pandas-3.0.6.tar.gz", hash = "sha256:66b07ef7315a31bfe1089cd3d71a7de781c9dca986762d0b4fe7c0ef17465d10", size = 4667686, upload-time = "2026-09-17T23:23:18.345Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7d/48/88e8d250d28efa8163294f6809a71683c7ee67f63ac7c33021c0503b3547/pandas-3.0.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:085e3786ae6b2e82b406266bce36690f72b9dc1421903ba9296b2981a9fcf586", size = 10391798, upload-time = "2026-09-17T23:20:20.96Z" }, - { url = "https://files.pythonhosted.org/packages/55/a6/39db5d41f3eb5d7846626312cb30e0cea48e988fc47a3725698bc931ad3c/pandas-3.0.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d7564d86a94c2eb8ab290b07f63ddaae5c032fa53897c29a2ff2197d43aee8af", size = 10023718, upload-time = "2026-09-17T23:20:25.094Z" }, - { url = "https://files.pythonhosted.org/packages/54/b7/707e966129f77ee8a39d41a41bbd989b790b3fef581c6e26b821315cba6b/pandas-3.0.6-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1e7c0afdcaf6661d795fcefc2f647ddd1136f62cdc153fba177c685d97a87808", size = 10612800, upload-time = "2026-09-17T23:20:27.99Z" }, - { url = "https://files.pythonhosted.org/packages/63/be/dfb6cc9329d0bbe76dadda8a1113d026bb996ec76c509221368dc68349f4/pandas-3.0.6-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:47121f9571503f724c9b93e297ab6254ac99c77adf5e9ed085ea419fd585c258", size = 11108900, upload-time = "2026-09-17T23:20:30.65Z" }, - { url = "https://files.pythonhosted.org/packages/1a/6f/3d58f15bbe972f3d7bfa13ee06d731785a76fe8d232c92b2655a8de14127/pandas-3.0.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:994a79608263fe1c14cc48ffa7300e2b834b7d1cb406ffe96a08828cb0cdd79b", size = 11630226, upload-time = "2026-09-17T23:20:33.582Z" }, - { url = "https://files.pythonhosted.org/packages/58/54/9b494de4a3dd92fc6eb19187db1b21afb50fdc21962f32ea1aca9f270cb2/pandas-3.0.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a3a22e07fe75347eaacc75b0e85297947af4fba6b4aae23916bd8b6828d0bba3", size = 12146942, upload-time = "2026-09-17T23:20:36.792Z" }, - { url = "https://files.pythonhosted.org/packages/d3/dc/d2df02854aec5d47659acfb2be352eecc691845b2f86e99c84f1010a8671/pandas-3.0.6-cp311-cp311-win_amd64.whl", hash = "sha256:2e5fa32ff162dfdbc280157d664f44d23049ae414725af9676df339c501d82cd", size = 9859246, upload-time = "2026-09-17T23:20:43.976Z" }, - { url = "https://files.pythonhosted.org/packages/79/1e/2a30df0d7dede5c195300a1820b0bc21cea3aa24e4c8b6c4431565ecc79a/pandas-3.0.6-cp311-cp311-win_arm64.whl", hash = "sha256:5e75072773c1b2f7cb63faa3a6f562aede11f3976f68ed34cb538bc091a28171", size = 9106923, upload-time = "2026-09-17T23:20:46.611Z" }, - { url = "https://files.pythonhosted.org/packages/77/4c/597d588c055d4373cff19cbbc32d4dd046c7be8fadee957585b5ba9e5b24/pandas-3.0.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7dac2d65e9087e8e7b5a45fe15c4920911a221df061ab629943ce016489145c7", size = 10397411, upload-time = "2026-09-17T23:20:49.465Z" }, - { url = "https://files.pythonhosted.org/packages/18/8f/48907c7c707b61a8e5018c32e1a3f70623209bfb59020a2f6196159d3aa7/pandas-3.0.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9dab635a549e58a053c7b0fa054dc0bd7be22f0ed9a720f4a85d5fb993276172", size = 10051279, upload-time = "2026-09-17T23:20:52.409Z" }, - { url = "https://files.pythonhosted.org/packages/67/fa/613d867c3d9554a61bafdec6f79565c8a3e73235feb52cc4a72ad2e0fa6a/pandas-3.0.6-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e3dccb584123b399c07562ac4d62543e90ede49ddf8ce3c13ffc64cbe828c281", size = 10303429, upload-time = "2026-09-17T23:20:55.597Z" }, - { url = "https://files.pythonhosted.org/packages/cb/67/0c0f18e38d7f2d2af8c24b3315bc4046e73bbdd4a5540405506671ad0c0d/pandas-3.0.6-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0704044b676496b8350e023b09f174a26772456c974a2b11c36bebb558c9490d", size = 10788193, upload-time = "2026-09-17T23:20:58.617Z" }, - { url = "https://files.pythonhosted.org/packages/39/53/1b57f3162501fe36687e4870e1b918a6458ca7386b173af75663ace95857/pandas-3.0.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e7c1905ef02c3d6d43d9dbd5b6ccb4da4870a0b0c821bbc103fbdb6f3ad2707b", size = 11323501, upload-time = "2026-09-17T23:21:01.911Z" }, - { url = "https://files.pythonhosted.org/packages/f2/d2/b1182e8d39100369d7f13f4a125a3fb6b096fef112c46d0566c25781ff68/pandas-3.0.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:569e114072b24fc4970c12e2b4bab252671668a40b324318903380cab0254c0c", size = 11838173, upload-time = "2026-09-17T23:21:04.85Z" }, - { url = "https://files.pythonhosted.org/packages/c7/33/5b717af24d2f27995e51e0875a269dddd373045216e34cf62e3aa764eaa1/pandas-3.0.6-cp312-cp312-pyemscripten_2024_0_wasm32.whl", hash = "sha256:2a8fc94be2ee5f1d86f97aacd8cc566f81680b6498e76f3007421bb5d98151bf", size = 7118499, upload-time = "2026-09-17T23:21:07.661Z" }, - { url = "https://files.pythonhosted.org/packages/bd/2a/14b3b17cd75cef4a1ee1af4234eb41cc1afe4103b98c2d80e8916abfd42b/pandas-3.0.6-cp312-cp312-win_amd64.whl", hash = "sha256:3ef908d28590b3f42d7070e7ad8f9b34b442b260b7f3c1afb57e0040c58cdb1b", size = 9661439, upload-time = "2026-09-17T23:21:10.959Z" }, - { url = "https://files.pythonhosted.org/packages/3b/11/3d580a604a1e35d69f6676847bd12db7d14344bc677b717f4413e79c5d0d/pandas-3.0.6-cp312-cp312-win_arm64.whl", hash = "sha256:f4e7c52eb108d752e7592268108fd3e98efd76d83a3125cdd06c621c2e44359b", size = 8959806, upload-time = "2026-09-17T23:21:13.851Z" }, - { url = "https://files.pythonhosted.org/packages/8e/1c/143605a1f6443ad50ebda78a31e5a3a10147fec2590e931584aaa5ff0a09/pandas-3.0.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9ae8073aed8e21d1a7fe263dcdc6840743549722a6738198a0a46000fa9476f2", size = 10418900, upload-time = "2026-09-17T23:21:16.594Z" }, - { url = "https://files.pythonhosted.org/packages/ea/ca/87f8548f73d452aab35e4a90f8b39ae303295e0f2ef0b4055c44d6b3f1be/pandas-3.0.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:60d81f9e1799b36f3739e7fff44d1fbb2e8fd5a271b3863e03de9715fccda0fa", size = 10064785, upload-time = "2026-09-17T23:21:19.677Z" }, - { url = "https://files.pythonhosted.org/packages/43/1a/d951442e5607c6e3b2462eff8f420797d428aa74b87c6ecfe4f48553626e/pandas-3.0.6-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:097090508a1dd335013d39106fc10b20f4fd4a171638e47b77d55798ed9dab6c", size = 10245290, upload-time = "2026-09-17T23:21:22.797Z" }, - { url = "https://files.pythonhosted.org/packages/50/fa/96d50e1e6cd0b08b5e2b7c838f65ae644940f75a124063380b5ef73b6866/pandas-3.0.6-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1e92d9fa834c7d877130027cddc0cad8dcff97c1f6cca26bd6310f847228b658", size = 10757657, upload-time = "2026-09-17T23:21:25.673Z" }, - { url = "https://files.pythonhosted.org/packages/7b/12/f82d13a2cb703e1a8acee7e01fdc2b898d9cd0c00f07d1dfce63af43e350/pandas-3.0.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b27c8d890e4aa2171437ae2a39de1d215e674158e4865c4023a8b31c932513b2", size = 11249114, upload-time = "2026-09-17T23:21:28.898Z" }, - { url = "https://files.pythonhosted.org/packages/1a/ce/8aef2e561a2f2c8b38c913c67373c65ba6748174e763d27c80271b24bd17/pandas-3.0.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f8029ec0f1f89e4f985929ce1f6626dabf3140d61a4e9c1215afdab34eaf9a5d", size = 11820511, upload-time = "2026-09-17T23:21:32.11Z" }, - { url = "https://files.pythonhosted.org/packages/c0/bd/63cb67e6903ef6d9c2871916dbcbc09d254da0fe8b870cf62e16b21945f2/pandas-3.0.6-cp313-cp313-win_amd64.whl", hash = "sha256:f3ce8a6968045481e91a3990e797e348ce13db45ee164a7095bbc824e26c09dd", size = 9638092, upload-time = "2026-09-17T23:21:34.883Z" }, - { url = "https://files.pythonhosted.org/packages/75/2e/e7b35b712edb068d382ddc8b2bea8a04974100515ba2daa22b478b265842/pandas-3.0.6-cp313-cp313-win_arm64.whl", hash = "sha256:cc39303913e2ea129915670de5d1c9fbd647f543bb72e5543bac8baa94e9e42f", size = 8952032, upload-time = "2026-09-17T23:21:37.729Z" }, - { url = "https://files.pythonhosted.org/packages/75/55/1a8875395b05ccd572cbca0b9255dcd2db6e6508e632a558c1a6884b39ad/pandas-3.0.6-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:ee913a91669056c1de1a6b733fbfeab711de9e54e3bee2dfa5fe79d9457247d1", size = 10492213, upload-time = "2026-09-17T23:21:40.746Z" }, - { url = "https://files.pythonhosted.org/packages/35/61/47ae13476995cc8a40cd609e93e7cf11f273d8692925c2903cb6d38aa0d1/pandas-3.0.6-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ff51a4459ed036e93d1eb1bb5e6e7b28685d3cb6b7c12b91c05b31024e234729", size = 10156618, upload-time = "2026-09-17T23:21:44.142Z" }, - { url = "https://files.pythonhosted.org/packages/bc/f2/cc5f2adb8d6e86a85d9fb5128f8cf205a61189336f70d1f7faf0d1b53ec9/pandas-3.0.6-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:654aae059295dbba6ecd2328ca12712a2cf1676214c8699f1c29213f7ccf9c34", size = 10375479, upload-time = "2026-09-17T23:21:47.159Z" }, - { url = "https://files.pythonhosted.org/packages/ca/ba/ffdcb19be4ff6bfe7d969e7cef2c567c633df5a3a1cc1053394ad053bca8/pandas-3.0.6-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:62f51d7f651c8054c5e82a69265c98082e795d1442df7ca6edc3a545d61214b1", size = 10783244, upload-time = "2026-09-17T23:21:50.367Z" }, - { url = "https://files.pythonhosted.org/packages/77/5b/e150075b2c6eb69fae896f2d9239bc6ed07db97735971d53d66de6553460/pandas-3.0.6-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:22172a92e7ee678ec0140c7af4fc9366b55413834a1cd86af78b3caa0b0574de", size = 11382223, upload-time = "2026-09-17T23:21:53.355Z" }, - { url = "https://files.pythonhosted.org/packages/d6/8a/b441c587dc7355bf6e1f68a91b4f76a6c29740f0c23be3acc5d4ebbeea6d/pandas-3.0.6-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:583be68728a31d0d750d5b8d9e00f02b153df0d4655f858bde93cb84cfc4227c", size = 11854881, upload-time = "2026-09-17T23:21:56.342Z" }, - { url = "https://files.pythonhosted.org/packages/b7/e9/f43410fada510b43fec09993c08f552086c3d247d3ee801a678f3cb10ea5/pandas-3.0.6-cp314-cp314-win_amd64.whl", hash = "sha256:77ccbe5057aece6fc172b9b77f19c04335af6882bc2e10c8f3ee4e6bfb3da553", size = 9791672, upload-time = "2026-09-17T23:21:59.332Z" }, - { url = "https://files.pythonhosted.org/packages/8b/9e/db14c059c21f9baa1907d436f8bf30e0c76c6288225c5e8b79a08ba8b2c5/pandas-3.0.6-cp314-cp314-win_arm64.whl", hash = "sha256:fb625f426b375bcc96e3a04c5d5d266cd7be6ae5d6866e0e703382ab5164068c", size = 9121246, upload-time = "2026-09-17T23:22:02.123Z" }, - { url = "https://files.pythonhosted.org/packages/67/ba/bad0f8dac020ab38a8637fddab01a57a82da7a496a6e6f19590aad53ab62/pandas-3.0.6-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:9e492cd4bdba6778de4fe0df7f4590c012161ebcf9902dce01b01dc683105514", size = 10920612, upload-time = "2026-09-17T23:22:05.404Z" }, - { url = "https://files.pythonhosted.org/packages/c4/a9/b500982e9aac6d52a58da4ad3f11e14168a315b06906b3f397c427878065/pandas-3.0.6-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:d7dcd21238cbb4828ff148481ba01cac8946dc5121457b5aeba28636f8f99a60", size = 10570213, upload-time = "2026-09-17T23:22:08.44Z" }, - { url = "https://files.pythonhosted.org/packages/4b/fa/e6ecd0073c98be8f840ac3125b955272835d7d9fd69f5944383b164deb5e/pandas-3.0.6-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6ff482fa91fa2bafd92e8fe66ce3645c851824310f295c1f0a2f96e928fc4541", size = 10252356, upload-time = "2026-09-17T23:22:11.302Z" }, - { url = "https://files.pythonhosted.org/packages/04/f5/001e230a7a7803590d9275a1a3f7e1bb605e3a495cfe5e8d3a532090621b/pandas-3.0.6-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:db7ec631f26223beee8e5c9e0b8f23c24d8197bbd1d982421d4e3188bea51965", size = 10655952, upload-time = "2026-09-17T23:22:14.283Z" }, - { url = "https://files.pythonhosted.org/packages/ca/ab/bab587148a3852c96aae26c4b5f9e04ce2221801ad94e166b4fbf969ede0/pandas-3.0.6-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:bd75ed0c840f709fc2ae26ddd9534ac77ca1a48ac0cce521a74acaa85f3340a7", size = 11274177, upload-time = "2026-09-17T23:22:17.352Z" }, - { url = "https://files.pythonhosted.org/packages/f3/32/74b48d87df2b80892d713c149abfe36d5db4de41b4eccb042a2bc07dafc1/pandas-3.0.6-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ef738d71d1059245b6bb03e312be06d8b3821326a83486c1ad03b9aba3710e44", size = 11719041, upload-time = "2026-09-17T23:22:20.227Z" }, - { url = "https://files.pythonhosted.org/packages/f6/c6/d64b72d64d7eb0fad9fe424d34138e45dee70ddbea1360dcd0adf30e28f6/pandas-3.0.6-cp314-cp314t-win_amd64.whl", hash = "sha256:429d9df32731ab01383ed98f2baa7a60368090d1a94fc06019a12062510e8630", size = 10191388, upload-time = "2026-09-17T23:22:23.524Z" }, - { url = "https://files.pythonhosted.org/packages/7a/30/5e5b2ccabeca73ae2b03fc82bca3eabb7466cf43737f05ac08d591665d47/pandas-3.0.6-cp314-cp314t-win_arm64.whl", hash = "sha256:a4dbd4dc65cbe645b92b8785d0f96dd7311010dc6606cf620e51b07b8788a12a", size = 9387796, upload-time = "2026-09-17T23:22:26.64Z" }, - { url = "https://files.pythonhosted.org/packages/b6/77/47c5fb0be8bdd00116814c2c40d9ec42dbeb943865ef95130fe58a898226/pandas-3.0.6-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:50c44cbf5820b6b91a5f74aae04972472aefadd3cd9fbd1010409d85528bd570", size = 10483960, upload-time = "2026-09-17T23:22:30.071Z" }, - { url = "https://files.pythonhosted.org/packages/09/08/a310cb2fefe6d2b4623ab2150da818d93d4e73e33b58e4d163bb74243c5a/pandas-3.0.6-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:eb6900de08ac85f93ac4948aa6b80842eba555875337b8359035ac9c43e92d34", size = 10149432, upload-time = "2026-09-17T23:22:32.818Z" }, - { url = "https://files.pythonhosted.org/packages/54/36/6af478ec3a26d7754555cd62c3101c589c0931b1d85398e4fa5403910a1c/pandas-3.0.6-cp315-cp315-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e25e2e1adee99ddfada6f7206a79ae8e9c8a8861b0e3eaaba165006d3eef18e", size = 10360981, upload-time = "2026-09-17T23:22:35.621Z" }, - { url = "https://files.pythonhosted.org/packages/43/20/5ece0e9cb79a6473216620db6a90546f578001c2bd857b77c444b27acad1/pandas-3.0.6-cp315-cp315-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4ff44b2cb51cbd691c91f92c4ea6c71e34003f239ebd67c2e857dc898466b49c", size = 10776585, upload-time = "2026-09-17T23:22:38.427Z" }, - { url = "https://files.pythonhosted.org/packages/2f/b6/cd3038f31ade5e8d2b4e1c9549d4b31e4d598469562f47142b2ad9171c0a/pandas-3.0.6-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:5edd0a7abb0986ecce1ac81f56d99b6763f86aa6946dceb6c661224f90af5a19", size = 11385249, upload-time = "2026-09-17T23:22:41.18Z" }, - { url = "https://files.pythonhosted.org/packages/0a/87/05bb3003737f80375d7311774916a24d161e2e591abe8c672aed7813defc/pandas-3.0.6-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:1bcb3e9ed29e74a7439cedff9e2aefd3ea65de84d7de9ccb6c194192541bd60e", size = 11848595, upload-time = "2026-09-17T23:22:44.207Z" }, - { url = "https://files.pythonhosted.org/packages/c0/30/1c0d46acf236d19ef975e9cdd5d1e0dd54924b03f0ed8287096ad4a18152/pandas-3.0.6-cp315-cp315-win_amd64.whl", hash = "sha256:253e12cb9081b0afbac607920f6142975966bc315135e09de275fdbaa415d2de", size = 9783810, upload-time = "2026-09-17T23:22:47.097Z" }, - { url = "https://files.pythonhosted.org/packages/87/03/df3304a9c2833c4810e7f1c887b04105b24731f9deef8b5d5d04522a375b/pandas-3.0.6-cp315-cp315-win_arm64.whl", hash = "sha256:97274c9adf6255bb48c620cd6959805efa7f09ea2167f0e0ae006a448cd2fca7", size = 9116820, upload-time = "2026-09-17T23:22:50.149Z" }, - { url = "https://files.pythonhosted.org/packages/47/25/d5f6cce5efa17c38e4f752a875b4a26d66cfadd529cb8c81672f0376d6a6/pandas-3.0.6-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:265f562fdd1079f69f3de96dd425c3405224038c0af4f920c54bd240ee2c4640", size = 10894614, upload-time = "2026-09-17T23:22:53.223Z" }, - { url = "https://files.pythonhosted.org/packages/ea/8b/e1876bfdc1df06bafc022d33202b5663bd86c81df2a6140aacafd0344669/pandas-3.0.6-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:c6e4aae3e9bea26c6c9a20d88d96c86ec4a99b4db5fd516bcb4e829ab2c0ee36", size = 10546973, upload-time = "2026-09-17T23:22:56.155Z" }, - { url = "https://files.pythonhosted.org/packages/e9/27/e0a27a5a5c27f7db66657b44def9e93121fd0ad4f0808355b9898fcbf204/pandas-3.0.6-cp315-cp315t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a77a1a44e4d88f1c6a2a64d3eb12efec8420875722e14279800b173a7c7c2804", size = 10239717, upload-time = "2026-09-17T23:22:59.603Z" }, - { url = "https://files.pythonhosted.org/packages/d5/4f/4eadb7d86a921c1e8bc70916cfe601ed667c4169118d17d69958611c21f8/pandas-3.0.6-cp315-cp315t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:86fa853a12e0b70927e2b1ee00d56d2224ec9cbb4b9d58348b5ad52d2f21150e", size = 10641275, upload-time = "2026-09-17T23:23:02.81Z" }, - { url = "https://files.pythonhosted.org/packages/c7/d1/eba72e9d905e84e79aefeefdcc6bc1abe15d9077973566643f4211636966/pandas-3.0.6-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:c826e9babb7790142c399f58599d8de679bea059d7b39c5b6efa2096fac37266", size = 11262919, upload-time = "2026-09-17T23:23:06.038Z" }, - { url = "https://files.pythonhosted.org/packages/9d/8a/1c5bd2642b450e374b6191189f47c49538fe41f82348a66de6f647e6ab59/pandas-3.0.6-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:8fe77b408d82e2615674dfed62533b95e18a03610573877422aada4f625d4947", size = 11709148, upload-time = "2026-09-17T23:23:09.082Z" }, - { url = "https://files.pythonhosted.org/packages/73/3d/1b142bd0d0f1326a5d98c91c955b923cd1e06b5eecb428cd9d8817fa01c0/pandas-3.0.6-cp315-cp315t-win_amd64.whl", hash = "sha256:83e91d15738d7783c050197cef2f2cf82fc6353dae9865aa87ed1fa16aa4d55a", size = 10161518, upload-time = "2026-09-17T23:23:12.365Z" }, - { url = "https://files.pythonhosted.org/packages/0b/a3/6419c14da2adc1f09a6a183b8f91d7494d325b287f4ca984ac04f663638a/pandas-3.0.6-cp315-cp315t-win_arm64.whl", hash = "sha256:963ca21199097a84c7827c4678b04e30833084fbf8ef44fde3fa7180a29f8fa0", size = 9364529, upload-time = "2026-09-17T23:23:15.274Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/55/b1/a10c0d1a7e3ce44eb42635771004aa503e3b9ddde0bd39c524dda65f7eff/pandas-3.1.0.tar.gz", hash = "sha256:ecb9b2d8a817b1e1ba1fdb90c0abe9eab31b4fdf2562b7a56b86ac66ace17e49", size = 5933421, upload-time = "2026-10-22T11:57:03.087Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/36/f2/d8d89ee886df6d959d7b9f483a55a0e5a1d5b034f08c32f4343a524af802/pandas-3.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d53387f8bbff4e7aa567619d4034ffdb1836db1e768a482851b904bbeeed8ef9", size = 11595013, upload-time = "2026-10-22T11:54:48.33Z" }, + { url = "https://files.pythonhosted.org/packages/ec/73/e5243378792b79d3d08f9a6782a0e23ae65cc8c6d2f476b9d2825946b071/pandas-3.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0d2ca3b380f0f6cc788780430eb23762619730c11d53989f3982ec96835bfb9e", size = 11159002, upload-time = "2026-10-22T11:54:51.011Z" }, + { url = "https://files.pythonhosted.org/packages/bf/4a/1064a6ec84ae047379a5969f6b331bd8d57013f08b30976564780fb0c9bd/pandas-3.1.0-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:388fad0c683ad061baabe23a76f43bf43c1d2c2fd46572b687082cfa195b7275", size = 11758276, upload-time = "2026-10-22T11:54:53.367Z" }, + { url = "https://files.pythonhosted.org/packages/14/41/2d264868728f7cb2add91a605bea5f2aabd3da41889ee97746a586ec34ee/pandas-3.1.0-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0a28602573f50453b57802d282f9f8cb7663aa10201127ecbbe95ed4a41c64ce", size = 12330371, upload-time = "2026-10-22T11:54:55.654Z" }, + { url = "https://files.pythonhosted.org/packages/7e/04/790ac6114ceb45575d7d7a909126210f829978f18f4f10ea8c6c847f0118/pandas-3.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e6f83e44846f9386e3593965bac017a54a5b5c611ea7b836ceab7f465bb4cb24", size = 12777723, upload-time = "2026-10-22T11:54:58.08Z" }, + { url = "https://files.pythonhosted.org/packages/56/b1/0513aaaac23bc1400b831881448665565a1b9b327d3eb7c5aca2b23c0b84/pandas-3.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ebbc0e652844e52e06d1525e5084ec1609158c086c8646aa6ccb34802102e533", size = 13411658, upload-time = "2026-10-22T11:55:00.372Z" }, + { url = "https://files.pythonhosted.org/packages/f5/1a/e33f6637445dd4b7323aa7bdaf4cc15c383805b4a2e99f958b7e32296e58/pandas-3.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:a7704e3d7367cfae71d3f8c95ee81e178c7a90bef8d6eac30df9815010722259", size = 10977787, upload-time = "2026-10-22T11:55:02.967Z" }, + { url = "https://files.pythonhosted.org/packages/2e/f7/86bd686a20a5052e15581fdc7f220098426b1982604fb545e78bdd5b5b2e/pandas-3.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:d3e660af30a6683fa57a5a2969555df117e873eaaeb418ad3b2e59249405ee70", size = 10305708, upload-time = "2026-10-22T11:55:05.046Z" }, + { url = "https://files.pythonhosted.org/packages/e9/12/b94c28870f351e9650c724e6daedf661455b348a9dca6d717c42c30fa216/pandas-3.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f4772320b7735d96dbbef437129f840dec4175f8a18a2391bd1222ed8943825c", size = 11587014, upload-time = "2026-10-22T11:55:07.208Z" }, + { url = "https://files.pythonhosted.org/packages/1b/4b/af8ca15261db52ab07f5d0c2d4c1bc28d822ea4c2a79756bc2606c012e7c/pandas-3.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:cc5e4e12d287ea77bbf4c4d4bf47ed152d82a990d3a6794dee7c350f887532b5", size = 11167712, upload-time = "2026-10-22T11:55:09.523Z" }, + { url = "https://files.pythonhosted.org/packages/49/c4/cd5127751e2bef3d10f07e0d856e120916a3df0df6450f759ddb48c78b1c/pandas-3.1.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9b4e696ff70ed06e986feeec09356c491ce878d0df69a36c8e591b6a38afa29b", size = 11476964, upload-time = "2026-10-22T11:55:11.889Z" }, + { url = "https://files.pythonhosted.org/packages/c5/a6/dc89cdd95e12093bc813e9b814a09f1a056be3e1304edaef9bf9dbee108a/pandas-3.1.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9433b5dc2f26ef9d9b56daaf4d41d078bf4372c5d88c42ed5b397a6eea80de16", size = 12019688, upload-time = "2026-10-22T11:55:14.246Z" }, + { url = "https://files.pythonhosted.org/packages/36/87/da14aa671da792612f15847451e731d5dc0f06f6747b76b15f197ffcce13/pandas-3.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3cba20fd269808e224d6212273008173978033a1dee0f3f217bfdbd58775a72c", size = 12490149, upload-time = "2026-10-22T11:55:16.675Z" }, + { url = "https://files.pythonhosted.org/packages/5e/a0/1ce996c7354ad37983955571058ba0dde585663163ab067275197183868f/pandas-3.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:743554ed96755ad1963397ccadbe9965d92db8777ca2ae8c1fe58bd9549d7f28", size = 13090624, upload-time = "2026-10-22T11:55:18.854Z" }, + { url = "https://files.pythonhosted.org/packages/92/72/d235f82b51a22478938a3583b68e8dc05811937543adf410f34bab76004a/pandas-3.1.0-cp312-cp312-pyemscripten_2024_0_wasm32.whl", hash = "sha256:ac2f833557874a79fc0b5fac4c86e402ea6498beba8753dc29ca23d08481689a", size = 8091628, upload-time = "2026-10-22T11:55:21.285Z" }, + { url = "https://files.pythonhosted.org/packages/10/72/06aefff8fe5efc5e1ca8bfb2695b648a435358f6dc224d4e40159314181a/pandas-3.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:b8c75dbb27c7d170b25c13dd35515da4bb92d40fe8bb3bd2c69a95de2218889b", size = 10748836, upload-time = "2026-10-22T11:55:23.47Z" }, + { url = "https://files.pythonhosted.org/packages/f6/b6/4c86f659f5b14790d14e17253becc93c4e3d1511b73f0ba2184f06b164f8/pandas-3.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:73b6cea22090b7123c78ee662a5108ff36616b0bec4fe47026f86fb04a49bce7", size = 10128898, upload-time = "2026-10-22T11:55:25.995Z" }, + { url = "https://files.pythonhosted.org/packages/01/54/eb85f8b2ce121c21286842cf81abaa670ec3570173c512298f91128927a5/pandas-3.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:849d5019dead65944875ce21fbf71a2ca18e982164cd7367623cbda8567c3122", size = 11609893, upload-time = "2026-10-22T11:55:28.592Z" }, + { url = "https://files.pythonhosted.org/packages/9b/f2/b327e814327b7e7e61b5c5cceafd5c0c20f0d38e94f99ebf2dd6c1cec85b/pandas-3.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c1e084760f3b275eed5863407d025b933d25f3247515fcc84bf664405e8bb3d2", size = 11182834, upload-time = "2026-10-22T11:55:30.912Z" }, + { url = "https://files.pythonhosted.org/packages/4e/6d/2b918a45ad31d85f898567c948f11d0789ffe7fbadd6f487565e99d179b6/pandas-3.1.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c1009f5b6819b81319cf177b4f01a2823ed54c81db8bd11cdf2375f42d064c02", size = 11421478, upload-time = "2026-10-22T11:55:33.257Z" }, + { url = "https://files.pythonhosted.org/packages/4b/6f/b63e1d43eef5746014ae3f4786c72e0020e61b6b373a2abb6ebf1be3df49/pandas-3.1.0-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:de6d0fc96e8551b83f400bf212611a0b3a2b389594d20a2540dacc61b21e7bce", size = 11991052, upload-time = "2026-10-22T11:55:35.556Z" }, + { url = "https://files.pythonhosted.org/packages/fd/43/2cf85145d822957a3f8959fd0a850e6bf687dac2823c612b3912b8888dae/pandas-3.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5611344f9f0ed51619ac7acdbf72b86f5043801a66e3f8730c8971257082e8ad", size = 12455560, upload-time = "2026-10-22T11:55:37.656Z" }, + { url = "https://files.pythonhosted.org/packages/7f/72/0cb19cea2e6afbb0854cbde0ed1345b61600e0de84eb7b73583fde3e3b55/pandas-3.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:39c67ed9e56ad3ac0727dc2ae6a1eef766988c762d5ce2e2a162613d81c8990b", size = 13045764, upload-time = "2026-10-22T11:55:39.969Z" }, + { url = "https://files.pythonhosted.org/packages/e3/5c/ea5472e8444f5871427f3a6818725684fcdce74223ca1b22aa7ca345d490/pandas-3.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:07807035ad66de0d833d68f9bee05801dbf08b88d56a233b0e9fd189372c3261", size = 10729719, upload-time = "2026-10-22T11:55:42.008Z" }, + { url = "https://files.pythonhosted.org/packages/45/f7/8cdd350c67bc62865d1e30cd7add43dd631420223487ba42dfcfd0d0e052/pandas-3.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:54fed4a50b8321ff8d413b46b7e31a023cecefd06c7210c02aa980a9836428b2", size = 10116651, upload-time = "2026-10-22T11:55:44.367Z" }, + { url = "https://files.pythonhosted.org/packages/c3/a9/e82d44fe20dc61dd2e15bdd17c78aa0023d860d55620e4fe82f9e644a089/pandas-3.1.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:a8c44d40cbb6d00128bf2d8f8bb66c8d561744b6d44686b5a1a01f3b3fd1245f", size = 11675868, upload-time = "2026-10-22T11:55:46.617Z" }, + { url = "https://files.pythonhosted.org/packages/83/38/6be618d98e2cbbb99a40349717532f0c2b6794ddc62f1199e3f83e45a40f/pandas-3.1.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:89b66823678cdd53cf97187e7cd6861402bdd147d11a383706093136a4e83f45", size = 11262553, upload-time = "2026-10-22T11:55:49.143Z" }, + { url = "https://files.pythonhosted.org/packages/06/5c/66e27fb0d691e835bd55b336bdf47312a541a66a17ba4d72a988d743bb79/pandas-3.1.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2a8dc7780720b9e2182799ee86279d4cc7d1fd30b230ea1b202da5b6f7e14c3a", size = 11548155, upload-time = "2026-10-22T11:55:51.674Z" }, + { url = "https://files.pythonhosted.org/packages/37/1e/42e81484c5a6791ab1b8c52c1ff1ef0483c119f710ed58ee603d241b8792/pandas-3.1.0-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e4455cdcc03e42c17233184e8e4ea65c3ed2925d8b1d9d7dd8e6fced3f6e43c1", size = 12029136, upload-time = "2026-10-22T11:55:53.823Z" }, + { url = "https://files.pythonhosted.org/packages/cd/c9/a3b4fd156457f3eb2585838cd56427a113eeeba679204de435a3224b8cc3/pandas-3.1.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:224a9267e5fa70c16bf4f8a317435eef98387940ce6c8c611f1f68f2c3415e10", size = 12575350, upload-time = "2026-10-22T11:55:56.006Z" }, + { url = "https://files.pythonhosted.org/packages/4e/25/23a017003d792117b297bbd87e35a1bb5658509f610ecd5aeaebb84762aa/pandas-3.1.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c261c7a808982557c2917d40ffd27916769f26aacb4e6775c01e4467bcd34c3b", size = 13096142, upload-time = "2026-10-22T11:55:58.434Z" }, + { url = "https://files.pythonhosted.org/packages/1c/d2/f4074b7d1234d112aaa23a796be1574441a9fa873707eb98312a237a6185/pandas-3.1.0-cp314-cp314-win_amd64.whl", hash = "sha256:e77112cfcabb3bef7281a72170670db9a5b31409a1cd3199939abb1fba67a7ad", size = 10885958, upload-time = "2026-10-22T11:56:00.895Z" }, + { url = "https://files.pythonhosted.org/packages/25/9f/bcfc9e0ea07f5c7583e129e62cd0529747317977ac1b85878b80235bb322/pandas-3.1.0-cp314-cp314-win_arm64.whl", hash = "sha256:6c51273b89784c9d54020ed2f60e20524169aef349bddf6cba9f5e83b46077a7", size = 10281257, upload-time = "2026-10-22T11:56:03.478Z" }, + { url = "https://files.pythonhosted.org/packages/05/e4/b3bf0b702c0a886e4abcfc2ebdc681310498592a10d9ca0ce5db6df47b70/pandas-3.1.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:84377fa82d0b40165379a0058d8a1a267ceb09e65160de6fcc75475d4d595884", size = 12134028, upload-time = "2026-10-22T11:56:05.696Z" }, + { url = "https://files.pythonhosted.org/packages/3e/ff/9e08cf048d73f4dfdb10da859790cd1a4213465dec39ea367a606d580c4a/pandas-3.1.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:37f88576f3449bfd86c1052f6b42274b61d7250c8d9dcc55f9dc4de7fe9883ae", size = 11729164, upload-time = "2026-10-22T11:56:08.335Z" }, + { url = "https://files.pythonhosted.org/packages/1f/ff/a0df60d70fac10006841457c30d35647ae0c93af33e420e5e047b15e058d/pandas-3.1.0-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:756fd67995f3efb6b25f8eaea95c0e893f84e0068e2c53c5fdbb65275041d4db", size = 11413041, upload-time = "2026-10-22T11:56:11.38Z" }, + { url = "https://files.pythonhosted.org/packages/55/04/e6742d85ba7d166a1a8a99ff9f4aa9e585fb24028522daa187859ce50b5d/pandas-3.1.0-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c4d9252b57bded194d09cd5ebf18d9b4edac238b16f56984ea8281d2ecfe39cd", size = 11869427, upload-time = "2026-10-22T11:56:13.637Z" }, + { url = "https://files.pythonhosted.org/packages/0a/a1/c9f4915408ef10b541f31b7812ca038dd903572c163b7be7bdcece8a5110/pandas-3.1.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:461b79db058639e446ddf90dd7f4804fae385acf0669f6e31321333e14cb6bc5", size = 12430367, upload-time = "2026-10-22T11:56:15.829Z" }, + { url = "https://files.pythonhosted.org/packages/9f/ed/636b9cc8d30b3e2027b43036dd5a8e9e7cfdd5aa86f4454235c5ef7f96db/pandas-3.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:bfac077874a396b7ae61a307b27aa6f0256cf31c143b7f96ac622765754cc300", size = 12945204, upload-time = "2026-10-22T11:56:18.419Z" }, + { url = "https://files.pythonhosted.org/packages/e6/5e/5c28942fa8c6cf2d8b4e941abee43a44ba0a123f9fef6411f0319aefddd5/pandas-3.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:1340f823a1d189fae8365de314c4d3c2ea0173c61bca66cc76a91385c64d9d3d", size = 11297553, upload-time = "2026-10-22T11:56:20.934Z" }, + { url = "https://files.pythonhosted.org/packages/cd/a4/5991dfb2cccd233ae754f2fb37a4b1474d90595d83f6790467dc5a7d8041/pandas-3.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:2bb3f8cc42fae0c0de5186778f4a4957656b9b3955aca48383abfdac44a83598", size = 10626068, upload-time = "2026-10-22T11:56:23.187Z" }, + { url = "https://files.pythonhosted.org/packages/77/26/5cd42609d8e6cb3991242116a0e499d09f29f5bcf574f72f0a4aba13a87e/pandas-3.1.0-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:04ca88727dfa86837030c493d7512bb1c008aa4f5f14149c5f8d3ff446791340", size = 11666949, upload-time = "2026-10-22T11:56:25.842Z" }, + { url = "https://files.pythonhosted.org/packages/97/5f/ca1ac99da700c215c52fb0f5063a7fbf14878c6e100f687704c21985640e/pandas-3.1.0-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:c0bfec4d188fb0c05bba47b9f9def87624b8c545913aa3585a28f19f27ce847e", size = 11255062, upload-time = "2026-10-22T11:56:28.201Z" }, + { url = "https://files.pythonhosted.org/packages/d3/eb/8d7aef062e7d1f48869288e5c5ae5d109923f37ed3bbd0fdcaecc87784e9/pandas-3.1.0-cp315-cp315-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bbfb087db83eb79ca348047961d367bdd4bf9820d90a5945f3652c7060da7363", size = 11547558, upload-time = "2026-10-22T11:56:30.492Z" }, + { url = "https://files.pythonhosted.org/packages/06/c4/a041c786bc29935afe44806f72c0111f2643cbac5cab761007a227e12e97/pandas-3.1.0-cp315-cp315-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4b5ce18612ca955e7a95d0bacc20120aedf97e2fbbebbe4de029fc24479e79b8", size = 12026259, upload-time = "2026-10-22T11:56:33.219Z" }, + { url = "https://files.pythonhosted.org/packages/d3/4a/ce6690c7e6081c96857ad7dcbe3b978394d8db682610755919e61863516f/pandas-3.1.0-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:a13199463b129363701a540c7b79586581b246b10b6a7ad682c41b268e7991d5", size = 12563821, upload-time = "2026-10-22T11:56:35.402Z" }, + { url = "https://files.pythonhosted.org/packages/75/ec/ae440ecbfa8f5ba0230541b4519c1fed0c4293ff362f18f7f353b12e2dca/pandas-3.1.0-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:8e4de3fe8017e3445f1174707cd977e4e1154a704af2c060a1f648862f81acb9", size = 13086426, upload-time = "2026-10-22T11:56:37.666Z" }, + { url = "https://files.pythonhosted.org/packages/e7/1e/6459d4b0ed9c3d72b9ab63387ec11e1e658579e702e0ef117be8b5d7d0d1/pandas-3.1.0-cp315-cp315-win_amd64.whl", hash = "sha256:eee72bba8a72f0bf9522132e5c87cd10fce9d77440b50f65326b093ec9fb8d43", size = 10877850, upload-time = "2026-10-22T11:56:39.994Z" }, + { url = "https://files.pythonhosted.org/packages/f5/22/e5fad1828e5f923e80a8d7ba9187f687159004a10f99f95dd5346b04c54e/pandas-3.1.0-cp315-cp315-win_arm64.whl", hash = "sha256:9ea2983fe096a3d997c8c4b9f26775fa51e7fb251b10634bd7f87e6fe3542d52", size = 10276384, upload-time = "2026-10-22T11:56:42.193Z" }, + { url = "https://files.pythonhosted.org/packages/59/76/89820c2e1950425f39d30695d2296a8c7d400c4c2d279e7618363a7289ac/pandas-3.1.0-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:84d73e0814a38711e4f2a24b272f748a0b4335169d2f9752407ae26bde22990f", size = 12107257, upload-time = "2026-10-22T11:56:44.498Z" }, + { url = "https://files.pythonhosted.org/packages/c8/2c/af4397e6e855d2c0dcb3392ab7788087be4ef53f6a3f166daad8b83c783e/pandas-3.1.0-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:d3920d9b4de89fbef76ba6b863d58a330b5bfb720c9f8f58eb9b7da9b711d034", size = 11704430, upload-time = "2026-10-22T11:56:46.791Z" }, + { url = "https://files.pythonhosted.org/packages/fd/9e/7791cd269099f9fca7dc4718955cfe4bb94aedb6634b59eeffefb1ed6a74/pandas-3.1.0-cp315-cp315t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a1d61e0eae5219cd024fcbc21304de759964b5642ddcdbb28d9a8ba442ab6901", size = 11378442, upload-time = "2026-10-22T11:56:49.113Z" }, + { url = "https://files.pythonhosted.org/packages/54/6c/da4013bd33c49a12eb3127771111caeed3222a985651e3465bc06f839ce7/pandas-3.1.0-cp315-cp315t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bbb121205af7bc5e587b94a07b5f67b5ba687a918665b9e8748f21388a88b044", size = 11850193, upload-time = "2026-10-22T11:56:51.558Z" }, + { url = "https://files.pythonhosted.org/packages/14/89/8c820bd6fb6ad8f0edb2877639bc7e164bfb4b8e4cdf7a2202f54959ea1b/pandas-3.1.0-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:684ef3add5a92abb99c1622f2bd04b54b8843075a266e8240106ca94ce1d1237", size = 12394022, upload-time = "2026-10-22T11:56:53.785Z" }, + { url = "https://files.pythonhosted.org/packages/92/f5/9d753d85c4ea85b6d1c3735496ae5b416d7ef6905f1845c70e9521d124d9/pandas-3.1.0-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:dde9a55ecd3c4f9100ac50ab15e852e4c54fcf372d800e4495e0b2159a0122ab", size = 12916085, upload-time = "2026-10-22T11:56:56.084Z" }, + { url = "https://files.pythonhosted.org/packages/e6/f9/4a6965677477dbbb19f8881befb01b67eb6009809db73f7f1b7a0132faf1/pandas-3.1.0-cp315-cp315t-win_amd64.whl", hash = "sha256:3fe234bfb9999552dc54c45851752e56e163218d7663897dca950b4a7c32af39", size = 11267097, upload-time = "2026-10-22T11:56:58.479Z" }, + { url = "https://files.pythonhosted.org/packages/99/35/e60fd8228db4eb848802988dbc6e3a0f1a81c57f06ad95ebe4cd534fa655/pandas-3.1.0-cp315-cp315t-win_arm64.whl", hash = "sha256:f1a93c24175129bfc9922ef748ddae1d8137c743ed835e2615be329f606fed4d", size = 10597861, upload-time = "2026-10-22T11:57:00.776Z" }, ] [[package]] @@ -1953,37 +1978,37 @@ wheels = [ [[package]] name = "pip" -version = "26.2.1" +version = "26.2.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ae/15/4500e320e6b101ec3b719ae85b697d9940b6cda672bc555bd6016fc60c6f/pip-26.2.1.tar.gz", hash = "sha256:f6ad667e89a1fe78046c8f13232b247200f5258d7828f3f7883d660878e0813f", size = 1848877, upload-time = "2026-08-04T22:51:14.148Z" } +sdist = { url = "https://files.pythonhosted.org/packages/fa/e8/0842d757e483e895e670f655084cfe8b8c72580c6769a76b30b2789a0608/pip-26.2.2.tar.gz", hash = "sha256:3a8e5c800492787f33a4114ef54444546c340df428974ebba17e0fd47e11af2f", size = 1863336, upload-time = "2026-10-20T16:36:29.008Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f3/6e/1736e5b4ae2b778ef2f81c47d797de9f891d4d8acb047a24ca37a60294dd/pip-26.2.1-py3-none-any.whl", hash = "sha256:71138adf1f4ca900cdb7d289c21b7494329f2332b6d85f0e1c42108c0384ed3e", size = 1816632, upload-time = "2026-08-04T22:51:12.472Z" }, + { url = "https://files.pythonhosted.org/packages/3f/06/3e4420302363fbb71be3c7f35ce042f54bfca330e3e66b3a2e3c4cca579f/pip-26.2.2-py3-none-any.whl", hash = "sha256:fd7f4dfa8190e014013b9f08f3e973f4e4c26ffd179a204895533a7254813a70", size = 1819850, upload-time = "2026-10-20T16:36:27.046Z" }, ] [[package]] name = "pluggy" -version = "1.6.0" +version = "1.7.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } +sdist = { url = "https://files.pythonhosted.org/packages/bf/db/7fc19e6f2dc92a966727031389fc2e08b558f0f25eb7403c1119ad4713cd/pluggy-1.7.0.tar.gz", hash = "sha256:d1eaa46ebb595891b860ab086b4d09c8588af65ebd4361b8e8f4bb8920b90ba8", size = 123304, upload-time = "2026-10-15T09:50:58.343Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, + { url = "https://files.pythonhosted.org/packages/40/9e/2b38731e0fc536806f16490e1a12d7f0dc2a1235aa8cc07bcc75416a7daa/pluggy-1.7.0-py3-none-any.whl", hash = "sha256:7dd7b0d8832ba3cb632c306926ded123429211b83641b35dc5c41ad2d34f9bec", size = 27082, upload-time = "2026-10-15T09:50:56.808Z" }, ] [[package]] name = "prek" -version = "0.5.5" +version = "0.5.7" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/9b/7b/11538b8e6cd7e269261feae62a2d08818fdcf8f43c3b1a253cec01a5488d/prek-0.5.5.tar.gz", hash = "sha256:16bf596768bb754779c7d42317e9ca16ac609c0cce7d13bae0829944ab40a26a", size = 570428, upload-time = "2026-10-05T06:08:59.165Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e0/0c/985c6f0fe1671e639fb97bf5a60f3945369f26d4ccf5a8a1b063d357e6da/prek-0.5.7.tar.gz", hash = "sha256:91049700f0f13b4575deb44045dd6dd51f7c4a70c53ec5d452b09925962e3b35", size = 580753, upload-time = "2026-10-21T00:46:48.231Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2e/77/189e554ea92baf6c1d33733b3e9d1ed819ab7d77e57eb2e3ead0bb1d8060/prek-0.5.5-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:50fbceb0f728b504023f6e6c9037ac0656436949d94f7a85bac5542e6c1a4a75", size = 6038669, upload-time = "2026-10-05T06:08:39.368Z" }, - { url = "https://files.pythonhosted.org/packages/63/ea/fc857c6b3f1e3fdb4d81bdca2051076de82e986cb1455a01b351ac7a5cd1/prek-0.5.5-py3-none-macosx_11_0_arm64.whl", hash = "sha256:2f610927b95e84b28173a4c810892a7b5d5bb55bee792ddf97178fa97654633e", size = 5548955, upload-time = "2026-10-05T06:08:41.428Z" }, - { url = "https://files.pythonhosted.org/packages/47/c3/2bab54f6393fb9f247225681d476086e24fa6242f329e9ca1e5bd638ec50/prek-0.5.5-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:120ef7d4b6bb58155e74827743c266e48501efb0e4c37ff726b14fe6e86a126b", size = 5848228, upload-time = "2026-10-05T06:08:44.051Z" }, - { url = "https://files.pythonhosted.org/packages/aa/f9/1279e1c83819de5904db1a09f4afc589932a2e62c7063713b1fe3a54ee46/prek-0.5.5-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2f6c453a12c21757b23bbbc97415aef78527841ad3f9e61f52cb35cf012f93e5", size = 6371296, upload-time = "2026-10-05T06:08:46.278Z" }, - { url = "https://files.pythonhosted.org/packages/93/fc/e566be5919eaffef4cc22488f16e4bbfd08961734bd67c06f85d405e4cbc/prek-0.5.5-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:73d509c0d9556171de2502bada4962be7a25871e7b23e782549074e1416dc850", size = 6269922, upload-time = "2026-10-05T06:08:48.645Z" }, - { url = "https://files.pythonhosted.org/packages/d2/39/a48d8e6203055bff7bb734b1f2f9aede4a4539bee6ab4c9273dbc01fa546/prek-0.5.5-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:76c867fdbd4e4f19e6b6b2988bd1e1407d1324170ebcf1428e4e34f8f0751a99", size = 5861518, upload-time = "2026-10-05T06:08:50.808Z" }, - { url = "https://files.pythonhosted.org/packages/b7/03/690fba9543033e25ea2e7b2ae1cca1ae8d8c2e32295f0d66f4b50934b8c7/prek-0.5.5-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:c69f60c2b3b4d481e38d0eab3b97ac05ec05d7648ae690925ae95ca9d22c7643", size = 6383685, upload-time = "2026-10-05T06:08:52.868Z" }, - { url = "https://files.pythonhosted.org/packages/8c/04/1e9ccf3a61a3bfd4107b83973f7afde9177b6ff8f2d5d4b00125e77c2100/prek-0.5.5-py3-none-win_amd64.whl", hash = "sha256:15bff9039349466ea9bf6b0c173b822ff08f8ae4cfbfdea782492e519c06d874", size = 5784759, upload-time = "2026-10-05T06:08:55.056Z" }, - { url = "https://files.pythonhosted.org/packages/41/d9/bb6811e438d3c74d26cb5d72e6343606e0a7a6283fee87521189f4ba1191/prek-0.5.5-py3-none-win_arm64.whl", hash = "sha256:3db3191d2509830c75438e20956a47c2259867d18a4106064ed75088b968a0c6", size = 5517355, upload-time = "2026-10-05T06:08:57.193Z" }, + { url = "https://files.pythonhosted.org/packages/0c/36/56c107d100cabfb7e7c7f2e62d363052c77c3fcf664707b66ef9e3b865a4/prek-0.5.7-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:621835811891d11d3feb5ca47102ca97973c5b4e6fbd1b35b1293d412d29d9cc", size = 6062121, upload-time = "2026-10-21T00:46:31.418Z" }, + { url = "https://files.pythonhosted.org/packages/51/59/1868e2d6b3581eb9f327cb7db4270c4f38e80c5b175517e439e106eac704/prek-0.5.7-py3-none-macosx_11_0_arm64.whl", hash = "sha256:ef10cf46c0660b011fc067b7ce20dc78f2c44b6f905bf8dc49402353f67283b1", size = 5686422, upload-time = "2026-10-21T00:46:34.006Z" }, + { url = "https://files.pythonhosted.org/packages/7f/e3/90901432ee7ca288b46872552ca8f658b2262cf56d9af3c1eb0416f206da/prek-0.5.7-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:b38075f9363066d72d1cd887b5a260ec938516fea9ea44def53e592ee75090d5", size = 5817816, upload-time = "2026-10-21T00:46:35.869Z" }, + { url = "https://files.pythonhosted.org/packages/b5/d1/f8e24b15906e433850a2c8409ae3a9270724b2a5d4a71b8c4eadafeb201b/prek-0.5.7-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6a8f7734931c4b6a5f2efca121fabed7979408df233ce6d9b043f4148873021c", size = 6276177, upload-time = "2026-10-21T00:46:37.511Z" }, + { url = "https://files.pythonhosted.org/packages/4b/15/c83456872211706839ff326474aa9f2d870eef7ee5ec2c436446519779d2/prek-0.5.7-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c139c494fb1d9826688f8107d1514fa2a58f8f4a0f0527b68e7970fa50a8b723", size = 6140221, upload-time = "2026-10-21T00:46:39.29Z" }, + { url = "https://files.pythonhosted.org/packages/cb/b5/996b05447d4c2a4399947a7c5c910b2cd2c544b3074c11153ae3e21b4112/prek-0.5.7-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:e86139ab5662124818dc1957854d8281f635414016e545ab72e63365c995824b", size = 5812638, upload-time = "2026-10-21T00:46:41.006Z" }, + { url = "https://files.pythonhosted.org/packages/83/e5/60e0e76cd6819d75026b3a8147ddbd2b3fcab27c02236bce8b3a559e0e64/prek-0.5.7-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:94bd6a93ccf8ebb1d30b822161515e3e31ea916ce7411febe8ceb5f607eb840d", size = 6298681, upload-time = "2026-10-21T00:46:42.618Z" }, + { url = "https://files.pythonhosted.org/packages/54/97/e14b12e7d8fbdb468581d778fb892c855fbca325d4344cc535c12e2851a5/prek-0.5.7-py3-none-win_amd64.whl", hash = "sha256:fa69837b6d1d2b6be5fe7fe4cd27fd5606f2155e784fec55ee7df7ed9f9bf211", size = 5692294, upload-time = "2026-10-21T00:46:44.385Z" }, + { url = "https://files.pythonhosted.org/packages/bf/28/9dc15c0644cc6019c16c5cdd44e5004b641f5e910488f830e80dfdd11042/prek-0.5.7-py3-none-win_arm64.whl", hash = "sha256:239ccb081e3766d83a6a9a5ebee1d5cbb7986fc8e4f230e785423d409cff95e3", size = 5626111, upload-time = "2026-10-21T00:46:46.341Z" }, ] [[package]] @@ -2109,54 +2134,68 @@ wheels = [ [[package]] name = "pyarrow" -version = "25.0.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/3d/e3/27f57f80141379d60defe6703eb50a707325706f07fedfd1312c7a751995/pyarrow-25.0.1.tar.gz", hash = "sha256:9150a83248bfed9813ea3c3af74c3856c1984d444aa28e58bf7733b9750ddf6a", size = 1201653, upload-time = "2026-08-10T12:40:53.904Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ee/8b/0d23b47702fcfe8b3618d5292035099675c5a1c48258932350c08020f7b5/pyarrow-25.0.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:51093dd9e10325fbdb3c10a2ae7c4806e5c822d94e74ae4938b26524a3323fee", size = 35946180, upload-time = "2026-08-10T12:37:18.934Z" }, - { url = "https://files.pythonhosted.org/packages/d8/17/707d17a5476c55a9541fde0db8213ac30979a792864d72415f176ba50c45/pyarrow-25.0.1-cp311-cp311-macosx_12_0_x86_64.whl", hash = "sha256:eb6203482ff3746a5632303a7279ae0b5a304c46985b49ed1378cb350ea6728d", size = 37644787, upload-time = "2026-08-10T12:37:25.795Z" }, - { url = "https://files.pythonhosted.org/packages/c1/b2/cdc98ecf1a6408280bc3a6a07054cdd99a3f4670acc0545d383ce113e87d/pyarrow-25.0.1-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:880523be3d29efcf83d3998835d206118ccf35e3871dbd2fb60408cf6b007a80", size = 46834633, upload-time = "2026-08-10T12:37:33.604Z" }, - { url = "https://files.pythonhosted.org/packages/c8/6e/d3fafc41f378b2c65be43b827798c0fae42049a641c8526633ed3eb573e2/pyarrow-25.0.1-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:25f8720bf6387d5dc2ebd2622112de630760419e4b66134405dd24110d15f37e", size = 50065507, upload-time = "2026-08-10T12:37:40.565Z" }, - { url = "https://files.pythonhosted.org/packages/d5/12/8d0698954b8c3001844a898e0a6900bebe83d7ee40c11195174c5122f324/pyarrow-25.0.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4facd65742a024a4a366328a1d2292062d72d6e023c1b7dda8d4c37544933a25", size = 49955690, upload-time = "2026-08-10T12:37:46.644Z" }, - { url = "https://files.pythonhosted.org/packages/d3/0b/1ecb936ac6409e90a34d58eea1c7cec09a9ae6d2141b9e49ad01a2b1ea47/pyarrow-25.0.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:aa0559502e1cd6254d6814614085dd9c5a3dd0419362978a936a3f68a9e5c3df", size = 53128198, upload-time = "2026-08-10T12:37:52.531Z" }, - { url = "https://files.pythonhosted.org/packages/8e/1c/5236033550633c9b7377b2a53660b2bbb06cb06dc09c4356332d67643ca1/pyarrow-25.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:62cd0d785b8aa6675ee355f9fc02252a340f4441257c42674937826fd7594325", size = 27857263, upload-time = "2026-08-10T12:37:56.943Z" }, - { url = "https://files.pythonhosted.org/packages/a6/e2/9ab15b88cbfac28e16419ce5439ec29234c5172cb8259301b4ba639bdec0/pyarrow-25.0.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:df961f2e7ae9cf496459259d798652c70625f6c080650d6952f8c04053c58ee9", size = 35861559, upload-time = "2026-08-10T12:38:02.567Z" }, - { url = "https://files.pythonhosted.org/packages/58/79/a0036dbe1eabe1f73127427342f1d99982584c4a2cde2651d6c93499c6f6/pyarrow-25.0.1-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:cc4aa407fde9fc660be3939e49ea31f50f3e9fec17c0ec63159f7711edd3efc9", size = 37628383, upload-time = "2026-08-10T12:38:09.083Z" }, - { url = "https://files.pythonhosted.org/packages/13/49/d93a57d375f4bf0cf82913dd6bb54acafde83dd993be2282c81ac5616cad/pyarrow-25.0.1-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:4340f0ba6c1d2e13f21658de1d7c662ca2545018568d0030a1e9afca159d87e3", size = 46820190, upload-time = "2026-08-10T12:38:15.458Z" }, - { url = "https://files.pythonhosted.org/packages/60/c9/711ca85d79f1ec98f29a5eae2b051e25b4ecec5de3e3c0e2d5c5dcb15664/pyarrow-25.0.1-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:5389cdf79447ed1515c9e31620e6e1e2302249564d603f2ad727d4f6d313e4c3", size = 50102437, upload-time = "2026-08-10T12:38:22.487Z" }, - { url = "https://files.pythonhosted.org/packages/80/53/8fb8359ff17cfb6263a1cf3ebf7caec9fe197de118719e84fcb1d0618026/pyarrow-25.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d51592cb7561e87877c506113e7adbf1342ab579e6c21f0ef44b8ba41cb74c80", size = 49942424, upload-time = "2026-08-10T12:38:28.755Z" }, - { url = "https://files.pythonhosted.org/packages/e8/83/4e5ae02a9341571b18a6fca380ac7a58ce6ddae7ab3c060208c0a1e79f02/pyarrow-25.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6109c94d8b9f3b17a041daca16cacb2f651ad8f1ef70a4232c2c0f37a23da2a8", size = 53144206, upload-time = "2026-08-10T12:38:34.862Z" }, - { url = "https://files.pythonhosted.org/packages/65/ee/197cbf47e49f83e6ebeb946a5259a48a638dea27ac774db42fe78022179d/pyarrow-25.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:8858d7bfc22e3f51529aeaa4077225029724623e4595dc9eff8c793935c34140", size = 27953934, upload-time = "2026-08-10T12:38:39.808Z" }, - { url = "https://files.pythonhosted.org/packages/cc/8d/8f271a7a034c834910ec925d56fa4b29733b1380f5289419f5aaa3b02777/pyarrow-25.0.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:c7c534ec03c358a76ea3e505e74c1b6aef290af90c444dfd092dbfe23e755b85", size = 35855328, upload-time = "2026-08-10T12:38:45.489Z" }, - { url = "https://files.pythonhosted.org/packages/d2/cd/5bac242f4e841b9971d5eb94fdfe2577e2b70be983e27401e72055786037/pyarrow-25.0.1-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:dda9470024204d7bbf2042b47c6e8a0e47a3eeb8e34405882dfaea6577e0c153", size = 37622415, upload-time = "2026-08-10T12:38:51.107Z" }, - { url = "https://files.pythonhosted.org/packages/63/1f/96d03b4e1506524f7087adb0fd6b2f69f0c9c7aaff1ec36d8030082e15a5/pyarrow-25.0.1-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:44a9120ce5bd81936b8ab9a88076e3fd47c2c6838e0e43630fed83626aca81d9", size = 46813813, upload-time = "2026-08-10T12:38:57.773Z" }, - { url = "https://files.pythonhosted.org/packages/98/d6/33a411115b61dbfc16ad6ad73e71730f6fea654ee3667673bc53ab0e2fe7/pyarrow-25.0.1-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:0befcf816e45a1af33ac775a9970b749e4868a230c7372f0ae5e932bee27039f", size = 50104452, upload-time = "2026-08-10T12:39:04.579Z" }, - { url = "https://files.pythonhosted.org/packages/33/ae/b1b97c9ca87f9f9ddbb5230c798df94eccce61bd79b9b45458c69a478588/pyarrow-25.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:3f89685964f46e4216103c75483aac0c0692a5f72212d7ca835adba5ede56ce3", size = 49951343, upload-time = "2026-08-10T12:39:11.8Z" }, - { url = "https://files.pythonhosted.org/packages/98/9e/a112df5cfd5a68cb1d9fc31cfe38c28d5aec9f10865ce37ecef2e4450873/pyarrow-25.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6943e2fe7954d29d84de45d29d34c8dc36ce96570e67d89aa9976e650a4a9138", size = 53144784, upload-time = "2026-08-10T12:39:20.503Z" }, - { url = "https://files.pythonhosted.org/packages/31/24/97e8bd98f1e3b07e2ba08bcdff690674fbe16d69a7d2712cc3884665e615/pyarrow-25.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:31e49a7888fcdf3a835da33ae777f6bb9a866334e5a789282fc26dcf426f7f15", size = 27870159, upload-time = "2026-08-10T12:39:26.161Z" }, - { url = "https://files.pythonhosted.org/packages/36/4c/b525824ad3094076919273cd97db61fb3d78252dee76fa3b8dc8f76774aa/pyarrow-25.0.1-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:bf0b672390cdcb640d7288f96b826d71ff4e9abb254a86c89890baf51a29cee6", size = 35885255, upload-time = "2026-08-10T12:39:32.366Z" }, - { url = "https://files.pythonhosted.org/packages/08/62/448bb0e940de41aec31d1a956e63ad9c54afdf122a103cc3ab20c2a3ce33/pyarrow-25.0.1-cp314-cp314-macosx_12_0_x86_64.whl", hash = "sha256:38a9a4b4b9613380e200641891495a56c3d5a98a092db4a870af9975e220471d", size = 37644461, upload-time = "2026-08-10T12:39:38.142Z" }, - { url = "https://files.pythonhosted.org/packages/6e/9a/13587e38bd4806fd218f50fd13b8903fab60588a699ff0c406372e5b4043/pyarrow-25.0.1-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:0b726ad7e7b669be982b0c71c07fe4b037d654354130da79a7902a669e93a66b", size = 46877146, upload-time = "2026-08-10T12:39:43.722Z" }, - { url = "https://files.pythonhosted.org/packages/8d/61/1c5d1229fa21da4cff5365e41e57177aaac57c563c727f35419b8513d1c1/pyarrow-25.0.1-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:9171748cdf796972d85a4b60157c279913e242992e350c90c7450182a9838b2a", size = 50131616, upload-time = "2026-08-10T12:39:49.304Z" }, - { url = "https://files.pythonhosted.org/packages/43/20/291e1d65cc0b09aa19f03cf25cf51a2f5fa94b5db315178f2d254ed5cad4/pyarrow-25.0.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b7a296aac7a71fa0886c08e155ddb6c636a50013f801f6178daafa0f9e726188", size = 50008879, upload-time = "2026-08-10T12:39:56.891Z" }, - { url = "https://files.pythonhosted.org/packages/8b/7c/1b7c9ec28e76576337e4f97b31141c9a181b89b6d1d6221e9d8205621a58/pyarrow-25.0.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:0fe7c8b6c03969b49c8c66182e4a18e3819ab92d07cfab5d8370c531b9369ef0", size = 53170864, upload-time = "2026-08-10T12:40:04.918Z" }, - { url = "https://files.pythonhosted.org/packages/b7/75/f3d789dc06011a765d14d86bda799cf72ac1d715b6a6edecaa0d73d95062/pyarrow-25.0.1-cp314-cp314-win_amd64.whl", hash = "sha256:f729cfdbd36fd99d543b67a914d2de044c84ebe45be8b34902b299b608c15c8f", size = 28620729, upload-time = "2026-08-10T12:40:51.41Z" }, - { url = "https://files.pythonhosted.org/packages/fc/05/647a8ee6f7c2662feb6921315617bc04dcd6034763fb61b1199720bf6162/pyarrow-25.0.1-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:59a2de54c0cbd954da861eee4d1d330f8e909c45b53455baef696380f2c55033", size = 36130288, upload-time = "2026-08-10T12:40:11.014Z" }, - { url = "https://files.pythonhosted.org/packages/93/f8/c9ee997554d7bea94520667dd1933f109ac1da3ee3556d2b49381e023484/pyarrow-25.0.1-cp314-cp314t-macosx_12_0_x86_64.whl", hash = "sha256:35935cd5de130aa5cf4dea052a63e6bf2e17006c35c3a468194242b9b2bf5956", size = 37762187, upload-time = "2026-08-10T12:40:16.592Z" }, - { url = "https://files.pythonhosted.org/packages/a2/08/a28c01c7fe9e96e8233ce2d13df1d402f4f999f848f51d2daacd6bb4c036/pyarrow-25.0.1-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:f3831aaa25c67a99f99dc8b05873cb9d64560390372e2aa197ce9dd4a3f06a44", size = 46888003, upload-time = "2026-08-10T12:40:23.242Z" }, - { url = "https://files.pythonhosted.org/packages/1b/b9/58612e977d28dc58c878448866838369ee8da2f1e7cc8ed2c84b952aafee/pyarrow-25.0.1-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:6a1fdfc6659b6b19022f2e50627fb5cf7156a66c46bf4299379955cbe742382a", size = 50079036, upload-time = "2026-08-10T12:40:29.169Z" }, - { url = "https://files.pythonhosted.org/packages/72/13/66e1402dcc860e1dc2760b1e0292c9a569b62b3bccab69def1b3e907d006/pyarrow-25.0.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:169d3429d5be7c752125890620f75a60776d38b0035eddae939651640822332e", size = 50040226, upload-time = "2026-08-10T12:40:35.186Z" }, - { url = "https://files.pythonhosted.org/packages/78/10/3f1a5497a7ef732ab0f03ecca3e66d89d9c0f57fdc61b4794c456b781f01/pyarrow-25.0.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:119297a6dc197e45d9c6d4415f7814a67ffa36c180d26f68c154c58067ae782d", size = 53149035, upload-time = "2026-08-10T12:40:41.454Z" }, - { url = "https://files.pythonhosted.org/packages/93/c0/37d4a7e8e2f7a6076283673d5298018ca26478b934c6ee369e10505ab32c/pyarrow-25.0.1-cp314-cp314t-win_amd64.whl", hash = "sha256:4288f27577352d608ca08553b0865e4a9b3aa14820c5d95b53337218d609835b", size = 28753071, upload-time = "2026-08-10T12:40:46.623Z" }, +version = "26.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ec/34/17c34cb38e5d940e38f0f0d9fdfa0e8a506676409ea9b85aff7e3079f831/pyarrow-26.0.0.tar.gz", hash = "sha256:0cccd36e00ea3afeb52ded61f2721ce71f604853d70c45365c58324eb773d6ae", size = 1239433, upload-time = "2026-10-09T08:26:25.315Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/07/68/e0707097cee93be7f693e7e89495fabfeb8bf95ee30619063f8b30fffc29/pyarrow-26.0.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:fcdd1e04982637c6042337d3e24d472f938f01fdc502e2b994844b726d12c3f4", size = 36370896, upload-time = "2026-10-09T08:13:28.874Z" }, + { url = "https://files.pythonhosted.org/packages/5c/f0/591211c00612aef83236daff1620412b24aeb07c646de08c18a8a6c95a39/pyarrow-26.0.0-cp311-cp311-macosx_12_0_x86_64.whl", hash = "sha256:f800e9e722c145ccd18012d82a864cb21bfee4ba4ceffde77100d25eced511a9", size = 38709806, upload-time = "2026-10-09T08:13:33.417Z" }, + { url = "https://files.pythonhosted.org/packages/50/ea/9b035a9d1556e06e64ea86169d9a985d0fc092d427ac5edbb3af7183289c/pyarrow-26.0.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:7aa12ab8e236789b1ecd2d6ecaef036b4e63d675ddf1864a43c6799d18f2d028", size = 50885975, upload-time = "2026-10-09T08:13:37.737Z" }, + { url = "https://files.pythonhosted.org/packages/e1/81/8e685683897a6d3d5887c3e2fd24f3c14bc5d6d6bb3a2387484e665c580e/pyarrow-26.0.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:6e89dee53aaeb50505ed6152ea55bc7ddfd4f4df264f5427ea255288d8f0e580", size = 53904793, upload-time = "2026-10-09T08:13:42.984Z" }, + { url = "https://files.pythonhosted.org/packages/9a/ad/d474a0b1b00110f3a879aa5df654f857c81929a32b2a4222869240de5220/pyarrow-26.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:f1c1b4263fd13abbc339a16f2bf19f3a5cbf2a620853d812b1256f03c5342cb8", size = 54458010, upload-time = "2026-10-09T08:13:47.778Z" }, + { url = "https://files.pythonhosted.org/packages/d4/86/2c2861e905810c59fed4d98c85b994c21e8613730c5c3b436781d89110f2/pyarrow-26.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ff1e816af7abff71f289242e109217036723ce36aca74ad6691e52d964a74afa", size = 57368406, upload-time = "2026-10-09T08:13:52.651Z" }, + { url = "https://files.pythonhosted.org/packages/0e/02/823e606633c15155bb965c7a0f3750c4f20dd47c4ab48213c7693df0e0ba/pyarrow-26.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:13b0972a3dc71b642050d1bc72664a3916e14f59c943d8c1368154d6e4b0c2d5", size = 28522657, upload-time = "2026-10-09T08:13:56.513Z" }, + { url = "https://files.pythonhosted.org/packages/b3/60/6793778f2617cce469383dac0ba08c4f2401cf342df0c7b9ca53939d9b46/pyarrow-26.0.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:90ddaf7c625307ad52f31a9b25c34fe5e4897c7529ee3481135822b2b6842ff1", size = 36333953, upload-time = "2026-10-09T08:14:00.387Z" }, + { url = "https://files.pythonhosted.org/packages/db/81/f944cc63ce8a753e5fbff25de6d1d475ebd7fffdf9cf98c65130294fc896/pyarrow-26.0.0-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:ee341973f78a0b46e073d065e88e75026a9c584051e97f98a0d05d96c6bac7dd", size = 38688456, upload-time = "2026-10-09T08:14:04.344Z" }, + { url = "https://files.pythonhosted.org/packages/f5/2d/7e5c722fa5d5d9f3b75e62fe11694b34217664d4f05ac88031197166b277/pyarrow-26.0.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:01c863a18bd9c8412453dd0d92de6d0ee7b2b3d6fb079d9734a4b2a3c8bd4453", size = 50867603, upload-time = "2026-10-09T08:14:09.115Z" }, + { url = "https://files.pythonhosted.org/packages/88/e4/9cd356d906e71bd79b0c3fc5c9a54e01a0020dcf14c152ccfbcb503c7298/pyarrow-26.0.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:6a628922ba20705fa964ca73e4ef959c2fb2f14b9bbec5589a6a1e68e6257c85", size = 53931932, upload-time = "2026-10-09T08:14:24.051Z" }, + { url = "https://files.pythonhosted.org/packages/bb/e4/5bae3133b7fe04c24907a20f3bc1fba388cbbde659199e7b76445982047a/pyarrow-26.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:954d971b363b16ee41f89389a4053315dc71265f2ce5c2468eb0a910b1166268", size = 54444720, upload-time = "2026-10-09T08:14:31.214Z" }, + { url = "https://files.pythonhosted.org/packages/ba/b4/ee422493bb6dafdbef776cfe2c2a73106a1063a79bf4e78d1e5f51176885/pyarrow-26.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5d5768d03426abe6526d5274adefa00abf00a7f81118c46e98b5a46390f5549e", size = 57388949, upload-time = "2026-10-09T08:14:38.964Z" }, + { url = "https://files.pythonhosted.org/packages/54/3c/1783aab1dac28e175dcf26dfc7123725efc474caecaed91e8a34cb89cad0/pyarrow-26.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:cc903e1069e9dd5e9dcf780324c0112e27e051e422ecfaff574fb33ed65d9160", size = 28567581, upload-time = "2026-10-09T08:14:44.279Z" }, + { url = "https://files.pythonhosted.org/packages/4d/35/ca95493712af97c46a312945c8e9d16b21c5fe2f148be5466168d0290505/pyarrow-26.0.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:a6ca849f90cf73fe361f08a5762c783ead9671e4548c1f558cc637b54c9103f2", size = 36336700, upload-time = "2026-10-09T08:14:51.399Z" }, + { url = "https://files.pythonhosted.org/packages/69/ef/b1a675f79c9babfd4fcd99af62141d3c2d1a78a524e311b0c6b80110445a/pyarrow-26.0.0-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:c2ba350957076b1b3a22f549261dc3e9c67ca20816d8bd5f79d7b9c69be4c4c2", size = 38698502, upload-time = "2026-10-09T08:14:57.114Z" }, + { url = "https://files.pythonhosted.org/packages/3b/7c/cea852a832a327a8de797b3a68e5c25ce0f5aa1d20503807671bd90ec642/pyarrow-26.0.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:e3b190ba1d3d22a5a8758597f797111b77d433473744352a184a5ee0a42d672e", size = 50865064, upload-time = "2026-10-09T08:20:01.614Z" }, + { url = "https://files.pythonhosted.org/packages/4f/d6/e95834b29360092376fe4da9956ba41bb7b021869efe6ee9d4172d05cb15/pyarrow-26.0.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:240bd18a7487f8767616a948a69dd4e740a8bc36a1c9da49e4dc9a32c5c2faed", size = 53926722, upload-time = "2026-10-09T08:23:10.829Z" }, + { url = "https://files.pythonhosted.org/packages/e0/7f/98257444e2aea2e1fddceee3af3bd2077236d550428413f80393bd1f888d/pyarrow-26.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2b5fcd69c0e1107b79e55839877db5a6ed04651b73fd6fec581d09e230bed5e4", size = 54443093, upload-time = "2026-10-09T08:23:16.971Z" }, + { url = "https://files.pythonhosted.org/packages/88/ca/dac99cfb25cfa62bf7194600cc99abc14a6bd2af50d7fdb7f15eeaf6e202/pyarrow-26.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f7444ea6975c49a857c68f9bd8fa11acae96dede63d120ffb3bf0a603ea82516", size = 57381937, upload-time = "2026-10-09T08:23:24.95Z" }, + { url = "https://files.pythonhosted.org/packages/c0/ed/138d29fddaf803b90f4527e124bb6aaddc18aaf4a6c50fd0a5f577c94989/pyarrow-26.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:3de30a7432b48b98b9decbd9e25a53bb9251d202c2e6c5a29a50869592ccb117", size = 28478571, upload-time = "2026-10-09T08:23:30.535Z" }, + { url = "https://files.pythonhosted.org/packages/8c/32/01858422a37f083911c2bb4d15cc32c5eeaa9d9b2bf5ddedee995a7146a6/pyarrow-26.0.0-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:5780d487ff6c6ed7b42298609680d87fe0036e529a9dc2e1105364bce9697f50", size = 36378402, upload-time = "2026-10-09T08:23:36.537Z" }, + { url = "https://files.pythonhosted.org/packages/00/85/f6b5976c2878b752d0804d371684e0495a71de296b6dc6559e6fbaa4311a/pyarrow-26.0.0-cp314-cp314-macosx_12_0_x86_64.whl", hash = "sha256:a0e4e92eeb088f1d7c2c04d6c7de8434c75abb4b4ccf0bbcd045aa7164c68d93", size = 38733074, upload-time = "2026-10-09T08:23:42.873Z" }, + { url = "https://files.pythonhosted.org/packages/81/bc/c90fcbbcf893631e23dab1b0fb3fa29a508a8614326571b03c0894eda00b/pyarrow-26.0.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:eaf9e7cc7ab59f6c760232bbde18f64d559bbc50544841303bfb32be53533297", size = 50929201, upload-time = "2026-10-09T08:23:50.507Z" }, + { url = "https://files.pythonhosted.org/packages/ec/c1/0c1ff38ab7df1b2cf54cf0ad9f19a516c4e416c6c9b4c966cc2c9d587f77/pyarrow-26.0.0-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:ab6914db225d7f399652ae1f08588dfbc9efe617612715701e3d9d5cfa5ca19f", size = 53951865, upload-time = "2026-10-09T08:23:57.692Z" }, + { url = "https://files.pythonhosted.org/packages/9f/70/6a6b170496925472adad45a32528770fc8632db35fc60d4edd1e9ce1be0b/pyarrow-26.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:41dd3661ef40790a78870052ad7a58ad827b27c67a4511f06962eb9e9b74d19b", size = 54496388, upload-time = "2026-10-09T08:24:05.23Z" }, + { url = "https://files.pythonhosted.org/packages/a8/32/033ef9dba80976820190e292a10a5a23e9406572b76bbeb4d685d90e5c8d/pyarrow-26.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6e949744dcfc2d379808f7013c5f9cafaf0f817656dff7d46c6931528dd1784b", size = 57411588, upload-time = "2026-10-09T08:24:12.043Z" }, + { url = "https://files.pythonhosted.org/packages/1e/ff/a74892c50aaf1f9f744a84493e08a2f99221e77c39d2d4a926de21a99edf/pyarrow-26.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:4a5fa8dc70dd50808990ff36faf44088e357b353d86c7682dd92d4b78d4c97d5", size = 29237858, upload-time = "2026-10-09T08:24:58.106Z" }, + { url = "https://files.pythonhosted.org/packages/03/10/f0ee0976ef08a851a743c57608917ac9a47623f688b9ee0efe5429975ba1/pyarrow-26.0.0-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:e2a1856e9565fe2679863b372478c681806aebbf7d0a6e72f33e77f804e647d6", size = 36495870, upload-time = "2026-10-09T08:24:16.479Z" }, + { url = "https://files.pythonhosted.org/packages/27/ca/0bc431a509bf10b4472dbb94f4184752ecbbddeb7f467152dac0fdaed469/pyarrow-26.0.0-cp314-cp314t-macosx_12_0_x86_64.whl", hash = "sha256:4bcba83299cb2b8f8e443d36c6ba6269a5034431879015fb0719495df8a14de2", size = 38819754, upload-time = "2026-10-09T08:24:20.875Z" }, + { url = "https://files.pythonhosted.org/packages/61/59/2be41d26af7a07fb71581fb753cae396403ba1a2978355fd553929d44a9a/pyarrow-26.0.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:3a4d235876f14b4136b4d616ec42eb469ea0d6ead336cae631aa1dd29b21c962", size = 50933671, upload-time = "2026-10-09T08:24:27.199Z" }, + { url = "https://files.pythonhosted.org/packages/4b/cb/b6d5048cf3178be9678f5c9c60040199894b2f69c3439c87ced91fd24da9/pyarrow-26.0.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:210cc9b83888b87cdc8f793eebb264f22b20d0dedbedefc73b9687a7047b4747", size = 53906419, upload-time = "2026-10-09T08:24:33.536Z" }, + { url = "https://files.pythonhosted.org/packages/09/2b/23e30fbd776c81d18d134d2592eb60daca13e8a57ab087d0fa042f9d9f3d/pyarrow-26.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ca77c43ca55bfc9a4eeb1f0cd5f093f08731b77c24cdba0829035f084959b0bb", size = 54527960, upload-time = "2026-10-09T08:24:41.292Z" }, + { url = "https://files.pythonhosted.org/packages/e2/23/fce251cd6b0546dfc181b00d5c8ef1c95a8c4cae83266bc3dfd5f719c62c/pyarrow-26.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:290a74c48e9491b436fd5edacfadf357943f82aa45c81110bd83a69aab33d1cf", size = 57388010, upload-time = "2026-10-09T08:24:48.186Z" }, + { url = "https://files.pythonhosted.org/packages/44/a5/0126fb0ef8d59bf257bdd68bb41623b72afc6e81790a0b4ac863a0f58861/pyarrow-26.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:515a10dae2a1d236bc9c9209d0317acb6746ea63cd4f98704904af7156d90ed1", size = 29406123, upload-time = "2026-10-09T08:24:53.387Z" }, + { url = "https://files.pythonhosted.org/packages/ed/66/8ada1b5165359d84b4b9b5384742304d1081da670f77d458fd9c9b8a2161/pyarrow-26.0.0-cp315-cp315-macosx_12_0_arm64.whl", hash = "sha256:e890816e5ee89c74a0f8b9379fe8b5ba83f46132b2a0bbb9b1c21359ec30dfda", size = 36373215, upload-time = "2026-10-09T08:25:03.067Z" }, + { url = "https://files.pythonhosted.org/packages/c4/83/74f10c3d803a6834b2acab21847724d4bdbc74d246eb17321432844707f3/pyarrow-26.0.0-cp315-cp315-macosx_12_0_x86_64.whl", hash = "sha256:9db18a9dc0af52135c9eac549d80a7a882696efbe5406cf882b044525d4ecc2e", size = 38730866, upload-time = "2026-10-09T08:25:07.924Z" }, + { url = "https://files.pythonhosted.org/packages/e2/5a/ea2fa2163b1bd8ff73efd39c4060be63fd6ddec03e7887a471acd1e042a4/pyarrow-26.0.0-cp315-cp315-manylinux_2_28_aarch64.whl", hash = "sha256:734312d3d99088d9ec28c5b17bad40389bd8373a1afc10acb60b83fd217af087", size = 50924443, upload-time = "2026-10-09T08:25:13.864Z" }, + { url = "https://files.pythonhosted.org/packages/78/80/8c47b6cf8cfd42826df65193eff026c1cc81fa6cb213a3c3f5d203e6f67a/pyarrow-26.0.0-cp315-cp315-manylinux_2_28_x86_64.whl", hash = "sha256:24f892fdf1ae1942d69d3f7742e2f49960ec95277cfb1a70b8a1d91f4a96d935", size = 53948540, upload-time = "2026-10-09T08:25:19.305Z" }, + { url = "https://files.pythonhosted.org/packages/69/1f/3a506a76d944ec5c5e4b7f01d8d0446b392a6fb384de627a12e503f616b4/pyarrow-26.0.0-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:879331ddea2a26479fa18fade71e6facf684a6cf19f67daec3775c871569e8e5", size = 54494863, upload-time = "2026-10-09T08:25:24.517Z" }, + { url = "https://files.pythonhosted.org/packages/3d/50/08c4bb04d651788d2eaca78065743f4f6ded974d4ef96ae3c473993e9d0c/pyarrow-26.0.0-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:5b827650e874f1f9f9392524ea3e9e3e8a245de5ba64acca1f81ab188090afb9", size = 57409877, upload-time = "2026-10-09T08:25:31.157Z" }, + { url = "https://files.pythonhosted.org/packages/d4/f3/c64781fbd7b6d3c07993b698c14944d0d195f07e800fa931c486ae6ab36a/pyarrow-26.0.0-cp315-cp315-win_amd64.whl", hash = "sha256:8e8e28c464552b5ca03e30d4504168c4425ce383884f8611b00e972f9fd933fc", size = 29236658, upload-time = "2026-10-09T08:26:22.607Z" }, + { url = "https://files.pythonhosted.org/packages/06/55/2ee3729daea999f19f061f03898d4895a242c4cd94f26e1324e5fdfbfe10/pyarrow-26.0.0-cp315-cp315t-macosx_12_0_arm64.whl", hash = "sha256:ce28748cbeb0f29c3ce9603782979c7117580fc76f16aa3ca448b38a22281adb", size = 36489011, upload-time = "2026-10-09T08:25:37.64Z" }, + { url = "https://files.pythonhosted.org/packages/6a/7d/3eb17f601f2bf13eda5f2ed28956379ca628b4dda97619cbb1cb1721622d/pyarrow-26.0.0-cp315-cp315t-macosx_12_0_x86_64.whl", hash = "sha256:106bb9290fc6fd9a84138a9440038ef184bac86463543c5ff099229cb30d996c", size = 38808480, upload-time = "2026-10-09T08:25:43.579Z" }, + { url = "https://files.pythonhosted.org/packages/0e/e3/f0047360b0f4bfc031b256dc0aec3837a61f245b2fb70f8363438e2db665/pyarrow-26.0.0-cp315-cp315t-manylinux_2_28_aarch64.whl", hash = "sha256:2e4a413046eba9896e632925066c74095182200ba32e19ff0166bf64d2f936ac", size = 50923273, upload-time = "2026-10-09T08:25:51.445Z" }, + { url = "https://files.pythonhosted.org/packages/38/d9/56d9fb91210407df31cbeb9b91138601c88c7c8fb5f6bf773b20d65509bf/pyarrow-26.0.0-cp315-cp315t-manylinux_2_28_x86_64.whl", hash = "sha256:d58798c4d8d629700058e9afc1e16b9801023f3ce4dc1c92d945e79b5ffe4e98", size = 53900905, upload-time = "2026-10-09T08:25:59.554Z" }, + { url = "https://files.pythonhosted.org/packages/cf/40/8e8a7e9e027c731520c7eb179dd00a153b76ebf0bc11d213c6c8f8502851/pyarrow-26.0.0-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:645917e976671debabf854abab6e2b75c571ca4f82adc33a2d338697f7c27d93", size = 54518345, upload-time = "2026-10-09T08:26:07.125Z" }, + { url = "https://files.pythonhosted.org/packages/be/89/1e768a3fdb88d34e708ad2dc00dbf8e4e30290784eb84198d59308963bea/pyarrow-26.0.0-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:7c3fda041e7078802589cf257750323ee3d0cd1e56e53a9b20ec845697fb3d28", size = 57379403, upload-time = "2026-10-09T08:26:13.624Z" }, + { url = "https://files.pythonhosted.org/packages/96/be/7b81a44d6a8e70581dcc1d6f01541f9000a973b1e5d75394aec91e7b179a/pyarrow-26.0.0-cp315-cp315t-win_amd64.whl", hash = "sha256:68cd662e9e2b00876a131950cf32336ace2d0865e1f9418763e3d3be8481dfa4", size = 29389953, upload-time = "2026-10-09T08:26:18.277Z" }, ] [[package]] name = "pyasn1" -version = "0.6.4" +version = "0.6.5" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a4/9a/23310166d960def5897e91fe20e5b724601b02a22e84ba1f94232c0b7f67/pyasn1-0.6.4.tar.gz", hash = "sha256:9c447d8431c947fe4c8febc4ed9e760bc29011a5b01e5c74b67025bd9fb8ce81", size = 151262, upload-time = "2026-07-09T01:12:33.988Z" } +sdist = { url = "https://files.pythonhosted.org/packages/88/7e/3c0da41061a2021cac7cd524fd57092adbe5b515e8bf9005702e366802e2/pyasn1-0.6.5.tar.gz", hash = "sha256:6b11616a6bcf3a40e635f082bb232fa57b5e0dd7f517bd7ddd69ec3794508a47", size = 155152, upload-time = "2026-10-27T06:32:49.125Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9a/3b/6163796d69c3977d1e4287bea4a6979161cbbdd170ebb430511e8e1999ce/pyasn1-0.6.4-py3-none-any.whl", hash = "sha256:deda9277cfd454080ec40b207fb6df82206a3a2688735233cdcd8d3d565f088b", size = 84410, upload-time = "2026-07-09T01:12:32.92Z" }, + { url = "https://files.pythonhosted.org/packages/19/1f/3eefd5c84c35cae44549ce1c7f7e58f7945b34f5a6bce07a135438522073/pyasn1-0.6.5-py3-none-any.whl", hash = "sha256:4017b0fce125096b8573b39c989fdb1a4b19cf761e683398184550fea0e66587", size = 84807, upload-time = "2026-10-27T06:32:48.045Z" }, ] [[package]] @@ -2173,11 +2212,11 @@ wheels = [ [[package]] name = "pycparser" -version = "3.0" +version = "3.12" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1b/7d/92392ff7815c21062bea51aa7b87d45576f649f16458d78b7cf94b9ab2e6/pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29", size = 103492, upload-time = "2026-01-21T14:26:51.89Z" } +sdist = { url = "https://files.pythonhosted.org/packages/cb/27/eeb9f307bf07060f3d9f9aa0fb8c15b01bdfded0a941e4b2719c7f94cacd/pycparser-3.12.tar.gz", hash = "sha256:f03387877eab82ce01bbdfd0ba7252ee55a90b1c708d5129e2ce915475cf1db6", size = 158785, upload-time = "2026-10-22T13:40:03.56Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992", size = 48172, upload-time = "2026-01-21T14:26:50.693Z" }, + { url = "https://files.pythonhosted.org/packages/b6/ae/315bbeee126a769cbde43cf4908acb4b40408d8bf8bbf334dd7368d8dadf/pycparser-3.12-py3-none-any.whl", hash = "sha256:a076f3f489b39b43ed13563a1dafe42447445bec2e294154ec5c57fcdda4c85e", size = 62600, upload-time = "2026-10-22T13:40:02.021Z" }, ] [[package]] @@ -2357,55 +2396,65 @@ wheels = [ [[package]] name = "pyqwest" -version = "0.11.0" +version = "0.12.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/99/62/d1f6fdeec5ffddf7af818e8921107f32c5932f228927d2707a8497836354/pyqwest-0.11.0.tar.gz", hash = "sha256:f9e495265537095aae0148031ea32a9fb51698ae4b52123313f262c29a318ba2", size = 509444, upload-time = "2026-09-25T05:03:58.797Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/00/f2/82eb9f787a182a56386667488c2e01df1ca0b7c60f94e09a3ac775fa9738/pyqwest-0.11.0-cp310-abi3-macosx_10_12_x86_64.whl", hash = "sha256:17b6456d0894de3f67074bf70c2fc7010903b5bfcbb7a54091128c674f5504c4", size = 5274121, upload-time = "2026-09-25T05:02:24.482Z" }, - { url = "https://files.pythonhosted.org/packages/13/06/9fe4bef6da580a99750ccaa37e6238885a05731ce4a3f5ed3958dcb94596/pyqwest-0.11.0-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:53e961bb7f41d57feba054f2fc7a9a2656a9f98383bdae3dc6daffd74e95072a", size = 5134835, upload-time = "2026-09-25T05:02:26.635Z" }, - { url = "https://files.pythonhosted.org/packages/8f/e2/1eaa06d78b4cf26bc6052414897d093c29d5ef48e62067eb039c92b8629b/pyqwest-0.11.0-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:de3d73a5bae31e6dec1a935175a1d0cf0c5d8cfa4d1812f9650dffe11f8b4b2b", size = 5660002, upload-time = "2026-09-25T05:02:28.599Z" }, - { url = "https://files.pythonhosted.org/packages/e4/e0/6b84f77181176ec1658755294e997e6e13a7e462b56663d8c219c3a6f699/pyqwest-0.11.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61ab2e4d83953490391f590053d2a84d6fe03b010ad1b2d3b74e20c6ca352541", size = 5616127, upload-time = "2026-09-25T05:02:30.96Z" }, - { url = "https://files.pythonhosted.org/packages/49/df/77bfc34da553879bf65b793f753e4789ffb575d9373bd9ad47ef59e12ef4/pyqwest-0.11.0-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:a6689d6aa2fd21ce3292293799dcc1b3edd115ad086cfc7564d4f4c911c63d78", size = 5828630, upload-time = "2026-09-25T05:02:33.187Z" }, - { url = "https://files.pythonhosted.org/packages/bb/57/6159490cda008500a9171b9d16b54f3c8227920fcc34ffb20cac816db1c3/pyqwest-0.11.0-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:06af1685b17871f13a2d3b47ef5d8562f9da11c56e08e4fee994ee624211d382", size = 6025477, upload-time = "2026-09-25T05:02:35.286Z" }, - { url = "https://files.pythonhosted.org/packages/b8/b2/bc7f14fc74d6943ec97e066b0b5cf61853f7185adbebd96c071e8c972d29/pyqwest-0.11.0-cp310-abi3-win_amd64.whl", hash = "sha256:c2f6905762c978fbb20b33160e63b69de77f74706c16896fbe2cacd07be27b32", size = 4913588, upload-time = "2026-09-25T05:02:37.405Z" }, - { url = "https://files.pythonhosted.org/packages/ec/88/823cbff6eabb4124904d1a1b9ad82aabeb6fb996cf67254804a8444fe07f/pyqwest-0.11.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:3e7a0868bf99c0e096e4fb253d92e79afcb7638145da17afd4c16d29ae50dedd", size = 5285016, upload-time = "2026-09-25T05:02:39.427Z" }, - { url = "https://files.pythonhosted.org/packages/ee/fd/71dd78593bfb524bedebb7eb7f3154924c774d00d116277e3ee7fe356685/pyqwest-0.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0d53236ab3d9a32dd92ab55c11ce74e5781542dd1512a7e40bd36dcdecba795a", size = 5136921, upload-time = "2026-09-25T05:02:41.363Z" }, - { url = "https://files.pythonhosted.org/packages/df/9b/21ebaa004487836ea92dc7c2993b94140890e35ecf007ee3d7a689277352/pyqwest-0.11.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:970f1039020a9eb0f9886cc17c8982c48c88623604b8479234f89635164261cf", size = 5667668, upload-time = "2026-09-25T05:02:44.39Z" }, - { url = "https://files.pythonhosted.org/packages/cf/b5/3f7eacaa912076e5fc7902d29acaf746a79b6dbfa83778ec484f68911c16/pyqwest-0.11.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d10dd93c4d91d01a943b8d1a0e1e9d3a941f76d8bcdb1bbbcd0df34b98d69f10", size = 5615500, upload-time = "2026-09-25T05:02:46.999Z" }, - { url = "https://files.pythonhosted.org/packages/2d/93/df06d1496e7e6b8c336856da9c600ea8e19b169281b5a4622ae8b44c29d4/pyqwest-0.11.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:de8dff8d1bbfdd8315b6937132522c3e88c22c29de0c2a1b4722ce6fc5f65c21", size = 5837114, upload-time = "2026-09-25T05:02:49.227Z" }, - { url = "https://files.pythonhosted.org/packages/38/f7/72ff4f14f506295aa7a7090e23d4f377de946f87f163d2e1451e67260169/pyqwest-0.11.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:525c9b6557ac92f8921566d57041fe03ad33b46fe649a4e56d29bed6f723614e", size = 6023011, upload-time = "2026-09-25T05:02:51.577Z" }, - { url = "https://files.pythonhosted.org/packages/17/5b/008fca17c07d737c2ce12fa3492a2b7c880aba645ba2052b1b931e992e12/pyqwest-0.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:478e38b0c2233b15d78cc8533c34013671e98bd975061a7b9a06eb493f1026bf", size = 4923605, upload-time = "2026-09-25T05:02:53.866Z" }, - { url = "https://files.pythonhosted.org/packages/bd/00/228ff02f7e55f3bcdd6ffbfcdde16aa07434aec17ab16ece60784dc4b34d/pyqwest-0.11.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:3218c260b8ff6a329cf395b97e1f3d1808df8a6d48ad3f0366ea9481006bf471", size = 5283007, upload-time = "2026-09-25T05:02:56.034Z" }, - { url = "https://files.pythonhosted.org/packages/a4/9f/1302fc91f8493f5317d54510e27c4b516c74145f64c21a4978e4457306ac/pyqwest-0.11.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3501954df728bcc50702923abe1d32dca527be81375a0cceec88b323357bfb72", size = 5134668, upload-time = "2026-09-25T05:02:58.255Z" }, - { url = "https://files.pythonhosted.org/packages/a4/59/a1ff5f20711ef349fbcc6b5123e85fbf93ddd2702c0cb9df221c344ea207/pyqwest-0.11.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:505c8571fc0bb2f9bfe4e6d58dd0fc95539adfd4be38d14c79632bfcb3f0fa05", size = 5664142, upload-time = "2026-09-25T05:03:00.481Z" }, - { url = "https://files.pythonhosted.org/packages/ec/a6/b8cc918d19abb80778dab00c4a01fd7f7da529f633b4931cb854b571a80c/pyqwest-0.11.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3cfad932a0d59b347153f586f1abfa550738b4f89c66903cbdb56ba15af86904", size = 5613697, upload-time = "2026-09-25T05:03:02.543Z" }, - { url = "https://files.pythonhosted.org/packages/8c/66/2fdb4a10b137b2f62f01d94e718990fbc144c9239cf94381ea54c1fb8dec/pyqwest-0.11.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b761f3b190d54138e630dcc1ebbf200c54412ce90a94ec00756768aacc08e1da", size = 5830430, upload-time = "2026-09-25T05:03:04.878Z" }, - { url = "https://files.pythonhosted.org/packages/8f/e2/69be36ced69dd7f23da3c3e9af786c308f3c0630b278176d485217beef3c/pyqwest-0.11.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:17922f382824ed2b590836c21791657d086677c62c3278a201ce58abb4812d7d", size = 6019387, upload-time = "2026-09-25T05:03:06.978Z" }, - { url = "https://files.pythonhosted.org/packages/a9/54/64f74822e6da2b6f1efc033106b0c56f947367b4332be18bd7ffafa5ad95/pyqwest-0.11.0-cp313-cp313-win_amd64.whl", hash = "sha256:d4acb407f3c040c89a9967f5fe9fa97649031d386ff51c0dffbd05054e561f28", size = 4921300, upload-time = "2026-09-25T05:03:09.149Z" }, - { url = "https://files.pythonhosted.org/packages/0c/c4/13a3d05b9e07cd914ff94903056012137d6c5d318765e08651c2eb564d36/pyqwest-0.11.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:2481ab9ebe390ceac00ac2dac38e85662ef38f94fbe423b30756957b93dba055", size = 5285511, upload-time = "2026-09-25T05:03:11.419Z" }, - { url = "https://files.pythonhosted.org/packages/88/68/9a7e55886170214b8581f97af21abe0ddb841b23ff9955561dcd37a54bbb/pyqwest-0.11.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:f15395e097240cfe826fd5303d1d22478e05e146629c01dac48a01a08e771018", size = 5139033, upload-time = "2026-09-25T05:03:13.539Z" }, - { url = "https://files.pythonhosted.org/packages/6e/3e/756eed46f1c556fd7f0aaee101d97b6020bf6c2ad831ef01ce2c80c876aa/pyqwest-0.11.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:932f9fac0f67e3d8f40afa0c022a6cc80275bc1f4817e755a7c94ccf5a1dc15e", size = 5666443, upload-time = "2026-09-25T05:03:15.751Z" }, - { url = "https://files.pythonhosted.org/packages/52/50/13277847349028896ffd3ee6d914f67160ac732dd223e02152c16019b692/pyqwest-0.11.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b72457e43bdf7fab7ed17672ed8a78ade6ecbb1c0aa57687b4852cc0bae1deaf", size = 5615230, upload-time = "2026-09-25T05:03:18.054Z" }, - { url = "https://files.pythonhosted.org/packages/6d/01/7f588a4d016cd406395dc02fc487ad7329f60ff7421a65de2153e71f8a53/pyqwest-0.11.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a4e4d4755681527274c128f86e0e516044966779a4e3c3319c9c67ebd96ffd9b", size = 5833055, upload-time = "2026-09-25T05:03:20.35Z" }, - { url = "https://files.pythonhosted.org/packages/9d/e7/bcfd86d89050aa5c2cb3c634ed1aaf9ac6b7776d355efff92436a5aa8390/pyqwest-0.11.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:03f55101f2b324f287e11f18736bc73325d8c38fa7000b67e84b6b482e25d82d", size = 6021155, upload-time = "2026-09-25T05:03:22.563Z" }, - { url = "https://files.pythonhosted.org/packages/9a/9d/e3654e985c62c65738c9ad11d942c5fadbddd70e46602eafcb563f3dd2c0/pyqwest-0.11.0-cp314-cp314-win_amd64.whl", hash = "sha256:0b2b717b1611e13d1238b7494964a8d54388e583c3c2971891095971759273ef", size = 4921464, upload-time = "2026-09-25T05:03:24.61Z" }, - { url = "https://files.pythonhosted.org/packages/33/96/eb99ba33667d2c03e57c76ff096178127b2582173c2a3511681edfc47f97/pyqwest-0.11.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:7af19bb91e2832023e1ff10736025dbde6556eea00fbf2f6c5e71d462f6749d8", size = 5275649, upload-time = "2026-09-25T05:03:27.121Z" }, - { url = "https://files.pythonhosted.org/packages/30/10/b997671b9f4877185b8df3a15816fa2afcc7df7defe5bdee16edf835911c/pyqwest-0.11.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:fa111198a8489788a68d2f78c44e0a7eb51229ac7747c4563b1d24a80dd2793c", size = 5118123, upload-time = "2026-09-25T05:03:29.177Z" }, - { url = "https://files.pythonhosted.org/packages/d1/e1/18d8559d324fae7be4a5778ad1ab5b2f8d99ede14d3864b7df81ad604795/pyqwest-0.11.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a057ba3e4ca93740cec3830b7b32db70ca2169e8de0a09307fd80988cdf46141", size = 5655774, upload-time = "2026-09-25T05:03:31.295Z" }, - { url = "https://files.pythonhosted.org/packages/95/3b/c1d3370687a8ee971092121dae48c3bb8482947f200704c4419e8e60d798/pyqwest-0.11.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8eaae193e83e16404c435c1471607b27d861e3167124cdd8afc946788cb49300", size = 5603151, upload-time = "2026-09-25T05:03:34.157Z" }, - { url = "https://files.pythonhosted.org/packages/08/97/24ff8ecf65a5234df196304d53cddae34f1fe57202c8f1a23cdb29d93fc8/pyqwest-0.11.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:818fdde12fc879c274a0957965fa31b70a187cf1f43277e03e62ce35e095c44a", size = 5819008, upload-time = "2026-09-25T05:03:36.476Z" }, - { url = "https://files.pythonhosted.org/packages/84/83/dfdf3ddd334165279c2dc7777ba49f6d598047c996e00634648ad1431cbf/pyqwest-0.11.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:64acd056ae20cfa6ebe68e97f00e9219dd86d22af93e0ed2c94630ad32d7ab48", size = 6009311, upload-time = "2026-09-25T05:03:38.589Z" }, - { url = "https://files.pythonhosted.org/packages/6b/50/b7a824d44c7de2b8a24c07fde3fb87452ede4f7d06e16886d634cef596ee/pyqwest-0.11.0-cp314-cp314t-win_amd64.whl", hash = "sha256:97f5c56dc6cb90fcf354f333ee38bf403b2a58921f4654c6175f460355246df6", size = 4898074, upload-time = "2026-09-25T05:03:40.946Z" }, - { url = "https://files.pythonhosted.org/packages/62/3b/fa4fec9d7bdd8954764959d50728bf3f93b73209a2a531bc869ba11823c6/pyqwest-0.11.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf2e41c0288f4a84eecc7e7cc9332f8e3bcb3253738756cb16eb31551ae1c546", size = 5678048, upload-time = "2026-09-25T05:03:43.471Z" }, - { url = "https://files.pythonhosted.org/packages/2a/04/784cdd165bc6a1f3d32a9af4d8efdb61c4410e09d6e65ea9cd4af44fe1f1/pyqwest-0.11.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8ba23ccb7d0a8da4e044e0474792f88246459bf0efa7d43399529388451cc149", size = 5616945, upload-time = "2026-09-25T05:03:45.732Z" }, - { url = "https://files.pythonhosted.org/packages/4d/8d/ff1fda15719cdf2cc28df766537e4697cdae4e85112caea218d5d718597d/pyqwest-0.11.0-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:7a20a61b4d54d9a61fa0b80f536f195e2d3ee79eddcf537cf619410a74880b5d", size = 5846236, upload-time = "2026-09-25T05:03:47.767Z" }, - { url = "https://files.pythonhosted.org/packages/87/f1/a68a44a7b1ba9eefeb4f73a44bf5af47534d490834e71c3f10a7fef001a5/pyqwest-0.11.0-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:c98b9592c492605f65e5e6fd7ff1acae910fa76bb65b433e552cffcee916f696", size = 6032553, upload-time = "2026-09-25T05:03:50.074Z" }, - { url = "https://files.pythonhosted.org/packages/25/77/18e15377cd494fd8bb631beda0187e74362f7bba04b965eb78fdb0b38656/pyqwest-0.11.0-pp311-pypy311_pp80-macosx_10_12_x86_64.whl", hash = "sha256:618df2a3b624d96523ded001eab9cd5399cbc2b22e996db66b541bb6564fbdc8", size = 5269271, upload-time = "2026-09-25T05:03:52.061Z" }, - { url = "https://files.pythonhosted.org/packages/01/f8/719177f1cd1fab1b0139df1ff61d72d8195cdcf7d793a603e947711c3aef/pyqwest-0.11.0-pp311-pypy311_pp80-macosx_11_0_arm64.whl", hash = "sha256:e2eb4b4f8e84a9e76dcac463088287676c0432071adb33af070811a99d26df7b", size = 5144424, upload-time = "2026-09-25T05:03:54.407Z" }, - { url = "https://files.pythonhosted.org/packages/94/95/2b1b81930008e87e37d37f61b0fb77107d5d5dc99b94c700db0589f2f3c3/pyqwest-0.11.0-pp311-pypy311_pp80-win_amd64.whl", hash = "sha256:1375fcd03a6c9bdc934ac93c94699fd7a9b461a89e9bd99b409e366ef34e85a7", size = 4927015, upload-time = "2026-09-25T05:03:56.767Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/24/2a/f8634142207082b1c559a499451db794c96130e586f1edb22dc1da4b05b2/pyqwest-0.12.0.tar.gz", hash = "sha256:83ff498bd53bee92a2d2bf14476679307a99c78421c02cae08ec113bed2fbd7d", size = 516105, upload-time = "2026-10-09T06:22:44.949Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2b/e9/bf753f3001ca4cc72986eb9de68c08c1042c010a1dc2e93be32b0290e04d/pyqwest-0.12.0-cp311-abi3-macosx_10_12_x86_64.whl", hash = "sha256:03e8b74a4f6ad32759f0cee9c79d7bae37d3e8e346a17fe6795de992995b23fd", size = 5331355, upload-time = "2026-10-09T06:21:17.876Z" }, + { url = "https://files.pythonhosted.org/packages/b1/a3/68e7e8a173a4b5f8a759d9a5a4a60c2feffe052969e9126f8a3448dc357e/pyqwest-0.12.0-cp311-abi3-macosx_11_0_arm64.whl", hash = "sha256:c1af3aad4eef813efc43f40a6e4e2791643d42f9218b6f1e9f9bdaa2a19bdc7d", size = 5189665, upload-time = "2026-10-09T06:21:19.744Z" }, + { url = "https://files.pythonhosted.org/packages/9b/e5/407de30d15456583af485488dcb62c99fa24bcac6fd1521b57ef848aac27/pyqwest-0.12.0-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:dd55f27e4d526babb30c0d0e19c5517d7d6ecb59ab4219a30a5115fac1288698", size = 5751518, upload-time = "2026-10-09T06:21:21.296Z" }, + { url = "https://files.pythonhosted.org/packages/29/9d/d7dcb2f3da4ea43c0e68894acea655063955d0c91401e74fb97f79c8265f/pyqwest-0.12.0-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:699662fca52a1595a1ed9c169816b7654c0b28308b9284091e96bbc79b8e408f", size = 5722887, upload-time = "2026-10-09T06:21:22.916Z" }, + { url = "https://files.pythonhosted.org/packages/28/dd/dee75b5c39132c49e24313eeb79caa6c178a1975578f7226aac4bed2dbe1/pyqwest-0.12.0-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:1a8815ab7b61dc932379d42424e422a66df9634499095942e3b91bfd8f5e1dfd", size = 5904177, upload-time = "2026-10-09T06:21:24.768Z" }, + { url = "https://files.pythonhosted.org/packages/24/2c/4a75b193f01a70375e6af2e080a12a64fd2057d342f4cd71090adaf6b949/pyqwest-0.12.0-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:0d4828c8dfbfee936c951cc467f0197891a752eb0d22d7469f1adb02152c400a", size = 6095821, upload-time = "2026-10-09T06:21:26.781Z" }, + { url = "https://files.pythonhosted.org/packages/d9/da/b618b54b17ed111b0bfd3a89d74aa4477ca3cb35dfe73d5b0f9916c6c07d/pyqwest-0.12.0-cp311-abi3-win_amd64.whl", hash = "sha256:3958c9ea1316e9b71290006f044e9c020daec5b5b652629bae9d2b26b6d5294d", size = 4969600, upload-time = "2026-10-09T06:21:28.615Z" }, + { url = "https://files.pythonhosted.org/packages/1c/74/2ef8a770aa6152da60c201e091cd5c692a591641e1c485348caed8591015/pyqwest-0.12.0-cp311-abi3-win_arm64.whl", hash = "sha256:ab6f54ea17a34628f631ca7b1e010f0dd5891f75fd9b743afcbdf1d29740120d", size = 4943859, upload-time = "2026-10-09T06:21:30.151Z" }, + { url = "https://files.pythonhosted.org/packages/88/49/494866a18aecda4593a52ca62397d53fb29938033075d41c009291096fe6/pyqwest-0.12.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:ba88e09d0cb6166b72a4a120a10df863f10c77dac8f53538a7ac266798e14167", size = 5344568, upload-time = "2026-10-09T06:21:31.89Z" }, + { url = "https://files.pythonhosted.org/packages/84/ee/09486016196b4a367286a8819e83185b78f771b01a3ca419e94d5c5589e6/pyqwest-0.12.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f2843c2f257db63580096f9bd4e83225a7c660a946693ec87777f906f5cdd21d", size = 5192763, upload-time = "2026-10-09T06:21:33.423Z" }, + { url = "https://files.pythonhosted.org/packages/cd/0d/42cb6e771aabb8745ccc7184bd132e4471a8659cdd7bee65f05dc94f0a78/pyqwest-0.12.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:6319e35a3f867a1a57071cfe63364b9f35b38d4ae3a6231fcab5d3a82e9d5705", size = 5751522, upload-time = "2026-10-09T06:21:35.123Z" }, + { url = "https://files.pythonhosted.org/packages/4c/a7/d09ef94ee9811ca7a698f2947a25618a60226c13f3e9193a2bc1880c4178/pyqwest-0.12.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:ba001c5f06a92426635d86c2ccb8a4c732f2bb1424361c68161f8726b5141520", size = 5714017, upload-time = "2026-10-09T06:21:36.672Z" }, + { url = "https://files.pythonhosted.org/packages/f9/b8/0770a8eaa97e845cbdf1e5fd9af36235580aae18f63d7e6ed0f257ffde01/pyqwest-0.12.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:39a7e94b109bc10b05a6151308bac6ead404a900768861216467544337a971d6", size = 5903886, upload-time = "2026-10-09T06:21:38.202Z" }, + { url = "https://files.pythonhosted.org/packages/df/f6/ad021859fb8380541604822ec6252c6e721f504b108e805b533ae8083a1f/pyqwest-0.12.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b8527b9795d7e72efaf0f2fcba6b293f85e1720201e44f94889ca33d263079e9", size = 6091049, upload-time = "2026-10-09T06:21:40.333Z" }, + { url = "https://files.pythonhosted.org/packages/0f/25/1167eab0e8fa4c07f8bbec5931749f7932d13bfea1ea3daa6dc78c67b8e1/pyqwest-0.12.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:d9f952e66e848612f5222794112f203a51070f93ca7129c4df5d6c7ef8f4762c", size = 5351261, upload-time = "2026-10-09T06:21:41.989Z" }, + { url = "https://files.pythonhosted.org/packages/d2/10/29daf8cffe7c547121d29b96a5b46d59fbd8f21bd80826c54a28cd89bc75/pyqwest-0.12.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4371bd32687cac6cd31ae61a424b3c072450fb9e1d82ad568c234bc9935e2db8", size = 5195343, upload-time = "2026-10-09T06:21:43.773Z" }, + { url = "https://files.pythonhosted.org/packages/bc/25/905f500e78216a729d69d24ccdf7f7a4c722321755dac97825620a98a366/pyqwest-0.12.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:469ac955bccf7080c6ed92afdd023c633d6d704b4be0653b82741fda3a60c85b", size = 5753112, upload-time = "2026-10-09T06:21:45.194Z" }, + { url = "https://files.pythonhosted.org/packages/34/93/b12629c268e743ed6cf39a08a78a696cbdb10d910fbcf4b0de6b0f89f143/pyqwest-0.12.0-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:05af8d0c6eb67661d6896824183b714a908321b3d10644594268b51d21a9cb1c", size = 5713673, upload-time = "2026-10-09T06:21:46.687Z" }, + { url = "https://files.pythonhosted.org/packages/ba/c3/b0f689e60356143661c93e7295edd959a738e0d42a101e4c3647045d23c9/pyqwest-0.12.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:5f1f6599c0f826fd7e6717a3c6709d9fc25e1debc9e70bbb4b613c25377937c8", size = 5905698, upload-time = "2026-10-09T06:21:48.297Z" }, + { url = "https://files.pythonhosted.org/packages/f1/d6/412b69950180abf077c2dbf0b8ddf425a5fd65466e775bfbd63e28aefd1a/pyqwest-0.12.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d4de6b62293d57153db308897988afdfd22e481c4399279c6332c9efbc183330", size = 6090438, upload-time = "2026-10-09T06:21:50.131Z" }, + { url = "https://files.pythonhosted.org/packages/de/c0/1f6fca89081737afd8842bf6b4a6e6014dedd8d7d44b4da2d73f502c3b4c/pyqwest-0.12.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:34399a6b4c2d03ece732d41ed051710ea35c70f7daed9590ce9916db6cde92e6", size = 5336542, upload-time = "2026-10-09T06:21:51.839Z" }, + { url = "https://files.pythonhosted.org/packages/3c/0f/7f364ac04e01258e0cfc37170a558cb36aaacff110f1a4c9d2f887e64be9/pyqwest-0.12.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1335062111f4da599d58223eeeb051f839a2c588274258facf6091aca92201a3", size = 5174602, upload-time = "2026-10-09T06:21:53.489Z" }, + { url = "https://files.pythonhosted.org/packages/11/9f/cb9c8ad10dc43e0b854041f4b471fde59624e8407c9c6da00f8907933c57/pyqwest-0.12.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:218f7a00d01678c7f5f02a0248d3b601d87d9f882179305f0c8f0545eb88d733", size = 5731531, upload-time = "2026-10-09T06:21:55.061Z" }, + { url = "https://files.pythonhosted.org/packages/52/2e/30a2b41d340d07f8205d02997a24c518c031d09898c8004ad671a8c9bc8b/pyqwest-0.12.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:27843d48330e2a163cf875e05294d9e4ea0e0e91bdbea956e3af630b678bd7d8", size = 5704167, upload-time = "2026-10-09T06:21:56.687Z" }, + { url = "https://files.pythonhosted.org/packages/60/c3/8c870cbf47857abd8490b8da2195cad71c3890556d776ecc01cd744bb35c/pyqwest-0.12.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:4cdb0f156e754caead9b8cf88fc2f2806ed81282960c9a39f8a07847110745fe", size = 5882872, upload-time = "2026-10-09T06:21:58.362Z" }, + { url = "https://files.pythonhosted.org/packages/e3/e4/45017cda255c95797c5a4e6da2f87a8035f368aa9db50d9c45abcbd97387/pyqwest-0.12.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:3208f670e601ddfbf6ff689c7717236dfeaf175fa934319d30a6c57df0e1c14c", size = 6074173, upload-time = "2026-10-09T06:21:59.918Z" }, + { url = "https://files.pythonhosted.org/packages/a3/d1/955421179cbd93070cd01769e6f937826c42d377fab9fade00172601e138/pyqwest-0.12.0-cp315-abi3.abi3t-macosx_10_12_x86_64.whl", hash = "sha256:f7476abcdac195e33165e38e4e76c9c6e90785e14033b766912f24ff8f829198", size = 5398661, upload-time = "2026-10-09T06:22:01.5Z" }, + { url = "https://files.pythonhosted.org/packages/e0/b0/c7474b1650d3bafe94f28f87446b0e4371d553acdfa79d2ca7e8151d1982/pyqwest-0.12.0-cp315-abi3.abi3t-macosx_11_0_arm64.whl", hash = "sha256:faf377620445e9988b2e290fb805dd3a04ffa62e0f6d1da11e269cecc1d5a837", size = 5241423, upload-time = "2026-10-09T06:22:03.084Z" }, + { url = "https://files.pythonhosted.org/packages/b8/11/3bdeb82f4425ed42d5ee877003974de7a716990f8681c6a637046a3d49eb/pyqwest-0.12.0-cp315-abi3.abi3t-manylinux_2_28_aarch64.whl", hash = "sha256:90fee9cd448324fbdc4ed2dff787749f19ac17315aa9e18d88ced0b843fc8dac", size = 5791319, upload-time = "2026-10-09T06:22:04.628Z" }, + { url = "https://files.pythonhosted.org/packages/9a/b5/024555a08ab26bdadbe63b7acb6115a5d0e536efa94c4c71c4bbce445a45/pyqwest-0.12.0-cp315-abi3.abi3t-manylinux_2_28_x86_64.whl", hash = "sha256:ae7b73b47e3fb2462a5292417d49e7780ce2dc9857e8bce7276ea2f090a90768", size = 5764278, upload-time = "2026-10-09T06:22:06.309Z" }, + { url = "https://files.pythonhosted.org/packages/61/44/57bba169ee17c02dfd0b2a6508ed4c027e0d14ee3765fa3c641399521e2d/pyqwest-0.12.0-cp315-abi3.abi3t-musllinux_1_2_aarch64.whl", hash = "sha256:5f83e15b7bebc761e7b6a0f1cbac334d058e698fb49ba1328306660b00c3c92d", size = 5939856, upload-time = "2026-10-09T06:22:07.915Z" }, + { url = "https://files.pythonhosted.org/packages/69/66/926b90a270e9893958840fa610531b5a0d47604e088e1c60305c13890938/pyqwest-0.12.0-cp315-abi3.abi3t-musllinux_1_2_x86_64.whl", hash = "sha256:8057eddcae424eadbf15c9ab67f4aebdc2ce5f2cf1d8d4f6c34ca9d9ab62fef5", size = 6139873, upload-time = "2026-10-09T06:22:09.498Z" }, + { url = "https://files.pythonhosted.org/packages/cc/6c/a09b970067a60c721ad063107ac10917e31475211bb3d66995d1482331a2/pyqwest-0.12.0-cp315-abi3.abi3t-win_amd64.whl", hash = "sha256:6c71f9f32cd344ac4f136010ea6a307201547123bce6ba61272421c93fb4fc6d", size = 5023514, upload-time = "2026-10-09T06:22:11.179Z" }, + { url = "https://files.pythonhosted.org/packages/d7/e5/06c6540c29596fe9230ccc160822309592b1ec85a05482105e1de6b98d14/pyqwest-0.12.0-cp315-abi3.abi3t-win_arm64.whl", hash = "sha256:9ca7d29a469326604f7c048bc19d4592970fbc7ac494bc253ef0779a57e1a9aa", size = 4991803, upload-time = "2026-10-09T06:22:12.867Z" }, + { url = "https://files.pythonhosted.org/packages/fb/4e/3f4e924e430cd859df54395b7a9ef21c342f544dc4d4da3af5b3f44c1e08/pyqwest-0.12.0-cp315-cp315-macosx_10_12_x86_64.whl", hash = "sha256:dca14e8c4aa3d79601c91e408e33df0bf463c8166539b394715637f49f90362a", size = 5348621, upload-time = "2026-10-09T06:22:14.718Z" }, + { url = "https://files.pythonhosted.org/packages/90/f2/da5defcc9d231ab9e4ce267704d1e18544b4d93c1cfb4e3c29a9a35892d3/pyqwest-0.12.0-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:729a81368abcbe6be708a96da1ec8802969082ffaab35d1e08a520e3c0453a49", size = 5195352, upload-time = "2026-10-09T06:22:16.435Z" }, + { url = "https://files.pythonhosted.org/packages/bc/98/871c99fdedaff6458a519e37d49400617beebf9c6560ed5e73bf02b972fe/pyqwest-0.12.0-cp315-cp315-manylinux_2_28_aarch64.whl", hash = "sha256:645072f8e9138556e27f6c9384dd1fe0bdef3f1be50534d2c1d9ffeea9a53de9", size = 5752432, upload-time = "2026-10-09T06:22:18.111Z" }, + { url = "https://files.pythonhosted.org/packages/0c/4e/d1686f7b74c2b5cb3f7939a397aa215054db3cb515a99503fe6e361ddc96/pyqwest-0.12.0-cp315-cp315-manylinux_2_28_x86_64.whl", hash = "sha256:d302f7bf0c072397aafe2cbe6a398743e5c37add3500048bbe0b26bba823075f", size = 5714200, upload-time = "2026-10-09T06:22:19.542Z" }, + { url = "https://files.pythonhosted.org/packages/6f/15/d4a71b8b9c85f74174f3e58ebe6cc34cf2d9175424da84af91088521b74f/pyqwest-0.12.0-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:ead61088e5226349624965025d297ed893dd947e05ccf315006322c824ef9a4e", size = 5903794, upload-time = "2026-10-09T06:22:21.083Z" }, + { url = "https://files.pythonhosted.org/packages/f4/b1/01928c04841f525748b4b21e46e29a473cc1b9d2c800abcf70d5d6a12b56/pyqwest-0.12.0-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:db85298d530bcd50234c48dfd18facbe75a3c8f0f16e04ace389617d48a3093f", size = 6091500, upload-time = "2026-10-09T06:22:22.713Z" }, + { url = "https://files.pythonhosted.org/packages/b1/ac/181acdc359a7cc293a4fe579ac315e72676654bd6f5ead732304caa125d0/pyqwest-0.12.0-cp315-cp315t-macosx_10_12_x86_64.whl", hash = "sha256:f87b16a83d8e0bc5b639163819c53a9e975ea2fbeec133ecbd2ffce0796dab27", size = 5336607, upload-time = "2026-10-09T06:22:24.437Z" }, + { url = "https://files.pythonhosted.org/packages/74/b1/7365631700810ec294119e3b405bd20d9897774979b021c5dc2c002e4773/pyqwest-0.12.0-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:fa846050a669d0eacc2c2aa425eda403b3e443a374ff84c5c16c9f82624a2a9d", size = 5174497, upload-time = "2026-10-09T06:22:25.915Z" }, + { url = "https://files.pythonhosted.org/packages/dd/8d/08bc79d4d1c3af8c24b78163c277226cc26c3e751d1bc562db8baf67ee7e/pyqwest-0.12.0-cp315-cp315t-manylinux_2_28_aarch64.whl", hash = "sha256:5f08aeda136ffcb454c11b1e7bb98a85853ea779424cc30e98a9e5e762b84fea", size = 5731691, upload-time = "2026-10-09T06:22:27.408Z" }, + { url = "https://files.pythonhosted.org/packages/39/65/e1a9cc3cf610122149ccd3a1e264ce6b47aa8bcf3d4abd5f27ba7acdd97f/pyqwest-0.12.0-cp315-cp315t-manylinux_2_28_x86_64.whl", hash = "sha256:0b770869bae9e15c13e8823ed71155d2a9e7fa3f4cba3da65d2b4b668e5b3a1e", size = 5704613, upload-time = "2026-10-09T06:22:29.387Z" }, + { url = "https://files.pythonhosted.org/packages/c4/75/a8b3fd3de3eab08377ae71fde7efa5526b7f43cf094eb8b028dd8acee275/pyqwest-0.12.0-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:a25bdfbdc859d937de818db60d839f523c9d4792c2b85a23667861cc0791246e", size = 5886056, upload-time = "2026-10-09T06:22:31.298Z" }, + { url = "https://files.pythonhosted.org/packages/93/23/6982518b22268151fdb7ae27d3700a3c4fdb0699d4783caaace1e1fa6deb/pyqwest-0.12.0-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:ac6a7de3327ee56e736108ab72a21efc5dfddd41ce4c19e4469c1e8448a6696e", size = 6074658, upload-time = "2026-10-09T06:22:32.919Z" }, + { url = "https://files.pythonhosted.org/packages/0d/78/31357c37d50ad3de0c8c0145908cef4d06128c3cc0cb6a8373d5bf45165b/pyqwest-0.12.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:d8e757161f3c812c27b885a0a6334aa12b98a113a0f1164725175420bdc451f5", size = 5332397, upload-time = "2026-10-09T06:22:34.399Z" }, + { url = "https://files.pythonhosted.org/packages/d0/83/8c9439e0f95b5d52566575cba4dbd8923e97eec2e511a6ab3c4a6ec59608/pyqwest-0.12.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:4579b040fb22226473741c5c901fae86655a695451eb4bfc399b758ca8370420", size = 5193341, upload-time = "2026-10-09T06:22:35.987Z" }, + { url = "https://files.pythonhosted.org/packages/6e/c9/bb1ad907a66d52c097bedc1c2c1bf7830566bca88e24aeb78ab2d34db6ec/pyqwest-0.12.0-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:cc9fa48ba09dd96568c1c46eaffda61809d66ef4013957b2a70152a802b19e34", size = 5750909, upload-time = "2026-10-09T06:22:37.675Z" }, + { url = "https://files.pythonhosted.org/packages/f3/41/0a6d6c72828881ed396131a4de45037144b036412c3e5353285052606968/pyqwest-0.12.0-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:38811c3d6a7cfacf9b013c6fe0d948f668d247896edc9545df7ca0b02de64360", size = 5719293, upload-time = "2026-10-09T06:22:39.415Z" }, + { url = "https://files.pythonhosted.org/packages/5e/05/91f6fb96a02d97ba9663092c50a6f28797e06aa853245bf9283cc4637144/pyqwest-0.12.0-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:e5d634ef3a5c569a3762c3393c297f9d5be618a7506c021238a263da80b1d9d0", size = 5904717, upload-time = "2026-10-09T06:22:41.161Z" }, + { url = "https://files.pythonhosted.org/packages/df/93/8bc6380d1c25729d5872c90225607892f6a491db0a01dad28cccd0967b04/pyqwest-0.12.0-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:dcef82a96cdfb1946b0b0705e41019eb077e498dc3ce5ade52a962d338f51ca6", size = 6094579, upload-time = "2026-10-09T06:22:42.86Z" }, ] [[package]] @@ -2426,15 +2475,15 @@ wheels = [ [[package]] name = "pytest-asyncio" -version = "1.4.0" +version = "1.4.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pytest" }, { name = "typing-extensions", marker = "python_full_version < '3.13'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/43/7c/d36d04db312ecf4298932ef77e6e4a9e8ad017906e24e34f0b0c361a2473/pytest_asyncio-1.4.0.tar.gz", hash = "sha256:c6c0d2259945122819f171a32ecea2c349ead889ee28176caaf492143424be42", size = 58514, upload-time = "2026-05-26T09:56:04.083Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f8/9a/887535e477847f5159377ab35303923644840b13c0ca03c0a467f84d4680/pytest_asyncio-1.4.1.tar.gz", hash = "sha256:e8266eb094e5ee9ffdb2068c9836f33e209ea77a970eb846933d416b5602669b", size = 145237, upload-time = "2026-10-25T21:05:37.135Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/03/e2/08a497ef684b88559c9cc5f4ad53a37e7b99e727094a86d6ea32536d5d3c/pytest_asyncio-1.4.0-py3-none-any.whl", hash = "sha256:933ca923a23075a87fb7070c0ec272a6848489824d887c85c812670932835aa1", size = 16930, upload-time = "2026-05-26T09:56:02.576Z" }, + { url = "https://files.pythonhosted.org/packages/46/d7/cd9e2708afdbf059658adaf08bdb0792ca52489871051d6ce7d498b51277/pytest_asyncio-1.4.1-py3-none-any.whl", hash = "sha256:507afa78ec77c6661164ef1a2368bc7820383fd1a14b2a7f813de8b5a92949cc", size = 17061, upload-time = "2026-10-25T21:05:35.473Z" }, ] [[package]] @@ -2442,7 +2491,7 @@ name = "pytest-cov" version = "7.1.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "coverage", extra = ["toml"] }, + { name = "coverage" }, { name = "pluggy" }, { name = "pytest" }, ] @@ -2520,74 +2569,91 @@ wheels = [ [[package]] name = "qh3" -version = "2.0.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/76/d3/692f1410aad9cfda0001e1e74533cb2cbdf0c525a8e7c2e4c587a51929ae/qh3-2.0.4.tar.gz", hash = "sha256:72b174c9dabfccdddf050dabc3c1b49972ed7a7564896d3db558e57376187f75", size = 380633, upload-time = "2026-10-01T08:40:22.204Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fd/0d/8b2f9939533132a5eae6fab2f562d842248cc96dc2694330f6a58fc0c506/qh3-2.0.4-cp313-cp313t-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:450914c25105e6ce2b4037b3e09bf4ac2516084c4308a2b17117a251eb0ede70", size = 4669169, upload-time = "2026-10-01T08:36:10.777Z" }, - { url = "https://files.pythonhosted.org/packages/a1/a5/2fa3a4f8a3fd94566830722be66c70b71814ef167bf5fb47274ffd27a943/qh3-2.0.4-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf65072302d50cd24a543d053cd4e465c353d78afd209bc39d43ff86fefa7870", size = 2288032, upload-time = "2026-10-01T08:36:12.609Z" }, - { url = "https://files.pythonhosted.org/packages/08/80/78e53b93a523116996117eff8e05f9e3cac435c8c9a28ee732fa8d22e61c/qh3-2.0.4-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4104481a7d026ca6fc8f42c741474f09f4c5caf3118e10ba97edb5a4684a6159", size = 2014560, upload-time = "2026-10-01T08:36:14.382Z" }, - { url = "https://files.pythonhosted.org/packages/e0/8a/24bd1ce85f1e12cb43b45adea70e12a4f35b97d1e34676357d6d61c315b4/qh3-2.0.4-cp313-cp313t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:77687d315857f3077ccee21300638fc5ede09b6452ca07dd2c5c722640e53520", size = 2200379, upload-time = "2026-10-01T08:36:16.195Z" }, - { url = "https://files.pythonhosted.org/packages/a6/d5/665dcb2b00dea31cc957484c28a6554e02ebad2a7f82e1512afd4f6d3157/qh3-2.0.4-cp313-cp313t-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:83babe9d129ad4bae3f6d8d98d48187d849312412f18e2d1858e1e67cd9ba165", size = 2188529, upload-time = "2026-10-01T08:36:17.967Z" }, - { url = "https://files.pythonhosted.org/packages/95/76/9a9f17c58ce5bf651a912fcefe5e355a22aad6a2eaa38e5d45924488117c/qh3-2.0.4-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8896bd885127ea94cda2421bfa554eeb02dc8cfde236195ec7c5faab8a019f65", size = 2209283, upload-time = "2026-10-01T08:36:19.798Z" }, - { url = "https://files.pythonhosted.org/packages/ec/db/36c584437b782fe630103a7a8fc71167a403b3475992b7dec7ce77cb1779/qh3-2.0.4-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:99aabab5245ed9be3056212a72695bcf192e20379bfaad6d0c68f3a7ea2b6b99", size = 2243832, upload-time = "2026-10-01T08:36:21.782Z" }, - { url = "https://files.pythonhosted.org/packages/c7/fc/a6349cbc5580e92b763265e4027a166b83661b801f1165a02c09972f7305/qh3-2.0.4-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:194765884027e1a6055facc4763bc6881de71b0bc4d573fe8fdec51a9ee94767", size = 2513297, upload-time = "2026-10-01T08:36:24.014Z" }, - { url = "https://files.pythonhosted.org/packages/6e/15/89f088d6bc1110d0908191622e04e9da404b5fd9b9fd5242cfa90c65c489/qh3-2.0.4-cp313-cp313t-manylinux_2_39_riscv64.whl", hash = "sha256:5e5f42683b5b8a9435babf735db0d44595bec7cb62938d7f3da143909a990824", size = 2136618, upload-time = "2026-10-01T08:36:26.252Z" }, - { url = "https://files.pythonhosted.org/packages/2b/d0/49c9866fd3e0cc35c699a6b9a14e6ddd8ba56071ae1ab6ac16228cd38c9c/qh3-2.0.4-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:d6980e0db66971aed7cf45e84aaca59c4cfc3cde4e838af9c37b1f76fb2f7ea2", size = 2481361, upload-time = "2026-10-01T08:36:28.207Z" }, - { url = "https://files.pythonhosted.org/packages/a9/54/178f4ffbb7ca7f73a70362974e74b48c46ae56e51ec26a35343ed54786d1/qh3-2.0.4-cp313-cp313t-musllinux_1_1_armv7l.whl", hash = "sha256:34802c8e0a5e9dc3139f292153771517aa22599534201136cde86c46764e243d", size = 2271701, upload-time = "2026-10-01T08:36:30.673Z" }, - { url = "https://files.pythonhosted.org/packages/3c/77/6a79a4e94ee50f623d9e1e37039a85b4d03cf35242b7acaad4f6b72e59b4/qh3-2.0.4-cp313-cp313t-musllinux_1_1_i686.whl", hash = "sha256:26ea067c0601345ac9c0a8b19204e7976a78f05644ca3ba53d6272787eb81c62", size = 2389324, upload-time = "2026-10-01T08:36:32.717Z" }, - { url = "https://files.pythonhosted.org/packages/7b/7f/f5347c28bb5173f74a008dc3d1a230560acadbf56e70bea28fe027ba4d09/qh3-2.0.4-cp313-cp313t-musllinux_1_1_riscv64.whl", hash = "sha256:0852cc816943ef3a9109feb2b4cd2e2a3233bced3e9653812d8f6ce8f287624f", size = 2245847, upload-time = "2026-10-01T08:36:34.548Z" }, - { url = "https://files.pythonhosted.org/packages/21/e5/0638a1c5de22ecaade29441eaae4c20f332100184e0fd096b74ae17074af/qh3-2.0.4-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:72114f1b8ef09e8c2b79212e05b35a6434f3c226ae5307fb09c7b5c8ca518d02", size = 2737552, upload-time = "2026-10-01T08:36:37.114Z" }, - { url = "https://files.pythonhosted.org/packages/2a/bb/3fca39f94e915b7625083bdfc2420057ffc99a0e313d4c0ec486806b3931/qh3-2.0.4-cp313-cp313t-win32.whl", hash = "sha256:18e9a092dcdefac66c57959e9c1254f990ff8e33f2afedcbe71ede27d91acde7", size = 2016042, upload-time = "2026-10-01T08:36:39.059Z" }, - { url = "https://files.pythonhosted.org/packages/2b/07/f5d1b84a9edeb8e5762910c2b5a7a2bff9dcba6b8e57978d1ffc34173fae/qh3-2.0.4-cp313-cp313t-win_amd64.whl", hash = "sha256:4678fc59d36c693bea489c9e3be5946a312d6f671a8fe226ac0ddee778a275de", size = 2272863, upload-time = "2026-10-01T08:36:40.864Z" }, - { url = "https://files.pythonhosted.org/packages/ab/4c/4c1d6e49c08913131159f3b384e004bb39bb5d596028dec44b8d0d3d175c/qh3-2.0.4-cp313-cp313t-win_arm64.whl", hash = "sha256:d609dd7815c63ba1117cb5d612b26d69d1dda12e5e1f7c7ec005f387e34c111d", size = 2094890, upload-time = "2026-10-01T08:36:42.833Z" }, - { url = "https://files.pythonhosted.org/packages/b3/9b/23facf63fac09a724b733e1a3967d0a5ff86d9a911ff6f831e50ca360709/qh3-2.0.4-cp314-cp314t-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:a64bf88e94862d54a0406db92b4c51e86775e2fa5223618f92431f7ea0e668a1", size = 4670152, upload-time = "2026-10-01T08:36:44.901Z" }, - { url = "https://files.pythonhosted.org/packages/4a/62/b2af91aed5ddd040ed6d1fa6bab0183b55e30cf9b0a0a412e6df6a890b30/qh3-2.0.4-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d9d22cdf5a643f3f3236b3e25462d33083f88d200425afd09958cc0d2c5ca54", size = 2288241, upload-time = "2026-10-01T08:36:47.042Z" }, - { url = "https://files.pythonhosted.org/packages/9d/dd/eaa827770d96b422d9b88d3789a327bd22c1d735add8ccaaf1336c7e4145/qh3-2.0.4-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6f9d726d83942a2cb20ee127e995a0249dbea3ffe2b3772eb113acfdfd3874a1", size = 2014730, upload-time = "2026-10-01T08:36:49.637Z" }, - { url = "https://files.pythonhosted.org/packages/8c/77/d83d0a386c73ca75dc1ae16c090db77671c0c56c927904f1918b0a4602cc/qh3-2.0.4-cp314-cp314t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:968a94dcdc85219794d44fdce7b15ce6a90ca295f2574881b623ebe06850311c", size = 2200409, upload-time = "2026-10-01T08:36:51.817Z" }, - { url = "https://files.pythonhosted.org/packages/e6/43/ebe3c0268d26089431977c087c2b917a2d0d08f61032e2728fe4edc0ba0f/qh3-2.0.4-cp314-cp314t-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:bcae368dc6b5449397f81208493361a5197df1f955d4883b164294915f83638b", size = 2189134, upload-time = "2026-10-01T08:36:54.03Z" }, - { url = "https://files.pythonhosted.org/packages/5e/78/dfd4ccb1b743b178617f453afda4413de1670ea07b95e035db5b9bccdf08/qh3-2.0.4-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:36f201b834d96759a241171a4cdc6ab7442a6c52260d1dcfb1879707b430c6d7", size = 2209549, upload-time = "2026-10-01T08:36:56.359Z" }, - { url = "https://files.pythonhosted.org/packages/f5/03/863f5a5fd376712752e29e77f627a326930679e617340bb03e5757f22c77/qh3-2.0.4-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2f47b1f2c5fe8d70472a6134f374b6022ad6f709d6be56282ad4024d28905829", size = 2243982, upload-time = "2026-10-01T08:36:58.176Z" }, - { url = "https://files.pythonhosted.org/packages/eb/9b/ec159ecf23d089b981cb13e072f889a6c366377283bb45d4ebbc7b5ca952/qh3-2.0.4-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:edc81973c3823533d75da5570e89214d87aded6205ac76cb9ae3bb9f25b5ffe6", size = 2513632, upload-time = "2026-10-01T08:37:00.055Z" }, - { url = "https://files.pythonhosted.org/packages/4c/5b/ea1d431681c20fa62fd6d4f029e8ef49e40adf634425c9742e1e343933ae/qh3-2.0.4-cp314-cp314t-manylinux_2_39_riscv64.whl", hash = "sha256:1964c6471d5e4c074a002bf6a95c1797576165c000d74448260426e10209bed0", size = 2137457, upload-time = "2026-10-01T08:37:02.249Z" }, - { url = "https://files.pythonhosted.org/packages/e1/40/5a2637158ef3ed7a2b8872163726888b22cf6f348911c86d4e59bc631064/qh3-2.0.4-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:690cf4bcd8c7baf075187d6197ab31cc933d2aa951311dc65729cb01dccc92dc", size = 2482032, upload-time = "2026-10-01T08:37:04.728Z" }, - { url = "https://files.pythonhosted.org/packages/49/19/975cba853ef505246dee37415bb3c59f49b4107b552010dc420d258df882/qh3-2.0.4-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:8449e99c275b04c866f13f24e6d656d609fe8875fedf5896201a93a60bbe1412", size = 2272089, upload-time = "2026-10-01T08:37:06.729Z" }, - { url = "https://files.pythonhosted.org/packages/48/f2/e995bbfca89bdcb5262c831e8d1007c5427217faa762134a42144511a085/qh3-2.0.4-cp314-cp314t-musllinux_1_1_i686.whl", hash = "sha256:db25d8132c966ccebc294b8aeb197d2f48d14f772e675ff8282ab958009b801b", size = 2389523, upload-time = "2026-10-01T08:37:08.681Z" }, - { url = "https://files.pythonhosted.org/packages/aa/83/1701570200d36d72265d0cc57753f0a297148b5f7e4d2ac8a342c967e22a/qh3-2.0.4-cp314-cp314t-musllinux_1_1_riscv64.whl", hash = "sha256:c03566def5374925a6270bbc1dafc8c8889b3ff3ecd14396979ea07fec9def26", size = 2246808, upload-time = "2026-10-01T08:37:10.786Z" }, - { url = "https://files.pythonhosted.org/packages/9b/85/72343d1ac93240e4719e3e665edf0d0db31ba429234fefde635e7beaf94e/qh3-2.0.4-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:19cd7751da3f4d581ea6f5f44510a2b30a19495daba4e632c4e96d8375856694", size = 2737900, upload-time = "2026-10-01T08:37:12.851Z" }, - { url = "https://files.pythonhosted.org/packages/01/04/3dbdcd11d46b376e982715e7bbce6a3fe4d3acee50ffe3c3d19fbd817bac/qh3-2.0.4-cp314-cp314t-win32.whl", hash = "sha256:c6414837e375fd06f4664b276fcc3e76b17fa2431f650f52f0e41c48489d7e81", size = 2016207, upload-time = "2026-10-01T08:37:14.834Z" }, - { url = "https://files.pythonhosted.org/packages/9e/c1/bbeb18b3f934546454f77849b03b1df8296295306bc2a4201d17f6a017df/qh3-2.0.4-cp314-cp314t-win_amd64.whl", hash = "sha256:1923830f3e5e236bcf024ea317625346e5a3e1b08e7852e83293c1e4b3b8d7db", size = 2273740, upload-time = "2026-10-01T08:37:16.777Z" }, - { url = "https://files.pythonhosted.org/packages/96/47/dddc83f5af33072ccd851c61f7b70a94b6daf49cc6ddbbc05948633ac5a0/qh3-2.0.4-cp314-cp314t-win_arm64.whl", hash = "sha256:9d505d790ab10e378cf4e5c2d6447cb8b602d9cc36a4113160f9ece7bba9abc6", size = 2095281, upload-time = "2026-10-01T08:37:18.798Z" }, - { url = "https://files.pythonhosted.org/packages/3f/f5/a56a4707bd64fbd0e8edf91f08f798e66596555a0440a61ca6245140e330/qh3-2.0.4-cp37-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:ec087930c9b43d1e32c5c4cdc8d550fcb72cbfcc957c1d922859ce11b1e55663", size = 4687298, upload-time = "2026-10-01T08:37:20.783Z" }, - { url = "https://files.pythonhosted.org/packages/cc/d8/9de98b875a4fd0d080b720add984ed3a2e6fc60c52362b4c9f2b1b7306f3/qh3-2.0.4-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:406577d5fc72bed8d12b6397099b4e164948171dd0a16be8e768b31f1ef155ae", size = 2293535, upload-time = "2026-10-01T08:37:22.979Z" }, - { url = "https://files.pythonhosted.org/packages/54/fc/39c7971eb10fbee02f9cf1d4c8df185f36d7e9063d57e87717db352c96cb/qh3-2.0.4-cp37-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2e94ba4e929e0e54f615c4e28493980af5d82a6d928c644a2da26983df73e110", size = 2018713, upload-time = "2026-10-01T08:37:25.11Z" }, - { url = "https://files.pythonhosted.org/packages/81/d2/aa469211fb8d3cc78779ef245f2c26f2c1124797f3005f5ac00a4e08bfbf/qh3-2.0.4-cp37-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:03015a711f8b1ab24825d6ac6d2af1874d4157ac886d236bc19f7a21e69377b6", size = 2204383, upload-time = "2026-10-01T08:37:26.931Z" }, - { url = "https://files.pythonhosted.org/packages/b9/9c/3fae765e1e3613569c062dfe0c6c72d63d1d8fb1611ae313fe359e5f47a5/qh3-2.0.4-cp37-abi3-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:fda466d648cebffccca1a68d9716b31d41eb09b284ecf17705b4a04991dbb881", size = 2199386, upload-time = "2026-10-01T08:37:29.283Z" }, - { url = "https://files.pythonhosted.org/packages/f5/c1/d73e1c6259c3b4883df041ceeb53778f509625b28df9af3840539d179547/qh3-2.0.4-cp37-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:06e8c7ad92fcb482de360af5ae00d8e4559e969d1559a5a88b68989c6bac22c4", size = 2215951, upload-time = "2026-10-01T08:37:31.679Z" }, - { url = "https://files.pythonhosted.org/packages/97/91/a37e5c5ee6beea12b6743af42893ea1f2d3b94b95cbadf57abc10db6e69f/qh3-2.0.4-cp37-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6c0907ab53bd1fb1cca0a309cf26df42c9862e43e40c79d0e5ac3d2b6b747689", size = 2250916, upload-time = "2026-10-01T08:37:33.8Z" }, - { url = "https://files.pythonhosted.org/packages/77/e0/25e888ca43626083989396eaa97394c87694f7032489586b33b455ffd363/qh3-2.0.4-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:af34fad8554560fa38304f61708176379d5efb192da2ece5fb25dec23c304b36", size = 2518986, upload-time = "2026-10-01T08:37:36.103Z" }, - { url = "https://files.pythonhosted.org/packages/f4/2a/4b929c53dfc99e685ff1489209618a4d90ec8b1ec534b212d875be58d90a/qh3-2.0.4-cp37-abi3-manylinux_2_39_riscv64.whl", hash = "sha256:b6760be9bc0401bf7e6c1e889181587619529f34ffcf3f8a8de5f074815dc2ab", size = 2143334, upload-time = "2026-10-01T08:37:38.022Z" }, - { url = "https://files.pythonhosted.org/packages/cd/86/56e64b90aa4c621f3051f296b56783ede536ccd4bbe86b8afe3b20c95555/qh3-2.0.4-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:a29a0c05b7986ae0400a4229ca7176805cd44774675663b4ab0d4b6ebef7655c", size = 2486716, upload-time = "2026-10-01T08:37:39.984Z" }, - { url = "https://files.pythonhosted.org/packages/9b/7b/76ee46893aab07fbac826f370f0dd1917e40a565dcf2cca9d6eab52a12e6/qh3-2.0.4-cp37-abi3-musllinux_1_1_armv7l.whl", hash = "sha256:3dcedaeaf791114a050762b097b18839f6cc491dbe83873137b080f7b6dd1411", size = 2276102, upload-time = "2026-10-01T08:37:42.274Z" }, - { url = "https://files.pythonhosted.org/packages/22/77/5ddf3e3c0c22767af0a044aaea4fca4a20edd311bd961643eebc66f3c4cc/qh3-2.0.4-cp37-abi3-musllinux_1_1_i686.whl", hash = "sha256:8a8fbc4f30683a47dfb2b9eebc95e5f8b76c3a1eb818b0dd3354aad54c2d99f6", size = 2393700, upload-time = "2026-10-01T08:37:45.343Z" }, - { url = "https://files.pythonhosted.org/packages/ad/fe/1bc12e8c21203e96b659bfe60306b6c86098257bcc5ab90635d981df16ad/qh3-2.0.4-cp37-abi3-musllinux_1_1_riscv64.whl", hash = "sha256:d777f66879bda1e2b73caa2ca624045362d3aab8b474d2db868d0f284822e24c", size = 2251101, upload-time = "2026-10-01T08:37:47.749Z" }, - { url = "https://files.pythonhosted.org/packages/97/b7/8d9c5e2cd35da5337619a144b4cec2502f5f74f47a9faaa80b0e104821ae/qh3-2.0.4-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:1dad9bae44479f682cad2e0c0ea8cbd384ce42cd2921f1f1b4c03e1299f6acbc", size = 2743308, upload-time = "2026-10-01T08:37:50.067Z" }, - { url = "https://files.pythonhosted.org/packages/d4/c6/41b844253571a2fd07606af62a255a6a8e1226853efd9668996627de31b1/qh3-2.0.4-cp37-abi3-win32.whl", hash = "sha256:45d57837ef9990c58c1776a12d551e7c88f2592a9885b6e781d30e18062b57c6", size = 2023298, upload-time = "2026-10-01T08:37:52.664Z" }, - { url = "https://files.pythonhosted.org/packages/63/3d/fd077e2b0a8108a63c8139545e2e220d7ba3e1fd2746fbc31b69ec31be39/qh3-2.0.4-cp37-abi3-win_amd64.whl", hash = "sha256:53f7180a74db23f5469a610a6bcdf2b29a4ed45e067a40276c83b1cead3816df", size = 2283134, upload-time = "2026-10-01T08:37:54.583Z" }, - { url = "https://files.pythonhosted.org/packages/12/6f/31e604f3cd5ff8ab0ec5658ecde85683eba382dd8adb4d4e8aae2f7c5531/qh3-2.0.4-cp37-abi3-win_arm64.whl", hash = "sha256:79e93801a8f33e651a7751bde3e0e120b565a93d18d146bf1fc8127d1669494f", size = 2099099, upload-time = "2026-10-01T08:37:56.972Z" }, - { url = "https://files.pythonhosted.org/packages/88/2d/e09ac439645631967921a7d37af9345d4a6b7b6f8d13e57fe6b3a68cdc9a/qh3-2.0.4-pp311-pypy311_pp73-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:0827e008ca4e0d28b60ad5826c9f56638acc9af23ed645ec7af0a683c0388439", size = 4678835, upload-time = "2026-10-01T08:38:28.34Z" }, - { url = "https://files.pythonhosted.org/packages/d9/82/bd21fcc04a42119f6355892c2aa61fdb7dc6b0874fc3ba330c4e5c94bc53/qh3-2.0.4-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7aeb06d18b13e3e64bf0e9f701bdbfb28687a8e37b6efd563e5d681354e2764d", size = 2289184, upload-time = "2026-10-01T08:38:30.546Z" }, - { url = "https://files.pythonhosted.org/packages/39/ab/50c118a5e234e1c476858ee73a317d1d699c443ce3e0176cf7711c5262c8/qh3-2.0.4-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b01a1597b563a07fc5b6ac5fa05c464439028604d4b8235ba92ab91c56dd9a9e", size = 2015860, upload-time = "2026-10-01T08:38:32.957Z" }, - { url = "https://files.pythonhosted.org/packages/c0/6d/8f6854cd7fc67136beb7c903cf51bdeff84b02b777931bb095a3e2821ea0/qh3-2.0.4-pp311-pypy311_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b03e7dd4e6e532be8d615407cc23e27f74c2f3525a0e03dedcae082238038ae7", size = 2203412, upload-time = "2026-10-01T08:38:34.877Z" }, - { url = "https://files.pythonhosted.org/packages/f7/30/49ec063df0d84f49cb0afc20279230b3be1bd50fb7f0c60f0c89bf22aa6d/qh3-2.0.4-pp311-pypy311_pp73-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:a7a9af2c985185420267904f88667778f919d2c42252bdc7d06e81d46802b125", size = 2194130, upload-time = "2026-10-01T08:38:36.873Z" }, - { url = "https://files.pythonhosted.org/packages/56/98/3e0dc84128ced4f8b21401bcf94e58745f8c94c674753d36e19dc99db550/qh3-2.0.4-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:55b5010819d3ff14d8e73fc9958aedf64824e6790bef2479fcf4bc9bc4a97673", size = 2210836, upload-time = "2026-10-01T08:38:38.874Z" }, - { url = "https://files.pythonhosted.org/packages/04/cd/228e9c0bad6018b5e87315aa1d6e6ba5ef948128734868be8cc135dcd13a/qh3-2.0.4-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4594a6d8706f28a0e9061a718da65b5c6dbeb173d739a858f2ae7519de72bc29", size = 2246462, upload-time = "2026-10-01T08:38:41.03Z" }, - { url = "https://files.pythonhosted.org/packages/7e/c2/2669a365d96ede12f1580b492eece06ec68d7f511c300a52adc9622f7632/qh3-2.0.4-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0d26dadce1f732169f89d01133f4b99b357ef6b64bda8f59a9d03d58e6f468cf", size = 2515570, upload-time = "2026-10-01T08:38:43.038Z" }, - { url = "https://files.pythonhosted.org/packages/f9/e7/1de0b9bd36d8ec2a3f594f6e7036934920d4017ecbfc28bd511e91e0fe78/qh3-2.0.4-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:f372246cc77bc7a0ffedf9eb6def981b371e2a6bea23bc283d6c5cf3b049074e", size = 2482930, upload-time = "2026-10-01T08:38:45.1Z" }, - { url = "https://files.pythonhosted.org/packages/e4/33/cebafbe1512e282d3442b971a20ad3b9a0ccf1ef4820c66dd62173963503/qh3-2.0.4-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:663983a37fd346955f8ff4fbbc762b14a283a08d80a16e12aca201ca1751bde3", size = 2273807, upload-time = "2026-10-01T08:38:47.12Z" }, - { url = "https://files.pythonhosted.org/packages/f2/62/748d2b20aa86fbd30e8f18d9a10e8f57c8a7ffe1d42c5d82e0602c5dca9c/qh3-2.0.4-pp311-pypy311_pp73-musllinux_1_1_i686.whl", hash = "sha256:90f360658db2ebb4cd051983d67eb6c39909d10aad35a89aa203a2f9061bc716", size = 2391891, upload-time = "2026-10-01T08:38:49.117Z" }, - { url = "https://files.pythonhosted.org/packages/5f/ea/0e32a16d177706c745e8cfb8c7d39f50c475e43d006d315267737cf08528/qh3-2.0.4-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:d0be90f8c2b332ad50337b74a10bed8c7ca0e81c2c517a34c2a547f014b9aec1", size = 2740307, upload-time = "2026-10-01T08:38:51.417Z" }, - { url = "https://files.pythonhosted.org/packages/78/a9/f0c012c3ad64f83808fbc70c78833a0511da19f15753b196d81551c71354/qh3-2.0.4-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:154094b02a069ec476b84b06ffbcec760d9c25e32856c8a345ff12215ba4ec57", size = 2277860, upload-time = "2026-10-01T08:38:53.518Z" }, +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/df/c3/777d963d852a19c1777eb74ab543d00d8b95592a849174d9a4f3b1c3f51a/qh3-2.1.0.tar.gz", hash = "sha256:622e3c63f2417039785f61d6af2cb26d0f0e5935ac2b84ef85a2bf76862fe6f3", size = 416198, upload-time = "2026-10-25T18:34:36.611Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/15/e463e025469339d2959357430a4bc38edd6d564919077762b6bf0549c7b1/qh3-2.1.0-cp313-cp313t-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:1393780b16de50f4f43221bcd5eb850ecaacbcd026494f896048651f72d29303", size = 4689485, upload-time = "2026-10-25T18:30:53.882Z" }, + { url = "https://files.pythonhosted.org/packages/89/9b/1d69545cb12858fb4b2d16a43aba0e30c2ec75335269bdeadcf5f05d310d/qh3-2.1.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c0b5b980799ea1a859b9ec5de1070afc1076937f953139b38f64bae3dd449aed", size = 2292143, upload-time = "2026-10-25T18:30:56.582Z" }, + { url = "https://files.pythonhosted.org/packages/de/dc/553b308b42ab7afa4e0c0f5443eb574cb546ed4b25bd4cd2225beb0a5edf/qh3-2.1.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9cd8b52cbbff5c3e671956eadd8251bed35a54d8d6404a532008636b2518cdee", size = 2023144, upload-time = "2026-10-25T18:30:58.301Z" }, + { url = "https://files.pythonhosted.org/packages/b4/39/1fb49e744cda75081edb821b5fa2c849ba6880555b930b88da755cd811fd/qh3-2.1.0-cp313-cp313t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:78cdf8de2c41a0d2f7164434c77f0559da055a709c51825ce2cc47d3b552982f", size = 2207365, upload-time = "2026-10-25T18:31:00.124Z" }, + { url = "https://files.pythonhosted.org/packages/d7/f4/eaf89fddaf5d22d2bae583704055d5cdea69f5d2ebca79c718cae578ca38/qh3-2.1.0-cp313-cp313t-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:a52c076b41ac91cc7d55aefcc8136bea39bcaa5c9d77adb7b23c9274410e8c68", size = 2202629, upload-time = "2026-10-25T18:31:01.852Z" }, + { url = "https://files.pythonhosted.org/packages/8a/87/8f5b5db604ecea59d5a490c93dc11aec831a5d809fcc873baedccfb428b1/qh3-2.1.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b84f5cc0da6e526b2c4ea57b7c5353d3084046c2eeb34c78014347b5dedce4de", size = 2221082, upload-time = "2026-10-25T18:31:03.633Z" }, + { url = "https://files.pythonhosted.org/packages/a4/38/9b2ec85ba6778eaba4128dc1a11805e3a70de582fa8c32b5c82f8674592c/qh3-2.1.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:34cdba8a19c7b1e38483bb24442a40608a84b91723be15b25edc6107a180c23d", size = 2255718, upload-time = "2026-10-25T18:31:05.087Z" }, + { url = "https://files.pythonhosted.org/packages/ef/5d/4b7059625ae90d4deab530f8a31ed343a7749023eb8d0ede201e07214b97/qh3-2.1.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb0ae31f0ae168b2d9d18fed0b42b8ef0a7fa6ea3ff5b41addafefebd77e7f1f", size = 2520917, upload-time = "2026-10-25T18:31:07.109Z" }, + { url = "https://files.pythonhosted.org/packages/3f/8a/8658f6d5fc5ff1285ceb856fa0b72bb6ef17d8c6b68ac7785fa988a89f34/qh3-2.1.0-cp313-cp313t-manylinux_2_39_riscv64.whl", hash = "sha256:700391db7767c52833b60500f9d03b8e929a654534a6721b998250e4ebb7f638", size = 2106012, upload-time = "2026-10-25T18:31:08.855Z" }, + { url = "https://files.pythonhosted.org/packages/56/3d/b712bbbf9c29aa4f688c51825e6f2d751f9b81934404f5e72f3a9616a7d9/qh3-2.1.0-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:72ed04a190f1b99cc10ebf360d6d6d0ea967f343566f49341b56899f39c494c0", size = 2488815, upload-time = "2026-10-25T18:31:10.599Z" }, + { url = "https://files.pythonhosted.org/packages/63/fe/2d75fde70de88f7a58fbbcfd67075dc8d3dad273834d270affb19049443a/qh3-2.1.0-cp313-cp313t-musllinux_1_1_armv7l.whl", hash = "sha256:b9c9f4602d138c89696ccbf570be119c294727864d66b91554bce5e28e0a054a", size = 2284543, upload-time = "2026-10-25T18:31:12.235Z" }, + { url = "https://files.pythonhosted.org/packages/d3/71/b82e2626adc445b1a9df122fa05126bd7480a3881b2df0fc44fb9b9a7f1d/qh3-2.1.0-cp313-cp313t-musllinux_1_1_i686.whl", hash = "sha256:d81103c015c27b1fbf7ba768e922ff8176eaa6461a00c6c99f166295db030987", size = 2397166, upload-time = "2026-10-25T18:31:13.856Z" }, + { url = "https://files.pythonhosted.org/packages/3f/04/82dd427c2f4648a0c855dae60271352653b35dd627ad6db60c6bd31bd73a/qh3-2.1.0-cp313-cp313t-musllinux_1_1_riscv64.whl", hash = "sha256:8cbadc13df9e529f18ed507c0a0d32a424c3247e5b4895377872aade8d1d880b", size = 2210993, upload-time = "2026-10-25T18:31:15.588Z" }, + { url = "https://files.pythonhosted.org/packages/ed/8d/1bd332607c3f46bac06b1294b887c8fe40cc9ad9c5bab4c68187775f0822/qh3-2.1.0-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:d88639721d5b0bfd90bb440242f570be778e2841799a68bfc29ed725b324a28e", size = 2744521, upload-time = "2026-10-25T18:31:17.066Z" }, + { url = "https://files.pythonhosted.org/packages/cc/14/d2226d640ab86d9b20798cd9409fd43e07a279faed84dff63d4a45c43318/qh3-2.1.0-cp313-cp313t-win32.whl", hash = "sha256:f116c53985220f1a06f84b317cbd2e66024d16bfd41dcf168df6890e4198b1f3", size = 2025197, upload-time = "2026-10-25T18:31:18.702Z" }, + { url = "https://files.pythonhosted.org/packages/1b/1f/65b436400a3dda2e33817d24703792e93e348ddd8971714f1e99614d0c9d/qh3-2.1.0-cp313-cp313t-win_amd64.whl", hash = "sha256:df3d5c5dcc590b642f1c43e5987cd9ae0886bd2cd214e7908dc62e79c4deafd8", size = 2291651, upload-time = "2026-10-25T18:31:20.511Z" }, + { url = "https://files.pythonhosted.org/packages/aa/97/f4c9bb99bcad8b14cc22ab40f3994f9ab60e547f2663735a0fa114b58711/qh3-2.1.0-cp313-cp313t-win_arm64.whl", hash = "sha256:135e7f722b7523ea22d36b7eab9873d19f25c3d3f585d24ded6673bd31ebe593", size = 2104967, upload-time = "2026-10-25T18:31:22.203Z" }, + { url = "https://files.pythonhosted.org/packages/0a/92/d264ccf9fe2a54d0dea3cb5e621e3bbab8c78cacf413d0bfd0eaf964468e/qh3-2.1.0-cp314-cp314t-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:ba62fe5a0a8394e3cff6b1e205d4cdb21f18d9191193d963fc887a1f7339d3cf", size = 4690342, upload-time = "2026-10-25T18:31:23.727Z" }, + { url = "https://files.pythonhosted.org/packages/9a/59/28d92e577656b5abe63a99a058c8ecc4a238f09ab4d7e3588c2b6d315d78/qh3-2.1.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:962cdfab72c26b5fa9549c5bd1fe645ca437a449ee2c1528aff226e3264452ff", size = 2292764, upload-time = "2026-10-25T18:31:25.39Z" }, + { url = "https://files.pythonhosted.org/packages/f2/78/c64a50433891f34b28cd5bf8d171ee50f88a436861c3309904df3eb384da/qh3-2.1.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7ec9b4cc8796a7a158f05667200fbc9906ccf8ab6804bf007c61b17901035ebe", size = 2022524, upload-time = "2026-10-25T18:31:27.22Z" }, + { url = "https://files.pythonhosted.org/packages/5a/13/eb6cec78c284cb87337d4cbac4b995c1defad2217efe8608f9de2b0a5ee0/qh3-2.1.0-cp314-cp314t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:65b2cb6aba980a5821223969d15452e102e01f1b656485b401c40dd0d4d93c8c", size = 2206837, upload-time = "2026-10-25T18:31:29.338Z" }, + { url = "https://files.pythonhosted.org/packages/0d/63/83c16002bef8b09fa4162e388b8ff6199ffb84ad7f09fdc8a1a2164a2edc/qh3-2.1.0-cp314-cp314t-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:82505dbb6e5a5aa814fac47789b185b21020692c37ccfd96d428d32c9822ee5d", size = 2202427, upload-time = "2026-10-25T18:31:30.932Z" }, + { url = "https://files.pythonhosted.org/packages/87/9f/27291cb15e4d664ec7359db5988c1af6358d0f428ed189a9a80289b5f45e/qh3-2.1.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e5d68107e1c2bb6538bfeddd0238cfd309396acb51e0d3bd0fd7ae87390219b6", size = 2221164, upload-time = "2026-10-25T18:31:32.666Z" }, + { url = "https://files.pythonhosted.org/packages/b1/a6/d52444f7e286cb33da29688d6356dcff638cd238f18555f65d9918ee4c0b/qh3-2.1.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e856c5c9393b1fb4c9e7458165ad2560ec8b0c14e6092c4e8f456dcba8ce3f1f", size = 2255435, upload-time = "2026-10-25T18:31:34.366Z" }, + { url = "https://files.pythonhosted.org/packages/a2/45/ead5d22957caab56e12e12a2f45ca04fa4710d890e1557ca847e9c96d933/qh3-2.1.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f647ef9f76bd289a28baab858dcfd34efd2255a7da274a9e8f45a020be36b795", size = 2520651, upload-time = "2026-10-25T18:31:35.9Z" }, + { url = "https://files.pythonhosted.org/packages/08/f3/684f31bc55b46dbc79ef0ee2461cce34d2967e62f2b3b108fab4db6c71ba/qh3-2.1.0-cp314-cp314t-manylinux_2_39_riscv64.whl", hash = "sha256:86ebca1a4d4874c619ef7d3eee4d699968b236e4292bb4cbd498c7054cdc5319", size = 2106061, upload-time = "2026-10-25T18:31:37.665Z" }, + { url = "https://files.pythonhosted.org/packages/6a/6a/656bf639c92dcc9ff158434a798e845ce479cdf2372461e66ee6a67c40f8/qh3-2.1.0-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:4c21a3133460449abd93a61cc675dbde893f1094f18bc6f286d44d3d3ae5d462", size = 2488422, upload-time = "2026-10-25T18:31:39.296Z" }, + { url = "https://files.pythonhosted.org/packages/52/95/727d54837a2cde950d4480d50943d1c8935133f0d72699e5d77a975e74c5/qh3-2.1.0-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:0b3d1ddf862bb9ffde56d4ef4477a0bb86fe60983d6b03b7e8869aa026c4c3d4", size = 2284266, upload-time = "2026-10-25T18:31:41.024Z" }, + { url = "https://files.pythonhosted.org/packages/8b/87/bdc71bdca9b9ad7b8fd034c7e7ae8683c87ceed76f7646664bc5d0a7d4fb/qh3-2.1.0-cp314-cp314t-musllinux_1_1_i686.whl", hash = "sha256:b812694d3884bf8f92ba05b02889f41ea519821fc98c2634010e5d595ec2c330", size = 2396753, upload-time = "2026-10-25T18:31:42.59Z" }, + { url = "https://files.pythonhosted.org/packages/06/69/799473c877c003cbb3455198d43168eaebabfc5c14d492530fe748c93acb/qh3-2.1.0-cp314-cp314t-musllinux_1_1_riscv64.whl", hash = "sha256:a80118168ebf68413b089930fcef0d6c7ba2907d61273a4fcd72414322281d75", size = 2210620, upload-time = "2026-10-25T18:31:44.378Z" }, + { url = "https://files.pythonhosted.org/packages/0f/66/9145d587aca2da40cb57118c810d878238169758b3b5f0b9a2321dda7fba/qh3-2.1.0-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:a6013c83b0988544ca084feea6c91b4dceb9b9a835a4c2eec0407faf820ba4d2", size = 2744036, upload-time = "2026-10-25T18:31:45.937Z" }, + { url = "https://files.pythonhosted.org/packages/09/c6/465bbceca908f614039b0ecf662b62d95b72ce366108bd4d838a9ae2938f/qh3-2.1.0-cp314-cp314t-win32.whl", hash = "sha256:358f66b86c6ff4ac515aaf8c6848bf0e61939b075f8fa455b977d0aa3e7e6860", size = 2025220, upload-time = "2026-10-25T18:31:47.486Z" }, + { url = "https://files.pythonhosted.org/packages/19/55/02c0f01f9048c79ba0782b658d91f92cbd0fbfe25dccd73f4b09e695c1a3/qh3-2.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:1bd9d323d3a059d79c3586c00f973d64ba803234218d47ca2e8a364ec885b9dd", size = 2291442, upload-time = "2026-10-25T18:31:49.084Z" }, + { url = "https://files.pythonhosted.org/packages/b5/5a/4189ea71debe60246ac6b212ddc316c73ba4e44f56002538915f3e3cd4f2/qh3-2.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:7644fc608c6d28012f567c5ce060b281c5a014ff60ddf4167ba9b6507ef3e576", size = 2104825, upload-time = "2026-10-25T18:31:50.778Z" }, + { url = "https://files.pythonhosted.org/packages/19/01/b6f10b2a365b68526b68e44a864981c1d50df16eb92d9b304340aeb92998/qh3-2.1.0-cp315-cp315t-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:a07c074dcb9d58b41d1d73207357bd70e2617ed9816f36bbc5824eeef4465c1b", size = 4680306, upload-time = "2026-10-25T18:31:52.533Z" }, + { url = "https://files.pythonhosted.org/packages/74/32/81b9a3e61c6ad8badf4a5ebc0fb7e7b19db95cf964a1a58ee29d1b6700fc/qh3-2.1.0-cp315-cp315t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6dd04f2718a3412bdebd580e35ff465128ba3d6dfa967c0ce4186f60a2df060b", size = 2286357, upload-time = "2026-10-25T18:31:54.297Z" }, + { url = "https://files.pythonhosted.org/packages/8c/91/74120b53de2ef4f5b13462c0e255b2e85834a4e59d4997b6550cf283af87/qh3-2.1.0-cp315-cp315t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b2304539fee9c70d385bd834402ae3fbfff58fcb2793adf7fd44bd53cc983be0", size = 2018498, upload-time = "2026-10-25T18:31:55.8Z" }, + { url = "https://files.pythonhosted.org/packages/1b/41/fc04957e2080c056c56687a0799162376e25b7be4a5f615f95e9f9cdf805/qh3-2.1.0-cp315-cp315t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:451ae203b2670e256333170afc2ccc558fa15de172412ec02c2ea3e03854645f", size = 2203739, upload-time = "2026-10-25T18:31:57.33Z" }, + { url = "https://files.pythonhosted.org/packages/a7/9c/aaa5d1a1484a66ec7a0117c8af575da7059106d4da7a63ceeb9ed4525d06/qh3-2.1.0-cp315-cp315t-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:129ff9821720341c847dffe99f8fe599914f583cfe0c725061ecfd5d578ee2b8", size = 2195664, upload-time = "2026-10-25T18:31:58.979Z" }, + { url = "https://files.pythonhosted.org/packages/11/92/fd0860779e5bddd01898a6acb5ac290bcf09c9cd48d82c49cdf9408132f8/qh3-2.1.0-cp315-cp315t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a648d7ef66894113a23b0b197b9c164e8d6cd6a1175e10c1ba5bfc1d5f02eab1", size = 2218418, upload-time = "2026-10-25T18:32:00.381Z" }, + { url = "https://files.pythonhosted.org/packages/48/6d/2e7d6136a59cf1f0dec5e32b89e23da480e538159027e1708f251c25b41e/qh3-2.1.0-cp315-cp315t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:833a93ff543f4e98b71b497841c5c3ac4c7f255d56f544f5223ffd413dca2efb", size = 2251483, upload-time = "2026-10-25T18:32:02.001Z" }, + { url = "https://files.pythonhosted.org/packages/92/05/4f131be92d11407586fd916eba3dfcf6a7e48bd408129cec0db89692829a/qh3-2.1.0-cp315-cp315t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e649419464428547e314c8048e5257d9a8cd09e15643405cc9fbe177e10cad37", size = 2517617, upload-time = "2026-10-25T18:32:03.632Z" }, + { url = "https://files.pythonhosted.org/packages/a7/dc/18d5ac783c9dbb330b393139975381726cbc5c4e2f6f6012e81786bb5022/qh3-2.1.0-cp315-cp315t-manylinux_2_39_riscv64.whl", hash = "sha256:1d6e9364a537785755b5b2724c330ed59fbae8bc29df28a1a74cb83ef1defd4c", size = 2100897, upload-time = "2026-10-25T18:32:05.079Z" }, + { url = "https://files.pythonhosted.org/packages/61/a9/bef0c2408c3c11933aba0f53f3c1ed6244ad9a9b890fb7484201bd2c8253/qh3-2.1.0-cp315-cp315t-musllinux_1_1_aarch64.whl", hash = "sha256:4c3fcb8ec0da9ad2a349a2a0b96902e0432830e6740163863911d1ea88439fad", size = 2482641, upload-time = "2026-10-25T18:32:06.654Z" }, + { url = "https://files.pythonhosted.org/packages/d6/f4/22b90ebf44bb427822498c21f76679ac97f835f946b60c610cf0fdd8bb0a/qh3-2.1.0-cp315-cp315t-musllinux_1_1_armv7l.whl", hash = "sha256:d15f365e295d1926bb283324c72dfb2568cdb145a2d521495b562c765a9e3507", size = 2278962, upload-time = "2026-10-25T18:32:08.481Z" }, + { url = "https://files.pythonhosted.org/packages/8e/74/2e691ff9a2e4a987bf9980ccc4196ae54e9f4603a0f5b70b6ad904bccf02/qh3-2.1.0-cp315-cp315t-musllinux_1_1_i686.whl", hash = "sha256:ba8598d3ee4c72325d099714920f23c35844a0e2f0bf20dd56b51d61545bc097", size = 2392700, upload-time = "2026-10-25T18:32:10.133Z" }, + { url = "https://files.pythonhosted.org/packages/d0/f9/b626778b70d08fe5b9b8569a98ae3485a75854896a3a7ee1ce505e934c4b/qh3-2.1.0-cp315-cp315t-musllinux_1_1_riscv64.whl", hash = "sha256:67e6377dad202507d50e4af854a6a464975ed310f4ecc272f83cda73811e9b71", size = 2204995, upload-time = "2026-10-25T18:32:11.838Z" }, + { url = "https://files.pythonhosted.org/packages/be/f0/18d65ae7ae4655a11d8ff59d023259f2b8dc20f10194c6d5fce900b11b65/qh3-2.1.0-cp315-cp315t-musllinux_1_1_x86_64.whl", hash = "sha256:9915fd34cf3010f186c783a492c9f3ec31c0e5cad8c5e16a6fe08d2dda1defee", size = 2740263, upload-time = "2026-10-25T18:32:14.177Z" }, + { url = "https://files.pythonhosted.org/packages/9c/76/182804ba95ef16f1e17e788eb60547c8a2fc50c24734ef02e6fc2dac9797/qh3-2.1.0-cp315-cp315t-win32.whl", hash = "sha256:71ffe60e5a8dc67b7ecd3b2ba6a105fe4cda8e770d6fae2ff6445b557d0b4d38", size = 2023027, upload-time = "2026-10-25T18:32:15.705Z" }, + { url = "https://files.pythonhosted.org/packages/4d/c7/d778b06705e274f0dbf152b0cffd371a459217aa254f7f7450bec58d9ee8/qh3-2.1.0-cp315-cp315t-win_amd64.whl", hash = "sha256:8a1a95c687bd2d26e7cb55dc14095b95ab55f1afcd8adfe1c885fa7818582ad7", size = 2287300, upload-time = "2026-10-25T18:32:17.453Z" }, + { url = "https://files.pythonhosted.org/packages/bc/af/d0518e0c21afaacf104f8849c048d192b4da3bf4c3b728f3a7539a70a4b0/qh3-2.1.0-cp315-cp315t-win_arm64.whl", hash = "sha256:2eefd993b952bb2bff34396b7abf4cffa46f25667cba85de0dff6e083d1e83ac", size = 2098935, upload-time = "2026-10-25T18:32:20.111Z" }, + { url = "https://files.pythonhosted.org/packages/5b/db/28b5f12044af0a284b91938ae0f567b3625f13c6b316be9e360de023b735/qh3-2.1.0-cp37-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:94412f03ec7b2d27e9f238404de0ab0a293ef4eb172ce1e6f6f6fd11ab857e58", size = 4702601, upload-time = "2026-10-25T18:32:21.688Z" }, + { url = "https://files.pythonhosted.org/packages/62/ab/1b9bd5f83fe3b1de0600853b90b7eec8cb338f57a91c742c02cc18618635/qh3-2.1.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b66c2ff3d8c5bad514f7f9362e0c9e3e4664209db5c0167278f1d90918bbd12b", size = 2299659, upload-time = "2026-10-25T18:32:23.301Z" }, + { url = "https://files.pythonhosted.org/packages/0c/b8/7b1bbed622b40c55fca01929d948e83b6a446ff491fee65fad24a9ef5b8a/qh3-2.1.0-cp37-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8982eae1cd88e5d7700802b82f29a7cb834d453bb557827010199a037d4062f9", size = 2027280, upload-time = "2026-10-25T18:32:24.77Z" }, + { url = "https://files.pythonhosted.org/packages/37/68/bf8963ec468dc5ce793966e8d6f48b47fa85e3e8be29959a34ebb5259acf/qh3-2.1.0-cp37-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fbdfefe22dcc6222b853be72f062fc7e84d6c333294310502bf0090e20ac0cf3", size = 2212422, upload-time = "2026-10-25T18:32:26.236Z" }, + { url = "https://files.pythonhosted.org/packages/82/b2/1cfdf7f7f3d3368c6a0c194b6e4eaca0e56d121beb4cd14ad51946282dab/qh3-2.1.0-cp37-abi3-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:341793106cb0f803985031690bc2e4e184bbeecbaed63d902921c7dfd07f1808", size = 2210619, upload-time = "2026-10-25T18:32:27.888Z" }, + { url = "https://files.pythonhosted.org/packages/b7/4d/fbe50cff7df4b081133f3947351005444dec1027189591c60b5e5aa1ce66/qh3-2.1.0-cp37-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9b205fd9b5fffcf467331b82ae1f4e360581bd60886d37adf626197e75e0e0bc", size = 2229445, upload-time = "2026-10-25T18:32:29.367Z" }, + { url = "https://files.pythonhosted.org/packages/4a/a1/308e91843b74e67bee948f65da454b4391c26e6c7bdc34827cd5690a91d6/qh3-2.1.0-cp37-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:27bd2e3a03985be5e6e6a04faa1f53106c75ad607f662d54b57585a0e52121c1", size = 2264523, upload-time = "2026-10-25T18:32:30.998Z" }, + { url = "https://files.pythonhosted.org/packages/ff/46/2c509a0237bc1f2469a53ae5c4dfa68b924baccaed37d99cbbb33542517e/qh3-2.1.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1808e123760e78482948b8e6bf5547a8a91637573b89caf165ffe948850d2ba5", size = 2524346, upload-time = "2026-10-25T18:32:32.575Z" }, + { url = "https://files.pythonhosted.org/packages/56/fc/afee6c2b6d76f5c21df9a9f2ed4a3abfb3bd6449820dc6823e3aac32cca2/qh3-2.1.0-cp37-abi3-manylinux_2_39_riscv64.whl", hash = "sha256:343780d1f7e8000a6e06458e6940d0a80e29cc2be7e47b60f24f714dd17da690", size = 2109918, upload-time = "2026-10-25T18:32:34.209Z" }, + { url = "https://files.pythonhosted.org/packages/25/21/aa9725997d3d321b4fa31b21ba211eb25781131a59f48a4960446ba4fb90/qh3-2.1.0-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:3cb6649dee2f4d3dba008dc9d0e15a75fdb0c16b864535725f2266adc55a54e9", size = 2496917, upload-time = "2026-10-25T18:32:35.751Z" }, + { url = "https://files.pythonhosted.org/packages/b2/ee/33f719e6f37b4b7d45b11bbbb7a91be8be9d3dd5a632bbf22743e2c956e3/qh3-2.1.0-cp37-abi3-musllinux_1_1_armv7l.whl", hash = "sha256:23ff27b10d88fae56fd3fb530bd93a19dcce7b3ee921b9cebc4d75767b2b71a1", size = 2286839, upload-time = "2026-10-25T18:32:37.225Z" }, + { url = "https://files.pythonhosted.org/packages/04/7c/fc0ee6da0ff2f3a96ba5afab913166c39860d1f04279f0639fe4a8ee912d/qh3-2.1.0-cp37-abi3-musllinux_1_1_i686.whl", hash = "sha256:a51ef02b036b2a06293ce87d48105a516a31694a77746062d16016a5aca6a013", size = 2400938, upload-time = "2026-10-25T18:32:38.716Z" }, + { url = "https://files.pythonhosted.org/packages/9a/44/bb8511574c859f4f831728ae5251bc8d155bfc8f061aa4c736b5e60dbf59/qh3-2.1.0-cp37-abi3-musllinux_1_1_riscv64.whl", hash = "sha256:81a03727a5ddf001fdb57251a86b950ffea47c7a3473641e2707d647d941b398", size = 2219264, upload-time = "2026-10-25T18:32:40.296Z" }, + { url = "https://files.pythonhosted.org/packages/dc/9e/0e78319e7be595e32ab815cfcf4cac73c453f69c5a901c457e991d9fa481/qh3-2.1.0-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:5b7e7300dc9352f7d31b4204e6bad6c0a310a12c4b40e36a0ff347f64c8d793f", size = 2748521, upload-time = "2026-10-25T18:32:42.019Z" }, + { url = "https://files.pythonhosted.org/packages/b4/66/5df8eb3e6593dbc3dacd0ce54eccf64f258298c8671e922ab25a5b07a6a2/qh3-2.1.0-cp37-abi3-win32.whl", hash = "sha256:19d1b93d669177215675bd21a303763d2222bc9ba1c9c3850e1e9a5bdfe868aa", size = 2033904, upload-time = "2026-10-25T18:32:43.69Z" }, + { url = "https://files.pythonhosted.org/packages/a8/19/8e4e9ac60b5e3f83a4f702b0094533de8c86d8d6baa77cb3ba6f7ef4d8a9/qh3-2.1.0-cp37-abi3-win_amd64.whl", hash = "sha256:85be2ef6a536a687d12c018f4605a5517b151d02ea7d42a3d9f4386985c735a5", size = 2299731, upload-time = "2026-10-25T18:32:45.316Z" }, + { url = "https://files.pythonhosted.org/packages/71/d2/922f63131bf1f72848198d0ded9dde9dbd7245a59c14c18dda07ba4d153f/qh3-2.1.0-cp37-abi3-win_arm64.whl", hash = "sha256:0c257cef34cf990da943ffbf12c4ee2ab44d131f4b4024383edae84d19428056", size = 2117482, upload-time = "2026-10-25T18:32:47.004Z" }, + { url = "https://files.pythonhosted.org/packages/ab/2c/408c39bf57138e49a652f029345af531216835be33a23b3aa9cd0b237b6d/qh3-2.1.0-pp311-pypy311_pp73-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:878c0bd25207296d38cc167571514cdd4a8cddae61e8d6344ed63f5e033bfea0", size = 4696644, upload-time = "2026-10-25T18:33:10.083Z" }, + { url = "https://files.pythonhosted.org/packages/5e/1c/08f0f0bbc0deeab5265994d0c20d7a2218c6c2bce72c172ae31a712f12d8/qh3-2.1.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:577369a497c2f918e3d3f972f1e1434bc74dc06c97ba3d12835e49692fda3928", size = 2296535, upload-time = "2026-10-25T18:33:12.126Z" }, + { url = "https://files.pythonhosted.org/packages/20/7c/cc48e377bfc47b57d3ca2ce068204a42d7f0aec15a3f9d70b023a1cc6909/qh3-2.1.0-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5fdddcf7385edc650ac4cfa43fde1d9f77d18a97c6c41a5fcc763f05e45ec18b", size = 2025718, upload-time = "2026-10-25T18:33:13.749Z" }, + { url = "https://files.pythonhosted.org/packages/3e/7b/d3d8e751cfb772d28a36f3c3cbe36d58249d85e71a2555fb16ced3d48bfd/qh3-2.1.0-pp311-pypy311_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:25d0f7f063292ea157ad1617b55cfd2620b1224757de41ab9db9d0e3f350c68a", size = 2211168, upload-time = "2026-10-25T18:33:15.706Z" }, + { url = "https://files.pythonhosted.org/packages/dc/99/4e7235d6e1069093a718c7787f71423aea91c1d0b757a63e69ea6351a209/qh3-2.1.0-pp311-pypy311_pp73-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:c816c6c1f950a96a088f59b5f2d82115061ba98274bc17b200d13f7a83ce75ca", size = 2207869, upload-time = "2026-10-25T18:33:17.17Z" }, + { url = "https://files.pythonhosted.org/packages/52/f9/64d91b2bd39b37a43e73ea10cdaefcbdf222edd3449271f3e2d2ad7ca966/qh3-2.1.0-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1cda2b70c7ff19a95b8075e5859de58cbd3b4627b8d702dc19369b40bbeeccd1", size = 2227144, upload-time = "2026-10-25T18:33:19.16Z" }, + { url = "https://files.pythonhosted.org/packages/68/78/cafd796ef29de38e0ab2910d3349d20f16e9e536d761f1663e0659906ee5/qh3-2.1.0-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a0abf2940898bf522723cc63624263a9c701223fc9fb04aeb50da79e65273be3", size = 2258274, upload-time = "2026-10-25T18:33:20.906Z" }, + { url = "https://files.pythonhosted.org/packages/95/0f/ca6d91f5fe12364e071ed98a340103a60fa3d019781251ad245d22239747/qh3-2.1.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:749898c44784fbf57ccde6600a6c441c068a729a358b45d68f2892a06d318364", size = 2522758, upload-time = "2026-10-25T18:33:22.701Z" }, + { url = "https://files.pythonhosted.org/packages/6a/af/d0178527171792fd851089f5a28525489948c9abb859e988753620b910b0/qh3-2.1.0-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:b41e0703e196e11e401b618f231c38035c8990a20977a8f97e4f927c7a48c832", size = 2493781, upload-time = "2026-10-25T18:33:24.222Z" }, + { url = "https://files.pythonhosted.org/packages/93/33/db746feca2a32219de160b8a0b8885e907b2ea0fa3c7ef435ecbb04a9a11/qh3-2.1.0-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:93f4c32c7c109038ebdb68ead658767d92690921061eee7236b40b7005bc2866", size = 2286093, upload-time = "2026-10-25T18:33:25.964Z" }, + { url = "https://files.pythonhosted.org/packages/99/c8/5730afa2abe1ba8e1d4e2eb219bccbf123562010512cd8b0000edc7bba9a/qh3-2.1.0-pp311-pypy311_pp73-musllinux_1_1_i686.whl", hash = "sha256:e624ebf3b8226b421137e9dc6b099bd6aff3663b58826ab496b02ed98b6131d7", size = 2399719, upload-time = "2026-10-25T18:33:27.395Z" }, + { url = "https://files.pythonhosted.org/packages/e1/30/b36f564ed9357f198e5458215c7bdff26afe17e649f3e32c3a4a981e5f3c/qh3-2.1.0-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f4ea26fd65372848530c9eec88936144f5c1fdcc39d23398ceef1913d27eb1e1", size = 2746674, upload-time = "2026-10-25T18:33:28.902Z" }, + { url = "https://files.pythonhosted.org/packages/2d/33/59865fd5f045f9e2e56c1a6a7e120f26c28b5b4323b3a25683c177bbb995/qh3-2.1.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:422592db11e864a40bb612153aacb1084d5660cdf6251fcf62202744a76d6fb7", size = 2295434, upload-time = "2026-10-25T18:33:30.491Z" }, ] [[package]] @@ -2621,99 +2687,114 @@ wheels = [ [[package]] name = "ruff" -version = "0.16.10" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c4/49/23802c45f093eb14bde54b141d2b2f058edfa63a06db7beed047308cc08f/ruff-0.16.10.tar.gz", hash = "sha256:eff4728c4eaae93f0955cd264d24b2ab348e74bf59986ccf282ba6dc16b3b017", size = 4958724, upload-time = "2026-10-01T18:03:21.697Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2f/21/ebce22e1d90cdb2cd691026b9c6e9bec6499f0b396089481755b5d49efff/ruff-0.16.10-py3-none-linux_armv6l.whl", hash = "sha256:488b0fe3f3574210e5cf80d9f59b9e3ab17a127a8155de3f307b392589cfb511", size = 10095557, upload-time = "2026-10-01T18:02:36.072Z" }, - { url = "https://files.pythonhosted.org/packages/cb/98/a54de85876a8b2612bfa0d84c7b9abfb39c6a3354aee7800b09c1649b9e3/ruff-0.16.10-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:e748ff95c934c4e978783b8e687bc174e7bd84e8ad24e3243e1ecfcda5e0282d", size = 10340577, upload-time = "2026-10-01T18:02:39.258Z" }, - { url = "https://files.pythonhosted.org/packages/9f/16/1a5a4a2657effe4806110f2b907313802f1367fcdbb29e8122766e407fab/ruff-0.16.10-py3-none-macosx_11_0_arm64.whl", hash = "sha256:3031a4a2e8e7b8a46f70be45f198c35a11ece509a94b80334d8d397a33c67550", size = 9774282, upload-time = "2026-10-01T18:02:41.79Z" }, - { url = "https://files.pythonhosted.org/packages/6e/fb/470085af734da396e68cd80fb0a3e7c109a459716ae59588c0f6fab8a17d/ruff-0.16.10-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:494401c86df4c4c25f69b9419605d944467ee98c42fb6ad405ef4fa40b8fb67d", size = 9920895, upload-time = "2026-10-01T18:02:44.458Z" }, - { url = "https://files.pythonhosted.org/packages/57/de/f10cffe4f88a37ea6615ec460f01bf76f0bc1477737a35d9ee61a630a78b/ruff-0.16.10-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d203abc0ff2b773ee33d00ab8df0bb67046fbc7c07b119332f08b9b345cf8221", size = 9902707, upload-time = "2026-10-01T18:02:47.041Z" }, - { url = "https://files.pythonhosted.org/packages/ff/44/3fdcedf83ae60ef837dd239170e480dce606a9142cfa2db911afdf855fd9/ruff-0.16.10-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bd83d1235a5258d318477bc5b576303974cbdef5df0c01a1bff14efcc23bd12a", size = 10619287, upload-time = "2026-10-01T18:02:49.485Z" }, - { url = "https://files.pythonhosted.org/packages/c1/62/02e76a5574002153618eafbb70e159468addc72a5d2c488e65cbb4639d2d/ruff-0.16.10-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bc2610fb269fa56dd8a68669ae470fa6272902668c0fc2ebc3aa112b2633d5b8", size = 11339942, upload-time = "2026-10-01T18:02:52.008Z" }, - { url = "https://files.pythonhosted.org/packages/6b/c4/cde27d47ad8d4126c587e608c67c46e7feac11763f606d261a1bc8a489e6/ruff-0.16.10-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3e70175e29cc94c26ea296c80e470180b744b7419026898e58f520c6ab32578e", size = 10934316, upload-time = "2026-10-01T18:02:54.811Z" }, - { url = "https://files.pythonhosted.org/packages/e4/03/17234145f302a645a123e8c3bb2411ecf4669fbc350de3b0d803230a1729/ruff-0.16.10-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f33f43a864a8483eebd160e713336c8bab02c934feaff0a33cf5ccb41546d09a", size = 10387968, upload-time = "2026-10-01T18:02:57.497Z" }, - { url = "https://files.pythonhosted.org/packages/71/29/2493af60240ee7644b38a4b821f5f9c3b5a4fa3178770fe1d0c685217395/ruff-0.16.10-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:1dfc6f0088149fb6a362c1c446bcbb3fd2157b3852fe2fa68409276eab9ad9b3", size = 10537906, upload-time = "2026-10-01T18:03:00.006Z" }, - { url = "https://files.pythonhosted.org/packages/2e/9a/f56b28f3b143bb9e518c34fb89ab191b626bca8b70dbf8af0d2e2d473572/ruff-0.16.10-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:6553498afc35f580f036030795810b9e6bcea31604b0fd9e8d352795473042e3", size = 10012938, upload-time = "2026-10-01T18:03:03.006Z" }, - { url = "https://files.pythonhosted.org/packages/c2/c4/fca37362848ea4d4d80712df13632e645e7c7cfb1bdedf140699ac7a090b/ruff-0.16.10-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:a3b8471dea115d37f123882be852bed13403746d3a76c11de4a19ec5f9ff5a03", size = 9897945, upload-time = "2026-10-01T18:03:05.818Z" }, - { url = "https://files.pythonhosted.org/packages/7d/c7/e0bc57664d6e0c61fd22f620af260fe9662d7e1ddb320ea7f160e76ef165/ruff-0.16.10-py3-none-musllinux_1_2_i686.whl", hash = "sha256:92e59a70bcbd9d3a5483656da906ec28edfdacfce00afd99edb8b4e9d15644be", size = 10333134, upload-time = "2026-10-01T18:03:08.25Z" }, - { url = "https://files.pythonhosted.org/packages/21/aa/5c9f3b68737c0e4a33a1db5dfab44f7b786d96ca91a7233112ddab1e6dc2/ruff-0.16.10-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:7ae7375f803b5520dc9f546bed7e3a0acb70b91812e9bb4b19927de22f25b77d", size = 10741702, upload-time = "2026-10-01T18:03:10.775Z" }, - { url = "https://files.pythonhosted.org/packages/78/fa/0f9c2020dc316c53d983be011720b8157cc052b97e3f4dfa7db6d0f880a6/ruff-0.16.10-py3-none-win32.whl", hash = "sha256:2a12e01cb9156c10c466f63b46eaae5ecea28dfbd21b5836353ae498e7d1349a", size = 10139176, upload-time = "2026-10-01T18:03:13.267Z" }, - { url = "https://files.pythonhosted.org/packages/99/29/cfb0df9448d4d4ad48c2de029ada9ebd71baa6da983a6c77ee6c6cd0fe82/ruff-0.16.10-py3-none-win_amd64.whl", hash = "sha256:97f2015c92aa97105b0eab19eb5d224884399281cfc5da86a92db4ab5e7fb2ca", size = 10584734, upload-time = "2026-10-01T18:03:16.006Z" }, - { url = "https://files.pythonhosted.org/packages/fc/05/c16957eb287c3fc062e032619a25868d93d408a844b725bcf514f7a378ff/ruff-0.16.10-py3-none-win_arm64.whl", hash = "sha256:25a65fe998c4e6861ec079ada5826a2fc605e6cbccbe9dcd7fac1f54e791621b", size = 10366440, upload-time = "2026-10-01T18:03:19.04Z" }, +version = "0.17.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/59/73/81f140c4edd9cc323de5478e6bbd3bbcfae9a82692df1a258de8cec6a6d2/ruff-0.17.3.tar.gz", hash = "sha256:a57049c8addb3662073859bb55a7ad35aa54f8a7c9ed268911d1333c5aadaaa9", size = 5339444, upload-time = "2026-10-27T00:34:05.914Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/8c/083365310c4e79c842f25bf5a842facbd9b0c0ec336f96dffa5074fe723d/ruff-0.17.3-py3-none-linux_armv6l.whl", hash = "sha256:ab86b84cd9d05e56d329742c0cabdcbe2d1d56c98f4a04aab9b5b66196febfb4", size = 10216522, upload-time = "2026-10-27T00:33:29.822Z" }, + { url = "https://files.pythonhosted.org/packages/72/26/4b2edf31cfebe9605d8f6ed8f1d6f376d3a53d4c9a123e62661c64c440f0/ruff-0.17.3-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:e407b4a6852853a9a2cdce96963e94ad9775578bb44e7561a0f2829f3cc04b11", size = 10217786, upload-time = "2026-10-27T00:33:33.949Z" }, + { url = "https://files.pythonhosted.org/packages/a2/7e/537aba4b7f54397a374d2f6a6fc93e9c8015e18553f31a4a7b7dbced48b5/ruff-0.17.3-py3-none-macosx_11_0_arm64.whl", hash = "sha256:b75cc113b3aca388933d8296b667b1ac401a80317ea3074c9b0a66541199da29", size = 9440362, upload-time = "2026-10-27T00:33:35.784Z" }, + { url = "https://files.pythonhosted.org/packages/3d/66/283e39734f66c8a5ebaa93c22eb7b7515779a0b1d5a5c0ad20e05c0edb41/ruff-0.17.3-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ab5fa7ed5ce18554ea69d850ad162cbaa0c85fc47c08d83a569f806681c9a6ad", size = 10094281, upload-time = "2026-10-27T00:33:38.216Z" }, + { url = "https://files.pythonhosted.org/packages/21/4c/1ffc768497bd025a32c72152771e40c0b99bddc5cc6e4f50c7949a395346/ruff-0.17.3-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c673c170e27834d1262e3a9c1e97bdc9b9c3b180c63d89173ce69747232474c1", size = 10022543, upload-time = "2026-10-27T00:33:40.181Z" }, + { url = "https://files.pythonhosted.org/packages/73/c3/f90db547301080fcb82c92abf914c8bd57b89911d9b65a6c4cc6b52d0635/ruff-0.17.3-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6a754c0341bc9ca151b7b335988b3263921c08859f84ca219b70cee825571b6f", size = 10683921, upload-time = "2026-10-27T00:33:42.182Z" }, + { url = "https://files.pythonhosted.org/packages/e5/98/92d4a8a26c6c96cf1a5b34dd59036d8a71e52d650aab065c58867e964e63/ruff-0.17.3-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4590b9d86c0b1543ebb09094254e191cd43fe8edaf330baa035aab03da251532", size = 11373408, upload-time = "2026-10-27T00:33:44.19Z" }, + { url = "https://files.pythonhosted.org/packages/43/02/1f24d2e3064c9753926deeb901afa57777b37ee46799b09ddd7aee6e5145/ruff-0.17.3-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bbde18beccd98c29a227c63c99022e3443b99258e08326df6425dfe0443705ef", size = 10977469, upload-time = "2026-10-27T00:33:46.179Z" }, + { url = "https://files.pythonhosted.org/packages/f0/20/fecedea2209f574ab92033b344e01a7f0c2dc76c6d6d19a14c9f8141173f/ruff-0.17.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7fa4b0f931cdbcb8a52044f370a3eb429fb42eb88807b0e800fcaa98dd42b24e", size = 10577491, upload-time = "2026-10-27T00:33:48.233Z" }, + { url = "https://files.pythonhosted.org/packages/33/36/b93fdf0bc6f84f0cd487aadfb0354deb40013669ea35ac3d26855de1e959/ruff-0.17.3-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:39405f341deec934b4289634324ad695f72df2970f9121582872eaf5b722f0f5", size = 10608748, upload-time = "2026-10-27T00:33:50.284Z" }, + { url = "https://files.pythonhosted.org/packages/5d/b7/7d3f5f14145daead1efb0b9a8289ed1daa100f419d60774375951723ce00/ruff-0.17.3-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:68074094f55ca3d29ab19491f640282f7a84061c4c7977aa5094b968ca457e9a", size = 10075057, upload-time = "2026-10-27T00:33:52.074Z" }, + { url = "https://files.pythonhosted.org/packages/b0/ed/93ff7a5909ee56e2bd4b982deae9e0efb2724417874a90c9e41ad66c6dd7/ruff-0.17.3-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:27bb571ac78fef178fe5118d2827df95c2aae74a48fff2b15f6acd03af5ff5bb", size = 10022431, upload-time = "2026-10-27T00:33:54.061Z" }, + { url = "https://files.pythonhosted.org/packages/9b/07/a83b9eeec8c1c3c0e4eda15f06c3a91001bfd2687ea2f07b750719bf9712/ruff-0.17.3-py3-none-musllinux_1_2_i686.whl", hash = "sha256:029c37cbd3de48ca37aa12117bead2d065f15da17d66bf84e0d4274013705db2", size = 10394903, upload-time = "2026-10-27T00:33:56.158Z" }, + { url = "https://files.pythonhosted.org/packages/af/b2/05c39857c2526fcbebe46ad93a8dc97424656dfef14d3ef933abe1ffd331/ruff-0.17.3-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:692fdb46d0fd9c71e380947dc5fc72f2c3f881073bc1f739e4579376231affbe", size = 10799780, upload-time = "2026-10-27T00:33:58.204Z" }, + { url = "https://files.pythonhosted.org/packages/ec/d9/47171a16739eb7a353936b31c20cf54340531e68c9af7d72fd7a784e235d/ruff-0.17.3-py3-none-win32.whl", hash = "sha256:46539cb031b01fd29ae0a02142483c46640b233ee12bb1c1b690bffa1555d5ca", size = 9908481, upload-time = "2026-10-27T00:34:00.087Z" }, + { url = "https://files.pythonhosted.org/packages/d0/a0/f08c1631bfd81faa6a90eaf70c37c786c11bed2046302b56bb506c25e1a0/ruff-0.17.3-py3-none-win_amd64.whl", hash = "sha256:56b8ffb3f3b3cbccb8b76b71b38261b7ed982ef5d20f5b447b1b61a7f4949b24", size = 10341333, upload-time = "2026-10-27T00:34:02.099Z" }, + { url = "https://files.pythonhosted.org/packages/99/d5/4ad4ed31413c301430918e70d3d38ae6f097af0766cb9ab8c9d502b2a4d6/ruff-0.17.3-py3-none-win_arm64.whl", hash = "sha256:81fce74bcd6be0e9da7c67c7553fd0f1f12964a2052cd728ef917b937a5ca1d9", size = 9854332, upload-time = "2026-10-27T00:34:03.847Z" }, ] [[package]] name = "s3transfer" -version = "0.19.2" +version = "0.19.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "botocore" }, ] -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" } +sdist = { url = "https://files.pythonhosted.org/packages/49/c5/0a5ce655109cb96b8dfad36908a4e108d15f5dc2eff8fbc3146c4312e704/s3transfer-0.19.3.tar.gz", hash = "sha256:8326acc3d056d7f7b8a37110c7d2aea41f67e83f6ae79cd3b5fbcca34c726406", size = 166087, upload-time = "2026-10-19T19:19:49.751Z" } wheels = [ - { 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" }, + { url = "https://files.pythonhosted.org/packages/e2/a4/88f9e10498759121060b8050a1554753dfc04df39bbbcc7aad9ecc8d9440/s3transfer-0.19.3-py3-none-any.whl", hash = "sha256:24eb7535804306355e31c952a90022bf5b2f9d760f9a11062c7493a217c08553", size = 90252, upload-time = "2026-10-19T19:19:48.309Z" }, ] [[package]] name = "shapely" -version = "2.1.2" +version = "2.2.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, - { name = "numpy", version = "2.5.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/4d/bc/0989043118a27cccb4e906a46b7565ce36ca7b57f5a18b78f4f1b0f72d9d/shapely-2.1.2.tar.gz", hash = "sha256:2ed4ecb28320a433db18a5bf029986aa8afcfd740745e78847e330d5d94922a9", size = 315489, upload-time = "2025-09-24T13:51:41.432Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/8f/8d/1ff672dea9ec6a7b5d422eb6d095ed886e2e523733329f75fdcb14ee1149/shapely-2.1.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:91121757b0a36c9aac3427a651a7e6567110a4a67c97edf04f8d55d4765f6618", size = 1820038, upload-time = "2025-09-24T13:50:15.628Z" }, - { url = "https://files.pythonhosted.org/packages/4f/ce/28fab8c772ce5db23a0d86bf0adaee0c4c79d5ad1db766055fa3dab442e2/shapely-2.1.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:16a9c722ba774cf50b5d4541242b4cce05aafd44a015290c82ba8a16931ff63d", size = 1626039, upload-time = "2025-09-24T13:50:16.881Z" }, - { url = "https://files.pythonhosted.org/packages/70/8b/868b7e3f4982f5006e9395c1e12343c66a8155c0374fdc07c0e6a1ab547d/shapely-2.1.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cc4f7397459b12c0b196c9efe1f9d7e92463cbba142632b4cc6d8bbbbd3e2b09", size = 3001519, upload-time = "2025-09-24T13:50:18.606Z" }, - { url = "https://files.pythonhosted.org/packages/13/02/58b0b8d9c17c93ab6340edd8b7308c0c5a5b81f94ce65705819b7416dba5/shapely-2.1.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:136ab87b17e733e22f0961504d05e77e7be8c9b5a8184f685b4a91a84efe3c26", size = 3110842, upload-time = "2025-09-24T13:50:21.77Z" }, - { url = "https://files.pythonhosted.org/packages/af/61/8e389c97994d5f331dcffb25e2fa761aeedfb52b3ad9bcdd7b8671f4810a/shapely-2.1.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:16c5d0fc45d3aa0a69074979f4f1928ca2734fb2e0dde8af9611e134e46774e7", size = 4021316, upload-time = "2025-09-24T13:50:23.626Z" }, - { url = "https://files.pythonhosted.org/packages/d3/d4/9b2a9fe6039f9e42ccf2cb3e84f219fd8364b0c3b8e7bbc857b5fbe9c14c/shapely-2.1.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6ddc759f72b5b2b0f54a7e7cde44acef680a55019eb52ac63a7af2cf17cb9cd2", size = 4178586, upload-time = "2025-09-24T13:50:25.443Z" }, - { url = "https://files.pythonhosted.org/packages/16/f6/9840f6963ed4decf76b08fd6d7fed14f8779fb7a62cb45c5617fa8ac6eab/shapely-2.1.2-cp311-cp311-win32.whl", hash = "sha256:2fa78b49485391224755a856ed3b3bd91c8455f6121fee0db0e71cefb07d0ef6", size = 1543961, upload-time = "2025-09-24T13:50:26.968Z" }, - { url = "https://files.pythonhosted.org/packages/38/1e/3f8ea46353c2a33c1669eb7327f9665103aa3a8dfe7f2e4ef714c210b2c2/shapely-2.1.2-cp311-cp311-win_amd64.whl", hash = "sha256:c64d5c97b2f47e3cd9b712eaced3b061f2b71234b3fc263e0fcf7d889c6559dc", size = 1722856, upload-time = "2025-09-24T13:50:28.497Z" }, - { url = "https://files.pythonhosted.org/packages/24/c0/f3b6453cf2dfa99adc0ba6675f9aaff9e526d2224cbd7ff9c1a879238693/shapely-2.1.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fe2533caae6a91a543dec62e8360fe86ffcdc42a7c55f9dfd0128a977a896b94", size = 1833550, upload-time = "2025-09-24T13:50:30.019Z" }, - { url = "https://files.pythonhosted.org/packages/86/07/59dee0bc4b913b7ab59ab1086225baca5b8f19865e6101db9ebb7243e132/shapely-2.1.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ba4d1333cc0bc94381d6d4308d2e4e008e0bd128bdcff5573199742ee3634359", size = 1643556, upload-time = "2025-09-24T13:50:32.291Z" }, - { url = "https://files.pythonhosted.org/packages/26/29/a5397e75b435b9895cd53e165083faed5d12fd9626eadec15a83a2411f0f/shapely-2.1.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0bd308103340030feef6c111d3eb98d50dc13feea33affc8a6f9fa549e9458a3", size = 2988308, upload-time = "2025-09-24T13:50:33.862Z" }, - { url = "https://files.pythonhosted.org/packages/b9/37/e781683abac55dde9771e086b790e554811a71ed0b2b8a1e789b7430dd44/shapely-2.1.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1e7d4d7ad262a48bb44277ca12c7c78cb1b0f56b32c10734ec9a1d30c0b0c54b", size = 3099844, upload-time = "2025-09-24T13:50:35.459Z" }, - { url = "https://files.pythonhosted.org/packages/d8/f3/9876b64d4a5a321b9dc482c92bb6f061f2fa42131cba643c699f39317cb9/shapely-2.1.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e9eddfe513096a71896441a7c37db72da0687b34752c4e193577a145c71736fc", size = 3988842, upload-time = "2025-09-24T13:50:37.478Z" }, - { url = "https://files.pythonhosted.org/packages/d1/a0/704c7292f7014c7e74ec84eddb7b109e1fbae74a16deae9c1504b1d15565/shapely-2.1.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:980c777c612514c0cf99bc8a9de6d286f5e186dcaf9091252fcd444e5638193d", size = 4152714, upload-time = "2025-09-24T13:50:39.9Z" }, - { url = "https://files.pythonhosted.org/packages/53/46/319c9dc788884ad0785242543cdffac0e6530e4d0deb6c4862bc4143dcf3/shapely-2.1.2-cp312-cp312-win32.whl", hash = "sha256:9111274b88e4d7b54a95218e243282709b330ef52b7b86bc6aaf4f805306f454", size = 1542745, upload-time = "2025-09-24T13:50:41.414Z" }, - { url = "https://files.pythonhosted.org/packages/ec/bf/cb6c1c505cb31e818e900b9312d514f381fbfa5c4363edfce0fcc4f8c1a4/shapely-2.1.2-cp312-cp312-win_amd64.whl", hash = "sha256:743044b4cfb34f9a67205cee9279feaf60ba7d02e69febc2afc609047cb49179", size = 1722861, upload-time = "2025-09-24T13:50:43.35Z" }, - { url = "https://files.pythonhosted.org/packages/c3/90/98ef257c23c46425dc4d1d31005ad7c8d649fe423a38b917db02c30f1f5a/shapely-2.1.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b510dda1a3672d6879beb319bc7c5fd302c6c354584690973c838f46ec3e0fa8", size = 1832644, upload-time = "2025-09-24T13:50:44.886Z" }, - { url = "https://files.pythonhosted.org/packages/6d/ab/0bee5a830d209adcd3a01f2d4b70e587cdd9fd7380d5198c064091005af8/shapely-2.1.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8cff473e81017594d20ec55d86b54bc635544897e13a7cfc12e36909c5309a2a", size = 1642887, upload-time = "2025-09-24T13:50:46.735Z" }, - { url = "https://files.pythonhosted.org/packages/2d/5e/7d7f54ba960c13302584c73704d8c4d15404a51024631adb60b126a4ae88/shapely-2.1.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fe7b77dc63d707c09726b7908f575fc04ff1d1ad0f3fb92aec212396bc6cfe5e", size = 2970931, upload-time = "2025-09-24T13:50:48.374Z" }, - { url = "https://files.pythonhosted.org/packages/f2/a2/83fc37e2a58090e3d2ff79175a95493c664bcd0b653dd75cb9134645a4e5/shapely-2.1.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7ed1a5bbfb386ee8332713bf7508bc24e32d24b74fc9a7b9f8529a55db9f4ee6", size = 3082855, upload-time = "2025-09-24T13:50:50.037Z" }, - { url = "https://files.pythonhosted.org/packages/44/2b/578faf235a5b09f16b5f02833c53822294d7f21b242f8e2d0cf03fb64321/shapely-2.1.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a84e0582858d841d54355246ddfcbd1fce3179f185da7470f41ce39d001ee1af", size = 3979960, upload-time = "2025-09-24T13:50:51.74Z" }, - { url = "https://files.pythonhosted.org/packages/4d/04/167f096386120f692cc4ca02f75a17b961858997a95e67a3cb6a7bbd6b53/shapely-2.1.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:dc3487447a43d42adcdf52d7ac73804f2312cbfa5d433a7d2c506dcab0033dfd", size = 4142851, upload-time = "2025-09-24T13:50:53.49Z" }, - { url = "https://files.pythonhosted.org/packages/48/74/fb402c5a6235d1c65a97348b48cdedb75fb19eca2b1d66d04969fc1c6091/shapely-2.1.2-cp313-cp313-win32.whl", hash = "sha256:9c3a3c648aedc9f99c09263b39f2d8252f199cb3ac154fadc173283d7d111350", size = 1541890, upload-time = "2025-09-24T13:50:55.337Z" }, - { url = "https://files.pythonhosted.org/packages/41/47/3647fe7ad990af60ad98b889657a976042c9988c2807cf322a9d6685f462/shapely-2.1.2-cp313-cp313-win_amd64.whl", hash = "sha256:ca2591bff6645c216695bdf1614fca9c82ea1144d4a7591a466fef64f28f0715", size = 1722151, upload-time = "2025-09-24T13:50:57.153Z" }, - { url = "https://files.pythonhosted.org/packages/3c/49/63953754faa51ffe7d8189bfbe9ca34def29f8c0e34c67cbe2a2795f269d/shapely-2.1.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:2d93d23bdd2ed9dc157b46bc2f19b7da143ca8714464249bef6771c679d5ff40", size = 1834130, upload-time = "2025-09-24T13:50:58.49Z" }, - { url = "https://files.pythonhosted.org/packages/7f/ee/dce001c1984052970ff60eb4727164892fb2d08052c575042a47f5a9e88f/shapely-2.1.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:01d0d304b25634d60bd7cf291828119ab55a3bab87dc4af1e44b07fb225f188b", size = 1642802, upload-time = "2025-09-24T13:50:59.871Z" }, - { url = "https://files.pythonhosted.org/packages/da/e7/fc4e9a19929522877fa602f705706b96e78376afb7fad09cad5b9af1553c/shapely-2.1.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8d8382dd120d64b03698b7298b89611a6ea6f55ada9d39942838b79c9bc89801", size = 3018460, upload-time = "2025-09-24T13:51:02.08Z" }, - { url = "https://files.pythonhosted.org/packages/a1/18/7519a25db21847b525696883ddc8e6a0ecaa36159ea88e0fef11466384d0/shapely-2.1.2-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:19efa3611eef966e776183e338b2d7ea43569ae99ab34f8d17c2c054d3205cc0", size = 3095223, upload-time = "2025-09-24T13:51:04.472Z" }, - { url = "https://files.pythonhosted.org/packages/48/de/b59a620b1f3a129c3fecc2737104a0a7e04e79335bd3b0a1f1609744cf17/shapely-2.1.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:346ec0c1a0fcd32f57f00e4134d1200e14bf3f5ae12af87ba83ca275c502498c", size = 4030760, upload-time = "2025-09-24T13:51:06.455Z" }, - { url = "https://files.pythonhosted.org/packages/96/b3/c6655ee7232b417562bae192ae0d3ceaadb1cc0ffc2088a2ddf415456cc2/shapely-2.1.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6305993a35989391bd3476ee538a5c9a845861462327efe00dd11a5c8c709a99", size = 4170078, upload-time = "2025-09-24T13:51:08.584Z" }, - { url = "https://files.pythonhosted.org/packages/a0/8e/605c76808d73503c9333af8f6cbe7e1354d2d238bda5f88eea36bfe0f42a/shapely-2.1.2-cp313-cp313t-win32.whl", hash = "sha256:c8876673449f3401f278c86eb33224c5764582f72b653a415d0e6672fde887bf", size = 1559178, upload-time = "2025-09-24T13:51:10.73Z" }, - { url = "https://files.pythonhosted.org/packages/36/f7/d317eb232352a1f1444d11002d477e54514a4a6045536d49d0c59783c0da/shapely-2.1.2-cp313-cp313t-win_amd64.whl", hash = "sha256:4a44bc62a10d84c11a7a3d7c1c4fe857f7477c3506e24c9062da0db0ae0c449c", size = 1739756, upload-time = "2025-09-24T13:51:12.105Z" }, - { url = "https://files.pythonhosted.org/packages/fc/c4/3ce4c2d9b6aabd27d26ec988f08cb877ba9e6e96086eff81bfea93e688c7/shapely-2.1.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:9a522f460d28e2bf4e12396240a5fc1518788b2fcd73535166d748399ef0c223", size = 1831290, upload-time = "2025-09-24T13:51:13.56Z" }, - { url = "https://files.pythonhosted.org/packages/17/b9/f6ab8918fc15429f79cb04afa9f9913546212d7fb5e5196132a2af46676b/shapely-2.1.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1ff629e00818033b8d71139565527ced7d776c269a49bd78c9df84e8f852190c", size = 1641463, upload-time = "2025-09-24T13:51:14.972Z" }, - { url = "https://files.pythonhosted.org/packages/a5/57/91d59ae525ca641e7ac5551c04c9503aee6f29b92b392f31790fcb1a4358/shapely-2.1.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f67b34271dedc3c653eba4e3d7111aa421d5be9b4c4c7d38d30907f796cb30df", size = 2970145, upload-time = "2025-09-24T13:51:16.961Z" }, - { url = "https://files.pythonhosted.org/packages/8a/cb/4948be52ee1da6927831ab59e10d4c29baa2a714f599f1f0d1bc747f5777/shapely-2.1.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:21952dc00df38a2c28375659b07a3979d22641aeb104751e769c3ee825aadecf", size = 3073806, upload-time = "2025-09-24T13:51:18.712Z" }, - { url = "https://files.pythonhosted.org/packages/03/83/f768a54af775eb41ef2e7bec8a0a0dbe7d2431c3e78c0a8bdba7ab17e446/shapely-2.1.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:1f2f33f486777456586948e333a56ae21f35ae273be99255a191f5c1fa302eb4", size = 3980803, upload-time = "2025-09-24T13:51:20.37Z" }, - { url = "https://files.pythonhosted.org/packages/9f/cb/559c7c195807c91c79d38a1f6901384a2878a76fbdf3f1048893a9b7534d/shapely-2.1.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:cf831a13e0d5a7eb519e96f58ec26e049b1fad411fc6fc23b162a7ce04d9cffc", size = 4133301, upload-time = "2025-09-24T13:51:21.887Z" }, - { url = "https://files.pythonhosted.org/packages/80/cd/60d5ae203241c53ef3abd2ef27c6800e21afd6c94e39db5315ea0cbafb4a/shapely-2.1.2-cp314-cp314-win32.whl", hash = "sha256:61edcd8d0d17dd99075d320a1dd39c0cb9616f7572f10ef91b4b5b00c4aeb566", size = 1583247, upload-time = "2025-09-24T13:51:23.401Z" }, - { url = "https://files.pythonhosted.org/packages/74/d4/135684f342e909330e50d31d441ace06bf83c7dc0777e11043f99167b123/shapely-2.1.2-cp314-cp314-win_amd64.whl", hash = "sha256:a444e7afccdb0999e203b976adb37ea633725333e5b119ad40b1ca291ecf311c", size = 1773019, upload-time = "2025-09-24T13:51:24.873Z" }, - { url = "https://files.pythonhosted.org/packages/a3/05/a44f3f9f695fa3ada22786dc9da33c933da1cbc4bfe876fe3a100bafe263/shapely-2.1.2-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:5ebe3f84c6112ad3d4632b1fd2290665aa75d4cef5f6c5d77c4c95b324527c6a", size = 1834137, upload-time = "2025-09-24T13:51:26.665Z" }, - { url = "https://files.pythonhosted.org/packages/52/7e/4d57db45bf314573427b0a70dfca15d912d108e6023f623947fa69f39b72/shapely-2.1.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5860eb9f00a1d49ebb14e881f5caf6c2cf472c7fd38bd7f253bbd34f934eb076", size = 1642884, upload-time = "2025-09-24T13:51:28.029Z" }, - { url = "https://files.pythonhosted.org/packages/5a/27/4e29c0a55d6d14ad7422bf86995d7ff3f54af0eba59617eb95caf84b9680/shapely-2.1.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b705c99c76695702656327b819c9660768ec33f5ce01fa32b2af62b56ba400a1", size = 3018320, upload-time = "2025-09-24T13:51:29.903Z" }, - { url = "https://files.pythonhosted.org/packages/9f/bb/992e6a3c463f4d29d4cd6ab8963b75b1b1040199edbd72beada4af46bde5/shapely-2.1.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a1fd0ea855b2cf7c9cddaf25543e914dd75af9de08785f20ca3085f2c9ca60b0", size = 3094931, upload-time = "2025-09-24T13:51:32.699Z" }, - { url = "https://files.pythonhosted.org/packages/9c/16/82e65e21070e473f0ed6451224ed9fa0be85033d17e0c6e7213a12f59d12/shapely-2.1.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:df90e2db118c3671a0754f38e36802db75fe0920d211a27481daf50a711fdf26", size = 4030406, upload-time = "2025-09-24T13:51:34.189Z" }, - { url = "https://files.pythonhosted.org/packages/7c/75/c24ed871c576d7e2b64b04b1fe3d075157f6eb54e59670d3f5ffb36e25c7/shapely-2.1.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:361b6d45030b4ac64ddd0a26046906c8202eb60d0f9f53085f5179f1d23021a0", size = 4169511, upload-time = "2025-09-24T13:51:36.297Z" }, - { url = "https://files.pythonhosted.org/packages/b1/f7/b3d1d6d18ebf55236eec1c681ce5e665742aab3c0b7b232720a7d43df7b6/shapely-2.1.2-cp314-cp314t-win32.whl", hash = "sha256:b54df60f1fbdecc8ebc2c5b11870461a6417b3d617f555e5033f1505d36e5735", size = 1602607, upload-time = "2025-09-24T13:51:37.757Z" }, - { url = "https://files.pythonhosted.org/packages/9a/f6/f09272a71976dfc138129b8faf435d064a811ae2f708cb147dccdf7aacdb/shapely-2.1.2-cp314-cp314t-win_amd64.whl", hash = "sha256:0036ac886e0923417932c2e6369b6c52e38e0ff5d9120b90eef5cd9a5fc5cae9", size = 1796682, upload-time = "2025-09-24T13:51:39.233Z" }, + { name = "numpy", version = "2.5.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f3/ab/924b6e202f796d270a3041a230151f7908db5ea48c74effe6f8023e9bd05/shapely-2.2.0.tar.gz", hash = "sha256:e8865e553d874a1ec4a032057ea81fca9def37b188cd8fb550af3b3480b3f88c", size = 380326, upload-time = "2026-10-07T09:18:01.001Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a5/43/11bfb01afed47a77540dc4075c7c9ad1ba6323a698fcdb01795df62f55b2/shapely-2.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:596b7994ceafa526b6e0522ca29fbc41d19f86459161d6efe1f251d0acd49f3f", size = 1786155, upload-time = "2026-10-07T09:15:56.158Z" }, + { url = "https://files.pythonhosted.org/packages/55/23/e3502c9312e0cd0dcee9e382ad90d0d3039144015b22de87084be20fffec/shapely-2.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7c0b262116bb75b86751440b42e19673911bc0a8f0d5ce723ce294c3d6e4d5c0", size = 1582454, upload-time = "2026-10-07T09:15:58.042Z" }, + { url = "https://files.pythonhosted.org/packages/11/97/dd0ce3bcc03b317298dec0f66e8b404e50cdb126dcb0a27f1c4801371ba5/shapely-2.2.0-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7765e0e5d51d63eae0a911861cbda87165a01677bc9bce6ed20d06858ccde99f", size = 2184352, upload-time = "2026-10-07T09:15:59.87Z" }, + { url = "https://files.pythonhosted.org/packages/dc/20/56289b91914b62a0849de3c1efe5f0cfd5b5d7ba168568567900087615be/shapely-2.2.0-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5d61088e2ef71dafad0dd4fae8a521cc1f20da4a89d3096bab5b3260b39b3052", size = 2307874, upload-time = "2026-10-07T09:16:01.748Z" }, + { url = "https://files.pythonhosted.org/packages/a4/a8/95a81840efdfc49902cdfceb91e0972563489acda5e92d93bce7fdabea5d/shapely-2.2.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0edec813c81effaf4e20c18b1aa86827925ce27c0315621f2a1a080e22e0de5e", size = 3251822, upload-time = "2026-10-07T09:16:04.238Z" }, + { url = "https://files.pythonhosted.org/packages/ab/f1/23aa57dd931a361d7a77512a32fbaedd2eb62ede201192c2ff391bf1d28a/shapely-2.2.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:8d6ffe94710f37535a47161120cd5f7f0f0d9bb800c2fddebbd089cb7f1b3453", size = 3397542, upload-time = "2026-10-07T09:16:06.52Z" }, + { url = "https://files.pythonhosted.org/packages/9a/8b/b812edc45ab4096d4dd3696e614fbe44207c916da16f26be3fe2305ed4e4/shapely-2.2.0-cp311-cp311-win32.whl", hash = "sha256:ce858295be3947143a3f44f145fa6dbacd5dcc5c4103801d42cd3be4a2034614", size = 1618880, upload-time = "2026-10-07T09:16:08.56Z" }, + { url = "https://files.pythonhosted.org/packages/1f/bc/1b4246004806869c1d420c0a53e97244c9b538a7175e10f5e30012bbeab7/shapely-2.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:806d399418b23eee7241736d572ad1e0b784782f9241d7c8e2cfceb00787831d", size = 1795293, upload-time = "2026-10-07T09:16:10.332Z" }, + { url = "https://files.pythonhosted.org/packages/e7/8b/aa11e4f696fbcda4e06f1a49c95fabe82f28a93265d1b9d0cc2c596f03df/shapely-2.2.0-cp311-cp311-win_arm64.whl", hash = "sha256:5b740c9a197e5feb30bdc6e64a5eb3ca2a7324d11498844136dfc317daac6a99", size = 1880736, upload-time = "2026-10-07T09:16:11.949Z" }, + { url = "https://files.pythonhosted.org/packages/63/af/ac371511dbf0f0a172544a647246f746ceb2b5d12b3e1238224bec3a3796/shapely-2.2.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:626fe4c0d32860a98e75ecffabf5a62254c6168eac96b633ad313cd62a38bb2b", size = 1785072, upload-time = "2026-10-07T09:16:13.707Z" }, + { url = "https://files.pythonhosted.org/packages/02/96/5c48977168f32de067bfafce7f584dd04becf152152bf088636cc034828e/shapely-2.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c36ccbff5c3374c349c370bfdac22c7676b268b4a707c98e9031f498965aa02d", size = 1584868, upload-time = "2026-10-07T09:16:15.795Z" }, + { url = "https://files.pythonhosted.org/packages/ae/34/b90723043091161f636fde302e850583dcebd610e798f9edd6e3245f5a2a/shapely-2.2.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a9a380624cdd7a7e661bf15a4d1625082766f07ccd2540cb0a9e0df1ad4f6c11", size = 2176960, upload-time = "2026-10-07T09:16:17.965Z" }, + { url = "https://files.pythonhosted.org/packages/ad/87/6842e4c996914a47b6bfd3ef14a543e67e993f86a9ea5679c34efc9314ab/shapely-2.2.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:650a5f4d8a8e3c96982079d8c99b6ddbe6602bbd1e34c75c2b95dbc0d28ac997", size = 2300970, upload-time = "2026-10-07T09:16:20.191Z" }, + { url = "https://files.pythonhosted.org/packages/54/ea/06295d871f0befc3eafa96b7bb87a31e848f042d03ba80c5938b12eb68dd/shapely-2.2.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a851e077f0f02a3383923e02eca5447a29ddbf234e39593b91c8b7ac75218133", size = 3244534, upload-time = "2026-10-07T09:16:22.36Z" }, + { url = "https://files.pythonhosted.org/packages/8b/2a/ab017941b2014f29b8fca233e3f5a75e5fe109a1f27d316ba85d95a515ff/shapely-2.2.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dc5faa593948aa64d9afae48331b80f43f7aacc68425d99064a4d6772f53f1ad", size = 3391336, upload-time = "2026-10-07T09:16:24.226Z" }, + { url = "https://files.pythonhosted.org/packages/63/ee/4ccaa854f3b7ecd9181920b913c2d2f4b15053331457b7f615139a9774cc/shapely-2.2.0-cp312-cp312-win32.whl", hash = "sha256:da47a0cc9e630b4dff0db46e8972b29d2d27f337425ce9d4c77fd046ce48eabd", size = 1617174, upload-time = "2026-10-07T09:16:26.277Z" }, + { url = "https://files.pythonhosted.org/packages/f6/26/ba9192f0a72c830aa8cc1c61e207ada57cc54b7de81668b1836f575ee717/shapely-2.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:90895df6542ae039fc6557dec6194e3509e883fbd6f5788e3c3e7a38fe46b257", size = 1791280, upload-time = "2026-10-07T09:16:27.94Z" }, + { url = "https://files.pythonhosted.org/packages/8e/92/4e4f93d7b7db9af2a77126c6c96af2c0c422635e2276f5abb533f67b42df/shapely-2.2.0-cp312-cp312-win_arm64.whl", hash = "sha256:7cf5b3a801b9b4febf774efde2e31280e647388deae8452693d8e6420b3a1ff2", size = 1879393, upload-time = "2026-10-07T09:16:29.684Z" }, + { url = "https://files.pythonhosted.org/packages/28/b6/9ba2a62ab6e831b911a248f0752f0f4120be7637a33ab33d8649ed4ede4d/shapely-2.2.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c037369c35510f51100dd6d386ee3203bac32f164d53e27ca12c3cea5bb643b1", size = 1785102, upload-time = "2026-10-07T09:16:31.662Z" }, + { url = "https://files.pythonhosted.org/packages/e9/8a/d7c11c2d1beef99a4df4183b255ea2d8669f3bcf7d049bb17fe56bf7cd72/shapely-2.2.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d75957716368f919c63016dae1977a0d007e15f06861cd178701edb91b08d2b0", size = 1584542, upload-time = "2026-10-07T09:16:33.413Z" }, + { url = "https://files.pythonhosted.org/packages/f0/bd/21ed8bfd340455ede2df0d25d896acf4e4bab2ed9b582bb67389e97b2250/shapely-2.2.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4ed79beb8d4b6cc7c67780fd381feed25848a5f9b8a2385ac5711eccd115647a", size = 2175369, upload-time = "2026-10-07T09:16:35.507Z" }, + { url = "https://files.pythonhosted.org/packages/5d/df/d67d5c56efddf9b8c2e6913c917c8eca78fdd9e7c6fb73b541dd56b1ce1d/shapely-2.2.0-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f340e7f99aaee3df5acd6b247cddf723051a7c93d1e1ef09025b80d84e4c0ded", size = 2299018, upload-time = "2026-10-07T09:16:37.246Z" }, + { url = "https://files.pythonhosted.org/packages/58/dd/6e2b5ac83edb4afb540925092feee393a15970216e711a8b211f5abe4478/shapely-2.2.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:17434cb9819c9974c3331333a3b878fa5bf8f85dd69cc3fb7ff5d260f6fbc102", size = 3243663, upload-time = "2026-10-07T09:16:39.093Z" }, + { url = "https://files.pythonhosted.org/packages/0c/dc/7c0461549c212b0d663f99383fe846eb082f4b06cd1fba3bb786b9927d22/shapely-2.2.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b2338ac40e6652c8bfb857936ea9be9a16f43a362c6f67eb3bad741b05fd5683", size = 3388136, upload-time = "2026-10-07T09:16:41.287Z" }, + { url = "https://files.pythonhosted.org/packages/1c/58/ac8f7de528c125ab41a001523ada72e95e2d5f746917487e723b25e1c5c4/shapely-2.2.0-cp313-cp313-win32.whl", hash = "sha256:40871d7135cd723f965d200181aa28418e9ec029fd85bdd010488259d1c01906", size = 1616372, upload-time = "2026-10-07T09:16:43.094Z" }, + { url = "https://files.pythonhosted.org/packages/25/ed/7fcd625c9796e61d815ca9545d4e44a16f075f83869c1532206de88f23f1/shapely-2.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:1eaa2cb64cdedaf65d6bc86f2819c9cd7d6d68f969aa3ebfdc93743ab581f437", size = 1790191, upload-time = "2026-10-07T09:16:44.852Z" }, + { url = "https://files.pythonhosted.org/packages/23/c9/947fcd5665e1945dd54f6e8890bc6dd04613dd169a5fbb4ba7497754b3a8/shapely-2.2.0-cp313-cp313-win_arm64.whl", hash = "sha256:f79b3b34ad2d067207f21f821489c720b14ce40f3bfda931987a193165f80133", size = 1878351, upload-time = "2026-10-07T09:16:46.656Z" }, + { url = "https://files.pythonhosted.org/packages/eb/a9/83531b7a5349568c507c5701179b1727e21f238af318ac55ba8d0800e764/shapely-2.2.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:000c0ce2a3ba49427e6288b7add9de5d8525d4e65d6ebc8840103040d4d57b86", size = 1785489, upload-time = "2026-10-07T09:16:48.795Z" }, + { url = "https://files.pythonhosted.org/packages/a2/c8/e8117528eb96feafcd5fced50a939ecfdc6242b3782959d202755536bb9e/shapely-2.2.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0a63e6b68ec785ef3aae3935c4aa9fb8edccced94e23c79d5d85276442c60859", size = 1585345, upload-time = "2026-10-07T09:16:50.527Z" }, + { url = "https://files.pythonhosted.org/packages/53/66/289a7055e3a383680771ba59764712db822fa406bbf52971a76e51d160d6/shapely-2.2.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:770d4db5cf0bfeed931a1c4aaf4f4eadad0f43f5fc72c27c88fe1f07904ae767", size = 2177801, upload-time = "2026-10-07T09:16:52.393Z" }, + { url = "https://files.pythonhosted.org/packages/d2/54/8f3d60050a703dcab48f7991ec4fb111772731d20ce6a1bf649465033476/shapely-2.2.0-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:74f4313af38d6e49ea83532d6cedfb4fe5e6c5485d7c40202bd61b19d6ff09bf", size = 2299593, upload-time = "2026-10-07T09:16:54.462Z" }, + { url = "https://files.pythonhosted.org/packages/cf/ec/3389afd3919494f479347a83db7b5672c3c73a339173426a902d9d295152/shapely-2.2.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:9ee11aeba1759d15a525ded58e17916d3edfa60d52110fd8df6a7609a871f066", size = 3246048, upload-time = "2026-10-07T09:16:56.477Z" }, + { url = "https://files.pythonhosted.org/packages/6f/b5/d0d4e3eaf232425a11be7af1a24aaf9c6792bc7a17dd17d722e42892f91a/shapely-2.2.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:24b175c570efc91d1180ac6cd527dc80e863bb7de37f8b2771703d822c65e023", size = 3389241, upload-time = "2026-10-07T09:16:59.055Z" }, + { url = "https://files.pythonhosted.org/packages/3a/3d/b9626c58982a3cf4278ad978644c7a315968c7e03cd7a8a6aaadde911074/shapely-2.2.0-cp314-cp314-win32.whl", hash = "sha256:4e5830637c080bdc646c5982ad6f7cc296b93038879649f7a6acd8e0f1c4db04", size = 1658287, upload-time = "2026-10-07T09:17:00.857Z" }, + { url = "https://files.pythonhosted.org/packages/0a/c1/b3acc1c764dff7e47485dd47fc7ff5fdc230257f02006fec049bf2b9449c/shapely-2.2.0-cp314-cp314-win_amd64.whl", hash = "sha256:48dd1d961391f314ab7fa8812c86ca2a727bee2bdca1478730eacaea007da18e", size = 1843568, upload-time = "2026-10-07T09:17:02.662Z" }, + { url = "https://files.pythonhosted.org/packages/53/12/3b4977cec6bbaee5d4538d8fbd8ffc75bf764fe3519aafb09d5eefb41daa/shapely-2.2.0-cp314-cp314-win_arm64.whl", hash = "sha256:c4127c064bc71f8b7f9b3f341d6627ed39977fd0b61a17c68d09179f5e0089ae", size = 1936825, upload-time = "2026-10-07T09:17:04.886Z" }, + { url = "https://files.pythonhosted.org/packages/cb/0c/8a8f59e344dc3b53c77d99e35e3eb81b8c46cceeb3ffcd44b41ed8d17d7e/shapely-2.2.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:c2915ae1b858e73d5832be7fb5e89497cc5140fa505da40a45223029dc6deace", size = 1793948, upload-time = "2026-10-07T09:17:07.07Z" }, + { url = "https://files.pythonhosted.org/packages/af/1e/76728b192507909866d7eaa398c9a558d326ca9a7dd14bc929362cce99c9/shapely-2.2.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:74028f468e05e461b30a479b08c1fb5094fa45062abeeec8e7905a6711761436", size = 1596025, upload-time = "2026-10-07T09:17:09.141Z" }, + { url = "https://files.pythonhosted.org/packages/11/be/e4b4219ab6414fe17f60d064693f31d5acd25bd8c98def75c4c2a1108b18/shapely-2.2.0-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6ec5178a39803fa8626322f69d298037f182461dd28e3ae96c2c7a4309a6bf30", size = 2177914, upload-time = "2026-10-07T09:17:11.011Z" }, + { url = "https://files.pythonhosted.org/packages/f4/36/c007a564ddfeda1aff3c79435d4beb2c0baf41b37d1ec212269ef62f8da8/shapely-2.2.0-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:593e51cd04fe1122f1ab3fae87b306c36b2be0184a5e0d9c26849c55ff4580dc", size = 2297494, upload-time = "2026-10-07T09:17:13.089Z" }, + { url = "https://files.pythonhosted.org/packages/cf/74/dd289ba822b8c50a2b47dc70f87a6f4933a6402f5fe4cc5cb999fdf1ea4a/shapely-2.2.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:3575a323b7665d7a2e391b16a626caa6b6f6348f399183aca3fc656febd7cf04", size = 3245678, upload-time = "2026-10-07T09:17:15.165Z" }, + { url = "https://files.pythonhosted.org/packages/04/d8/bd58de9c4f325369bbc7edc4f7cce1a56cfab61e2b1c534176ea58d89db4/shapely-2.2.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:776cc8571d53e42be8fa6d42ad52a599b8e2186dd0c752922831508099af71e2", size = 3387773, upload-time = "2026-10-07T09:17:17.685Z" }, + { url = "https://files.pythonhosted.org/packages/69/4a/6d6e41cab51bb8aa1256ddc28d94d74d016683312e6e0e874afc8b874f3b/shapely-2.2.0-cp314-cp314t-win32.whl", hash = "sha256:f8cd733a66a2a10f461a70dde9fad7b2b62c6a48c7a66cea57ee6f1cd9f2bd2f", size = 1676626, upload-time = "2026-10-07T09:17:19.523Z" }, + { url = "https://files.pythonhosted.org/packages/22/06/6ab21f86fc08aa95b6eb3dc8f8d64701359ce89aae471c15b896e5be5afe/shapely-2.2.0-cp314-cp314t-win_amd64.whl", hash = "sha256:7f68c1fbacab81c0c066d1c3051eeb0f680b7a7a2c511e741f77741640187896", size = 1868388, upload-time = "2026-10-07T09:17:21.376Z" }, + { url = "https://files.pythonhosted.org/packages/07/85/5c0452ee08cfd72b8945ac26fbd5ae559a7af7184aa989a0d83f68f07cf9/shapely-2.2.0-cp314-cp314t-win_arm64.whl", hash = "sha256:9147ebc3b116a0511dca043937f85caf1a41690815643d5b89c8bc472f51c850", size = 1946897, upload-time = "2026-10-07T09:17:23.413Z" }, + { url = "https://files.pythonhosted.org/packages/1d/d0/c994c26df87119e530b715f7109960036242861dba37db17a1b9f44b6e56/shapely-2.2.0-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:715561ceda03b09ca1c6baf9922179392d8c2bc53a1b877965225f0dfb487a58", size = 1785017, upload-time = "2026-10-07T09:17:25.31Z" }, + { url = "https://files.pythonhosted.org/packages/0b/60/2a8975ee00697cb33b17e140def38f2600323760e52eaa6423183a522f06/shapely-2.2.0-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:556f20346a7d96fefbb71b74640d84ca14041703d60f0d2ff47b29d9b3e0093d", size = 1585001, upload-time = "2026-10-07T09:17:27.622Z" }, + { url = "https://files.pythonhosted.org/packages/26/07/45cd192ede49dd821c804fd53177ba5fa2739867ceeb542cfeb259ca4314/shapely-2.2.0-cp315-cp315-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ff9e87b534edf35af65758fafb31ad3b797354cba9323899e263f450c69a2ff2", size = 2177659, upload-time = "2026-10-07T09:17:29.501Z" }, + { url = "https://files.pythonhosted.org/packages/f1/7f/55a7f6ae91c10aa58005e985d01756048b6e4ff82e731ea39003e1eeda3e/shapely-2.2.0-cp315-cp315-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fdb599ec540cea5b635ac47bf24fca4cdfd1c39730ffc0b6cf0d2666b0dd9a33", size = 2299128, upload-time = "2026-10-07T09:17:31.352Z" }, + { url = "https://files.pythonhosted.org/packages/58/2f/49eb352f7c0c0c6ec17bc0bee33f9f449d397f8bfc9c2694c384e752f25e/shapely-2.2.0-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:b8cb04906b74db26f848f76744fa995cd6abeae9145d27cc405277de1f949660", size = 3245354, upload-time = "2026-10-07T09:17:33.291Z" }, + { url = "https://files.pythonhosted.org/packages/1a/c6/3f4f736d615013b2c117cec4d716e775659b2452bb039643c0411d131750/shapely-2.2.0-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:d9b11d712ac72f1d869f2b6964dea5bd9f20b89901adcd796d6712496144ab22", size = 3388587, upload-time = "2026-10-07T09:17:35.261Z" }, + { url = "https://files.pythonhosted.org/packages/27/ea/cb26677d3e34e1663a00a1395fc17c4a2acb5fef638297f94d5cdb9a63f0/shapely-2.2.0-cp315-cp315-win32.whl", hash = "sha256:1af6935acde1db0b6a1bcbea30cbad5ae900723dfd398367ae1488470dc53667", size = 1658324, upload-time = "2026-10-07T09:17:37.362Z" }, + { url = "https://files.pythonhosted.org/packages/14/7d/351c43d812b94197fe279dc3e0defc6886e4be5a144fc191b8635b0fd839/shapely-2.2.0-cp315-cp315-win_amd64.whl", hash = "sha256:96e5101ad2d73df869255bae4c55537f372d32066e2328c376e09841f0f66800", size = 1843506, upload-time = "2026-10-07T09:17:39.336Z" }, + { url = "https://files.pythonhosted.org/packages/82/de/9b62659a23fe8b9d590cf8e4698051d8eab5c5cdddfd86c531019e9d0d2d/shapely-2.2.0-cp315-cp315-win_arm64.whl", hash = "sha256:446b2d5a323bddd1c2a27f41325fdb3a3e8e33c1f8f0f840bdb63e8c1515b29e", size = 1936521, upload-time = "2026-10-07T09:17:41.121Z" }, + { url = "https://files.pythonhosted.org/packages/91/c9/5e16b2ac8853ec587406496a85cbeb1f3465b53c5e8bf0f222b4a39a44a1/shapely-2.2.0-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:c88b21a0e9599ebb741e08f71a95c8f07a434af909efb088828a9874d234d06d", size = 1792605, upload-time = "2026-10-07T09:17:43.211Z" }, + { url = "https://files.pythonhosted.org/packages/19/87/ebaf70f25565ed82d75ab84b1d0eb3a8b803302020c16bb98234f67c0477/shapely-2.2.0-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:cbe184e1946cfe115a9dfeadd2effd88ab4a237ab1a4335d106defa80fbc2d82", size = 1594776, upload-time = "2026-10-07T09:17:44.972Z" }, + { url = "https://files.pythonhosted.org/packages/e6/a1/e6210ff8aa7d065c2a94d2a3486342675bcb3f4d740ec686a414ace0c3b9/shapely-2.2.0-cp315-cp315t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8bc985ad731da2f2cedde9c3cfb3c3d946fe6fc63d2ca557673dc33dd1e389b9", size = 2175890, upload-time = "2026-10-07T09:17:46.92Z" }, + { url = "https://files.pythonhosted.org/packages/96/19/4df2a474cdc24beb06ef557d433dbe936fa274600d4846ea19877ae47094/shapely-2.2.0-cp315-cp315t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c3caa4c6308e7eaf18f4661134a1575eb290a56df78d0ae1b02f919a4cc7bd9d", size = 2296432, upload-time = "2026-10-07T09:17:48.895Z" }, + { url = "https://files.pythonhosted.org/packages/c5/29/2b38bbe8b9b2dba0838b7718819e8751490e3d946cff232049d0e707f98e/shapely-2.2.0-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:2fd87e55d7a7d310553b527378545cdc6ef8702473ed9294926b892c3cfb2ba0", size = 3243397, upload-time = "2026-10-07T09:17:50.885Z" }, + { url = "https://files.pythonhosted.org/packages/e8/1c/5430d8d6559c944ac673984f25989121abfd2bac4254ec3fff1d7673b7bc/shapely-2.2.0-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:7416db8ff3a1003687d4118e741343b3cf9ac2a4a925a59d44d98a865ac4e9e7", size = 3386299, upload-time = "2026-10-07T09:17:52.969Z" }, + { url = "https://files.pythonhosted.org/packages/5b/00/feaeb392e96063717387ec09e6c8e38b29fe4088ddfe976470255c69792e/shapely-2.2.0-cp315-cp315t-win32.whl", hash = "sha256:778421a19085bef1fb38bc0699db1ee9b08fdd0e30a8768788d601a4371f2de0", size = 1675823, upload-time = "2026-10-07T09:17:55.022Z" }, + { url = "https://files.pythonhosted.org/packages/0e/30/0b77618f33fecbc2209c767cecca58bbf83947fc0018571542bf2b859865/shapely-2.2.0-cp315-cp315t-win_amd64.whl", hash = "sha256:287ec7602f7a114b862ae0123880e57160cebe059843a4c7028aaee9e74287f6", size = 1866197, upload-time = "2026-10-07T09:17:56.991Z" }, + { url = "https://files.pythonhosted.org/packages/06/2b/9837e94408335520f778b09067fced0a5d4b2feffa5ebf7119412eb18b00/shapely-2.2.0-cp315-cp315t-win_arm64.whl", hash = "sha256:e414c78bc81aadd76a429111a350f4ef3d05fc13019805617b524951258468e5", size = 1945749, upload-time = "2026-10-07T09:17:59.116Z" }, ] [[package]] @@ -2750,11 +2831,11 @@ wheels = [ [[package]] name = "tenacity" -version = "9.1.4" +version = "9.2.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/47/c6/ee486fd809e357697ee8a44d3d69222b344920433d3b6666ccd9b374630c/tenacity-9.1.4.tar.gz", hash = "sha256:adb31d4c263f2bd041081ab33b498309a57c77f9acf2db65aadf0898179cf93a", size = 49413, upload-time = "2026-02-07T10:45:33.841Z" } +sdist = { url = "https://files.pythonhosted.org/packages/82/9e/497c1c8ebe5a5b5d1d4a7511aea22c0bb1a97e3170d98abdef0e1b34265a/tenacity-9.2.1.tar.gz", hash = "sha256:a606b5c808d0cded4a359d5b9932d867ff2a6a6b64d37350260fd01bbdf83839", size = 58261, upload-time = "2026-10-07T12:13:01.633Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d7/c1/eb8f9debc45d3b7918a32ab756658a0904732f75e555402972246b0b8e71/tenacity-9.1.4-py3-none-any.whl", hash = "sha256:6095a360c919085f28c6527de529e76a06ad89b23659fa881ae0649b867a9d55", size = 28926, upload-time = "2026-02-07T10:45:32.24Z" }, + { url = "https://files.pythonhosted.org/packages/d6/26/1ff2b0721ac66a3ec5b1402b333110b352ab0a8724052ac279a7b82d40c4/tenacity-9.2.1-py3-none-any.whl", hash = "sha256:9e56f17539296baab7beabb08b92f6ee3d7be92d8be72d763360677c2ad6580e", size = 32310, upload-time = "2026-10-07T12:13:00.102Z" }, ] [[package]] @@ -2795,7 +2876,7 @@ source = { editable = "tilebox-datasets" } dependencies = [ { name = "lz4" }, { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, - { name = "numpy", version = "2.5.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "numpy", version = "2.5.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, { name = "pandas" }, { name = "shapely" }, { name = "tilebox-core" }, @@ -3007,60 +3088,6 @@ dev = [ { name = "tilebox-datasets", editable = "tilebox-datasets" }, ] -[[package]] -name = "tomli" -version = "2.4.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/22/de/48c59722572767841493b26183a0d1cc411d54fd759c5607c4590b6563a6/tomli-2.4.1.tar.gz", hash = "sha256:7c7e1a961a0b2f2472c1ac5b69affa0ae1132c39adcb67aba98568702b9cc23f", size = 17543, upload-time = "2026-03-25T20:22:03.828Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f4/11/db3d5885d8528263d8adc260bb2d28ebf1270b96e98f0e0268d32b8d9900/tomli-2.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f8f0fc26ec2cc2b965b7a3b87cd19c5c6b8c5e5f436b984e85f486d652285c30", size = 154704, upload-time = "2026-03-25T20:21:10.473Z" }, - { url = "https://files.pythonhosted.org/packages/6d/f7/675db52c7e46064a9aa928885a9b20f4124ecb9bc2e1ce74c9106648d202/tomli-2.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4ab97e64ccda8756376892c53a72bd1f964e519c77236368527f758fbc36a53a", size = 149454, upload-time = "2026-03-25T20:21:12.036Z" }, - { url = "https://files.pythonhosted.org/packages/61/71/81c50943cf953efa35bce7646caab3cf457a7d8c030b27cfb40d7235f9ee/tomli-2.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96481a5786729fd470164b47cdb3e0e58062a496f455ee41b4403be77cb5a076", size = 237561, upload-time = "2026-03-25T20:21:13.098Z" }, - { url = "https://files.pythonhosted.org/packages/48/c1/f41d9cb618acccca7df82aaf682f9b49013c9397212cb9f53219e3abac37/tomli-2.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a881ab208c0baf688221f8cecc5401bd291d67e38a1ac884d6736cbcd8247e9", size = 243824, upload-time = "2026-03-25T20:21:14.569Z" }, - { url = "https://files.pythonhosted.org/packages/22/e4/5a816ecdd1f8ca51fb756ef684b90f2780afc52fc67f987e3c61d800a46d/tomli-2.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:47149d5bd38761ac8be13a84864bf0b7b70bc051806bc3669ab1cbc56216b23c", size = 242227, upload-time = "2026-03-25T20:21:15.712Z" }, - { url = "https://files.pythonhosted.org/packages/6b/49/2b2a0ef529aa6eec245d25f0c703e020a73955ad7edf73e7f54ddc608aa5/tomli-2.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ec9bfaf3ad2df51ace80688143a6a4ebc09a248f6ff781a9945e51937008fcbc", size = 247859, upload-time = "2026-03-25T20:21:17.001Z" }, - { url = "https://files.pythonhosted.org/packages/83/bd/6c1a630eaca337e1e78c5903104f831bda934c426f9231429396ce3c3467/tomli-2.4.1-cp311-cp311-win32.whl", hash = "sha256:ff2983983d34813c1aeb0fa89091e76c3a22889ee83ab27c5eeb45100560c049", size = 97204, upload-time = "2026-03-25T20:21:18.079Z" }, - { url = "https://files.pythonhosted.org/packages/42/59/71461df1a885647e10b6bb7802d0b8e66480c61f3f43079e0dcd315b3954/tomli-2.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:5ee18d9ebdb417e384b58fe414e8d6af9f4e7a0ae761519fb50f721de398dd4e", size = 108084, upload-time = "2026-03-25T20:21:18.978Z" }, - { url = "https://files.pythonhosted.org/packages/b8/83/dceca96142499c069475b790e7913b1044c1a4337e700751f48ed723f883/tomli-2.4.1-cp311-cp311-win_arm64.whl", hash = "sha256:c2541745709bad0264b7d4705ad453b76ccd191e64aa6f0fc66b69a293a45ece", size = 95285, upload-time = "2026-03-25T20:21:20.309Z" }, - { url = "https://files.pythonhosted.org/packages/c1/ba/42f134a3fe2b370f555f44b1d72feebb94debcab01676bf918d0cb70e9aa/tomli-2.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c742f741d58a28940ce01d58f0ab2ea3ced8b12402f162f4d534dfe18ba1cd6a", size = 155924, upload-time = "2026-03-25T20:21:21.626Z" }, - { url = "https://files.pythonhosted.org/packages/dc/c7/62d7a17c26487ade21c5422b646110f2162f1fcc95980ef7f63e73c68f14/tomli-2.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7f86fd587c4ed9dd76f318225e7d9b29cfc5a9d43de44e5754db8d1128487085", size = 150018, upload-time = "2026-03-25T20:21:23.002Z" }, - { url = "https://files.pythonhosted.org/packages/5c/05/79d13d7c15f13bdef410bdd49a6485b1c37d28968314eabee452c22a7fda/tomli-2.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ff18e6a727ee0ab0388507b89d1bc6a22b138d1e2fa56d1ad494586d61d2eae9", size = 244948, upload-time = "2026-03-25T20:21:24.04Z" }, - { url = "https://files.pythonhosted.org/packages/10/90/d62ce007a1c80d0b2c93e02cab211224756240884751b94ca72df8a875ca/tomli-2.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:136443dbd7e1dee43c68ac2694fde36b2849865fa258d39bf822c10e8068eac5", size = 253341, upload-time = "2026-03-25T20:21:25.177Z" }, - { url = "https://files.pythonhosted.org/packages/1a/7e/caf6496d60152ad4ed09282c1885cca4eea150bfd007da84aea07bcc0a3e/tomli-2.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5e262d41726bc187e69af7825504c933b6794dc3fbd5945e41a79bb14c31f585", size = 248159, upload-time = "2026-03-25T20:21:26.364Z" }, - { url = "https://files.pythonhosted.org/packages/99/e7/c6f69c3120de34bbd882c6fba7975f3d7a746e9218e56ab46a1bc4b42552/tomli-2.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5cb41aa38891e073ee49d55fbc7839cfdb2bc0e600add13874d048c94aadddd1", size = 253290, upload-time = "2026-03-25T20:21:27.46Z" }, - { url = "https://files.pythonhosted.org/packages/d6/2f/4a3c322f22c5c66c4b836ec58211641a4067364f5dcdd7b974b4c5da300c/tomli-2.4.1-cp312-cp312-win32.whl", hash = "sha256:da25dc3563bff5965356133435b757a795a17b17d01dbc0f42fb32447ddfd917", size = 98141, upload-time = "2026-03-25T20:21:28.492Z" }, - { url = "https://files.pythonhosted.org/packages/24/22/4daacd05391b92c55759d55eaee21e1dfaea86ce5c571f10083360adf534/tomli-2.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:52c8ef851d9a240f11a88c003eacb03c31fc1c9c4ec64a99a0f922b93874fda9", size = 108847, upload-time = "2026-03-25T20:21:29.386Z" }, - { url = "https://files.pythonhosted.org/packages/68/fd/70e768887666ddd9e9f5d85129e84910f2db2796f9096aa02b721a53098d/tomli-2.4.1-cp312-cp312-win_arm64.whl", hash = "sha256:f758f1b9299d059cc3f6546ae2af89670cb1c4d48ea29c3cacc4fe7de3058257", size = 95088, upload-time = "2026-03-25T20:21:30.677Z" }, - { url = "https://files.pythonhosted.org/packages/07/06/b823a7e818c756d9a7123ba2cda7d07bc2dd32835648d1a7b7b7a05d848d/tomli-2.4.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:36d2bd2ad5fb9eaddba5226aa02c8ec3fa4f192631e347b3ed28186d43be6b54", size = 155866, upload-time = "2026-03-25T20:21:31.65Z" }, - { url = "https://files.pythonhosted.org/packages/14/6f/12645cf7f08e1a20c7eb8c297c6f11d31c1b50f316a7e7e1e1de6e2e7b7e/tomli-2.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:eb0dc4e38e6a1fd579e5d50369aa2e10acfc9cace504579b2faabb478e76941a", size = 149887, upload-time = "2026-03-25T20:21:33.028Z" }, - { url = "https://files.pythonhosted.org/packages/5c/e0/90637574e5e7212c09099c67ad349b04ec4d6020324539297b634a0192b0/tomli-2.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c7f2c7f2b9ca6bdeef8f0fa897f8e05085923eb091721675170254cbc5b02897", size = 243704, upload-time = "2026-03-25T20:21:34.51Z" }, - { url = "https://files.pythonhosted.org/packages/10/8f/d3ddb16c5a4befdf31a23307f72828686ab2096f068eaf56631e136c1fdd/tomli-2.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f3c6818a1a86dd6dca7ddcaaf76947d5ba31aecc28cb1b67009a5877c9a64f3f", size = 251628, upload-time = "2026-03-25T20:21:36.012Z" }, - { url = "https://files.pythonhosted.org/packages/e3/f1/dbeeb9116715abee2485bf0a12d07a8f31af94d71608c171c45f64c0469d/tomli-2.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d312ef37c91508b0ab2cee7da26ec0b3ed2f03ce12bd87a588d771ae15dcf82d", size = 247180, upload-time = "2026-03-25T20:21:37.136Z" }, - { url = "https://files.pythonhosted.org/packages/d3/74/16336ffd19ed4da28a70959f92f506233bd7cfc2332b20bdb01591e8b1d1/tomli-2.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:51529d40e3ca50046d7606fa99ce3956a617f9b36380da3b7f0dd3dd28e68cb5", size = 251674, upload-time = "2026-03-25T20:21:38.298Z" }, - { url = "https://files.pythonhosted.org/packages/16/f9/229fa3434c590ddf6c0aa9af64d3af4b752540686cace29e6281e3458469/tomli-2.4.1-cp313-cp313-win32.whl", hash = "sha256:2190f2e9dd7508d2a90ded5ed369255980a1bcdd58e52f7fe24b8162bf9fedbd", size = 97976, upload-time = "2026-03-25T20:21:39.316Z" }, - { url = "https://files.pythonhosted.org/packages/6a/1e/71dfd96bcc1c775420cb8befe7a9d35f2e5b1309798f009dca17b7708c1e/tomli-2.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:8d65a2fbf9d2f8352685bc1364177ee3923d6baf5e7f43ea4959d7d8bc326a36", size = 108755, upload-time = "2026-03-25T20:21:40.248Z" }, - { url = "https://files.pythonhosted.org/packages/83/7a/d34f422a021d62420b78f5c538e5b102f62bea616d1d75a13f0a88acb04a/tomli-2.4.1-cp313-cp313-win_arm64.whl", hash = "sha256:4b605484e43cdc43f0954ddae319fb75f04cc10dd80d830540060ee7cd0243cd", size = 95265, upload-time = "2026-03-25T20:21:41.219Z" }, - { url = "https://files.pythonhosted.org/packages/3c/fb/9a5c8d27dbab540869f7c1f8eb0abb3244189ce780ba9cd73f3770662072/tomli-2.4.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fd0409a3653af6c147209d267a0e4243f0ae46b011aa978b1080359fddc9b6cf", size = 155726, upload-time = "2026-03-25T20:21:42.23Z" }, - { url = "https://files.pythonhosted.org/packages/62/05/d2f816630cc771ad836af54f5001f47a6f611d2d39535364f148b6a92d6b/tomli-2.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a120733b01c45e9a0c34aeef92bf0cf1d56cfe81ed9d47d562f9ed591a9828ac", size = 149859, upload-time = "2026-03-25T20:21:43.386Z" }, - { url = "https://files.pythonhosted.org/packages/ce/48/66341bdb858ad9bd0ceab5a86f90eddab127cf8b046418009f2125630ecb/tomli-2.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:559db847dc486944896521f68d8190be1c9e719fced785720d2216fe7022b662", size = 244713, upload-time = "2026-03-25T20:21:44.474Z" }, - { url = "https://files.pythonhosted.org/packages/df/6d/c5fad00d82b3c7a3ab6189bd4b10e60466f22cfe8a08a9394185c8a8111c/tomli-2.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01f520d4f53ef97964a240a035ec2a869fe1a37dde002b57ebc4417a27ccd853", size = 252084, upload-time = "2026-03-25T20:21:45.62Z" }, - { url = "https://files.pythonhosted.org/packages/00/71/3a69e86f3eafe8c7a59d008d245888051005bd657760e96d5fbfb0b740c2/tomli-2.4.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7f94b27a62cfad8496c8d2513e1a222dd446f095fca8987fceef261225538a15", size = 247973, upload-time = "2026-03-25T20:21:46.937Z" }, - { url = "https://files.pythonhosted.org/packages/67/50/361e986652847fec4bd5e4a0208752fbe64689c603c7ae5ea7cb16b1c0ca/tomli-2.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ede3e6487c5ef5d28634ba3f31f989030ad6af71edfb0055cbbd14189ff240ba", size = 256223, upload-time = "2026-03-25T20:21:48.467Z" }, - { url = "https://files.pythonhosted.org/packages/8c/9a/b4173689a9203472e5467217e0154b00e260621caa227b6fa01feab16998/tomli-2.4.1-cp314-cp314-win32.whl", hash = "sha256:3d48a93ee1c9b79c04bb38772ee1b64dcf18ff43085896ea460ca8dec96f35f6", size = 98973, upload-time = "2026-03-25T20:21:49.526Z" }, - { url = "https://files.pythonhosted.org/packages/14/58/640ac93bf230cd27d002462c9af0d837779f8773bc03dee06b5835208214/tomli-2.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:88dceee75c2c63af144e456745e10101eb67361050196b0b6af5d717254dddf7", size = 109082, upload-time = "2026-03-25T20:21:50.506Z" }, - { url = "https://files.pythonhosted.org/packages/d5/2f/702d5e05b227401c1068f0d386d79a589bb12bf64c3d2c72ce0631e3bc49/tomli-2.4.1-cp314-cp314-win_arm64.whl", hash = "sha256:b8c198f8c1805dc42708689ed6864951fd2494f924149d3e4bce7710f8eb5232", size = 96490, upload-time = "2026-03-25T20:21:51.474Z" }, - { url = "https://files.pythonhosted.org/packages/45/4b/b877b05c8ba62927d9865dd980e34a755de541eb65fffba52b4cc495d4d2/tomli-2.4.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:d4d8fe59808a54658fcc0160ecfb1b30f9089906c50b23bcb4c69eddc19ec2b4", size = 164263, upload-time = "2026-03-25T20:21:52.543Z" }, - { url = "https://files.pythonhosted.org/packages/24/79/6ab420d37a270b89f7195dec5448f79400d9e9c1826df982f3f8e97b24fd/tomli-2.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7008df2e7655c495dd12d2a4ad038ff878d4ca4b81fccaf82b714e07eae4402c", size = 160736, upload-time = "2026-03-25T20:21:53.674Z" }, - { url = "https://files.pythonhosted.org/packages/02/e0/3630057d8eb170310785723ed5adcdfb7d50cb7e6455f85ba8a3deed642b/tomli-2.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1d8591993e228b0c930c4bb0db464bdad97b3289fb981255d6c9a41aedc84b2d", size = 270717, upload-time = "2026-03-25T20:21:55.129Z" }, - { url = "https://files.pythonhosted.org/packages/7a/b4/1613716072e544d1a7891f548d8f9ec6ce2faf42ca65acae01d76ea06bb0/tomli-2.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:734e20b57ba95624ecf1841e72b53f6e186355e216e5412de414e3c51e5e3c41", size = 278461, upload-time = "2026-03-25T20:21:56.228Z" }, - { url = "https://files.pythonhosted.org/packages/05/38/30f541baf6a3f6df77b3df16b01ba319221389e2da59427e221ef417ac0c/tomli-2.4.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8a650c2dbafa08d42e51ba0b62740dae4ecb9338eefa093aa5c78ceb546fcd5c", size = 274855, upload-time = "2026-03-25T20:21:57.653Z" }, - { url = "https://files.pythonhosted.org/packages/77/a3/ec9dd4fd2c38e98de34223b995a3b34813e6bdadf86c75314c928350ed14/tomli-2.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:504aa796fe0569bb43171066009ead363de03675276d2d121ac1a4572397870f", size = 283144, upload-time = "2026-03-25T20:21:59.089Z" }, - { url = "https://files.pythonhosted.org/packages/ef/be/605a6261cac79fba2ec0c9827e986e00323a1945700969b8ee0b30d85453/tomli-2.4.1-cp314-cp314t-win32.whl", hash = "sha256:b1d22e6e9387bf4739fbe23bfa80e93f6b0373a7f1b96c6227c32bef95a4d7a8", size = 108683, upload-time = "2026-03-25T20:22:00.214Z" }, - { url = "https://files.pythonhosted.org/packages/12/64/da524626d3b9cc40c168a13da8335fe1c51be12c0a63685cc6db7308daae/tomli-2.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:2c1c351919aca02858f740c6d33adea0c5deea37f9ecca1cc1ef9e884a619d26", size = 121196, upload-time = "2026-03-25T20:22:01.169Z" }, - { url = "https://files.pythonhosted.org/packages/5a/cd/e80b62269fc78fc36c9af5a6b89c835baa8af28ff5ad28c7028d60860320/tomli-2.4.1-cp314-cp314t-win_arm64.whl", hash = "sha256:eab21f45c7f66c13f2a9e0e1535309cee140182a9cdae1e041d02e47291e8396", size = 100393, upload-time = "2026-03-25T20:22:02.137Z" }, - { url = "https://files.pythonhosted.org/packages/7b/61/cceae43728b7de99d9b847560c262873a1f6c98202171fd5ed62640b494b/tomli-2.4.1-py3-none-any.whl", hash = "sha256:0d85819802132122da43cb86656f8d1f8c6587d54ae7dcaf30e90533028b49fe", size = 14583, upload-time = "2026-03-25T20:22:03.012Z" }, -] - [[package]] name = "tqdm" version = "4.70.1" @@ -3084,36 +3111,36 @@ wheels = [ [[package]] name = "ty" -version = "0.0.84" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e1/f6/34f8869c45ea87fe6a931ffa824b1fe4428167173543d92988d631add355/ty-0.0.84.tar.gz", hash = "sha256:0cefdb0cd5d399418dbe83c349ba605047b7dff815ae6f460c80e8522b40be67", size = 7409537, upload-time = "2026-09-24T13:16:24.367Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/aa/0c/600648778e1c79133ce6b6a5c4c2531ae16b90c9b30f5041b38b574f61b8/ty-0.0.84-py3-none-linux_armv6l.whl", hash = "sha256:868438e2b9abfc835dd5b5b07ed693db95d8348ac40e3228252511d63ae87695", size = 13935333, upload-time = "2026-09-24T13:15:31.167Z" }, - { url = "https://files.pythonhosted.org/packages/c5/3b/27f06f49945c36170765fad82076898039ae3873338e9865f876dd62f12c/ty-0.0.84-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b00f39b521f0b559cde0f6abf8dd47213b513b04f33d58efeb9b98486a16bb17", size = 13511328, upload-time = "2026-09-24T13:15:34.446Z" }, - { url = "https://files.pythonhosted.org/packages/fc/b6/b12dd130e7f30141f0645614f454c925991cd389b1cc6c8284fd6bf32326/ty-0.0.84-py3-none-macosx_11_0_arm64.whl", hash = "sha256:8f78c2915567f2ee62ce0c24d86091bc76f81577677755e242d09951db588b9a", size = 13445445, upload-time = "2026-09-24T13:15:37.476Z" }, - { url = "https://files.pythonhosted.org/packages/44/96/f012de4b3c1d9a4773326621380d768b4d48c47e620dc012775c5e01c7d7/ty-0.0.84-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:61155cad0921e890d86d1d911a40b3a0e924ef4bdc3b4f3865a6c89c7e3012dc", size = 13471235, upload-time = "2026-09-24T13:15:40.64Z" }, - { url = "https://files.pythonhosted.org/packages/05/32/dca630f5f5353fa7bf67eebccf1549df99b16656d21071d0df6677c826a5/ty-0.0.84-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:417c3bf14947ed16b0b92976ab333c27915590354e96d4f581709068d3811355", size = 13680704, upload-time = "2026-09-24T13:15:43.817Z" }, - { url = "https://files.pythonhosted.org/packages/44/8d/33702c8f62f05ad45b33644fb914d483de6a0a7bc8e51884e9b8773e6d69/ty-0.0.84-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e06ef117759f654f5379efe9ae79bda8a186016c016f1023d05c34f8e15c1546", size = 14518923, upload-time = "2026-09-24T13:15:46.802Z" }, - { url = "https://files.pythonhosted.org/packages/c8/a1/da3246e6ef2ae0e842bca4058b25c54bee55ab058ffff4ee54e72517ec01/ty-0.0.84-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a05d1d53db8b64410ab38c6e32c5898510584869d1d5dd9c0605db81c9aef7df", size = 15027731, upload-time = "2026-09-24T13:15:49.755Z" }, - { url = "https://files.pythonhosted.org/packages/20/17/b0099098a560aeb4f9fde6c912f9a81622c3e44e6555e1fbe322b368ec31/ty-0.0.84-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b92400494ab855cfb1204ac1101e3f53b2d09a8e21d171758b2cf9acdab1809e", size = 14748260, upload-time = "2026-09-24T13:15:52.713Z" }, - { url = "https://files.pythonhosted.org/packages/60/d5/32cd67aedb271f006e716ad093806c96c5d7e2af86be8255469dbd3d76c8/ty-0.0.84-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:af17eb391ae3027fed9be1780bea555c2b8a25ba68e184c627b4981b2c9eaab7", size = 14205858, upload-time = "2026-09-24T13:15:55.677Z" }, - { url = "https://files.pythonhosted.org/packages/43/c4/638ac62ca2e8eca1b92e3e16273085744fada728b993500819b5c258ca15/ty-0.0.84-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:44cf06af0e7ec78ea6d8ff33a3450fc19627756a4fba681f7410d4e3ef63047a", size = 14573196, upload-time = "2026-09-24T13:15:59.165Z" }, - { url = "https://files.pythonhosted.org/packages/a6/f6/9ee81c5d8921e5fd5f10889ae563c95cd3272b31df2ada528919ca1cef6b/ty-0.0.84-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:c6e56b443ff9ba462e34bfbc1e582004c2719ed35f8f93a433744b0ba0afb2d9", size = 13480760, upload-time = "2026-09-24T13:16:02.313Z" }, - { url = "https://files.pythonhosted.org/packages/f6/83/fbe5d1177667ae16d84c3242846b143809c9cc09568b333e5a9b4d5908f6/ty-0.0.84-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:8b6562fcc48820c1030e2a4509ef2c14ef93043ca4860280074314ac3a825b95", size = 13696346, upload-time = "2026-09-24T13:16:05.391Z" }, - { url = "https://files.pythonhosted.org/packages/86/9a/1b4ac9ddc34cc0ab4624bc219825d66af52f1ad87f4778ad08ad03fa06d5/ty-0.0.84-py3-none-musllinux_1_2_i686.whl", hash = "sha256:dee7421451efcbd70f03e95a8f2c8d9e5ec3d35edcd27b4095ee00ee062abfc4", size = 13959410, upload-time = "2026-09-24T13:16:08.661Z" }, - { url = "https://files.pythonhosted.org/packages/c9/04/70e289437a67dfd686056ab4fd91880261a8233033a6a95d29393741fb97/ty-0.0.84-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:31e96307cf621babe969307dd657783d672e2d8bdbd06493710da635bf63fa07", size = 14350294, upload-time = "2026-09-24T13:16:11.865Z" }, - { url = "https://files.pythonhosted.org/packages/9c/f4/ae7dbd118425c850981d23efb850f0d9d7f6b8d2b852bde2767434f668b4/ty-0.0.84-py3-none-win32.whl", hash = "sha256:4bdc85f91cbaae35825f2b17bb4ac5de0563367875cb0f42b2bf07a1bf7ff2ba", size = 13176439, upload-time = "2026-09-24T13:16:15.049Z" }, - { url = "https://files.pythonhosted.org/packages/29/ec/994d87252f065dda5a500bc8afc726d18f6582d81d6f4ff1feb8a267c800/ty-0.0.84-py3-none-win_amd64.whl", hash = "sha256:71cc9aa7a7c89d4e12c6a814e9c0c3310ad5bee44efedbd6f5a42d91e9ac0f0d", size = 13962879, upload-time = "2026-09-24T13:16:18.191Z" }, - { url = "https://files.pythonhosted.org/packages/6d/1c/e6e91852b72566b1ab9aca504712857dfb89324c12092a5bd61d42d34427/ty-0.0.84-py3-none-win_arm64.whl", hash = "sha256:6c9d3718c4a0d318bc9c92af9b2aa85c3294e7dff54cf1d69ad79568f56516da", size = 13656453, upload-time = "2026-09-24T13:16:21.51Z" }, +version = "0.0.92" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/45/f8/2629d3533019eec56b0008d93198b2cb1575795e017c52759d6ddbdad353/ty-0.0.92.tar.gz", hash = "sha256:d08ff5d89bb63780e30837446ea08ddae3db73ae0b872a441a5b514d37726459", size = 8071788, upload-time = "2026-10-26T03:13:58.999Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c4/cb/47bdaffb41500ebbd222952fdf5262b4efffca8c8df237b91915fd4f2849/ty-0.0.92-py3-none-linux_armv6l.whl", hash = "sha256:924af587c0d450e3589f4773de416d284483b68bb5cf5580159ad78c272e3298", size = 14365057, upload-time = "2026-10-26T03:13:07.962Z" }, + { url = "https://files.pythonhosted.org/packages/2e/68/1a18a4511b3cf12af9193f6466884f2cbe0ce2aed962ab9db6a1ddf586da/ty-0.0.92-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:ce0232a3beee089de8ee120fdde4ea3b8ca02758e07128b83138970a4ded8db6", size = 13922188, upload-time = "2026-10-26T03:13:11.422Z" }, + { url = "https://files.pythonhosted.org/packages/18/6c/01c6680ea6d02e3b0a6a444dafbf549419fe4971cf7e139ea9c3f087a3c1/ty-0.0.92-py3-none-macosx_11_0_arm64.whl", hash = "sha256:b1753fdfa241af570c1dbd3fbfae9ff600d2c479ba60efe528bbb47ca15860b0", size = 13064190, upload-time = "2026-10-26T03:13:14.029Z" }, + { url = "https://files.pythonhosted.org/packages/4e/f2/79faf9b5ee7defd6df4fc97ff693502d21d34762e16b9031e1cbb4b3f51c/ty-0.0.92-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cfaa6f4825144d0e873654d7223c72dda2ec8f9db6b1049e6897efbfb0a78680", size = 13852740, upload-time = "2026-10-26T03:13:17.019Z" }, + { url = "https://files.pythonhosted.org/packages/5c/aa/7e05fde03b145bac15164df206a844a806442570718fe4f478160607a2e3/ty-0.0.92-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e0041d6786f0d33768afd4f2d21650140534a2600a27576a0292812af49408ec", size = 14093792, upload-time = "2026-10-26T03:13:19.936Z" }, + { url = "https://files.pythonhosted.org/packages/49/21/cae3d9687922c4ce0e8c112634c3b43756cccad24733dca380ba1339fd71/ty-0.0.92-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2c9d8060d840a291ae746ccfedd0e963786e09a7288599d370cde73168b208da", size = 14994799, upload-time = "2026-10-26T03:13:22.963Z" }, + { url = "https://files.pythonhosted.org/packages/34/77/bcf546f2fe4dd8eea9ff35b5eb6b9e0603dcc47b5c8a8f15d41f4a82030f/ty-0.0.92-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2088a253b98f04851e261e8e597b384988b790075451ff47b011e6018bf8bc80", size = 15473722, upload-time = "2026-10-26T03:13:26.062Z" }, + { url = "https://files.pythonhosted.org/packages/74/5e/c5c405a08d91874479887d8a8539ab4194a2322a47987785a3cf2c6a009b/ty-0.0.92-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a0a63965d320c569265ba65788fcfd4cd17f0b0d20c6e88bcfa21dc5fb509a4c", size = 15443218, upload-time = "2026-10-26T03:13:29.115Z" }, + { url = "https://files.pythonhosted.org/packages/26/77/6fd2ec1fd78eb783e93b89f1e1aee7fce9095b32dc5f6cc0fc831bd903f4/ty-0.0.92-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9b6b40225ddd0c049b299e0a655ce04d4de594c6cac3fb9c1e020b113494924", size = 14700747, upload-time = "2026-10-26T03:13:31.851Z" }, + { url = "https://files.pythonhosted.org/packages/fc/d3/b994c68a51f32029a60d56180ee33e9c708bb60997ea5f50b91b3027bd31/ty-0.0.92-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:020ccc79656c0d0f8f353e5e0760988775cdaa253fa4427e265ed96b4994e7fe", size = 14936345, upload-time = "2026-10-26T03:13:34.908Z" }, + { url = "https://files.pythonhosted.org/packages/d4/68/5e5732af9d38c1e00a31b7478f0a81b9e5557cca88f8848dc2573e00f665/ty-0.0.92-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:be5c1fc769335ab03d96f08ecc9878b598a57fd983c36cf5a04137d754828b63", size = 13781405, upload-time = "2026-10-26T03:13:37.684Z" }, + { url = "https://files.pythonhosted.org/packages/2f/6a/e833260c7bf85a5fdc3e08515d1351c6ce8e1186ee8f82cfeb93175f53df/ty-0.0.92-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:a9f389840ec585075531ec1dfd62d25c73aeda0fb409a676ed996e426fb0a7bf", size = 14104200, upload-time = "2026-10-26T03:13:40.476Z" }, + { url = "https://files.pythonhosted.org/packages/66/94/79ea7b18132bcf33fb18cfe7ff5cd31f64ff34a13da56e2afe5ef4e81370/ty-0.0.92-py3-none-musllinux_1_2_i686.whl", hash = "sha256:6a59f712c41b37f964b5b53f0c4207fcf25f4a0ae6cdd4db216f7c19d48c0673", size = 14410183, upload-time = "2026-10-26T03:13:43.707Z" }, + { url = "https://files.pythonhosted.org/packages/e6/da/a68a8dd13cee1df46270d68b2649eba40ae700d737c271bd44c03e9c3c90/ty-0.0.92-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:ca63bad30a6efb95a705de80db2b3d9fc8f120a03e0c7fe765a0413cd8e290e3", size = 14809277, upload-time = "2026-10-26T03:13:46.629Z" }, + { url = "https://files.pythonhosted.org/packages/03/3c/08d37a4c00675c3bd36a03ce3a18c3da284bbca02860df0f788617ceb7ee/ty-0.0.92-py3-none-win32.whl", hash = "sha256:7425c361755fb811e0067c9b29c4b8be65015f694fb2fcca63f99453f8966c30", size = 13311040, upload-time = "2026-10-26T03:13:50.558Z" }, + { url = "https://files.pythonhosted.org/packages/77/b1/f455ba90564e00fbef1e58ea8b29bfebaad6c67ea050a3386fba32db55ef/ty-0.0.92-py3-none-win_amd64.whl", hash = "sha256:646bed75c51c7ca3ae4cba015c699f384241bc873d6ad775e0c0d66629532312", size = 13942099, upload-time = "2026-10-26T03:13:53.17Z" }, + { url = "https://files.pythonhosted.org/packages/6e/70/8369564e77a7e896d37819570c27f52573fa356d1c225312bf16ee402524/ty-0.0.92-py3-none-win_arm64.whl", hash = "sha256:37eaf557f9a9723515bece5c4e8228eb0e2286184de6115c0071ce08d8a21267", size = 13411491, upload-time = "2026-10-26T03:13:56.15Z" }, ] [[package]] name = "types-s3transfer" -version = "0.16.0" +version = "0.19.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/fe/64/42689150509eb3e6e82b33ee3d89045de1592488842ddf23c56957786d05/types_s3transfer-0.16.0.tar.gz", hash = "sha256:b4636472024c5e2b62278c5b759661efeb52a81851cde5f092f24100b1ecb443", size = 13557, upload-time = "2025-12-08T08:13:09.928Z" } +sdist = { url = "https://files.pythonhosted.org/packages/89/b2/4f2d72b5d234c81caa07dee688cacee2c8dc5bd793e9ae70a573f00d6425/types_s3transfer-0.19.3.tar.gz", hash = "sha256:99422ef028a5f854cd92d1e72cf4208bd0569f68a87936ca02fb9f91a68cdcd3", size = 13780, upload-time = "2026-10-20T02:24:19.016Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/98/27/e88220fe6274eccd3bdf95d9382918716d312f6f6cef6a46332d1ee2feff/types_s3transfer-0.16.0-py3-none-any.whl", hash = "sha256:1c0cd111ecf6e21437cb410f5cddb631bfb2263b77ad973e79b9c6d0cb24e0ef", size = 19247, upload-time = "2025-12-08T08:13:08.426Z" }, + { url = "https://files.pythonhosted.org/packages/f5/98/972fa03c9a6e1cc9184a28ea0354af50a6764bb172fe2d0751798a295f07/types_s3transfer-0.19.3-py3-none-any.whl", hash = "sha256:6843143f3b79e3cb73e58b6f45738e28e88d9f681fd32cc5ef7fa16f0c193356", size = 19476, upload-time = "2026-10-20T02:24:17.682Z" }, ] [[package]] @@ -3145,16 +3172,16 @@ wheels = [ [[package]] name = "urllib3-future" -version = "2.25.902" +version = "2.25.904" 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/a7/71/b8238b130ed77fafb067d87d26045fe67963494150a5977d4658712a826a/urllib3_future-2.25.902.tar.gz", hash = "sha256:5c443668b9f87bce38bad1fdec7d7950c47e9c5b7328b1fd029a429741f3be70", size = 1530236, upload-time = "2026-10-04T08:50:58.392Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7b/69/e2eeaf260c29b21ecc48b5c8ba6ade1e2e79be6499a8367627d1ce4d4bb5/urllib3_future-2.25.904.tar.gz", hash = "sha256:2ea5bcd64433e6d05ff1684a3cef51e94f33506f08e8fed344108d2abd0f416b", size = 1703564, upload-time = "2026-10-27T04:41:22.716Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d7/bc/c2a384f5bc7b98a48fcb58ded963bd0862f753f8442572ce76037a753e7b/urllib3_future-2.25.902-py3-none-any.whl", hash = "sha256:88e6f3c19f553931e78f84f011a34220f827c971e6cf06b3a075eaa788c2bf7d", size = 862229, upload-time = "2026-10-04T08:50:56.575Z" }, + { url = "https://files.pythonhosted.org/packages/02/89/f3c2b4db103a3a794da560355ec4bceb631128523d719b883a595df92d39/urllib3_future-2.25.904-py3-none-any.whl", hash = "sha256:530e6588ccaed48385098e98e35c4cf61439e18dc635995a9d1ab0c781d380d9", size = 890163, upload-time = "2026-10-27T04:41:20.659Z" }, ] [[package]] @@ -3172,47 +3199,47 @@ wheels = [ [[package]] name = "wassima" -version = "2.1.4" +version = "2.1.5" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/5f/92/fe256733440f7c36084ce924f373fbbf709277ddf92262b2295e8caf26d5/wassima-2.1.4.tar.gz", hash = "sha256:21bb77253bb8032c05393172c4d2df395f423b7e704538f3b2407f50c032034d", size = 143472, upload-time = "2026-08-27T06:15:48.889Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5c/8e/9167e3e39e921833d35119849422cfbbba68c90ad0723c2a9c7965303195/wassima-2.1.5.tar.gz", hash = "sha256:b02c1985e527c9539396cc85b909857d810dafaaa318a30266e585a0e7135f54", size = 141994, upload-time = "2026-10-22T07:24:05.966Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/56/1d/032b95952208fa3e623d54efb650bf0a134e2a02e7400b36c63363c27b74/wassima-2.1.4-py3-none-any.whl", hash = "sha256:26022272d4618aada0e26df2e79dbc0e034be8be9d6d5396512690ffc6a94e7b", size = 134348, upload-time = "2026-08-27T06:15:47.274Z" }, + { url = "https://files.pythonhosted.org/packages/27/0e/9bc13dc706f86752a6afe36228f452be797c50183bddba513efad60a0272/wassima-2.1.5-py3-none-any.whl", hash = "sha256:53d9a56cf55fc969f8cdd1993f8dc69f857d1c32229fb9084dc2b267044b6dde", size = 132848, upload-time = "2026-10-22T07:24:04.368Z" }, ] [[package]] name = "wcwidth" -version = "0.9.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f0/b4/7830542634bb2d3e62aa3b586a72d5b3b6c91c3168929e7000ef3fed041d/wcwidth-0.9.2.tar.gz", hash = "sha256:ae0ef90b90f6af38b54f1fe6d58662ec33b3cb4b8391958a62416d654231727b", size = 955039, upload-time = "2026-10-05T00:24:05.521Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/59/1e/4532a81fb9dfbf4114a816775e0a36c3a64ee1d1f4bba2094e2da50be5dc/wcwidth-0.9.2-cp310-abi3-macosx_10_9_x86_64.whl", hash = "sha256:7ef5a940bd5e30bac6e721f1a48fce0cd7bb3ece19e9c5d139e72c76c35cfd07", size = 605343, upload-time = "2026-10-05T00:23:22.649Z" }, - { url = "https://files.pythonhosted.org/packages/a0/07/cb6940e81134b7ed25fa312ee9ab536a63db0793b149f88a90e603ceace9/wcwidth-0.9.2-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:ae0800c5339423cc53d33a266ad264b42ba8aaa16d4464f6e6b1bee607f50b17", size = 609960, upload-time = "2026-10-05T00:23:27.049Z" }, - { url = "https://files.pythonhosted.org/packages/a4/80/15ad05d40bfa99155639fb9e13b3d77083aa0fab893c816db2543d29005c/wcwidth-0.9.2-cp310-abi3-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:9e542f1f8475b78452a295495d7a5bc3ead565112e9446a64dc93462a41c2a79", size = 762566, upload-time = "2026-10-05T00:23:38.322Z" }, - { url = "https://files.pythonhosted.org/packages/bc/f0/b8ef7758003d66b60f093695831a86dcc726aac01ee6446ffcbda27b61e3/wcwidth-0.9.2-cp310-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:674b518af28d38ee645ff97b74f5760abee5fad4bac74413bfc4b881ef2ce724", size = 771632, upload-time = "2026-10-05T00:23:32.448Z" }, - { url = "https://files.pythonhosted.org/packages/db/6c/f940133c71427c208575910e981942bd78c98b1f7cd0d1425ca4b7457c04/wcwidth-0.9.2-cp310-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:751bef0ab404b6a1dc028b56b4b85d46486be1c55833f80da533e42dc691f389", size = 769763, upload-time = "2026-10-05T00:23:40.175Z" }, - { url = "https://files.pythonhosted.org/packages/92/8f/285f862826f721964ec7c42f81dc53d23afbd723a0f4cd989651f8218e25/wcwidth-0.9.2-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:c3d80f39ba4653a595edae9aa46a509d14883790a8fc23c5db221ceb207f64b7", size = 782134, upload-time = "2026-10-05T00:23:33.926Z" }, - { url = "https://files.pythonhosted.org/packages/c2/2d/64aa54882a5d556d3654c1f926d9118b797461033e23a158409941a37c8f/wcwidth-0.9.2-cp310-abi3-musllinux_1_2_i686.whl", hash = "sha256:0a47e03d8293590ecce66c45dc20ff7b4b885e3c78093722239585eca0d77ab2", size = 782851, upload-time = "2026-10-05T00:23:41.974Z" }, - { url = "https://files.pythonhosted.org/packages/59/39/52389f6de7fe2e9c14ceb8253dd99034bd86e1c87847ea3c100a97dded9a/wcwidth-0.9.2-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:67d901a4ad99249eb775b4ee4769ca97fa405d35a75f46e83166910a47003f04", size = 784621, upload-time = "2026-10-05T00:23:43.449Z" }, - { url = "https://files.pythonhosted.org/packages/b3/8b/20225500a076ace27bbcc8a6fd7c55125133c57a618816c7b7b8b73070b1/wcwidth-0.9.2-cp310-abi3-win32.whl", hash = "sha256:ee1fd0db9d9fd711a70f3e7765e0e04c05d26982fa05361456163062549d7da4", size = 593123, upload-time = "2026-10-05T00:23:55.953Z" }, - { url = "https://files.pythonhosted.org/packages/5a/d6/b0690f55ea0483530a18bac917fbadbf54f35122510446fc370f5f1c2453/wcwidth-0.9.2-cp310-abi3-win_amd64.whl", hash = "sha256:2a9746de704242bd4fdaabb31dd46b82f694a56a8d21081ad89b679a89da9fec", size = 597827, upload-time = "2026-10-05T00:23:57.489Z" }, - { url = "https://files.pythonhosted.org/packages/e5/11/6ecf4e9e268ab1a4ec617ffcccc2ee4a71301625f5490912dbaba462fa9c/wcwidth-0.9.2-cp310-abi3-win_arm64.whl", hash = "sha256:b9c6ab615e03723b7f8760ea2f27758d656e7e13b51515c9dca5c3e8b04612fa", size = 598415, upload-time = "2026-10-05T00:23:51.517Z" }, - { url = "https://files.pythonhosted.org/packages/4e/41/549eef1ab767032bdbdc1f0ab655d404b082b1e9a1dab1361dbba90f64ed/wcwidth-0.9.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:eda88ffdc97c0fbf193d407114f2c7a54b379f67f6e52a7531ee3b9fe749eca7", size = 606555, upload-time = "2026-10-05T00:23:24.188Z" }, - { url = "https://files.pythonhosted.org/packages/9b/64/a875ed7ea71cacadc0ae11b5fd3fac3486efd58bb25e67a7344248dceadd/wcwidth-0.9.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1bf361c8705576760623b4724ae564666d73b016f9a778bcfd1c7345378ef4ec", size = 610829, upload-time = "2026-10-05T00:23:28.563Z" }, - { url = "https://files.pythonhosted.org/packages/c6/98/513095e484fe79b6f2613d6a72f855f5d56b65e15c215c2a6746fbc638f5/wcwidth-0.9.2-cp314-cp314t-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:97b878d1e158da5ed9ac5aac53fa3a55e282103af6a09ec353865613d1a31a76", size = 775819, upload-time = "2026-10-05T00:23:45.116Z" }, - { url = "https://files.pythonhosted.org/packages/22/fc/c02f3eec57224731e78f84b68e272250f784b6205acc7e0dcef6a7c23a0e/wcwidth-0.9.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:59dab4049cbd982b478bca098528df2c79a9160636a3a163ffebffcbd7d1b892", size = 786915, upload-time = "2026-10-05T00:23:35.323Z" }, - { url = "https://files.pythonhosted.org/packages/3d/6f/b0529a79bac3fe8d94f32b4237a13dbc3f955508753f6a6f06c73d679dc2/wcwidth-0.9.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:bb08ceb501d6aaf94066c3ee122dd825b152df40ff0bd0df4dc27126233b948e", size = 784186, upload-time = "2026-10-05T00:23:46.366Z" }, - { url = "https://files.pythonhosted.org/packages/d5/bd/6357c84ca9a734bfc735b7c48dbe21336b3777fab8a4101d14976dfe49a7/wcwidth-0.9.2-cp314-cp314t-win32.whl", hash = "sha256:8b4e381590b9b7390e07e22b2c0c1bb96ce50e1d2243c866d9387600362d51ed", size = 600791, upload-time = "2026-10-05T00:23:59.398Z" }, - { url = "https://files.pythonhosted.org/packages/98/de/037591ca18d897cc2179559dde72e6efc6ce0c90e9cd1e6bca4e87c38b4b/wcwidth-0.9.2-cp314-cp314t-win_amd64.whl", hash = "sha256:f2f7b3bba5a5d5f31fc350fd36ce5b84b693c83b7eb95ee630b720da5a5ce06f", size = 605463, upload-time = "2026-10-05T00:24:01.049Z" }, - { url = "https://files.pythonhosted.org/packages/d0/07/c9d96e106d938d26f7ab639bc80b8199359a1645ba6e3498413313ab6f38/wcwidth-0.9.2-cp314-cp314t-win_arm64.whl", hash = "sha256:734aa9405b321d1042301aa19c943c4731ee9e3460e4f8feea3299c064c97a14", size = 605936, upload-time = "2026-10-05T00:23:52.765Z" }, - { url = "https://files.pythonhosted.org/packages/82/8a/a28d61d910005ac93dfe48be3a0ebaa49352d88cebd25323e69e6ff2f4a8/wcwidth-0.9.2-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:42dbcb76ce8af39e2c9db410ac3f9bdf4e47eb41d6f44525952f172d3d98f724", size = 606575, upload-time = "2026-10-05T00:23:25.663Z" }, - { url = "https://files.pythonhosted.org/packages/01/c2/a3c66bd32766c8f4d6dc47d572532ba014fe5be30489f2576aff7cada363/wcwidth-0.9.2-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:138e1f8898e431b2f2d7881f8ca8d75591c1d3c21aa53f54e989bd6b39811da2", size = 611045, upload-time = "2026-10-05T00:23:30.421Z" }, - { url = "https://files.pythonhosted.org/packages/ec/8a/d39964f8f8c019d7d439b9b501d3e7bb42fee69f00354040ba0b27b5824c/wcwidth-0.9.2-cp315-cp315t-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:5175609bf8cc7398a5f48aa35207bd64ebf9f45e4c70df65f7fdc7a988041a3c", size = 778143, upload-time = "2026-10-05T00:23:47.7Z" }, - { url = "https://files.pythonhosted.org/packages/2f/53/525da13e8f9ff7b5b4e74ec6f8d68bdee63905796972e086c6b1b96670d2/wcwidth-0.9.2-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e5f669ae8c3d969c72032f9cdee019674b666e522d45e1e2099a2e9dda4a341d", size = 786630, upload-time = "2026-10-05T00:23:36.967Z" }, - { url = "https://files.pythonhosted.org/packages/ef/9f/d6a0c6df354b9d93466548a65cbf4ffcb48c719bbd307504cf3e76740837/wcwidth-0.9.2-cp315-cp315t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:196b47cf32f9df27ccda6dc513237f3c2429c4c659db428d60a5bc443d10f270", size = 784839, upload-time = "2026-10-05T00:23:49.88Z" }, - { url = "https://files.pythonhosted.org/packages/bf/d7/3021feed1ed7926021ec134943ad3b24a2f7ea742cc9976461171482ed77/wcwidth-0.9.2-cp315-cp315t-win32.whl", hash = "sha256:0cd4f7f2e53905dcb110d213a4c8529b6733fa3d232d8c717f946cc69a10349b", size = 600845, upload-time = "2026-10-05T00:24:02.497Z" }, - { url = "https://files.pythonhosted.org/packages/63/80/6a03356d8ee38261e3a78cf89ee03d8e7f12c572d969237be00869e2dc73/wcwidth-0.9.2-cp315-cp315t-win_amd64.whl", hash = "sha256:33df042f96c61ed3cd5fb3742fba427553a635bc578799857a48aa79f774a0b9", size = 605441, upload-time = "2026-10-05T00:24:04.052Z" }, - { url = "https://files.pythonhosted.org/packages/0c/48/1a308a86a833fd12ff7a08d0d2491ff4a72c8a92d12f5ead8317630f771e/wcwidth-0.9.2-cp315-cp315t-win_arm64.whl", hash = "sha256:48719a9bc76c2f84238693fe5013571fa5beffa3621cf228f1f3a9e30dae84b8", size = 605885, upload-time = "2026-10-05T00:23:54.274Z" }, - { url = "https://files.pythonhosted.org/packages/9c/b4/0bfa065af506540d9d558e3e5548cff00bc1f9b24e6e2a8512498e8628de/wcwidth-0.9.2-py3-none-any.whl", hash = "sha256:89ca642c5bf0101157a09366be69fad0379db1f700ae39a920e103234573670e", size = 301667, upload-time = "2026-10-05T00:23:21.097Z" }, +version = "0.9.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ea/01/e5ac23733628a6bbb91913a765fcf55ee8070d8d991bc88fdaaf805052c7/wcwidth-0.9.4.tar.gz", hash = "sha256:fd8d57d3cdfe570e3265c8fab1885c24e34ef40889c0759c0c304a19e9835954", size = 970051, upload-time = "2026-10-24T18:11:44.954Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bf/b8/d251589a100250b71a7b8e83fedfcabfd8bd60e78cd1e3329372f63bcc93/wcwidth-0.9.4-cp310-abi3-macosx_10_9_x86_64.whl", hash = "sha256:b7969c9aa258d536df54c8ac8af5cad0c74a6268c9972e1fb60a5f29feffb66d", size = 612952, upload-time = "2026-10-24T18:10:55.925Z" }, + { url = "https://files.pythonhosted.org/packages/04/e9/dfdca30ca36c472de3e260406dee29fc108499ec590c63b3fe279887ee9d/wcwidth-0.9.4-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:f279f32c8e206a9092fa350e53ca0c2bd8072e45b1c9fd31bb6d476b59b591c4", size = 618904, upload-time = "2026-10-24T18:11:02.008Z" }, + { url = "https://files.pythonhosted.org/packages/5d/b6/d7416f38054a107d2fb17c2b73b68723c6c334f69c4e29545fb9e6df81eb/wcwidth-0.9.4-cp310-abi3-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:a1526cd25c6420e5153bb7d764c7509e692215ee3ad9b725db1ad665d47eae09", size = 778780, upload-time = "2026-10-24T18:11:17.299Z" }, + { url = "https://files.pythonhosted.org/packages/7d/5b/42bdf9a4b0e4f9126584af2ec73642de495f3bd8add907430f13c3bb98d2/wcwidth-0.9.4-cp310-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:093469e01d881bff3db93ff3f56c364dc87c441ea01f9f41c22de0c0bdf5c6ef", size = 789012, upload-time = "2026-10-24T18:11:07.829Z" }, + { url = "https://files.pythonhosted.org/packages/fa/e8/0485850748dfdba9443f4151c7b2442cfa4987ba6cd871038eb5d6b8eca9/wcwidth-0.9.4-cp310-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b185c8b927b23af2721be1783499d1cfa5c028b098b0858951b4ae224477f1c2", size = 785946, upload-time = "2026-10-24T18:11:18.748Z" }, + { url = "https://files.pythonhosted.org/packages/e9/11/dd3d0379c6094524df6585b76ae3d9727161ff29c003177e3661c36437c1/wcwidth-0.9.4-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:17619066f95c10f44d7ae08ee456602456bc66cc9d5e91966ec73d799acebc5f", size = 795348, upload-time = "2026-10-24T18:11:09.931Z" }, + { url = "https://files.pythonhosted.org/packages/3c/48/1b5299983d9ec084be897162b7cc6014622dfe62847224fa3272b6d79ea5/wcwidth-0.9.4-cp310-abi3-musllinux_1_2_i686.whl", hash = "sha256:b52f265a694dda6b3ad9050c8bd89158ad519109ef7c5eb110b906cc49f25763", size = 793420, upload-time = "2026-10-24T18:11:20.551Z" }, + { url = "https://files.pythonhosted.org/packages/7a/be/e3097d859b075344a7be57774a58d468eafb0ca88e02aad93eb31d69f6ba/wcwidth-0.9.4-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:d8905f1390d828d745fdd1904374c90386d902d3cfa9ee88e9875099f75a4e17", size = 796192, upload-time = "2026-10-24T18:11:22.552Z" }, + { url = "https://files.pythonhosted.org/packages/5e/cd/739710c8cf0de461239db5bb6ee678c9a33d30aaa22beff55cc356781818/wcwidth-0.9.4-cp310-abi3-win32.whl", hash = "sha256:4831662c396dd4c59a016ec53c0ac96738006d653a00482777d9a2e2ea323d62", size = 593229, upload-time = "2026-10-24T18:11:35.609Z" }, + { url = "https://files.pythonhosted.org/packages/d6/f1/c123f902bc2ff9e9c2ab66241b8872204df87cbb18ecdf035e120860fedc/wcwidth-0.9.4-cp310-abi3-win_amd64.whl", hash = "sha256:f28d77153303450ee13ebbe888297e75cf532c754555b6cabafc9bdda16c421c", size = 597780, upload-time = "2026-10-24T18:11:36.952Z" }, + { url = "https://files.pythonhosted.org/packages/4b/88/1cb5ddaf5510b918e01aa95fc4b68df91dead784b63e4931037c5673e85f/wcwidth-0.9.4-cp310-abi3-win_arm64.whl", hash = "sha256:2b58fc8cf2de1bf9cdb68b8beaa5be198742414a7c8cd97aa51ec039783ffa49", size = 598786, upload-time = "2026-10-24T18:11:30.886Z" }, + { url = "https://files.pythonhosted.org/packages/b0/cc/46a31c12b4cdad04f546a3c0aca5d000d77aa5069ae88b4db24525cc29c4/wcwidth-0.9.4-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:87e4101789415a6cf55ec4c53eceeb4782c2654c88692941cb588f19dca1959f", size = 613805, upload-time = "2026-10-24T18:10:58.887Z" }, + { url = "https://files.pythonhosted.org/packages/18/f6/5a8327af9a65153ea9823d6a12fc182f325264050aa25c05a5deadc189c9/wcwidth-0.9.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ab1e5190fb82476e75fbf963e9d59b4fb1c1e1f4ced82069a0c989016aa959f7", size = 619936, upload-time = "2026-10-24T18:11:04.073Z" }, + { url = "https://files.pythonhosted.org/packages/1f/d3/bb8a7f4aa9b915e56e09841b07636ba49c82ec987ad3d2022e1696c038d9/wcwidth-0.9.4-cp314-cp314t-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:8f892713ab5795d3abae181e9c02db0eb24de4084f9ee080535369d3d98d7bb5", size = 794563, upload-time = "2026-10-24T18:11:24.4Z" }, + { url = "https://files.pythonhosted.org/packages/12/0c/986acadef534688a57502d484a40e0f30bdca2d39514fe0a311f7d575f70/wcwidth-0.9.4-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:befbf46467100e43fd8c8cc0c031a0105f13f5418c3be95fdcf0c47214487967", size = 806565, upload-time = "2026-10-24T18:11:14.297Z" }, + { url = "https://files.pythonhosted.org/packages/9b/1d/66a94b9ac07037d7cf2c1f1288867c9932215d467893eaa5ec8d548c7f2f/wcwidth-0.9.4-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:9c4b0dd04cca520792100f6110d10be262c94b285a281d1759c551f159214633", size = 802372, upload-time = "2026-10-24T18:11:26.275Z" }, + { url = "https://files.pythonhosted.org/packages/d1/00/1d50b8e8b45a8330b582695bf8196081a609317928a3718cf21b3d14a899/wcwidth-0.9.4-cp314-cp314t-win32.whl", hash = "sha256:14410eb73178cc492aa9bf22f7c12e3bf930e5470b7220cf6f530efbfa2e03f3", size = 598917, upload-time = "2026-10-24T18:11:38.369Z" }, + { url = "https://files.pythonhosted.org/packages/b4/fc/3e39a33fc8f5c4e07611578710275039c9d4a6655e2be7cf3459401a30cc/wcwidth-0.9.4-cp314-cp314t-win_amd64.whl", hash = "sha256:c36954849aafae1092d6f9de9548c5280c1d6841637a27ca288b2241c802c4ab", size = 603536, upload-time = "2026-10-24T18:11:39.922Z" }, + { url = "https://files.pythonhosted.org/packages/e1/8c/b1f399fd2f8449e42978192967ab6df441d7cf894b10117983c36c062b18/wcwidth-0.9.4-cp314-cp314t-win_arm64.whl", hash = "sha256:79e09e9824c88f5e275ca99e921685567a5c5c1bb6165c16e27ddd1adab33a95", size = 605127, upload-time = "2026-10-24T18:11:32.367Z" }, + { url = "https://files.pythonhosted.org/packages/04/46/839780e772e9068580619d4d691348b7a5f9c4fa5ffe501e522eefeb27cc/wcwidth-0.9.4-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:a8c9ce14c6b4a499e61de6de7e9e456de11be8bc991554e2ab2ed4feb40b9664", size = 613850, upload-time = "2026-10-24T18:11:00.725Z" }, + { url = "https://files.pythonhosted.org/packages/d2/66/b2559c7012bbd4268f5b5c0288cb793c78235d354e306871a55893aa9d1a/wcwidth-0.9.4-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:d68f09093c9bec9226a52edc47487042ed4f64c5df91d06f00419495071c0279", size = 620207, upload-time = "2026-10-24T18:11:05.975Z" }, + { url = "https://files.pythonhosted.org/packages/d1/fe/7d2c39e28924b2430b20099e36276f14e00e75c7bbe4ba5b1ed44d1d465a/wcwidth-0.9.4-cp315-cp315t-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:d76b7c715c5ca0103b28915f699f1020ae21f06f1d22cdd85a7a97f7b1536eed", size = 797056, upload-time = "2026-10-24T18:11:27.911Z" }, + { url = "https://files.pythonhosted.org/packages/2b/d7/f4f9967a411e02b89b74df2f49f44977c4badccf29738f3ae6941619e27c/wcwidth-0.9.4-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:29c0685f5f5f8815b88308fdcbe2cd12a92996a21a5b1e18352d165174ef7a6d", size = 806169, upload-time = "2026-10-24T18:11:15.724Z" }, + { url = "https://files.pythonhosted.org/packages/ac/bb/e2b40c6522f3aa1222558487dda3551d297e07e245761b4816f6caeebc29/wcwidth-0.9.4-cp315-cp315t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e05458491759aa42b7b85f36345b3e6df082d6d2d9bf616299668fd598e0209d", size = 803462, upload-time = "2026-10-24T18:11:29.269Z" }, + { url = "https://files.pythonhosted.org/packages/a8/f2/ab69009f2dd0d3772d3e07f025fa046b33c60c195e4f2e5f000297d23547/wcwidth-0.9.4-cp315-cp315t-win32.whl", hash = "sha256:9e5090d253f5df99659cddfd3ed90dd21f6ff51989b0421331a63fb2681cbe27", size = 598931, upload-time = "2026-10-24T18:11:41.659Z" }, + { url = "https://files.pythonhosted.org/packages/63/6f/b321f10c2298e7d16cffc1a5f05f3d7e5231b45f9e68af5ee378e23bb2e5/wcwidth-0.9.4-cp315-cp315t-win_amd64.whl", hash = "sha256:12786620375dc83fabf4b2b2aa52416fef2bc6094522ea29c8a53c75b0dfe9b6", size = 603485, upload-time = "2026-10-24T18:11:43.052Z" }, + { url = "https://files.pythonhosted.org/packages/4e/cc/7eeb28059186cae72a770df79e744f232ef068192a5a588b718473e35bb4/wcwidth-0.9.4-cp315-cp315t-win_arm64.whl", hash = "sha256:2cca7b5d0b9d0ae49068d3ce806e965326a987f555b62eaa322223396e10d01b", size = 605198, upload-time = "2026-10-24T18:11:33.75Z" }, + { url = "https://files.pythonhosted.org/packages/6f/0c/309b1813b230d1dbbd6f2c2ab2bb21ef68992358977605d9ef289bbfedcc/wcwidth-0.9.4-py3-none-any.whl", hash = "sha256:901eace6d2dd25ebb917d0ce5db18e5c537e1504c905cf32f0d006c12098b05d", size = 301806, upload-time = "2026-10-24T18:10:52.651Z" }, ] [[package]] @@ -3238,88 +3265,88 @@ wheels = [ [[package]] name = "wrapt" -version = "2.5.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/3e/d2/a254a26d8ceaea87e0eee2e89fcfe53ddc1858418647493bb2937549ab6f/wrapt-2.5.0.tar.gz", hash = "sha256:c48cdb6c904dca76d9915a579e4a5fab6b0c25f650c1019ce78a78effaf7a345", size = 183288, upload-time = "2026-09-27T01:41:56.874Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7f/d0/7c23187af053bfa54c99964f87b94b96cea76b78f019b06e42fa86752e5d/wrapt-2.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:57fa1a3fd1279b3ca7655b943ad61d298f2a2464a4cdca7ff298058e408322f9", size = 105327, upload-time = "2026-09-27T01:39:46.242Z" }, - { url = "https://files.pythonhosted.org/packages/3d/fa/6f7f880207b2d74162a44b35e41217d999fd4af48338afb356c650f89a07/wrapt-2.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:63e58f96849f622ce769dcd705f83c7445cd9829bce1dae00e78bb031aec8096", size = 105501, upload-time = "2026-09-27T01:39:47.592Z" }, - { url = "https://files.pythonhosted.org/packages/6f/19/b0b7e7cf1a5499a25bd5260b38deb3b17aa9ca7b268ef29e546dcb683aa7/wrapt-2.5.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fd91203e156d610ecb28b9ccd7b764af7a7b38662d7c163090babab0d10def0c", size = 239849, upload-time = "2026-09-27T01:39:48.838Z" }, - { url = "https://files.pythonhosted.org/packages/7d/cd/b6cce206889497626f9b8b9d2ecb74324287d853bcca13c8bfab9b99a981/wrapt-2.5.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ff909b934b1958e31784d412abab5cbb0709fdbc01c86f22965e1d15331371ba", size = 241355, upload-time = "2026-09-27T01:39:50.368Z" }, - { url = "https://files.pythonhosted.org/packages/70/6b/11b3b25915bfed1b47b7486a306602f3c3698b5a6b73b05bbd59bd891650/wrapt-2.5.0-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:148052fc55013930217f531c6978e918ab210a12bd73cc9bd6de661a7adaf620", size = 226349, upload-time = "2026-09-27T01:39:51.619Z" }, - { url = "https://files.pythonhosted.org/packages/f6/97/9901b407cacfd7b3efca60f808713294e5758dda023586fe201fe203d134/wrapt-2.5.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e5d4885c5625c9d2dcb49458700851574e9d0ea046c7a265526e990282f8ae8e", size = 239003, upload-time = "2026-09-27T01:39:53.01Z" }, - { url = "https://files.pythonhosted.org/packages/5c/68/b7883fe1c6b445df44e32a2a3a19a544a3a73749c5064a539c10f9f730a4/wrapt-2.5.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:672dd1bab4256311db1520b1b50e7a10cbaeaae2b0ac6bc5d858cc387ee605a2", size = 223603, upload-time = "2026-09-27T01:39:54.606Z" }, - { url = "https://files.pythonhosted.org/packages/f7/05/f9d527f0da33b901684ad326b6b9fc07593013f9594b9db4ebb0ddc1e127/wrapt-2.5.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c3a78a3161b3a9bf07725822fd24d379c1f3f161b6db49166c4131096ea73b4c", size = 229164, upload-time = "2026-09-27T01:39:55.904Z" }, - { url = "https://files.pythonhosted.org/packages/2f/e0/4a05ad93a003272619cd98dbfbfb7b45a9c7d1041215473d789d9c6ce8ed/wrapt-2.5.0-cp311-cp311-win32.whl", hash = "sha256:0810e060e58f7960405172ad21080df8e7335841c9fe97417bd7d3f05af24f90", size = 100422, upload-time = "2026-09-27T01:39:57.216Z" }, - { url = "https://files.pythonhosted.org/packages/1e/96/5dcc944f39724e5df8a51f9095e856463df231764d503a7a935503eebeb9/wrapt-2.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:99f8ea48f14a71c5e2df8763e9a490e8af63096dcd67755b7bab0a4b74fc7cd7", size = 105616, upload-time = "2026-09-27T01:39:58.43Z" }, - { url = "https://files.pythonhosted.org/packages/a3/91/c927dac776c8939616ce865efa4bf3e62c933393528487e8fde981aad356/wrapt-2.5.0-cp311-cp311-win_arm64.whl", hash = "sha256:2ac82ef59ee05e259902bc7cf73dee5e6397845e8ccdc376d9d25536b59a877c", size = 102562, upload-time = "2026-09-27T01:39:59.672Z" }, - { url = "https://files.pythonhosted.org/packages/2a/a6/44589f9b34160280a1fbccbcf206b18df034a1b3e9584336d3a7f039a33c/wrapt-2.5.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b898caea081303006decc562c7fca5126f7c96507e78dd8f1ae3285dfa50ddc7", size = 106192, upload-time = "2026-09-27T01:40:00.941Z" }, - { url = "https://files.pythonhosted.org/packages/ce/2b/94db3ba2e9528400e4173cbb67a56822a4644d4c2b319feb0cc638094b36/wrapt-2.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8837fbe708cb9d8a2d32a37dee836d24a531f02560db26418e2b181986fa21cb", size = 105828, upload-time = "2026-09-27T01:40:02.494Z" }, - { url = "https://files.pythonhosted.org/packages/11/0e/3ce67af67525c0068680637c0eba823ce2f0fc6eff2d1779cab656fb8c3d/wrapt-2.5.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0cabb9c17ab79b2549d1f23b36f436473ad9253ef53995a817feba26fae69d5b", size = 250177, upload-time = "2026-09-27T01:40:03.819Z" }, - { url = "https://files.pythonhosted.org/packages/d3/3b/262b2c3c38aca6fa32cfea6407dbb346ce26ff788179b98137b3b3f3d8ca/wrapt-2.5.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6761765cc520ff9616fb035c02a85d1d744f7f70edd4649718fd0d09c589eacf", size = 250332, upload-time = "2026-09-27T01:40:05.15Z" }, - { url = "https://files.pythonhosted.org/packages/2e/ec/d54d273a2223d1964ecd5b5954aca18e8fbf2c5d8471e60ef3cbaaed9325/wrapt-2.5.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a145a7826eddea3eb5814903f98f93756042b919bb5305544cb1331daa2705b1", size = 229600, upload-time = "2026-09-27T01:40:06.47Z" }, - { url = "https://files.pythonhosted.org/packages/8e/88/33c75ac47b13b0bf77b44d96d02b1d0a2e6db66180909bacce86ef8fcd8e/wrapt-2.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6a9ee62a970075738909909bdbef3a7da9f7ae03dfca584db283547a29503b56", size = 246890, upload-time = "2026-09-27T01:40:07.838Z" }, - { url = "https://files.pythonhosted.org/packages/d2/0e/c3a3a158801e5bee7a0d9aa3561a127bd80f166b02b7b520a39e45d8f9b9/wrapt-2.5.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:691671ea05684f921ffc2e935fd3f9311c1795a10fbbfa006b46269733668f66", size = 226463, upload-time = "2026-09-27T01:40:09.206Z" }, - { url = "https://files.pythonhosted.org/packages/7c/2c/4c48ba51698a87e2e8299fc04856bbef391cb725f195bc93f0bc6196e1f2/wrapt-2.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e716f47c7f61e11709d3c0904213c94fc22999abf0c41461276cef886c1e8b4d", size = 238583, upload-time = "2026-09-27T01:40:10.631Z" }, - { url = "https://files.pythonhosted.org/packages/33/d2/077835618ed96b131730f74301a7236efd841ec2f341afe36303389b56ef/wrapt-2.5.0-cp312-cp312-win32.whl", hash = "sha256:5421acb5c363a9bc959122a8645e3f1f42010c932dc53885b11a5ff5b5a6d730", size = 100221, upload-time = "2026-09-27T01:40:11.91Z" }, - { url = "https://files.pythonhosted.org/packages/96/9f/9e56db2a3492275be809082c6f63a7a063f52b3e1812aae1eb03ec33f64c/wrapt-2.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:ab45839c912777e2738fed369636589c8b2a6d9c44ca56de0fd0814581d467c2", size = 105786, upload-time = "2026-09-27T01:40:13.208Z" }, - { url = "https://files.pythonhosted.org/packages/0c/e4/e37ff75e5254564aa50e13bc4313383d02d5ec78f5bd3b9b1994dd7052d5/wrapt-2.5.0-cp312-cp312-win_arm64.whl", hash = "sha256:ce4cab32c37ef71e69cf88f909b7febd0dd79543e5ae3650e2b874e0f3d3b975", size = 102937, upload-time = "2026-09-27T01:40:14.534Z" }, - { url = "https://files.pythonhosted.org/packages/d6/4b/cc7bb5668f7ddc0e73e236e96a0c06cab8fddfca9c53538c9dffac62db6f/wrapt-2.5.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b312b3cc87951faaed3cfef984d768ee8bee7f935d9cc929aaa9946b0b96a98c", size = 105808, upload-time = "2026-09-27T01:40:15.826Z" }, - { url = "https://files.pythonhosted.org/packages/4a/13/5d15ef0e2f42d5f084930dc4863e6e52c160c28301c8780aae170c58421c/wrapt-2.5.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c57ddae24cf72eb6bd18112638a987cafe6109d90f2df111e6934362cc03ac1a", size = 105931, upload-time = "2026-09-27T01:40:17.136Z" }, - { url = "https://files.pythonhosted.org/packages/1e/02/c7174e78b0c38bb279b2d3c25a6bd7fb9d3b0200c3e5a8fad084e7cc3e85/wrapt-2.5.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b95a6eca3b927853529eea958310563c83140ae8451dd5dc4399c7da385dc4f3", size = 248259, upload-time = "2026-09-27T01:40:18.446Z" }, - { url = "https://files.pythonhosted.org/packages/a4/f9/47ae1d7ef325c3f6c81ae3c1fb4a3fef9d98c8025ed676c0bfc1550903ce/wrapt-2.5.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6058e12e9caa33468f9a36fb88c15a4bb30a479f997b37834b83abdbf062f264", size = 248224, upload-time = "2026-09-27T01:40:19.713Z" }, - { url = "https://files.pythonhosted.org/packages/38/7b/a394448bcbbaf8e5a3f856520edbbb1b92fc42061def56284c9083f3ac87/wrapt-2.5.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0d245ac03f5ae77f1eea6eb19edd9e778c2f772490c20496c2f1cd3a102ee1b6", size = 227137, upload-time = "2026-09-27T01:40:21.359Z" }, - { url = "https://files.pythonhosted.org/packages/41/45/fc252bda5aa1ca01bc838d3b108778e786a2a13d0c52fd17c5f6179aa246/wrapt-2.5.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f4ef4935962f7029b2058a99f1a47ccbffc3be919dddb3becb6c2c48eac3d9f0", size = 244710, upload-time = "2026-09-27T01:40:22.693Z" }, - { url = "https://files.pythonhosted.org/packages/0b/1c/527d1bde7371dcc2c378d88c97de03b121b486fb4cd3dbb399bc332c0676/wrapt-2.5.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:f12e80c3089ebc03727d368f8205b811b5af2cd4a72b5e4cac75e901dd316e39", size = 225053, upload-time = "2026-09-27T01:40:24.345Z" }, - { url = "https://files.pythonhosted.org/packages/81/8e/2b823fded8c3b815408c58633929812eacd29d824fe57548b7868c4ee422/wrapt-2.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a346408f19b6d589bf029f25f65c0b4cdeed6302ef8f40da4e5d1552d22dc037", size = 236884, upload-time = "2026-09-27T01:40:25.654Z" }, - { url = "https://files.pythonhosted.org/packages/00/d7/5d185c1193b073a0bf4cbe862b5d31f81067eddc39eff30ae632f346563d/wrapt-2.5.0-cp313-cp313-win32.whl", hash = "sha256:79e68f0fd7d381b9bbd71776f602a2d5440d4d2077459128e02fd6607465422c", size = 100198, upload-time = "2026-09-27T01:40:27.111Z" }, - { url = "https://files.pythonhosted.org/packages/ce/9a/51d95640e01d0ebdd04a7223755f076e4936b0c124ce99bb01a12b53e66c/wrapt-2.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:77f0a74ff6f6cf89f5b673a732d5afe1911a6e6b1c017260836fdfdf85518dc1", size = 105610, upload-time = "2026-09-27T01:40:28.465Z" }, - { url = "https://files.pythonhosted.org/packages/67/52/183d5ce7c2a9391774e6a623be6ae564351545713ec9a3693528f89c8e85/wrapt-2.5.0-cp313-cp313-win_arm64.whl", hash = "sha256:b620d7559b6b2197c5730332fab0867ecf1c8cb74d45533ebbcbcad1eacf4616", size = 102641, upload-time = "2026-09-27T01:40:30Z" }, - { url = "https://files.pythonhosted.org/packages/f3/4b/0009086ab8f2d5fb32405ef49fdd11104ce40f69ae9f4cdfba8326462816/wrapt-2.5.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:65f2ee406dc592a5b22a7dc6abac13e8a3e8de4b2ecf5dc3c22937865496e4b6", size = 106182, upload-time = "2026-09-27T01:40:31.503Z" }, - { url = "https://files.pythonhosted.org/packages/5d/35/8f38339a4c55a42df00296dcf6ad50598d2280049f7a6ffa525e9a1f66d1/wrapt-2.5.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:d75d6366203c8d025c1a74bae0b565952187ddae79bd5c7bf10687652a56f020", size = 106123, upload-time = "2026-09-27T01:40:32.927Z" }, - { url = "https://files.pythonhosted.org/packages/23/38/285b433121d73c7a447b5b82d93c91dc3330f33ab5853975f34551e0c773/wrapt-2.5.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b640460f0ffb346b192686bd6fac5589e35a6c6640501c59a9fb6e82b0dd6bd8", size = 251245, upload-time = "2026-09-27T01:40:34.309Z" }, - { url = "https://files.pythonhosted.org/packages/7b/a6/3f63f4637e89484c1839a9ba3aedda5b2912e7ce12617034c6bd49752cfa/wrapt-2.5.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1f17c5a3836397bf59fd57b0e5b0dc42969b1daa70d31aa361c6e13cbf138b5a", size = 250483, upload-time = "2026-09-27T01:40:35.841Z" }, - { url = "https://files.pythonhosted.org/packages/e9/cd/f24ee96016da222dbb921cfb22e2beb5ca189a7b730ef49e8bb106b49449/wrapt-2.5.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4343880acd72e74233baf092285aaaf4306244e31d7601828bd2600316027df0", size = 231341, upload-time = "2026-09-27T01:40:37.295Z" }, - { url = "https://files.pythonhosted.org/packages/23/eb/c9b180124271e494f615a130f966be56143e3e26e87706bc28582f94bc09/wrapt-2.5.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ea4fdc79c0045d6bb1603c109127145245cafe888588888444e1e37fbeadbac3", size = 247736, upload-time = "2026-09-27T01:40:38.653Z" }, - { url = "https://files.pythonhosted.org/packages/fd/60/345b8c213389809435d1950136b09991a1af2a66b988d0cd930ecd1b9f19/wrapt-2.5.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:f42239c89430eee2d8a6dec39e34677abdbb67fff63caf2467dd6124ea4d4d58", size = 228533, upload-time = "2026-09-27T01:40:40.12Z" }, - { url = "https://files.pythonhosted.org/packages/ea/15/c79f0f5827a9062c6be4fc25dc73e92fe1c014c7bfde2e61c8c0b56a91af/wrapt-2.5.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:bad63bb4dea3c58e8078a3a173259ac2df5442a437e49c632b4099c0250e803b", size = 238830, upload-time = "2026-09-27T01:40:41.505Z" }, - { url = "https://files.pythonhosted.org/packages/5f/de/79a95ac238c9cae7ae7eb3a18501afc646e3ed61d8d108c725b17bbee301/wrapt-2.5.0-cp314-cp314-win32.whl", hash = "sha256:b58138d19f34e32833e62de5e910bc2a8baae43310b921d783bd39b15227c2dd", size = 100664, upload-time = "2026-09-27T01:40:42.884Z" }, - { url = "https://files.pythonhosted.org/packages/f0/15/32de0f1e6a46a82c773430672562d53203406df14a6d73c93abb59b679e9/wrapt-2.5.0-cp314-cp314-win_amd64.whl", hash = "sha256:1a3c4035d2026b87ef23dd8d165f1f8d3853ee2bd02791cfd22bd8c6226c41ce", size = 105986, upload-time = "2026-09-27T01:40:44.652Z" }, - { url = "https://files.pythonhosted.org/packages/d9/2a/10a7ff69097385de15b3db7d91587a54c26f8025fcbf36a1d9e83a1e0ad1/wrapt-2.5.0-cp314-cp314-win_arm64.whl", hash = "sha256:def66258d97ebf1e4e97def12c5daa542d1cc728a3da83ed3a43933f56df6dab", size = 103718, upload-time = "2026-09-27T01:40:45.956Z" }, - { url = "https://files.pythonhosted.org/packages/f7/01/963f893b1906ac6c2aecb777c36e9ab2156a4cf89fabf6125e953ec4ad52/wrapt-2.5.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:23a9d6cb6413359b76f030d6bbb75340b7669c69da245dde2919a4c93708993b", size = 109290, upload-time = "2026-09-27T01:40:47.22Z" }, - { url = "https://files.pythonhosted.org/packages/cd/6c/30e04d2b1284de2eea5411850008e0411d1876bf4552dc0990c904a0a783/wrapt-2.5.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:21cfe343ef9c2deb865ad0d5c57822266447c88dcf6d8805dd8c363fe367f30c", size = 109597, upload-time = "2026-09-27T01:40:49Z" }, - { url = "https://files.pythonhosted.org/packages/6a/34/3980fe5a899b69454f66db2991c144ecc828dbbd355ce6cd7b881056ebbc/wrapt-2.5.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0dfc38cb672af51fc29696ba9c6f05d2315f5e62c4af2564e50f07f81198a163", size = 283840, upload-time = "2026-09-27T01:40:50.406Z" }, - { url = "https://files.pythonhosted.org/packages/b6/b4/b37001235fd5871b3f31941229f8fef608279353b772dab3ccb248fd8726/wrapt-2.5.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:181a45000506a6382eb337354ca7e8525690702f1ccf2eae4f23a210ff339543", size = 293622, upload-time = "2026-09-27T01:40:51.868Z" }, - { url = "https://files.pythonhosted.org/packages/09/b3/9b751c6268fa2111efc7e43895105bc0f60a83896b08581009e77563f8c7/wrapt-2.5.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:58b2a87c65cbfb20917ec48ace47f71b1962c1f81dbf18a4052e3037abf72028", size = 262840, upload-time = "2026-09-27T01:40:53.297Z" }, - { url = "https://files.pythonhosted.org/packages/47/7d/b7b51d601981ccc1f7b9e6023991548dec43dc9d40317597fbe0085bc876/wrapt-2.5.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0ea62bc142f4fa8b2e0ab058f50699ccd679ef6199c8fa3cc1c2396c7a659000", size = 288781, upload-time = "2026-09-27T01:40:54.756Z" }, - { url = "https://files.pythonhosted.org/packages/d1/82/1a84f288246905d0a71d44aa1f470ff8c75df2b96ef791d2938124449cb0/wrapt-2.5.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:425349a99b8c9540399d36620c376dc26e6aca93071cd6fafa239c2f1b5d53a4", size = 260116, upload-time = "2026-09-27T01:40:56.667Z" }, - { url = "https://files.pythonhosted.org/packages/5a/0e/0572224d1c4a3f0846f82614702ec3110d45c843dcc7781c5f33779e1fdc/wrapt-2.5.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:fe09aac4837ec720af606a493e814dcc3f65631984e1b7231b589efcf9917024", size = 278485, upload-time = "2026-09-27T01:40:58.443Z" }, - { url = "https://files.pythonhosted.org/packages/76/44/5a5c111f8ac6dd15f54437c2161588431d3924718a7e4de59c471cd794e9/wrapt-2.5.0-cp314-cp314t-win32.whl", hash = "sha256:bc5607c1911c92530cb402ea90d818933cffb28bd8de9b453a2542279816d8c7", size = 103157, upload-time = "2026-09-27T01:40:59.995Z" }, - { url = "https://files.pythonhosted.org/packages/e6/80/96cc2da58cbc0893f5165f6a0f4f9cb75d7574f409022ad792aa80a0ff3f/wrapt-2.5.0-cp314-cp314t-win_amd64.whl", hash = "sha256:7138b0e7990e5555a905c519e8dad17c1da1f20e08b283e202c414229065740f", size = 109683, upload-time = "2026-09-27T01:41:01.43Z" }, - { url = "https://files.pythonhosted.org/packages/c7/70/10dab499970e66c926ba6d404ff456318b68092b8ad0c4608a52160e43a2/wrapt-2.5.0-cp314-cp314t-win_arm64.whl", hash = "sha256:a5bb346a34499e091e4fa23df58251ad192173c088e5413d893ca1c730c133c7", size = 105953, upload-time = "2026-09-27T01:41:03.041Z" }, - { url = "https://files.pythonhosted.org/packages/ab/18/5154954f69afdbf5bdeddc07ed60f30bf6e83ed1e9fb6f96c66cc20e4223/wrapt-2.5.0-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:a45a5249a6965d91aac9f991fda7c17e6b8b41fe91592a6099f182bf53c82724", size = 106186, upload-time = "2026-09-27T01:41:04.472Z" }, - { url = "https://files.pythonhosted.org/packages/5d/43/7db9952d26b1a89afcf22da8ec7948e6ca55c48721488d53f84754eed89d/wrapt-2.5.0-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:cbd45dfba6b5c1bfbabe1feb3c0f117fbd62416e98268d9a7cd9ad8802875356", size = 106155, upload-time = "2026-09-27T01:41:05.795Z" }, - { url = "https://files.pythonhosted.org/packages/57/b6/41a0d7f9cf1f8e6aaecbf4b5b4eaacf4036fa3396c7d814364e07728a041/wrapt-2.5.0-cp315-cp315-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:bc6491d3008ecabf685b0746f03ad8241a0950336939b14addb03af39b51a316", size = 251754, upload-time = "2026-09-27T01:41:07.26Z" }, - { url = "https://files.pythonhosted.org/packages/55/d4/dd2de1260a490cd55d083b3c1bc47a36aff0e8363249d108d3b34c091c0e/wrapt-2.5.0-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:47abb2bb7f15b416e72fbe5e68e49a6f09331dae6af1ca6055f5aa2251d2bd2f", size = 250768, upload-time = "2026-09-27T01:41:08.681Z" }, - { url = "https://files.pythonhosted.org/packages/4c/40/d08297feb5728cd6d3c1133633cad0249c2b7a82eb0213062ebab9cc1266/wrapt-2.5.0-cp315-cp315-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7e25e9697f60af41fb86b08697e470b1e7eb6cd6ac0eb25e4b1f519839adc271", size = 232618, upload-time = "2026-09-27T01:41:10.293Z" }, - { url = "https://files.pythonhosted.org/packages/54/52/d8ca61b26c2a34927cc999fc250f1018f415741691581280e6a76cced736/wrapt-2.5.0-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:bcd42e7b69c8c1e33a29b79b28de03bdc08876a49745830f9162a3af860e06d0", size = 248043, upload-time = "2026-09-27T01:41:12.132Z" }, - { url = "https://files.pythonhosted.org/packages/8c/5e/ba02904736e2d3b05afd7447b4ddff677ce61762265939555541adb8c668/wrapt-2.5.0-cp315-cp315-musllinux_1_2_riscv64.whl", hash = "sha256:36703cafc2ec059e118c2175e6cb7ad7299c2924aecdcb1b7a7ebbf7a3e20c19", size = 229577, upload-time = "2026-09-27T01:41:13.746Z" }, - { url = "https://files.pythonhosted.org/packages/a0/94/23968c18a6e37a8a130706dc52ccf4344a71f1fe53c99965eb0a7715a459/wrapt-2.5.0-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:1ebc0d09906057ada57a32158a657364ca40b8f86e604da3e7d979069b601502", size = 239372, upload-time = "2026-09-27T01:41:15.459Z" }, - { url = "https://files.pythonhosted.org/packages/ee/ee/8437e73fffa57c96a5f0f6721f942ccf7b1461b64cd965f83e2181e25252/wrapt-2.5.0-cp315-cp315-win32.whl", hash = "sha256:76fb341d5a707a4f211631b8c77259b2df149147e9d9c245ae6ba3dd936bfdfb", size = 100673, upload-time = "2026-09-27T01:41:16.925Z" }, - { url = "https://files.pythonhosted.org/packages/37/6d/6d640f98197d68e61fbeaded20478e4aa840f9c88d11e7a49c8d6d14ae15/wrapt-2.5.0-cp315-cp315-win_amd64.whl", hash = "sha256:6269637d9a54990430b4a769df15833935a46c4d004d9fe8a153bbadf0b9a097", size = 105999, upload-time = "2026-09-27T01:41:18.372Z" }, - { url = "https://files.pythonhosted.org/packages/0d/3e/8b8a0c94f2698c99afb499510824b107c81e9c4a34b3db7e877233a634b7/wrapt-2.5.0-cp315-cp315-win_arm64.whl", hash = "sha256:1122f4f9e363da804ccba05a9f0f39baa3716eb82452c929258bc3f1420c899b", size = 103728, upload-time = "2026-09-27T01:41:19.729Z" }, - { url = "https://files.pythonhosted.org/packages/37/96/88f08f58759ee3739544cc51941853e946df1f400ba60c6efbeccfb589d7/wrapt-2.5.0-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:e3c6fb1c1a516881353186bed9cfcb8899f968c03b3509720c79db0d967acf3b", size = 109275, upload-time = "2026-09-27T01:41:21.213Z" }, - { url = "https://files.pythonhosted.org/packages/29/cc/68846aa92814d0704d4b128a30d7707368be6951e8a8f42f1254ce4ab31c/wrapt-2.5.0-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:0a7a369e7fca9fc8c2c50df634382009b09af416853da3d4515e4bb048a5b9ee", size = 109610, upload-time = "2026-09-27T01:41:22.647Z" }, - { url = "https://files.pythonhosted.org/packages/fe/87/bbaa188dace348b6a403bbf3cc483f3f419ac97700274340e17f2dbc700e/wrapt-2.5.0-cp315-cp315t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:88bb24b9fdccb1d805258d5648533206eb58c58b6554985c47db53a89c11be85", size = 284602, upload-time = "2026-09-27T01:41:24.094Z" }, - { url = "https://files.pythonhosted.org/packages/be/2e/8a3309b0cbd3ab809ee6b76812c3be211f5a08732f321f131d26bb4f078a/wrapt-2.5.0-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2e3d110d7f99644946f249927c346d9dba507a78815d1bcebdbf0d94c14c5649", size = 294893, upload-time = "2026-09-27T01:41:25.654Z" }, - { url = "https://files.pythonhosted.org/packages/15/b7/eda8bbdb6a3b7343d2c71e23fb0ebfc15c12fd470e3cce7ea42f7a57aaac/wrapt-2.5.0-cp315-cp315t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1ffb2823c95dbeb8a47fedfba9636b2afeb0a8ef94b66df97bd081bdfe5a263f", size = 264658, upload-time = "2026-09-27T01:41:27.375Z" }, - { url = "https://files.pythonhosted.org/packages/41/f0/589bad71ca3ce5444a626ee10d657fd6aa5080ada76b3bcd4550468aa16e/wrapt-2.5.0-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:557ebf4ce5568588368675014a2540405db687c2e4c7ad1eb83aa7e857be1864", size = 289974, upload-time = "2026-09-27T01:41:29.032Z" }, - { url = "https://files.pythonhosted.org/packages/dc/97/c48f3c820ae6687e87b537041cd49ad4caa41e05a8f0d8ec08e449ca303c/wrapt-2.5.0-cp315-cp315t-musllinux_1_2_riscv64.whl", hash = "sha256:05246a100da68259af521b88788f131ba005465f1c95d358cc3c03ec5e351b52", size = 261945, upload-time = "2026-09-27T01:41:30.941Z" }, - { url = "https://files.pythonhosted.org/packages/fd/ad/d96898f500cb1e4185474bac6cb14bb7ea670a32f37e8c354a0c647e3a92/wrapt-2.5.0-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:c932273bc43b068538f3874fa5e6c2a60f33fa0b11c1ebc7768652f6a0608943", size = 279744, upload-time = "2026-09-27T01:41:32.617Z" }, - { url = "https://files.pythonhosted.org/packages/5d/8b/7981d2ac838d0dc07e81145cb1c9911812e060b98e8c5a47c84fb92f8f81/wrapt-2.5.0-cp315-cp315t-win32.whl", hash = "sha256:2fd8a61c31220840c7f52621cf51c961af5058bd009a55bcdf4a6732bdb13b35", size = 103124, upload-time = "2026-09-27T01:41:34.117Z" }, - { url = "https://files.pythonhosted.org/packages/2a/1d/374cec175b6087e1067a780374d81d74ca966e1e5e39e666402eaee19a65/wrapt-2.5.0-cp315-cp315t-win_amd64.whl", hash = "sha256:1d4da5f0e9a719471502b0db80d5c97503aeca796b7aeb9ab8f47403b2be76e6", size = 109671, upload-time = "2026-09-27T01:41:35.553Z" }, - { url = "https://files.pythonhosted.org/packages/c7/93/fc9e477a1771bec52d7677eee5e8404afe662a47efe1859405a18fff206c/wrapt-2.5.0-cp315-cp315t-win_arm64.whl", hash = "sha256:78b7bdaa8b27b7f7607c66bdb6ab15c1dcbd9e9a1556a253a347dad511f615d1", size = 105969, upload-time = "2026-09-27T01:41:36.973Z" }, - { url = "https://files.pythonhosted.org/packages/87/7d/5ed859fad4b5eddd598a846150aaab2703730ed4886c5c5e03b0df0cfdd5/wrapt-2.5.0-py3-none-any.whl", hash = "sha256:107eea1a511e98a3a5033b0c2cb403fbb37f05dee6ac1fb85c0460d311ec278c", size = 81586, upload-time = "2026-09-27T01:41:55.479Z" }, +version = "2.5.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/04/22/89e2f3bdae5cb34e0cab0cd86d7172dbf418de4b46c9b17b9c7a560dfa44/wrapt-2.5.1.tar.gz", hash = "sha256:f595bb0185aab3e9dc31950c95d914f56ea8278810c3b928f3426e12ed6d27bc", size = 184455, upload-time = "2026-10-14T00:39:39.24Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c4/2b/0f2ae9e355a0491c202a1331ec405d794c249f4fe9b4db952c0246909786/wrapt-2.5.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:aed178902c2386d7c5d3d23eb96d32c100e34cb8c2390e7ece0e4901ae43f0e7", size = 105587, upload-time = "2026-10-14T00:37:01.614Z" }, + { url = "https://files.pythonhosted.org/packages/0a/54/a5b9904d341ae255bc5618ac43830ab68ed6420dac62af9a305a6191062a/wrapt-2.5.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1910be5adc0232cc6e8c0673bf3f41c2ee724547543526bed8d00734458e7bc5", size = 105759, upload-time = "2026-10-14T00:37:03.02Z" }, + { url = "https://files.pythonhosted.org/packages/2b/bf/8edaec939d7411a58bccb4dc4310246caea5050576ff91b5f5abf6079a9c/wrapt-2.5.1-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c25c594f58ecb676358d6d6b0ff068b8bbbc506dc831c6d17876460c66ce39c2", size = 240110, upload-time = "2026-10-14T00:37:04.448Z" }, + { url = "https://files.pythonhosted.org/packages/9e/8c/18ad7f24c82cbf689324521abe4dc90078d20a5498d991ec83e03dc60c85/wrapt-2.5.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e85a9db9e5a5ccc326edb19e35a5106ba16e451d570a2ec8ea9deb1ea52a3c42", size = 241618, upload-time = "2026-10-14T00:37:06.1Z" }, + { url = "https://files.pythonhosted.org/packages/d2/1b/607e1fc9a8e8838f1a8516f5b87565c9dc415b474ec09479f9443a5a7305/wrapt-2.5.1-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:2c642a83b6703804b571caa3b8b205aacd341b1b37e2b2d89cd70e03e0e9caa6", size = 226604, upload-time = "2026-10-14T00:37:07.899Z" }, + { url = "https://files.pythonhosted.org/packages/b4/fb/6f637ca3e71ea046148dd17622e023df159a16d99762aad4bbdbac11c76e/wrapt-2.5.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:920f700ef41ee774a1e4778c1f4295e117f1ff3435a7e0cd3e997d10da819d32", size = 239265, upload-time = "2026-10-14T00:37:09.45Z" }, + { url = "https://files.pythonhosted.org/packages/f3/fc/b746f3a72ee7f56d238debeb3842b2569511cf357f5a35f455e24258dc4f/wrapt-2.5.1-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:3f93ceb0ac4896de45d5a45a8f4e69474da583440589de10b362ddc1db4691ed", size = 223864, upload-time = "2026-10-14T00:37:11.363Z" }, + { url = "https://files.pythonhosted.org/packages/97/12/290a6385393fddcb01bcc83d78c16a24c50a392fe72fb4936999bfe573ec/wrapt-2.5.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a88370a7d89fcb1c4953a87673fdd7b4a0eb14a1a4dfce49771f0c827ef44893", size = 229426, upload-time = "2026-10-14T00:37:12.868Z" }, + { url = "https://files.pythonhosted.org/packages/29/af/b52bb81d4217ed7f0fe285de6754e44cb5e6a8b316fc78c605743f463709/wrapt-2.5.1-cp311-cp311-win32.whl", hash = "sha256:12bee472452019706fa1d4ead093f52a9683b4fe6617953e15bab9acdfdc013f", size = 100684, upload-time = "2026-10-14T00:37:14.59Z" }, + { url = "https://files.pythonhosted.org/packages/e6/d4/ae9ca837038a7a9aadb906eeca76b42df54e299edb29f8ac2cbad1d55b4f/wrapt-2.5.1-cp311-cp311-win_amd64.whl", hash = "sha256:ce3889e3815f97d46414eb574bffdd9bdb41ff70f503097e2707615a87d4e92c", size = 105878, upload-time = "2026-10-14T00:37:16.057Z" }, + { url = "https://files.pythonhosted.org/packages/15/5e/0605567a81c7105446cec682bc2235172a37e2bc817e2de3f5bad95ee969/wrapt-2.5.1-cp311-cp311-win_arm64.whl", hash = "sha256:ca7b967e96384abdf7e7182c79f71529997981ece8169f8a8ddb31bc5b57cbec", size = 102825, upload-time = "2026-10-14T00:37:17.492Z" }, + { url = "https://files.pythonhosted.org/packages/66/9b/c7f97d5493a33b5ed01d3c85745f9bdfdd2e5c2785471b8d8b55a3c273d6/wrapt-2.5.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6e3eff05ae616671b40d7ad0a504210329e4adc9fb91415663570aca93c5f5cc", size = 106452, upload-time = "2026-10-14T00:37:18.951Z" }, + { url = "https://files.pythonhosted.org/packages/7f/b0/335b0af2930938678fcde954b29780b26308961b93df5e0192fc182e8b7e/wrapt-2.5.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c44dd9881626da7d621c23805f26726f6b023cf3e9755f48d092bc9cbef4a8e7", size = 106087, upload-time = "2026-10-14T00:37:20.392Z" }, + { url = "https://files.pythonhosted.org/packages/4a/5a/2a34ba5a468e9d3d6e5b0733280e1ae3c850bfc5f1d681fc0e97f564d1f2/wrapt-2.5.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:bfaa998ceeea4d0aa72b40cdd0023d19409504e244b439ff2aa9f01729341c5f", size = 250438, upload-time = "2026-10-14T00:37:21.882Z" }, + { url = "https://files.pythonhosted.org/packages/b3/d5/3d4ad322af74d3ab2a14f69ba844cdd3edefb555edfc1c1976ec0112d5c4/wrapt-2.5.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d6d274ec50a5b208be75596dc44ea253e65deaa6ee3a600babc86dafbb957dfc", size = 250594, upload-time = "2026-10-14T00:37:23.497Z" }, + { url = "https://files.pythonhosted.org/packages/37/1a/3cbf48425ec2c66aa9645218458da1e19e315abb9766604d3c49e579076c/wrapt-2.5.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1a96e2671c60f9f09ae547b5a815cecb29af16caa68d73693387d0028788cb32", size = 229856, upload-time = "2026-10-14T00:37:25.029Z" }, + { url = "https://files.pythonhosted.org/packages/cc/e7/b2ea57f4c51258659200565af8617d76992b0fe65e6aad7162dd5720ef05/wrapt-2.5.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:729d644b6acaf4846a4ef81b037857b66a01dea6d227f827c6d71c0b6d656d6c", size = 247153, upload-time = "2026-10-14T00:37:26.67Z" }, + { url = "https://files.pythonhosted.org/packages/9d/c1/4714743e672ed1084a035a2a4f0edeef7838399753b4856a0dc46ef9487d/wrapt-2.5.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:859f67bfc31eb7ab55f237b629cd4ab0441b075912446481f910f7d02066811e", size = 226726, upload-time = "2026-10-14T00:37:28.425Z" }, + { url = "https://files.pythonhosted.org/packages/91/e3/c00401bcc3485eb9937c3fe4a1cc8fc3b61800b1378ea3a143ea1c30f6f6/wrapt-2.5.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:29b62e87fcd6a1893f669abfd02a596a7fc5cfa79fa57e42c4e650a6c170c67b", size = 238843, upload-time = "2026-10-14T00:37:30.075Z" }, + { url = "https://files.pythonhosted.org/packages/76/b5/c16759fb0721e63df92b576c2222ce1f11690a8b300fd91b49c55436865c/wrapt-2.5.1-cp312-cp312-win32.whl", hash = "sha256:f1c911818fb076910ef509f2298dfcb966a54a6ff068eebd459632102cf589fb", size = 100488, upload-time = "2026-10-14T00:37:31.625Z" }, + { url = "https://files.pythonhosted.org/packages/22/d5/39d5a704650f18799f37841442b464edb81cf2015f006eaef26068acc6ea/wrapt-2.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:c39c7130ea0702c4ab0faf12da1df1e02d5174305c17edf02309e2f058c4114f", size = 106048, upload-time = "2026-10-14T00:37:33.188Z" }, + { url = "https://files.pythonhosted.org/packages/21/bf/65743adeeb5476920c62dad6cded7bc8789e19bd4f9a336d4ac812adb8de/wrapt-2.5.1-cp312-cp312-win_arm64.whl", hash = "sha256:e089a22ff5af1290b8c759a610830bdb2a829ef9c3d7797e4ee32c2f795ed482", size = 103200, upload-time = "2026-10-14T00:37:34.673Z" }, + { url = "https://files.pythonhosted.org/packages/e4/6d/cfe55762435f36107815d56a2cfbebe7e3129b593c47a670c6eb1d7917d3/wrapt-2.5.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f98eaf784cd12bc69c77af398084174531007cd81849c962163ccfc6e791f3ea", size = 106068, upload-time = "2026-10-14T00:37:36.087Z" }, + { url = "https://files.pythonhosted.org/packages/01/b9/41642877fe741db56d240833c8822188b663c4c5d52beb087964774035d4/wrapt-2.5.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ab6db7d2a18d366cc57c2228253cf26443190aba0a6dd0939b3c1e8ac6e29e2c", size = 106190, upload-time = "2026-10-14T00:37:37.768Z" }, + { url = "https://files.pythonhosted.org/packages/37/62/20edad100b93552ec5c172e509a9db898a73d5043ae701fcb6e9986f9d33/wrapt-2.5.1-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f1630201b0e2a96bb26304b7adfbd91a4ef486abb5a4c48377444a0bed749f37", size = 248519, upload-time = "2026-10-14T00:37:39.321Z" }, + { url = "https://files.pythonhosted.org/packages/3d/e9/8d81185bc9a40cfb43d91fc70a1e80ecde752c95dc98f5452cae82037976/wrapt-2.5.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d800c7689154622b0ba2922ceca44a3cf2ef61c3b9a4c4eeb1d8b3050d7ededa", size = 248488, upload-time = "2026-10-14T00:37:40.96Z" }, + { url = "https://files.pythonhosted.org/packages/8a/88/8431df4fd81f0dfa83e8ede463eed311d083c5a279a56891dc0396b07b0e/wrapt-2.5.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5b53000b424dc2133eaaf22838a2352d3497f5d7c2e7d9a2acfe675ab7225bb1", size = 227392, upload-time = "2026-10-14T00:37:42.599Z" }, + { url = "https://files.pythonhosted.org/packages/db/8a/ee6f8542eeccad6874faf0b7b2e129952c527a482f1d28940e2111fec2d6/wrapt-2.5.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:76f230a9b07e3cb66646d265398f579abb6128b1bb4cb97c74b1ae5d09e96f31", size = 244972, upload-time = "2026-10-14T00:37:44.209Z" }, + { url = "https://files.pythonhosted.org/packages/4d/1f/32c59e7fd522409f3863dfecdab5315ee9ba37f96020b6f0adee9d223310/wrapt-2.5.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:fd3f878a4aac3c262447ddf43c5f4c18fc67dfc3ba69c4fb1c7a4c4af96abe7e", size = 225316, upload-time = "2026-10-14T00:37:45.948Z" }, + { url = "https://files.pythonhosted.org/packages/ae/d6/1b9abc1244592034c5db744571e17d663f0f1b0ce6c8ba279c60f6f9c3a8/wrapt-2.5.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0c9480bdee340a1602cae5a777146ab4be3e384fdcb569fffdf8721032314645", size = 237145, upload-time = "2026-10-14T00:37:47.535Z" }, + { url = "https://files.pythonhosted.org/packages/f0/ce/8f3b5482f768c1d60fd2557d049c766543fef5ec707037cb410a57eb65ee/wrapt-2.5.1-cp313-cp313-win32.whl", hash = "sha256:dc401274fcc7b15b3b2c12df2ff34024a11925243a7d3daee91c6d7d14f9addf", size = 100462, upload-time = "2026-10-14T00:37:49.21Z" }, + { url = "https://files.pythonhosted.org/packages/7b/dc/6a5735874ea79816f85c1ec9d92139d7073c20d1881c15ff2108c211354b/wrapt-2.5.1-cp313-cp313-win_amd64.whl", hash = "sha256:09b1893ee4063706574c1813abf479b8b51926633fbdb6f96aab8dc7b0976668", size = 105870, upload-time = "2026-10-14T00:37:50.745Z" }, + { url = "https://files.pythonhosted.org/packages/08/83/a4e8b5a5a32f8dfc5dad8344f1e2b908f7d8d84b11c3c336bf7f79a5144a/wrapt-2.5.1-cp313-cp313-win_arm64.whl", hash = "sha256:f280c115ea64eff3dcbd68a668ce3f63476a4ba386bbabb318017e286196ea2c", size = 102903, upload-time = "2026-10-14T00:37:52.323Z" }, + { url = "https://files.pythonhosted.org/packages/25/3d/ec1937283863bbe0d90528e09f2b27cfc0dd7e608fc2b65c804967dee369/wrapt-2.5.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:cf63fffcdcd8c60f223d3967bb92cc4fc2e8b46f09e75b67a6a75e6f47c0fc43", size = 106439, upload-time = "2026-10-14T00:37:53.853Z" }, + { url = "https://files.pythonhosted.org/packages/93/39/cca8afb80dbb9fce6103e59db507a9415291c4dbe97ea055875ff62901fb/wrapt-2.5.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:9f0750cbc2e29e4f3c9529d3587d4e7ed8f60638ceafb80b87a95833b0c5acd9", size = 106378, upload-time = "2026-10-14T00:37:55.386Z" }, + { url = "https://files.pythonhosted.org/packages/aa/a0/e784d7a9fd277a2ee395490ec4df96608b7fe218bb1ef7dced2a1caea490/wrapt-2.5.1-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:3cf273b7e8d2038abb7f0a8c6550aff4f617b9d486a9965c8e8acc96a3a04de9", size = 251505, upload-time = "2026-10-14T00:37:57.022Z" }, + { url = "https://files.pythonhosted.org/packages/81/56/01ebc86b88056f5782b9d50f962fb398c6b82aa11efc98f50c64896d94e3/wrapt-2.5.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:380f72610181883f66b41442cfc7c0f7552b42169efb2113def26e6380013d37", size = 250744, upload-time = "2026-10-14T00:37:58.652Z" }, + { url = "https://files.pythonhosted.org/packages/5f/5c/0e8eaaf31e2d6e7bf13c6eae2fd5b85eaa24e21e06466e6e7a0f35532689/wrapt-2.5.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cef2a8f006410b6134a0d273ec037fea8cc7a6a914f1bd7555ad9788ad788c6e", size = 231598, upload-time = "2026-10-14T00:38:00.505Z" }, + { url = "https://files.pythonhosted.org/packages/6a/6b/6a3e257e65de6cc0027e78b423942697c74451520ba3797fd86455ec4df8/wrapt-2.5.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:9bad4dbb4e61624fcce5f301e37f9e743ecae4f1259a3777b3207eb7eba3dccd", size = 247998, upload-time = "2026-10-14T00:38:02.63Z" }, + { url = "https://files.pythonhosted.org/packages/22/38/b2b8f3ee22b05f33f5aefb052844a36a0d7edd1eaff2ff4249f97792bea8/wrapt-2.5.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:9a34640eb6295f33ca23462977de275fe8f3a50ab339b8918b96d69a7451e2e1", size = 228795, upload-time = "2026-10-14T00:38:04.317Z" }, + { url = "https://files.pythonhosted.org/packages/29/39/e6c86552286ac27b855042fb9c229c580ed2d4c3ced5d0a1dad5f5ee8c11/wrapt-2.5.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:26313f38d18d40a9975123a4ebff9da125ec63ab9ece4f05320a3d8d37d2c1fe", size = 239090, upload-time = "2026-10-14T00:38:06.119Z" }, + { url = "https://files.pythonhosted.org/packages/e7/7a/aea209f64e894573935b17de26ecfa0efe139b63f170a60be9f13734e0f5/wrapt-2.5.1-cp314-cp314-win32.whl", hash = "sha256:0591e6eace0d186c9ef1ecd1244be5a04e98041424cfca425b684ffe4f0d8030", size = 100919, upload-time = "2026-10-14T00:38:07.874Z" }, + { url = "https://files.pythonhosted.org/packages/57/24/847096aa49d42990137ed3b940743c8a6da806d39f6c455317114e8ebfde/wrapt-2.5.1-cp314-cp314-win_amd64.whl", hash = "sha256:25ed8b1b39234140d5b5c6a273130c7595e0abece417c3ca3cb378fcea5cd0fe", size = 106243, upload-time = "2026-10-14T00:38:09.49Z" }, + { url = "https://files.pythonhosted.org/packages/9c/ff/1cdc742133b9fb8558cdf42b2a6c2699bd7c72f7d0606286ec2f9142e20a/wrapt-2.5.1-cp314-cp314-win_arm64.whl", hash = "sha256:6201c7e122f40060a9b50696d80deec8f93b1a235ec0443f51d7a8a42f7044a6", size = 103974, upload-time = "2026-10-14T00:38:11.354Z" }, + { url = "https://files.pythonhosted.org/packages/fc/6f/c32dc64900f1970a7f991ff5d06788cd636ca2f3ee2f99709d82577ca198/wrapt-2.5.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:da847332447db5505162759a4cd5ac374eb8b74841fe97a98ef3de14edd2586d", size = 109552, upload-time = "2026-10-14T00:38:12.965Z" }, + { url = "https://files.pythonhosted.org/packages/e9/73/a9c8cc82b166e3de42f5fbd88089d2ef9b72e87aac7d6cdddb070335c20b/wrapt-2.5.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:9f437dd704abc4ee1bd03bb2d796d362d0e75915e8f3113a7900b3b7ec5f8b47", size = 109853, upload-time = "2026-10-14T00:38:14.565Z" }, + { url = "https://files.pythonhosted.org/packages/f6/48/f341d82e69ae47df2755847af1c744ff0732543482dbc2373e5e57676621/wrapt-2.5.1-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:03aa7d2256309b57ddbf317bff2cae5f47e50ea9ae8d582780ebe0b554347b42", size = 284103, upload-time = "2026-10-14T00:38:16.23Z" }, + { url = "https://files.pythonhosted.org/packages/fd/50/b87c6374377b08ee0783b6c5c31cd41a5e103bc54e7e857e800a0a965550/wrapt-2.5.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fcccaa1484f7dd1091602970988ab741491f9f974013c844f70e45ac1196b80d", size = 293883, upload-time = "2026-10-14T00:38:17.986Z" }, + { url = "https://files.pythonhosted.org/packages/38/e0/6d0810ae73f7a5180ec366577588ab3ad55fc1bc7e3623e82d5f8528dd2a/wrapt-2.5.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8078186f719a92693199f1e06c4ec72e1e6d374c2e459da18ed5c39d6966d727", size = 263097, upload-time = "2026-10-14T00:38:20.107Z" }, + { url = "https://files.pythonhosted.org/packages/d6/d3/c890a46f4d395a7935e5a7436f374ceefa362eb612fdd39376dd775e0283/wrapt-2.5.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:1425fcf0e70b27053bd610d57bae975856e7897e3f6ba1456d2b80b9d7fd15d1", size = 289045, upload-time = "2026-10-14T00:38:21.913Z" }, + { url = "https://files.pythonhosted.org/packages/e5/c6/042e30e0d851ca6ea743e6978902527f9166da42d736f074245d1cc54c8f/wrapt-2.5.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:b238e955ba34ef2b8897f358b7b868b41b9a02ffd338014b62985fa91898cc4a", size = 260378, upload-time = "2026-10-14T00:38:23.662Z" }, + { url = "https://files.pythonhosted.org/packages/31/a4/5e65f90bf414c2f1c7eefc3d26c33af01d87ba33c4b879db4e1d4ba7fc3b/wrapt-2.5.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:25eb4d928a9abeaf70ca786a35861b46d1ab37cc4ce49ea70a070dacdead4dfe", size = 278746, upload-time = "2026-10-14T00:38:25.523Z" }, + { url = "https://files.pythonhosted.org/packages/4b/86/17a85475e218e05225a4f6d65237b12b280596e04520df0e3d8841c4eae3/wrapt-2.5.1-cp314-cp314t-win32.whl", hash = "sha256:df6e3a36170cda0d313be50fe5065948e7f12f3a181b38cbc262e9f2ee4824e1", size = 103415, upload-time = "2026-10-14T00:38:27.409Z" }, + { url = "https://files.pythonhosted.org/packages/27/1c/495b3aebbbe5aebf52ae5f9e8ddd0e072a412b9f5a9bc68e5eba43fa26ba/wrapt-2.5.1-cp314-cp314t-win_amd64.whl", hash = "sha256:bc5c0203d383403043fb86c964bd0bab4fcbfb26004ff4bb9c6d02ebc1d608ae", size = 109940, upload-time = "2026-10-14T00:38:29.145Z" }, + { url = "https://files.pythonhosted.org/packages/28/4d/030ecd98da4d052c264290c4fb9f984706c9a19026154c833580e8624a05/wrapt-2.5.1-cp314-cp314t-win_arm64.whl", hash = "sha256:a424e8a9776c06aef6313af1d0e3fe6e0838af4241d0c09eb0a3b46f2c9a5ff3", size = 106214, upload-time = "2026-10-14T00:38:30.795Z" }, + { url = "https://files.pythonhosted.org/packages/90/2b/eec5745baaad284fa19232f47796914134e1ea2dd21e289b012aa7981323/wrapt-2.5.1-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:a18e63910252eb75d8806b4baefbc3a03612502f63eab042e3741b00b719f043", size = 106441, upload-time = "2026-10-14T00:38:32.504Z" }, + { url = "https://files.pythonhosted.org/packages/29/f3/976b0f014a08654289358d41a799c2d24642151b091cfe18a8b91766ee28/wrapt-2.5.1-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:183bf0bb893f783c9d22f953cb01fababb9f618e098763f8e66337b575b0647a", size = 106411, upload-time = "2026-10-14T00:38:34.338Z" }, + { url = "https://files.pythonhosted.org/packages/c7/f4/4b94583d9bec0ff0573a5f10fab295675e3b9a64b701c4609b1a1982c390/wrapt-2.5.1-cp315-cp315-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:a1e823aecb3746b8f9e0aee2e1413887871ee2f5c502a3e0ef8d466dbd4adde1", size = 252017, upload-time = "2026-10-14T00:38:36.142Z" }, + { url = "https://files.pythonhosted.org/packages/5e/3c/4f9ba033343b2935a453188f97866f0bd4f7748748ab307aaefb18be3a0a/wrapt-2.5.1-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bde5d1b37101b1e9dd3da1f35072e2e7028e9c5e3511f7d76d3fdd4d071b7663", size = 251030, upload-time = "2026-10-14T00:38:38.003Z" }, + { url = "https://files.pythonhosted.org/packages/69/a1/704c761913be404ed893d05702eeda5ff96c5d8448271c28b80101202bcb/wrapt-2.5.1-cp315-cp315-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:12d3d2b9d6553df6e2421ab99e1cc5413509076788f57fcb3169f5ce100a19d1", size = 232875, upload-time = "2026-10-14T00:38:40.124Z" }, + { url = "https://files.pythonhosted.org/packages/f0/3a/779ca20fb8c70238069efd0a2b60ea3da450e57c4f103748db0247f52f3a/wrapt-2.5.1-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:521bd5ef2a33171fac08a0a302d51a983c19c3519406c1ee8da7ce29285488da", size = 248302, upload-time = "2026-10-14T00:38:42.138Z" }, + { url = "https://files.pythonhosted.org/packages/03/02/80e13786204ce8e1002edb66d06a3194906c0dbbc038bce7bbda426d0f2b/wrapt-2.5.1-cp315-cp315-musllinux_1_2_riscv64.whl", hash = "sha256:129cab3c7b21e68e693c2819a95c47f3b1c41a834b931154688c83b6aef6bdab", size = 229839, upload-time = "2026-10-14T00:38:44.039Z" }, + { url = "https://files.pythonhosted.org/packages/9d/d1/14c0d041375ae5d0a12445b5c5bc61b109df954cd5bfb852736cd4281cbd/wrapt-2.5.1-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:8a7c078323e6e1534968cb85488c5eb7ee2b9bbd0f8a291095213a763da40dab", size = 239634, upload-time = "2026-10-14T00:38:45.896Z" }, + { url = "https://files.pythonhosted.org/packages/97/6e/dacc92526fbed1013eb903406ce961da6ff2a1e66b7349a253f439b979aa/wrapt-2.5.1-cp315-cp315-win32.whl", hash = "sha256:736c1de0230c6d24327b14684794214167b2c5ebb6332e28a10f504641b600df", size = 100931, upload-time = "2026-10-14T00:38:47.718Z" }, + { url = "https://files.pythonhosted.org/packages/e8/e4/84bbd88554052958ecbbb481a75559b1e40753aba52ec86e3e1efb50ccf0/wrapt-2.5.1-cp315-cp315-win_amd64.whl", hash = "sha256:69fd0fbb3daf7c8c6f5e062847a0061f880f347374d74cf1daba57220fb64cd0", size = 106252, upload-time = "2026-10-14T00:38:49.511Z" }, + { url = "https://files.pythonhosted.org/packages/b7/98/98d4c4524e8af70ccf35b66864be29ea9d232e5a918efc1dbcf5c87a039d/wrapt-2.5.1-cp315-cp315-win_arm64.whl", hash = "sha256:051220e5071fdfb1a6678707c8abb7bbf4824d40f99758394b2b4d64855fb284", size = 103980, upload-time = "2026-10-14T00:38:51.384Z" }, + { url = "https://files.pythonhosted.org/packages/75/d9/4b242519d6d29eabb73cb9e50e645e014eb2c13f022601151953b3e81946/wrapt-2.5.1-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:711e73da3d7983547fc9dd208973b6b0c52640822f5d477910ba24622df6ba64", size = 109538, upload-time = "2026-10-14T00:38:53.124Z" }, + { url = "https://files.pythonhosted.org/packages/58/05/e434f56fcceaafb251cc56c03107ae278e99158466b49f4146f7b33a2532/wrapt-2.5.1-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:5be9816d9de88f02fce23cf55f392403411d9bd9c7ae57fdc965a43b22e2de5e", size = 109874, upload-time = "2026-10-14T00:38:54.941Z" }, + { url = "https://files.pythonhosted.org/packages/23/09/d2c0b34d02804018225279a157307b873c8d0f8452790efdd7f0004387a8/wrapt-2.5.1-cp315-cp315t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4b3f410c416752e1dba53d361e2e6562f22c2c3ec855740dfa5836e061b22571", size = 284866, upload-time = "2026-10-14T00:38:57.081Z" }, + { url = "https://files.pythonhosted.org/packages/71/6f/2b56319c0565d9a6b63eeba2f11f3e699dc324aaa460f1aae079bd4507b0/wrapt-2.5.1-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:094b847491b813b6e6c1775e03770930d75078c0821adf929ac712830951ef25", size = 295154, upload-time = "2026-10-14T00:38:59.007Z" }, + { url = "https://files.pythonhosted.org/packages/3c/1b/9ac4238a1a839457d6b687b9e9c35d57ba1b2050a42d6bf8c93176bc1bdf/wrapt-2.5.1-cp315-cp315t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:26d8ea2ec6818aeb656bd8a9e745a6f1fb0edfcd8f54291ccd94f62eb5f5e3bd", size = 264914, upload-time = "2026-10-14T00:39:01.154Z" }, + { url = "https://files.pythonhosted.org/packages/4f/95/9faed8e5f6e5431edd36b2cfb4f305df520c197ba3639e1c78d11c70a068/wrapt-2.5.1-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:0a526227efe17dd94bd16b123d170f879bce42c15f10eb92495a745f54caa943", size = 290238, upload-time = "2026-10-14T00:39:03.052Z" }, + { url = "https://files.pythonhosted.org/packages/b5/52/cae26590ef8ee46b55aa8b211c507f6e5ec0fbd241a6730bf7da024b5dab/wrapt-2.5.1-cp315-cp315t-musllinux_1_2_riscv64.whl", hash = "sha256:36d7d0ad593c4f1a651e4032de834db59aee1a929ee396cd483895b673328e51", size = 262208, upload-time = "2026-10-14T00:39:05.008Z" }, + { url = "https://files.pythonhosted.org/packages/00/f3/34e5008307be4169592e99de52946cd8800b98097e2a145da11484c7b3e4/wrapt-2.5.1-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:89d9a8607b7028054bb6fd01d437f205534a5d59d53c3665d15949a99a2fce0d", size = 280005, upload-time = "2026-10-14T00:39:07.048Z" }, + { url = "https://files.pythonhosted.org/packages/f3/f9/64e000aa84a88c52a481c7c8b011c80a8ae60bd5f68598e567a918a624a0/wrapt-2.5.1-cp315-cp315t-win32.whl", hash = "sha256:ad81bf81b0a0b6c6ec74169638202851962843e86749570c463eecc55072f93b", size = 103378, upload-time = "2026-10-14T00:39:09.336Z" }, + { url = "https://files.pythonhosted.org/packages/2f/e4/69efa7c6e8535c5188e041ac278079949fb2daaa97e6f08beb91cf31b3d1/wrapt-2.5.1-cp315-cp315t-win_amd64.whl", hash = "sha256:d5b665a43fe0d3b390cbdd3c003d61c92fa07bd5e3fb1ed3f47920c2d03cd9fd", size = 109928, upload-time = "2026-10-14T00:39:11.156Z" }, + { url = "https://files.pythonhosted.org/packages/13/77/6e414b3388b9f1ecb76107ef4a2aae501f1bdfcab85c8e34ef78f7db22db/wrapt-2.5.1-cp315-cp315t-win_arm64.whl", hash = "sha256:6405ff2160af9d59132ebb076eda0304db44d9d09809582932412ef7c0788a36", size = 106226, upload-time = "2026-10-14T00:39:13.061Z" }, + { url = "https://files.pythonhosted.org/packages/bc/0c/7da7513ddcc8f1d831ec4bfbedc9f7f174ecb91042bc16916fc1e0d06b22/wrapt-2.5.1-py3-none-any.whl", hash = "sha256:c6e6c226b1ca5402d7ae5fb34a0d21f1b49124fe4200e5884d1e19e53c47ac1d", size = 81849, upload-time = "2026-10-14T00:39:37.441Z" }, ] [[package]] @@ -3328,7 +3355,7 @@ version = "2026.9.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, - { name = "numpy", version = "2.5.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "numpy", version = "2.5.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, { name = "packaging" }, { name = "pandas" }, ] From 6bb2e200e86a6f313aa32b5e540acdd7cdc3855e Mon Sep 17 00:00:00 2001 From: Lukas Bindreiter Date: Tue, 27 Oct 2026 15:01:00 +0100 Subject: [PATCH 4/4] Fix typing issues --- .../tilebox/workflows/observability/tracing.py | 6 +++--- tilebox-workflows/tilebox/workflows/runner/executor.py | 4 ++-- tilebox-workflows/tilebox/workflows/runner/task_runner.py | 6 +++--- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/tilebox-workflows/tilebox/workflows/observability/tracing.py b/tilebox-workflows/tilebox/workflows/observability/tracing.py index b3cb391..43001ef 100644 --- a/tilebox-workflows/tilebox/workflows/observability/tracing.py +++ b/tilebox-workflows/tilebox/workflows/observability/tracing.py @@ -1,7 +1,7 @@ import atexit import logging import os -from collections.abc import Iterator +from collections.abc import Generator from contextlib import contextmanager from datetime import UTC, datetime, timedelta from typing import Any, Protocol, cast @@ -123,7 +123,7 @@ def _configure_provider(self, source_provider: TracerProvider) -> None: self._tracer = provider.get_tracer(_INSTRUMENTATION_MODULE_NAME) @contextmanager - def span(self, name: str, *args: Any, **kwargs: Any) -> Iterator[OTSpan]: + def span(self, name: str, *args: Any, **kwargs: Any) -> Generator[OTSpan, None, None]: parent_task_id = getattr(get_current_span(), "attributes", {}).get("task_id") with self._tracer.start_as_current_span(name, *args, **kwargs) as span: if isinstance(parent_task_id, str): @@ -150,7 +150,7 @@ def _configure_provider(self, source_provider: TracerProvider) -> None: @contextmanager -def start_job_span(tracer: WorkflowTracer, job: Job, span_name: str) -> Iterator[OTSpan]: +def start_job_span(tracer: WorkflowTracer, job: Job, span_name: str) -> Generator[OTSpan, None, None]: """Start a new span for a workflow job, using the trace parent from the job to continue the trace.""" context = _PROPAGATOR.extract({"traceparent": job.trace_parent}) with tracer._tracer.start_as_current_span(span_name, context=context) as span: # noqa: SLF001 diff --git a/tilebox-workflows/tilebox/workflows/runner/executor.py b/tilebox-workflows/tilebox/workflows/runner/executor.py index fcef73e..db4f740 100644 --- a/tilebox-workflows/tilebox/workflows/runner/executor.py +++ b/tilebox-workflows/tilebox/workflows/runner/executor.py @@ -4,7 +4,7 @@ import inspect import json import logging -from collections.abc import Awaitable, Callable, Iterator, MutableMapping, Sequence +from collections.abc import Awaitable, Callable, Generator, Iterator, MutableMapping, Sequence from concurrent.futures import ThreadPoolExecutor from contextlib import AbstractContextManager, contextmanager from contextvars import copy_context @@ -312,5 +312,5 @@ async def _await_execute(result: Awaitable[None]) -> None: @contextmanager -def _noop_context_manager(task: Task, context: ExecutionContext) -> Iterator[None]: # noqa: ARG001 +def _noop_context_manager(task: Task, context: ExecutionContext) -> Generator[None, None, None]: # noqa: ARG001 yield diff --git a/tilebox-workflows/tilebox/workflows/runner/task_runner.py b/tilebox-workflows/tilebox/workflows/runner/task_runner.py index dbb692e..959b541 100644 --- a/tilebox-workflows/tilebox/workflows/runner/task_runner.py +++ b/tilebox-workflows/tilebox/workflows/runner/task_runner.py @@ -1,7 +1,7 @@ import random import signal import threading -from collections.abc import Callable, Iterator +from collections.abc import Callable, Generator from contextlib import contextmanager from datetime import timedelta from multiprocessing import get_context @@ -164,7 +164,7 @@ def run(self) -> None: lease_renewer(self._url, self._token, self._new_leases, self._done_tasks, transport=self._transport) @contextmanager - def lease_extension(self, task_id: UUID, task_lease: TaskLease) -> Iterator[None]: + def lease_extension(self, task_id: UUID, task_lease: TaskLease) -> Generator[None, None, None]: """Context manager to extend the lease for a task.""" logger.debug(f"Task {task_id} started") self._new_leases.put((task_id, task_lease)) @@ -397,7 +397,7 @@ def _execute(self, task: Task, shutdown_context: _GracefulShutdown) -> Task | Id raise TypeError(f"Unexpected task execution result: {type(result)}") @contextmanager - def _active(self, task: Task) -> Iterator[None]: + def _active(self, task: Task) -> Generator[None, None, None]: with self._active_mutex: self._active_task = task try: