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.
@@ -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: _Engine) -> None:
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._step_config(retry=retry, at_most_once=at_most_once),
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.wait_for_condition_config(
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.map_config(**self._batch_config(options)),
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.parallel_config(**self._batch_config(options)),
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.parallel_branch(func=run_named, name=branch.name)
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 _batch_config(self, options: BatchOptions | None) -> dict[str, Any]:
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.duration.from_seconds(seconds)
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, Any, BinaryIO, Protocol, TypeGuard, cast
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, Any]:
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 isinstance(payload, Mapping):
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(cast("Mapping[str, Any]", values))
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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: volcano-sdk-python
3
- Version: 0.10.4
3
+ Version: 0.11.0
4
4
  Summary: Official Python SDK for Volcano
5
5
  Project-URL: Homepage, https://volcano.dev
6
6
  Project-URL: Repository, https://github.com/Kong/volcano-sdk-python
@@ -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=L8qK694e0aPLnRLIDQm9dbYGy4M0NPf9wZYECkVrOfA,35842
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=kh5xN5Pu1gp3fOKyJlmrMGE9ZnN7U9jkLp_l2Ovj1_Y,28081
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.10.4.dist-info/METADATA,sha256=HGwNfdnW1A-y9EZipIB3jxJbC8y9PDbHDjJBQY0O8-A,45774
814
- volcano_sdk_python-0.10.4.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
815
- volcano_sdk_python-0.10.4.dist-info/licenses/LICENSE,sha256=AIHJy-5MFqM8fHfuGFTTb9a8rvJGypcVgN4aZ-7XTWw,11387
816
- volcano_sdk_python-0.10.4.dist-info/RECORD,,
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,,