"
-
- 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 gRPC
-
-
-
- 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..1d696cd 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,8 @@ 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 +69,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"")
return Diagram(svg=b"")
- 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_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_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_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/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/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/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..43001ef 100644
--- a/tilebox-workflows/tilebox/workflows/observability/tracing.py
+++ b/tilebox-workflows/tilebox/workflows/observability/tracing.py
@@ -1,6 +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
@@ -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,20 +96,34 @@ 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)
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):
@@ -125,13 +141,16 @@ 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
@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 c5390e0..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
@@ -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,10 +161,10 @@ 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]:
+ 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))
@@ -391,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:
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..5d0bcd2 100644
--- a/uv.lock
+++ b/uv.lock
@@ -15,6 +15,7 @@ resolution-markers = [
[manifest]
members = [
+ "tilebox-core",
"tilebox-datasets",
"tilebox-grpc",
"tilebox-python",
@@ -77,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'" },
]
@@ -136,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" },
@@ -145,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]
@@ -185,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]]
@@ -222,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]]
@@ -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,134 +558,141 @@ 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]]
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]]
@@ -796,41 +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" },
-]
-
-[[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" },
+ { 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]
@@ -838,166 +838,16 @@ 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"
-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" }
-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"
+version = "1.76.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" }
+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/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" },
+ { 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]]
@@ -1011,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]]
@@ -1108,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]]
@@ -1167,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]]
@@ -1475,7 +1330,7 @@ wheels = [
[[package]]
name = "moto"
-version = "5.2.3"
+version = "5.2.4"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "boto3" },
@@ -1486,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]]
@@ -1708,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'",
@@ -1718,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]]
@@ -1801,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]]
@@ -1856,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" },
@@ -1869,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" },
@@ -1949,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]]
@@ -2018,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]]
@@ -2110,48 +1978,39 @@ 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]]
-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"
@@ -2164,18 +2023,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"
@@ -2287,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]]
@@ -2351,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]]
@@ -2535,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]]
@@ -2604,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]]
@@ -2620,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" },
]
@@ -2698,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]]
@@ -2799,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]]
@@ -2928,31 +2831,25 @@ 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]]
-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 +2857,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.4", 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 +2894,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" },
]
@@ -3034,13 +2940,11 @@ dev = [
{ name = "pyarrow" },
{ name = "ruff" },
{ name = "ty" },
- { name = "types-protobuf" },
]
[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" },
]
@@ -3055,7 +2959,6 @@ dev = [
{ name = "pyarrow", specifier = ">=17.0.0" },
{ name = "ruff" },
{ name = "ty" },
- { name = "types-protobuf", specifier = ">=6.30" },
]
[[package]]
@@ -3070,6 +2973,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 +2995,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 +3015,6 @@ dependencies = [
{ name = "azure-identity" },
{ name = "boto3" },
{ name = "google-auth", extra = ["requests"] },
- { name = "grpcio" },
{ name = "msgspec" },
{ name = "obstore" },
{ name = "opentelemetry-api" },
@@ -3119,8 +3023,8 @@ dependencies = [
{ name = "opentelemetry-proto" },
{ name = "opentelemetry-sdk" },
{ name = "tenacity" },
- { name = "tilebox-datasets" },
- { name = "tilebox-grpc" },
+ { name = "tilebox-core" },
+ { name = "uvicorn" },
]
[package.optional-dependencies]
@@ -3138,8 +3042,6 @@ notebook = [
[package.dev-dependencies]
dev = [
{ name = "boto3-stubs", extra = ["s3"] },
- { name = "google-cloud-storage" },
- { name = "grpcio-status" },
{ name = "hypothesis" },
{ name = "ipywidgets" },
{ name = "moto" },
@@ -3147,6 +3049,7 @@ dev = [
{ name = "pytest" },
{ name = "pytest-asyncio" },
{ name = "pytest-cov" },
+ { name = "tilebox-datasets" },
]
[package.metadata]
@@ -3155,7 +3058,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,16 +3070,14 @@ 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"]
[package.metadata.requires-dev]
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,60 +3085,7 @@ dev = [
{ name = "pytest", specifier = ">=8.4" },
{ name = "pytest-asyncio", specifier = ">=1.4.0" },
{ name = "pytest-cov", specifier = ">=5.0.0" },
-]
-
-[[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" },
+ { name = "tilebox-datasets", editable = "tilebox-datasets" },
]
[[package]]
@@ -3264,45 +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" },
-]
-
-[[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" },
+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]]
@@ -3334,61 +3172,74 @@ 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/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]]
+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/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/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"
+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]]
@@ -3414,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]]
@@ -3504,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" },
]