durabletask 1.4.0.dev30__tar.gz → 1.4.0.dev32__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {durabletask-1.4.0.dev30 → durabletask-1.4.0.dev32}/PKG-INFO +1 -1
- {durabletask-1.4.0.dev30 → durabletask-1.4.0.dev32}/durabletask/__init__.py +3 -0
- {durabletask-1.4.0.dev30 → durabletask-1.4.0.dev32}/durabletask/client.py +127 -19
- durabletask-1.4.0.dev32/durabletask/grpc_options.py +102 -0
- durabletask-1.4.0.dev32/durabletask/history.py +535 -0
- durabletask-1.4.0.dev32/durabletask/internal/history_helpers.py +68 -0
- {durabletask-1.4.0.dev30 → durabletask-1.4.0.dev32}/durabletask/internal/shared.py +44 -10
- {durabletask-1.4.0.dev30 → durabletask-1.4.0.dev32}/durabletask/testing/in_memory_backend.py +71 -2
- {durabletask-1.4.0.dev30 → durabletask-1.4.0.dev32}/durabletask/worker.py +23 -7
- {durabletask-1.4.0.dev30 → durabletask-1.4.0.dev32}/durabletask.egg-info/PKG-INFO +1 -1
- {durabletask-1.4.0.dev30 → durabletask-1.4.0.dev32}/durabletask.egg-info/SOURCES.txt +3 -0
- {durabletask-1.4.0.dev30 → durabletask-1.4.0.dev32}/pyproject.toml +1 -1
- {durabletask-1.4.0.dev30 → durabletask-1.4.0.dev32}/LICENSE +0 -0
- {durabletask-1.4.0.dev30 → durabletask-1.4.0.dev32}/README.md +0 -0
- {durabletask-1.4.0.dev30 → durabletask-1.4.0.dev32}/durabletask/entities/__init__.py +0 -0
- {durabletask-1.4.0.dev30 → durabletask-1.4.0.dev32}/durabletask/entities/durable_entity.py +0 -0
- {durabletask-1.4.0.dev30 → durabletask-1.4.0.dev32}/durabletask/entities/entity_context.py +0 -0
- {durabletask-1.4.0.dev30 → durabletask-1.4.0.dev32}/durabletask/entities/entity_instance_id.py +0 -0
- {durabletask-1.4.0.dev30 → durabletask-1.4.0.dev32}/durabletask/entities/entity_lock.py +0 -0
- {durabletask-1.4.0.dev30 → durabletask-1.4.0.dev32}/durabletask/entities/entity_metadata.py +0 -0
- {durabletask-1.4.0.dev30 → durabletask-1.4.0.dev32}/durabletask/entities/entity_operation_failed_exception.py +0 -0
- {durabletask-1.4.0.dev30 → durabletask-1.4.0.dev32}/durabletask/extensions/__init__.py +0 -0
- {durabletask-1.4.0.dev30 → durabletask-1.4.0.dev32}/durabletask/extensions/azure_blob_payloads/__init__.py +0 -0
- {durabletask-1.4.0.dev30 → durabletask-1.4.0.dev32}/durabletask/extensions/azure_blob_payloads/blob_payload_store.py +0 -0
- {durabletask-1.4.0.dev30 → durabletask-1.4.0.dev32}/durabletask/extensions/azure_blob_payloads/options.py +0 -0
- {durabletask-1.4.0.dev30 → durabletask-1.4.0.dev32}/durabletask/internal/client_helpers.py +0 -0
- {durabletask-1.4.0.dev30 → durabletask-1.4.0.dev32}/durabletask/internal/entity_state_shim.py +0 -0
- {durabletask-1.4.0.dev30 → durabletask-1.4.0.dev32}/durabletask/internal/exceptions.py +0 -0
- {durabletask-1.4.0.dev30 → durabletask-1.4.0.dev32}/durabletask/internal/grpc_interceptor.py +0 -0
- {durabletask-1.4.0.dev30 → durabletask-1.4.0.dev32}/durabletask/internal/helpers.py +0 -0
- {durabletask-1.4.0.dev30 → durabletask-1.4.0.dev32}/durabletask/internal/json_encode_output_exception.py +0 -0
- {durabletask-1.4.0.dev30 → durabletask-1.4.0.dev32}/durabletask/internal/orchestration_entity_context.py +0 -0
- {durabletask-1.4.0.dev30 → durabletask-1.4.0.dev32}/durabletask/internal/orchestrator_service_pb2.py +0 -0
- {durabletask-1.4.0.dev30 → durabletask-1.4.0.dev32}/durabletask/internal/orchestrator_service_pb2.pyi +0 -0
- {durabletask-1.4.0.dev30 → durabletask-1.4.0.dev32}/durabletask/internal/orchestrator_service_pb2_grpc.py +0 -0
- {durabletask-1.4.0.dev30 → durabletask-1.4.0.dev32}/durabletask/internal/proto_task_hub_sidecar_service_stub.py +0 -0
- {durabletask-1.4.0.dev30 → durabletask-1.4.0.dev32}/durabletask/internal/tracing.py +0 -0
- {durabletask-1.4.0.dev30 → durabletask-1.4.0.dev32}/durabletask/payload/__init__.py +0 -0
- {durabletask-1.4.0.dev30 → durabletask-1.4.0.dev32}/durabletask/payload/helpers.py +0 -0
- {durabletask-1.4.0.dev30 → durabletask-1.4.0.dev32}/durabletask/payload/store.py +0 -0
- {durabletask-1.4.0.dev30 → durabletask-1.4.0.dev32}/durabletask/py.typed +0 -0
- {durabletask-1.4.0.dev30 → durabletask-1.4.0.dev32}/durabletask/task.py +0 -0
- {durabletask-1.4.0.dev30 → durabletask-1.4.0.dev32}/durabletask/testing/__init__.py +0 -0
- {durabletask-1.4.0.dev30 → durabletask-1.4.0.dev32}/durabletask.egg-info/dependency_links.txt +0 -0
- {durabletask-1.4.0.dev30 → durabletask-1.4.0.dev32}/durabletask.egg-info/requires.txt +0 -0
- {durabletask-1.4.0.dev30 → durabletask-1.4.0.dev32}/durabletask.egg-info/top_level.txt +0 -0
- {durabletask-1.4.0.dev30 → durabletask-1.4.0.dev32}/setup.cfg +0 -0
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
"""Durable Task SDK for Python"""
|
|
5
5
|
|
|
6
|
+
from durabletask.grpc_options import GrpcChannelOptions, GrpcRetryPolicyOptions
|
|
6
7
|
from durabletask.payload.store import LargePayloadStorageOptions, PayloadStore
|
|
7
8
|
from durabletask.worker import (
|
|
8
9
|
ActivityWorkItemFilter,
|
|
@@ -17,6 +18,8 @@ __all__ = [
|
|
|
17
18
|
"ActivityWorkItemFilter",
|
|
18
19
|
"ConcurrencyOptions",
|
|
19
20
|
"EntityWorkItemFilter",
|
|
21
|
+
"GrpcChannelOptions",
|
|
22
|
+
"GrpcRetryPolicyOptions",
|
|
20
23
|
"LargePayloadStorageOptions",
|
|
21
24
|
"OrchestrationWorkItemFilter",
|
|
22
25
|
"PayloadStore",
|
|
@@ -6,14 +6,17 @@ import uuid
|
|
|
6
6
|
from dataclasses import dataclass
|
|
7
7
|
from datetime import datetime
|
|
8
8
|
from enum import Enum
|
|
9
|
-
from typing import Any, List, Optional, Sequence, TypeVar, Union
|
|
9
|
+
from typing import Any, Generic, List, Optional, Sequence, TypeVar, Union
|
|
10
10
|
|
|
11
11
|
import grpc
|
|
12
12
|
import grpc.aio
|
|
13
13
|
|
|
14
|
+
import durabletask.history as history
|
|
14
15
|
from durabletask.entities import EntityInstanceId
|
|
15
16
|
from durabletask.entities.entity_metadata import EntityMetadata
|
|
17
|
+
from durabletask.grpc_options import GrpcChannelOptions
|
|
16
18
|
import durabletask.internal.helpers as helpers
|
|
19
|
+
import durabletask.internal.history_helpers as history_helpers
|
|
17
20
|
import durabletask.internal.orchestrator_service_pb2 as pb
|
|
18
21
|
import durabletask.internal.orchestrator_service_pb2_grpc as stubs
|
|
19
22
|
import durabletask.internal.shared as shared
|
|
@@ -37,6 +40,7 @@ from durabletask.payload.store import PayloadStore
|
|
|
37
40
|
|
|
38
41
|
TInput = TypeVar('TInput')
|
|
39
42
|
TOutput = TypeVar('TOutput')
|
|
43
|
+
TItem = TypeVar('TItem')
|
|
40
44
|
|
|
41
45
|
|
|
42
46
|
class OrchestrationStatus(Enum):
|
|
@@ -99,6 +103,12 @@ class PurgeInstancesResult:
|
|
|
99
103
|
is_complete: bool
|
|
100
104
|
|
|
101
105
|
|
|
106
|
+
@dataclass
|
|
107
|
+
class Page(Generic[TItem]):
|
|
108
|
+
items: List[TItem]
|
|
109
|
+
continuation_token: Optional[str]
|
|
110
|
+
|
|
111
|
+
|
|
102
112
|
@dataclass
|
|
103
113
|
class CleanEntityStorageResult:
|
|
104
114
|
empty_entities_removed: int
|
|
@@ -152,18 +162,22 @@ class TaskHubGrpcClient:
|
|
|
152
162
|
metadata: Optional[list[tuple[str, str]]] = None,
|
|
153
163
|
log_handler: Optional[logging.Handler] = None,
|
|
154
164
|
log_formatter: Optional[logging.Formatter] = None,
|
|
165
|
+
channel: Optional[grpc.Channel] = None,
|
|
155
166
|
secure_channel: bool = False,
|
|
156
167
|
interceptors: Optional[Sequence[shared.ClientInterceptor]] = None,
|
|
168
|
+
channel_options: Optional[GrpcChannelOptions] = None,
|
|
157
169
|
default_version: Optional[str] = None,
|
|
158
170
|
payload_store: Optional[PayloadStore] = None):
|
|
159
171
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
172
|
+
self._owns_channel = channel is None
|
|
173
|
+
if channel is None:
|
|
174
|
+
interceptors = prepare_sync_interceptors(metadata, interceptors)
|
|
175
|
+
channel = shared.get_grpc_channel(
|
|
176
|
+
host_address=host_address,
|
|
177
|
+
secure_channel=secure_channel,
|
|
178
|
+
interceptors=interceptors,
|
|
179
|
+
channel_options=channel_options,
|
|
180
|
+
)
|
|
167
181
|
self._channel = channel
|
|
168
182
|
self._stub = stubs.TaskHubSidecarServiceStub(channel)
|
|
169
183
|
self._logger = shared.get_logger("client", log_handler, log_formatter)
|
|
@@ -171,8 +185,15 @@ class TaskHubGrpcClient:
|
|
|
171
185
|
self._payload_store = payload_store
|
|
172
186
|
|
|
173
187
|
def close(self) -> None:
|
|
174
|
-
"""Closes the underlying gRPC channel.
|
|
175
|
-
|
|
188
|
+
"""Closes the underlying gRPC channel.
|
|
189
|
+
|
|
190
|
+
Only closes channels created internally. If a pre-configured channel
|
|
191
|
+
was passed via the ``channel`` constructor parameter, this method is
|
|
192
|
+
a no-op — the caller retains ownership and is responsible for closing
|
|
193
|
+
it.
|
|
194
|
+
"""
|
|
195
|
+
if self._owns_channel:
|
|
196
|
+
self._channel.close()
|
|
176
197
|
|
|
177
198
|
def schedule_new_orchestration(self, orchestrator: Union[task.Orchestrator[TInput, TOutput], str], *,
|
|
178
199
|
input: Optional[TInput] = None,
|
|
@@ -218,6 +239,44 @@ class TaskHubGrpcClient:
|
|
|
218
239
|
payload_helpers.deexternalize_payloads(res, self._payload_store)
|
|
219
240
|
return new_orchestration_state(req.instanceId, res)
|
|
220
241
|
|
|
242
|
+
def get_orchestration_history(self,
|
|
243
|
+
instance_id: str, *,
|
|
244
|
+
execution_id: Optional[str] = None,
|
|
245
|
+
for_work_item_processing: bool = False) -> List[history.HistoryEvent]:
|
|
246
|
+
req = pb.StreamInstanceHistoryRequest(
|
|
247
|
+
instanceId=instance_id,
|
|
248
|
+
executionId=helpers.get_string_value(execution_id),
|
|
249
|
+
forWorkItemProcessing=for_work_item_processing,
|
|
250
|
+
)
|
|
251
|
+
self._logger.info(f"Retrieving history for instance '{instance_id}'.")
|
|
252
|
+
stream = self._stub.StreamInstanceHistory(req)
|
|
253
|
+
return history_helpers.collect_history_events(stream, self._payload_store)
|
|
254
|
+
|
|
255
|
+
def list_instance_ids(self,
|
|
256
|
+
runtime_status: Optional[List[OrchestrationStatus]] = None,
|
|
257
|
+
completed_time_from: Optional[datetime] = None,
|
|
258
|
+
completed_time_to: Optional[datetime] = None,
|
|
259
|
+
page_size: Optional[int] = None,
|
|
260
|
+
continuation_token: Optional[str] = None) -> Page[str]:
|
|
261
|
+
req = pb.ListInstanceIdsRequest(
|
|
262
|
+
runtimeStatus=[status.value for status in runtime_status] if runtime_status else [],
|
|
263
|
+
completedTimeFrom=helpers.new_timestamp(completed_time_from) if completed_time_from else None,
|
|
264
|
+
completedTimeTo=helpers.new_timestamp(completed_time_to) if completed_time_to else None,
|
|
265
|
+
pageSize=page_size or 0,
|
|
266
|
+
lastInstanceKey=helpers.get_string_value(continuation_token),
|
|
267
|
+
)
|
|
268
|
+
self._logger.info(
|
|
269
|
+
"Listing terminal instance IDs with filters: "
|
|
270
|
+
f"runtime_status={[str(status) for status in runtime_status] if runtime_status else None}, "
|
|
271
|
+
f"completed_time_from={completed_time_from}, "
|
|
272
|
+
f"completed_time_to={completed_time_to}, "
|
|
273
|
+
f"page_size={page_size}, "
|
|
274
|
+
f"continuation_token={continuation_token}"
|
|
275
|
+
)
|
|
276
|
+
resp: pb.ListInstanceIdsResponse = self._stub.ListInstanceIds(req)
|
|
277
|
+
next_token = resp.lastInstanceKey.value if resp.HasField("lastInstanceKey") else None
|
|
278
|
+
return Page(items=list(resp.instanceIds), continuation_token=next_token)
|
|
279
|
+
|
|
221
280
|
def get_all_orchestration_states(self,
|
|
222
281
|
orchestration_query: Optional[OrchestrationQuery] = None
|
|
223
282
|
) -> List[OrchestrationState]:
|
|
@@ -433,18 +492,22 @@ class AsyncTaskHubGrpcClient:
|
|
|
433
492
|
metadata: Optional[list[tuple[str, str]]] = None,
|
|
434
493
|
log_handler: Optional[logging.Handler] = None,
|
|
435
494
|
log_formatter: Optional[logging.Formatter] = None,
|
|
495
|
+
channel: Optional[grpc.aio.Channel] = None,
|
|
436
496
|
secure_channel: bool = False,
|
|
437
497
|
interceptors: Optional[Sequence[shared.AsyncClientInterceptor]] = None,
|
|
498
|
+
channel_options: Optional[GrpcChannelOptions] = None,
|
|
438
499
|
default_version: Optional[str] = None,
|
|
439
500
|
payload_store: Optional[PayloadStore] = None):
|
|
440
501
|
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
502
|
+
self._owns_channel = channel is None
|
|
503
|
+
if channel is None:
|
|
504
|
+
interceptors = prepare_async_interceptors(metadata, interceptors)
|
|
505
|
+
channel = shared.get_async_grpc_channel(
|
|
506
|
+
host_address=host_address,
|
|
507
|
+
secure_channel=secure_channel,
|
|
508
|
+
interceptors=interceptors,
|
|
509
|
+
channel_options=channel_options,
|
|
510
|
+
)
|
|
448
511
|
self._channel = channel
|
|
449
512
|
self._stub = stubs.TaskHubSidecarServiceStub(channel)
|
|
450
513
|
self._logger = shared.get_logger("async_client", log_handler, log_formatter)
|
|
@@ -452,8 +515,15 @@ class AsyncTaskHubGrpcClient:
|
|
|
452
515
|
self._payload_store = payload_store
|
|
453
516
|
|
|
454
517
|
async def close(self) -> None:
|
|
455
|
-
"""Closes the underlying gRPC channel.
|
|
456
|
-
|
|
518
|
+
"""Closes the underlying gRPC channel.
|
|
519
|
+
|
|
520
|
+
Only closes channels created internally. If a pre-configured channel
|
|
521
|
+
was passed via the ``channel`` constructor parameter, this method is
|
|
522
|
+
a no-op — the caller retains ownership and is responsible for closing
|
|
523
|
+
it.
|
|
524
|
+
"""
|
|
525
|
+
if self._owns_channel:
|
|
526
|
+
await self._channel.close()
|
|
457
527
|
|
|
458
528
|
async def __aenter__(self):
|
|
459
529
|
return self
|
|
@@ -502,6 +572,44 @@ class AsyncTaskHubGrpcClient:
|
|
|
502
572
|
await payload_helpers.deexternalize_payloads_async(res, self._payload_store)
|
|
503
573
|
return new_orchestration_state(req.instanceId, res)
|
|
504
574
|
|
|
575
|
+
async def get_orchestration_history(self,
|
|
576
|
+
instance_id: str, *,
|
|
577
|
+
execution_id: Optional[str] = None,
|
|
578
|
+
for_work_item_processing: bool = False) -> List[history.HistoryEvent]:
|
|
579
|
+
req = pb.StreamInstanceHistoryRequest(
|
|
580
|
+
instanceId=instance_id,
|
|
581
|
+
executionId=helpers.get_string_value(execution_id),
|
|
582
|
+
forWorkItemProcessing=for_work_item_processing,
|
|
583
|
+
)
|
|
584
|
+
self._logger.info(f"Retrieving history for instance '{instance_id}'.")
|
|
585
|
+
stream = self._stub.StreamInstanceHistory(req)
|
|
586
|
+
return await history_helpers.collect_history_events_async(stream, self._payload_store)
|
|
587
|
+
|
|
588
|
+
async def list_instance_ids(self,
|
|
589
|
+
runtime_status: Optional[List[OrchestrationStatus]] = None,
|
|
590
|
+
completed_time_from: Optional[datetime] = None,
|
|
591
|
+
completed_time_to: Optional[datetime] = None,
|
|
592
|
+
page_size: Optional[int] = None,
|
|
593
|
+
continuation_token: Optional[str] = None) -> Page[str]:
|
|
594
|
+
req = pb.ListInstanceIdsRequest(
|
|
595
|
+
runtimeStatus=[status.value for status in runtime_status] if runtime_status else [],
|
|
596
|
+
completedTimeFrom=helpers.new_timestamp(completed_time_from) if completed_time_from else None,
|
|
597
|
+
completedTimeTo=helpers.new_timestamp(completed_time_to) if completed_time_to else None,
|
|
598
|
+
pageSize=page_size or 0,
|
|
599
|
+
lastInstanceKey=helpers.get_string_value(continuation_token),
|
|
600
|
+
)
|
|
601
|
+
self._logger.info(
|
|
602
|
+
"Listing terminal instance IDs with filters: "
|
|
603
|
+
f"runtime_status={[str(status) for status in runtime_status] if runtime_status else None}, "
|
|
604
|
+
f"completed_time_from={completed_time_from}, "
|
|
605
|
+
f"completed_time_to={completed_time_to}, "
|
|
606
|
+
f"page_size={page_size}, "
|
|
607
|
+
f"continuation_token={continuation_token}"
|
|
608
|
+
)
|
|
609
|
+
resp: pb.ListInstanceIdsResponse = await self._stub.ListInstanceIds(req)
|
|
610
|
+
next_token = resp.lastInstanceKey.value if resp.HasField("lastInstanceKey") else None
|
|
611
|
+
return Page(items=list(resp.instanceIds), continuation_token=next_token)
|
|
612
|
+
|
|
505
613
|
async def get_all_orchestration_states(self,
|
|
506
614
|
orchestration_query: Optional[OrchestrationQuery] = None
|
|
507
615
|
) -> List[OrchestrationState]:
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# Copyright (c) Microsoft Corporation.
|
|
2
|
+
# Licensed under the MIT License.
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
from dataclasses import dataclass, field
|
|
7
|
+
import json
|
|
8
|
+
from typing import Any, Optional
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass
|
|
12
|
+
class GrpcRetryPolicyOptions:
|
|
13
|
+
"""Configuration for transport-level gRPC retries."""
|
|
14
|
+
|
|
15
|
+
max_attempts: int = 4
|
|
16
|
+
initial_backoff_seconds: float = 0.05
|
|
17
|
+
max_backoff_seconds: float = 0.25
|
|
18
|
+
backoff_multiplier: float = 2.0
|
|
19
|
+
retryable_status_codes: list[str] = field(default_factory=lambda: ["UNAVAILABLE"])
|
|
20
|
+
|
|
21
|
+
def __post_init__(self) -> None:
|
|
22
|
+
if self.max_attempts < 2:
|
|
23
|
+
raise ValueError("max_attempts must be >= 2")
|
|
24
|
+
if self.initial_backoff_seconds <= 0:
|
|
25
|
+
raise ValueError("initial_backoff_seconds must be > 0")
|
|
26
|
+
if self.max_backoff_seconds <= 0:
|
|
27
|
+
raise ValueError("max_backoff_seconds must be > 0")
|
|
28
|
+
if self.backoff_multiplier <= 0:
|
|
29
|
+
raise ValueError("backoff_multiplier must be > 0")
|
|
30
|
+
if self.max_backoff_seconds < self.initial_backoff_seconds:
|
|
31
|
+
raise ValueError("max_backoff_seconds must be >= initial_backoff_seconds")
|
|
32
|
+
if len(self.retryable_status_codes) == 0:
|
|
33
|
+
raise ValueError("retryable_status_codes cannot be empty")
|
|
34
|
+
# Validate that backoff values are representable as non-zero gRPC duration strings.
|
|
35
|
+
self._format_duration(self.initial_backoff_seconds)
|
|
36
|
+
self._format_duration(self.max_backoff_seconds)
|
|
37
|
+
|
|
38
|
+
@staticmethod
|
|
39
|
+
def _format_duration(seconds: float) -> str:
|
|
40
|
+
formatted = f"{seconds:.9f}".rstrip('0')
|
|
41
|
+
if formatted.endswith('.'):
|
|
42
|
+
formatted += '0'
|
|
43
|
+
if float(formatted) == 0:
|
|
44
|
+
raise ValueError(
|
|
45
|
+
f"Duration {seconds!r} rounds to zero; use a value large enough to "
|
|
46
|
+
"produce a non-zero gRPC duration string."
|
|
47
|
+
)
|
|
48
|
+
return f"{formatted}s"
|
|
49
|
+
|
|
50
|
+
def to_service_config(self) -> dict[str, Any]:
|
|
51
|
+
return {
|
|
52
|
+
"methodConfig": [
|
|
53
|
+
{
|
|
54
|
+
"name": [{}],
|
|
55
|
+
"retryPolicy": {
|
|
56
|
+
"maxAttempts": self.max_attempts,
|
|
57
|
+
"initialBackoff": self._format_duration(self.initial_backoff_seconds),
|
|
58
|
+
"maxBackoff": self._format_duration(self.max_backoff_seconds),
|
|
59
|
+
"backoffMultiplier": self.backoff_multiplier,
|
|
60
|
+
"retryableStatusCodes": self.retryable_status_codes,
|
|
61
|
+
},
|
|
62
|
+
}
|
|
63
|
+
]
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@dataclass
|
|
68
|
+
class GrpcChannelOptions:
|
|
69
|
+
"""Configuration for transport-level gRPC channel behavior."""
|
|
70
|
+
|
|
71
|
+
max_receive_message_length: Optional[int] = None
|
|
72
|
+
max_send_message_length: Optional[int] = None
|
|
73
|
+
keepalive_time_ms: Optional[int] = None
|
|
74
|
+
keepalive_timeout_ms: Optional[int] = None
|
|
75
|
+
keepalive_permit_without_calls: Optional[bool] = None
|
|
76
|
+
retry_policy: Optional[GrpcRetryPolicyOptions] = None
|
|
77
|
+
raw_options: list[tuple[str, Any]] = field(default_factory=list)
|
|
78
|
+
|
|
79
|
+
def to_grpc_options(self) -> list[tuple[str, Any]]:
|
|
80
|
+
options = list(self.raw_options)
|
|
81
|
+
|
|
82
|
+
if self.max_receive_message_length is not None:
|
|
83
|
+
options.append(("grpc.max_receive_message_length", self.max_receive_message_length))
|
|
84
|
+
if self.max_send_message_length is not None:
|
|
85
|
+
options.append(("grpc.max_send_message_length", self.max_send_message_length))
|
|
86
|
+
if self.keepalive_time_ms is not None:
|
|
87
|
+
options.append(("grpc.keepalive_time_ms", self.keepalive_time_ms))
|
|
88
|
+
if self.keepalive_timeout_ms is not None:
|
|
89
|
+
options.append(("grpc.keepalive_timeout_ms", self.keepalive_timeout_ms))
|
|
90
|
+
if self.keepalive_permit_without_calls is not None:
|
|
91
|
+
options.append(
|
|
92
|
+
(
|
|
93
|
+
"grpc.keepalive_permit_without_calls",
|
|
94
|
+
1 if self.keepalive_permit_without_calls else 0,
|
|
95
|
+
)
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
if self.retry_policy is not None:
|
|
99
|
+
options.append(("grpc.enable_retries", 1))
|
|
100
|
+
options.append(("grpc.service_config", json.dumps(self.retry_policy.to_service_config())))
|
|
101
|
+
|
|
102
|
+
return options
|