volcano-sdk-python 0.10.4__py3-none-any.whl → 0.11.0__py3-none-any.whl
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.
- volcano_sdk/durable_authoring.py +121 -89
- volcano_sdk/storage.py +11 -7
- {volcano_sdk_python-0.10.4.dist-info → volcano_sdk_python-0.11.0.dist-info}/METADATA +1 -1
- {volcano_sdk_python-0.10.4.dist-info → volcano_sdk_python-0.11.0.dist-info}/RECORD +6 -6
- {volcano_sdk_python-0.10.4.dist-info → volcano_sdk_python-0.11.0.dist-info}/WHEEL +0 -0
- {volcano_sdk_python-0.10.4.dist-info → volcano_sdk_python-0.11.0.dist-info}/licenses/LICENSE +0 -0
volcano_sdk/durable_authoring.py
CHANGED
|
@@ -131,6 +131,24 @@ class DurableRuntimeMissingError(Exception):
|
|
|
131
131
|
self.__cause__ = cause
|
|
132
132
|
|
|
133
133
|
|
|
134
|
+
class _DurableEngine(Protocol):
|
|
135
|
+
"""The operations the Volcano facade needs from the optional runtime."""
|
|
136
|
+
|
|
137
|
+
def seconds(self, value: int) -> object: ...
|
|
138
|
+
|
|
139
|
+
def step_options(self, *, retry: Retry, at_most_once: bool) -> object: ...
|
|
140
|
+
|
|
141
|
+
def wait_condition_options(self, options: WaitUntilOptions) -> object: ...
|
|
142
|
+
|
|
143
|
+
def map_options(self, options: BatchOptions | None) -> object: ...
|
|
144
|
+
|
|
145
|
+
def parallel_options(self, options: BatchOptions | None) -> object: ...
|
|
146
|
+
|
|
147
|
+
def named_branch(
|
|
148
|
+
self, run: Callable[[object], object], name: str | None
|
|
149
|
+
) -> object: ...
|
|
150
|
+
|
|
151
|
+
|
|
134
152
|
class _Engine:
|
|
135
153
|
"""The durable protocol, from the AWS durable execution SDK.
|
|
136
154
|
|
|
@@ -186,6 +204,101 @@ class _Engine:
|
|
|
186
204
|
cls._loaded = cls()
|
|
187
205
|
return cls._loaded
|
|
188
206
|
|
|
207
|
+
def seconds(self, value: int) -> EngineDuration:
|
|
208
|
+
"""Build the runtime's duration from whole seconds."""
|
|
209
|
+
return self.duration.from_seconds(value)
|
|
210
|
+
|
|
211
|
+
def step_options(self, *, retry: Retry, at_most_once: bool) -> StepConfig:
|
|
212
|
+
"""Build the runtime's step options."""
|
|
213
|
+
config: dict[str, Any] = {}
|
|
214
|
+
if at_most_once:
|
|
215
|
+
config["step_semantics"] = self.step_semantics.AT_MOST_ONCE_PER_RETRY
|
|
216
|
+
strategy = self._retry_strategy(retry)
|
|
217
|
+
if strategy is not None:
|
|
218
|
+
config["retry_strategy"] = strategy
|
|
219
|
+
return self.step_config(**config)
|
|
220
|
+
|
|
221
|
+
def _retry_strategy(self, retry: object) -> Any:
|
|
222
|
+
if retry is None or retry is True:
|
|
223
|
+
return None
|
|
224
|
+
# False disables the runtime's default retry policy for this step.
|
|
225
|
+
if retry is False:
|
|
226
|
+
return self._never_retry()
|
|
227
|
+
if isinstance(retry, RetryOptions):
|
|
228
|
+
return self.create_retry_strategy(
|
|
229
|
+
self.retry_strategy_config(**self._retry_kwargs(retry))
|
|
230
|
+
)
|
|
231
|
+
if callable(retry):
|
|
232
|
+
return retry
|
|
233
|
+
raise TypeError(_INVALID_RETRY)
|
|
234
|
+
|
|
235
|
+
def _never_retry(self) -> Callable[[Exception, int], RetryDecision]:
|
|
236
|
+
no_delay = self.seconds(0)
|
|
237
|
+
|
|
238
|
+
def never_retry(_error: Exception, _attempt: int) -> RetryDecision:
|
|
239
|
+
return self.retry_decision(should_retry=False, delay=no_delay)
|
|
240
|
+
|
|
241
|
+
return never_retry
|
|
242
|
+
|
|
243
|
+
def _retry_kwargs(self, retry: RetryOptions) -> dict[str, Any]:
|
|
244
|
+
return _engine_kwargs(
|
|
245
|
+
max_attempts=retry.attempts,
|
|
246
|
+
initial_delay=self._optional_duration(retry.initial_delay, "initial_delay"),
|
|
247
|
+
max_delay=self._optional_duration(retry.max_delay, "max_delay"),
|
|
248
|
+
backoff_rate=retry.backoff_rate,
|
|
249
|
+
retryable_errors=(None if retry.retry_on is None else list(retry.retry_on)),
|
|
250
|
+
retryable_error_types=(
|
|
251
|
+
None if retry.retry_on_types is None else list(retry.retry_on_types)
|
|
252
|
+
),
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
def _optional_duration(
|
|
256
|
+
self, value: Duration | None, field_name: str
|
|
257
|
+
) -> EngineDuration | None:
|
|
258
|
+
return None if value is None else self.seconds(_to_seconds(value, field_name))
|
|
259
|
+
|
|
260
|
+
def wait_condition_options(self, options: WaitUntilOptions) -> object:
|
|
261
|
+
"""Build the runtime's polling options."""
|
|
262
|
+
until = options.until
|
|
263
|
+
|
|
264
|
+
def keep_polling(state: Any) -> bool:
|
|
265
|
+
return not until(state)
|
|
266
|
+
|
|
267
|
+
strategy = self.wait_strategy_config(
|
|
268
|
+
**_engine_kwargs(
|
|
269
|
+
should_continue_polling=keep_polling,
|
|
270
|
+
max_attempts=options.max_attempts,
|
|
271
|
+
initial_delay=self._optional_duration(options.interval, "interval"),
|
|
272
|
+
max_delay=self._optional_duration(options.max_interval, "max_interval"),
|
|
273
|
+
backoff_rate=options.backoff_rate,
|
|
274
|
+
)
|
|
275
|
+
)
|
|
276
|
+
return self.wait_for_condition_config(
|
|
277
|
+
wait_strategy=self.create_wait_strategy(strategy),
|
|
278
|
+
initial_state=options.initial_state,
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
def _batch_options(self, options: BatchOptions | None) -> dict[str, Any]:
|
|
282
|
+
resolved = BatchOptions() if options is None else options
|
|
283
|
+
config = _engine_kwargs(max_concurrency=resolved.concurrency)
|
|
284
|
+
if resolved.min_succeeded is not None:
|
|
285
|
+
config["completion_config"] = self.completion_config(
|
|
286
|
+
min_successful=resolved.min_succeeded
|
|
287
|
+
)
|
|
288
|
+
return config
|
|
289
|
+
|
|
290
|
+
def map_options(self, options: BatchOptions | None) -> object:
|
|
291
|
+
"""Build the runtime's map options."""
|
|
292
|
+
return self.map_config(**self._batch_options(options))
|
|
293
|
+
|
|
294
|
+
def parallel_options(self, options: BatchOptions | None) -> ParallelConfig:
|
|
295
|
+
"""Build the runtime's parallel options."""
|
|
296
|
+
return self.parallel_config(**self._batch_options(options))
|
|
297
|
+
|
|
298
|
+
def named_branch(self, run: Callable[[object], object], name: str | None) -> object:
|
|
299
|
+
"""Build a named runtime branch."""
|
|
300
|
+
return self.parallel_branch(func=run, name=name)
|
|
301
|
+
|
|
189
302
|
|
|
190
303
|
@dataclass(frozen=True, slots=True)
|
|
191
304
|
class RetryOptions:
|
|
@@ -436,7 +549,7 @@ class DurableContext:
|
|
|
436
549
|
|
|
437
550
|
__slots__ = ("_context", "_engine", "log")
|
|
438
551
|
|
|
439
|
-
def __init__(self, context: Any, engine:
|
|
552
|
+
def __init__(self, context: Any, engine: _DurableEngine) -> None:
|
|
440
553
|
"""Wrap an engine context."""
|
|
441
554
|
self._context = context
|
|
442
555
|
self._engine = engine
|
|
@@ -478,7 +591,7 @@ class DurableContext:
|
|
|
478
591
|
self._context.step(
|
|
479
592
|
run,
|
|
480
593
|
step_name,
|
|
481
|
-
self.
|
|
594
|
+
self._engine.step_options(retry=retry, at_most_once=at_most_once),
|
|
482
595
|
),
|
|
483
596
|
)
|
|
484
597
|
|
|
@@ -543,29 +656,13 @@ class DurableContext:
|
|
|
543
656
|
check_func = _callable(check, "wait_until")
|
|
544
657
|
_validate_wait_options(options)
|
|
545
658
|
engine = self._engine
|
|
546
|
-
until = options.until
|
|
547
|
-
|
|
548
|
-
def keep_polling(state: Any) -> bool:
|
|
549
|
-
return not until(state)
|
|
550
659
|
|
|
551
660
|
def check_state(state: Any, scope: Any) -> Any:
|
|
552
661
|
return check_func(state, StepScope(scope.logger, scope.attempt))
|
|
553
662
|
|
|
554
|
-
strategy = engine.wait_strategy_config(
|
|
555
|
-
**_engine_kwargs(
|
|
556
|
-
should_continue_polling=keep_polling,
|
|
557
|
-
max_attempts=options.max_attempts,
|
|
558
|
-
initial_delay=self._optional_duration(options.interval, "interval"),
|
|
559
|
-
max_delay=self._optional_duration(options.max_interval, "max_interval"),
|
|
560
|
-
backoff_rate=options.backoff_rate,
|
|
561
|
-
)
|
|
562
|
-
)
|
|
563
663
|
return self._context.wait_for_condition(
|
|
564
664
|
check_state,
|
|
565
|
-
engine.
|
|
566
|
-
wait_strategy=engine.create_wait_strategy(strategy),
|
|
567
|
-
initial_state=options.initial_state,
|
|
568
|
-
),
|
|
665
|
+
engine.wait_condition_options(options),
|
|
569
666
|
name,
|
|
570
667
|
)
|
|
571
668
|
|
|
@@ -601,7 +698,7 @@ class DurableContext:
|
|
|
601
698
|
list(items),
|
|
602
699
|
run,
|
|
603
700
|
name,
|
|
604
|
-
engine.
|
|
701
|
+
engine.map_options(options),
|
|
605
702
|
)
|
|
606
703
|
)
|
|
607
704
|
|
|
@@ -623,7 +720,7 @@ class DurableContext:
|
|
|
623
720
|
self._context.parallel(
|
|
624
721
|
[self._branch(branch) for branch in branches],
|
|
625
722
|
name,
|
|
626
|
-
engine.
|
|
723
|
+
engine.parallel_options(options),
|
|
627
724
|
)
|
|
628
725
|
)
|
|
629
726
|
|
|
@@ -650,7 +747,7 @@ class DurableContext:
|
|
|
650
747
|
def run_named(context: Any) -> Any:
|
|
651
748
|
return named(DurableContext(context, engine))
|
|
652
749
|
|
|
653
|
-
return engine.
|
|
750
|
+
return engine.named_branch(run_named, branch.name)
|
|
654
751
|
require_callable(branch, _INVALID_BRANCH)
|
|
655
752
|
bare = branch
|
|
656
753
|
|
|
@@ -659,72 +756,7 @@ class DurableContext:
|
|
|
659
756
|
|
|
660
757
|
return run_bare
|
|
661
758
|
|
|
662
|
-
def
|
|
663
|
-
resolved = BatchOptions() if options is None else options
|
|
664
|
-
config = _engine_kwargs(max_concurrency=resolved.concurrency)
|
|
665
|
-
if resolved.min_succeeded is not None:
|
|
666
|
-
config["completion_config"] = self._engine.completion_config(
|
|
667
|
-
min_successful=resolved.min_succeeded
|
|
668
|
-
)
|
|
669
|
-
return config
|
|
670
|
-
|
|
671
|
-
def _step_config(self, *, retry: Retry, at_most_once: bool) -> StepConfig:
|
|
672
|
-
engine = self._engine
|
|
673
|
-
config: dict[str, Any] = {}
|
|
674
|
-
if at_most_once:
|
|
675
|
-
config["step_semantics"] = engine.step_semantics.AT_MOST_ONCE_PER_RETRY
|
|
676
|
-
strategy = self._retry_strategy(retry)
|
|
677
|
-
if strategy is not None:
|
|
678
|
-
config["retry_strategy"] = strategy
|
|
679
|
-
return engine.step_config(**config)
|
|
680
|
-
|
|
681
|
-
def _retry_strategy(self, retry: object) -> Any:
|
|
682
|
-
engine = self._engine
|
|
683
|
-
if retry is None or retry is True:
|
|
684
|
-
return None
|
|
685
|
-
# retry=False means "fail on the first error", which is not the same as
|
|
686
|
-
# leaving retry unset: the platform retries by default, and a step that
|
|
687
|
-
# is not safe to repeat wants the opposite.
|
|
688
|
-
if retry is False:
|
|
689
|
-
return self._never_retry()
|
|
690
|
-
if isinstance(retry, RetryOptions):
|
|
691
|
-
return engine.create_retry_strategy(
|
|
692
|
-
engine.retry_strategy_config(**self._retry_kwargs(retry))
|
|
693
|
-
)
|
|
694
|
-
if callable(retry):
|
|
695
|
-
return retry
|
|
696
|
-
raise TypeError(_INVALID_RETRY)
|
|
697
|
-
|
|
698
|
-
def _never_retry(self) -> Callable[[Exception, int], RetryDecision]:
|
|
699
|
-
engine = self._engine
|
|
700
|
-
no_delay = engine.duration.from_seconds(0)
|
|
701
|
-
|
|
702
|
-
def never_retry(_error: Exception, _attempt: int) -> RetryDecision:
|
|
703
|
-
return engine.retry_decision(should_retry=False, delay=no_delay)
|
|
704
|
-
|
|
705
|
-
return never_retry
|
|
706
|
-
|
|
707
|
-
def _retry_kwargs(self, retry: RetryOptions) -> dict[str, Any]:
|
|
708
|
-
return _engine_kwargs(
|
|
709
|
-
max_attempts=retry.attempts,
|
|
710
|
-
initial_delay=self._optional_duration(retry.initial_delay, "initial_delay"),
|
|
711
|
-
max_delay=self._optional_duration(retry.max_delay, "max_delay"),
|
|
712
|
-
backoff_rate=retry.backoff_rate,
|
|
713
|
-
retryable_errors=(None if retry.retry_on is None else list(retry.retry_on)),
|
|
714
|
-
retryable_error_types=(
|
|
715
|
-
None if retry.retry_on_types is None else list(retry.retry_on_types)
|
|
716
|
-
),
|
|
717
|
-
)
|
|
718
|
-
|
|
719
|
-
def _optional_duration(
|
|
720
|
-
self, value: Duration | None, field_name: str
|
|
721
|
-
) -> EngineDuration | None:
|
|
722
|
-
return None if value is None else self._duration(value, field_name)
|
|
723
|
-
|
|
724
|
-
def _duration(self, value: object, field_name: str) -> EngineDuration:
|
|
725
|
-
return self._engine.duration.from_seconds(_to_seconds(value, field_name))
|
|
726
|
-
|
|
727
|
-
def _wait_duration(self, value: object) -> EngineDuration:
|
|
759
|
+
def _wait_duration(self, value: object) -> object:
|
|
728
760
|
seconds = _to_seconds(value, "wait")
|
|
729
761
|
if seconds < _MIN_WAIT_SECONDS:
|
|
730
762
|
message = f"wait must be at least {_MIN_WAIT_SECONDS} second"
|
|
@@ -732,7 +764,7 @@ class DurableContext:
|
|
|
732
764
|
if seconds > _MAX_WAIT_SECONDS:
|
|
733
765
|
message = f"wait must be at most {_MAX_WAIT_SECONDS} seconds (366 days)"
|
|
734
766
|
raise TypeError(message)
|
|
735
|
-
return self._engine.
|
|
767
|
+
return self._engine.seconds(seconds)
|
|
736
768
|
|
|
737
769
|
|
|
738
770
|
@overload
|
volcano_sdk/storage.py
CHANGED
|
@@ -11,7 +11,7 @@ from dataclasses import dataclass
|
|
|
11
11
|
from datetime import datetime
|
|
12
12
|
from io import SEEK_END, BytesIO
|
|
13
13
|
from tempfile import TemporaryFile
|
|
14
|
-
from typing import TYPE_CHECKING,
|
|
14
|
+
from typing import TYPE_CHECKING, BinaryIO, Protocol, TypeGuard, cast
|
|
15
15
|
from urllib.parse import quote
|
|
16
16
|
|
|
17
17
|
from ._transport import (
|
|
@@ -463,6 +463,13 @@ def _upload_content_type(value: object) -> str:
|
|
|
463
463
|
return value
|
|
464
464
|
|
|
465
465
|
|
|
466
|
+
def _is_string_keyed_mapping(value: object) -> TypeGuard[Mapping[str, object]]:
|
|
467
|
+
if not isinstance(value, Mapping):
|
|
468
|
+
return False
|
|
469
|
+
mapping = cast("Mapping[object, object]", value)
|
|
470
|
+
return all(isinstance(key, str) for key in mapping)
|
|
471
|
+
|
|
472
|
+
|
|
466
473
|
@dataclass(frozen=True, slots=True)
|
|
467
474
|
class StorageBucket:
|
|
468
475
|
"""Operations scoped to one storage bucket."""
|
|
@@ -476,7 +483,7 @@ class StorageBucket:
|
|
|
476
483
|
data: bytes | BinaryIO,
|
|
477
484
|
*,
|
|
478
485
|
content_type: str | None = None,
|
|
479
|
-
) -> dict[str,
|
|
486
|
+
) -> dict[str, object]:
|
|
480
487
|
"""Upload bytes or the remaining contents of a binary stream.
|
|
481
488
|
|
|
482
489
|
Returns:
|
|
@@ -499,12 +506,9 @@ class StorageBucket:
|
|
|
499
506
|
binding=binding,
|
|
500
507
|
)
|
|
501
508
|
payload = response_payload(response, 201)
|
|
502
|
-
if not
|
|
503
|
-
raise TypeError(_INVALID_UPLOAD_RESPONSE)
|
|
504
|
-
values = cast("Mapping[object, object]", payload)
|
|
505
|
-
if not all(isinstance(key, str) for key in values):
|
|
509
|
+
if not _is_string_keyed_mapping(payload):
|
|
506
510
|
raise TypeError(_INVALID_UPLOAD_RESPONSE)
|
|
507
|
-
return dict(
|
|
511
|
+
return dict(payload)
|
|
508
512
|
|
|
509
513
|
def download(self, path: str, *, byte_range: str | None = None) -> bytes:
|
|
510
514
|
"""Download bytes from a path in this bucket.
|
|
@@ -14,7 +14,7 @@ volcano_sdk/client.py,sha256=KH52e5WC4ii-hnipB9vNzSDEyfvvpOMBCdtwzRqNUOI,12701
|
|
|
14
14
|
volcano_sdk/connection_string.py,sha256=p87P-fd9wVfia-LLRRsFTkW6tzx9FXn-GAy9OIOoDvE,2664
|
|
15
15
|
volcano_sdk/database.py,sha256=XuXGqlfEPvlOBB33jyTVNhRGe4vrgzOBne2rTRzkQis,13806
|
|
16
16
|
volcano_sdk/durable.py,sha256=YQa_5tT4kxkHTyP4nRMhjWyExnSICXD_RAipSQ1cax8,13422
|
|
17
|
-
volcano_sdk/durable_authoring.py,sha256=
|
|
17
|
+
volcano_sdk/durable_authoring.py,sha256=92ENW366IdeP7P-ABqo-HB8V7YsG8KkE_ekVWnu9ZXU,36899
|
|
18
18
|
volcano_sdk/errors.py,sha256=ffs66FIN_jhyh5SMj7KdXb-zVV3DSgVK6xJEPNA5h9Y,1613
|
|
19
19
|
volcano_sdk/functions.py,sha256=7loNEF1tkojAnp_C0rBWWcvO3l22Tx2JQn28l6PoV2Q,13264
|
|
20
20
|
volcano_sdk/locks.py,sha256=Ctr4OKAmJqCPD03lkPEjH7JzYfdpESHEYFOmTj0z1Ds,10894
|
|
@@ -22,7 +22,7 @@ volcano_sdk/logs.py,sha256=l71mT5ejIq2_9O_aug5_pZuZPAYkdJQa03RhPvSDH9g,4075
|
|
|
22
22
|
volcano_sdk/models.py,sha256=ZIo2YdZ-Ba7rCmHBo3eV9LGjpEftVTeVmrrfq-zGujs,11182
|
|
23
23
|
volcano_sdk/py.typed,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
24
24
|
volcano_sdk/realtime.py,sha256=qulBz-rUm8SFId_jt40z7Q-wDtTb572gi1SKMDqbAto,64213
|
|
25
|
-
volcano_sdk/storage.py,sha256=
|
|
25
|
+
volcano_sdk/storage.py,sha256=FUuE5SnF5IV-S4OF-sFtMqzidWvUC_Gt5Xw5NW7aUss,28137
|
|
26
26
|
volcano_sdk/_generated/__init__.py,sha256=ZlB8wMYdN6EjpUS5W_lX3jsxq9G1kOcmUr-j3Fccfn8,164
|
|
27
27
|
volcano_sdk/_generated/client.py,sha256=GDzw6T2uRmPlUb1DImpBGxwy0dNbhmBknfvvIQW0GuU,12387
|
|
28
28
|
volcano_sdk/_generated/errors.py,sha256=trp-p5qn1_JLRxGZhdHtICaNPaCrcDCe4TgIihBravk,546
|
|
@@ -810,7 +810,7 @@ volcano_sdk/_generated/models/usage_data_point.py,sha256=d4U08JbVTiIMM_l2BmRILL1
|
|
|
810
810
|
volcano_sdk/_generated/models/variable.py,sha256=t8XaBfTwIH42L8RL4DFDtdqbhVfDEnRS5Axq2q6Tpvk,6405
|
|
811
811
|
volcano_sdk/_generated/models/variable_deploy_source.py,sha256=G_aCiFHzQx8WhJFs0GvYZEFclfTrfkDvd3kT1e3vty8,463
|
|
812
812
|
volcano_sdk/_generated/models/variable_status.py,sha256=THSsKBSiAYG4kAtBzGA4TZqxjgx8hdL0Q4qCdZfOj00,391
|
|
813
|
-
volcano_sdk_python-0.
|
|
814
|
-
volcano_sdk_python-0.
|
|
815
|
-
volcano_sdk_python-0.
|
|
816
|
-
volcano_sdk_python-0.
|
|
813
|
+
volcano_sdk_python-0.11.0.dist-info/METADATA,sha256=paWdliEZVJYayjiBqVjB_zjpTx8faq62pbpH5ZZOHZM,45774
|
|
814
|
+
volcano_sdk_python-0.11.0.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
|
|
815
|
+
volcano_sdk_python-0.11.0.dist-info/licenses/LICENSE,sha256=AIHJy-5MFqM8fHfuGFTTb9a8rvJGypcVgN4aZ-7XTWw,11387
|
|
816
|
+
volcano_sdk_python-0.11.0.dist-info/RECORD,,
|
|
File without changes
|
{volcano_sdk_python-0.10.4.dist-info → volcano_sdk_python-0.11.0.dist-info}/licenses/LICENSE
RENAMED
|
File without changes
|