volcano-sdk-python 0.10.1__py3-none-any.whl → 0.10.2__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.
@@ -3,16 +3,17 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import asyncio
6
+ from collections.abc import Mapping
6
7
  from dataclasses import dataclass
7
- from typing import TYPE_CHECKING, Any, Generic, TypeVar
8
+ from typing import TYPE_CHECKING, Generic, TypeVar
9
+
10
+ from .models import JSONValue
8
11
 
9
12
  if TYPE_CHECKING:
10
13
  from collections.abc import Awaitable, Callable
11
14
 
12
- from .realtime import _PostgresFetchRequest
13
-
14
15
  FallbackT = TypeVar("FallbackT")
15
- PostgresRecord = dict[str, Any]
16
+ PostgresRecord = Mapping[str, JSONValue]
16
17
  _WORKER_CLOSED = "Postgres fetch worker is closed"
17
18
  _INVALID_QUEUE_LIMIT = "queue_limit must be positive"
18
19
  _INVALID_BATCH_WINDOW = "batch_window_seconds cannot be negative"
@@ -20,11 +21,21 @@ _INVALID_BATCH_SIZE = "max_batch_size must be between 1 and queue_limit"
20
21
  _INVALID_RESULT_COUNT = "Postgres fetch returned an unexpected result count"
21
22
 
22
23
 
24
+ @dataclass(frozen=True, slots=True)
25
+ class PostgresFetchRequest:
26
+ """Capture the database, credential, and row identity for a queued fetch."""
27
+
28
+ database_name: str
29
+ access_token: str
30
+ table: str
31
+ row_id: JSONValue
32
+
33
+
23
34
  @dataclass(frozen=True, slots=True)
24
35
  class PostgresFetchJob(Generic[FallbackT]):
25
36
  """Pair a captured row request with its lightweight fallback."""
26
37
 
27
- request: _PostgresFetchRequest | None
38
+ request: PostgresFetchRequest | None
28
39
  fallback: FallbackT
29
40
 
30
41
 
@@ -57,8 +68,8 @@ async def _wait_for_close(
57
68
  try:
58
69
  task.result()
59
70
  except BaseException:
60
- stop_task.cancel()
61
- await asyncio.gather(stop_task, return_exceptions=True)
71
+ _ = stop_task.cancel()
72
+ _ = await asyncio.gather(stop_task, return_exceptions=True)
62
73
  raise
63
74
  await asyncio.shield(stop_task)
64
75
  await asyncio.shield(task)
@@ -70,7 +81,7 @@ class PostgresFetchWorker(Generic[FallbackT]):
70
81
  def __init__(
71
82
  self,
72
83
  fetch: Callable[
73
- [tuple[_PostgresFetchRequest, ...]],
84
+ [tuple[PostgresFetchRequest, ...]],
74
85
  Awaitable[tuple[PostgresRecord | None, ...]],
75
86
  ],
76
87
  deliver: Callable[[PostgresFetchOutcome[FallbackT]], Awaitable[None]],
@@ -94,17 +105,22 @@ class PostgresFetchWorker(Generic[FallbackT]):
94
105
  raise ValueError(_INVALID_BATCH_WINDOW)
95
106
  if not 1 <= max_batch_size <= queue_limit:
96
107
  raise ValueError(_INVALID_BATCH_SIZE)
97
- self._fetch = fetch
98
- self._deliver = deliver
99
- self._batch_window_seconds = batch_window_seconds
100
- self._max_batch_size = max_batch_size
108
+ self._fetch: Callable[
109
+ [tuple[PostgresFetchRequest, ...]],
110
+ Awaitable[tuple[PostgresRecord | None, ...]],
111
+ ] = fetch
112
+ self._deliver: Callable[[PostgresFetchOutcome[FallbackT]], Awaitable[None]] = (
113
+ deliver
114
+ )
115
+ self._batch_window_seconds: float = batch_window_seconds
116
+ self._max_batch_size: int = max_batch_size
101
117
  self._queue: asyncio.Queue[PostgresFetchJob[FallbackT] | _StopWorker] = (
102
118
  asyncio.Queue(maxsize=queue_limit)
103
119
  )
104
- self._state_lock = asyncio.Lock()
120
+ self._state_lock: asyncio.Lock = asyncio.Lock()
105
121
  self._task: asyncio.Task[None] | None = None
106
122
  self._stop_task: asyncio.Task[None] | None = None
107
- self._closed = False
123
+ self._closed: bool = False
108
124
 
109
125
  async def enqueue(self, job: PostgresFetchJob[FallbackT]) -> None:
110
126
  """Queue one fetch, applying backpressure when the queue is full.
@@ -144,14 +160,14 @@ class PostgresFetchWorker(Generic[FallbackT]):
144
160
  task = self._task
145
161
  stop_task = self._stop_task
146
162
  if stop_task is not None and not stop_task.done():
147
- stop_task.cancel()
163
+ _ = stop_task.cancel()
148
164
  if task is not None and not task.done():
149
- task.cancel()
165
+ _ = task.cancel()
150
166
  pending = tuple(
151
167
  candidate for candidate in (task, stop_task) if candidate is not None
152
168
  )
153
169
  if pending:
154
- await asyncio.gather(*pending, return_exceptions=True)
170
+ _ = await asyncio.gather(*pending, return_exceptions=True)
155
171
  self._discard_pending()
156
172
 
157
173
  def _raise_worker_failure(self) -> None:
@@ -172,8 +188,8 @@ class PostgresFetchWorker(Generic[FallbackT]):
172
188
  )
173
189
  finally:
174
190
  if not put_task.done():
175
- put_task.cancel()
176
- await asyncio.gather(put_task, return_exceptions=True)
191
+ _ = put_task.cancel()
192
+ _ = await asyncio.gather(put_task, return_exceptions=True)
177
193
  if task in completed:
178
194
  if task.cancelled():
179
195
  raise RuntimeError(_WORKER_CLOSED)
@@ -182,7 +198,7 @@ class PostgresFetchWorker(Generic[FallbackT]):
182
198
 
183
199
  def _discard_pending(self) -> None:
184
200
  while not self._queue.empty():
185
- self._queue.get_nowait()
201
+ _ = self._queue.get_nowait()
186
202
  self._queue.task_done()
187
203
 
188
204
  async def _run(self) -> None:
@@ -239,8 +255,8 @@ class PostgresFetchWorker(Generic[FallbackT]):
239
255
 
240
256
  @staticmethod
241
257
  def _same_fetch(
242
- first: _PostgresFetchRequest,
243
- request: _PostgresFetchRequest,
258
+ first: PostgresFetchRequest,
259
+ request: PostgresFetchRequest,
244
260
  ) -> bool:
245
261
  return (
246
262
  first.database_name,
@@ -255,7 +271,7 @@ class PostgresFetchWorker(Generic[FallbackT]):
255
271
  @staticmethod
256
272
  def _repeats_row(
257
273
  batch: list[PostgresFetchJob[FallbackT]],
258
- request: _PostgresFetchRequest,
274
+ request: PostgresFetchRequest,
259
275
  ) -> bool:
260
276
  return any(
261
277
  job.request is not None and job.request.row_id == request.row_id
volcano_sdk/_transport.py CHANGED
@@ -7,7 +7,7 @@ from collections.abc import Mapping
7
7
  from dataclasses import dataclass
8
8
  from io import BytesIO
9
9
  from pathlib import PurePosixPath
10
- from typing import TYPE_CHECKING, Any, Protocol, cast
10
+ from typing import TYPE_CHECKING, Any, ParamSpec, Protocol, TypeVar, cast
11
11
  from uuid import UUID, uuid4
12
12
 
13
13
  import httpx
@@ -310,7 +310,7 @@ class TransportResponse(Protocol):
310
310
  def status_code(self) -> int: ...
311
311
 
312
312
  @property
313
- def payload(self) -> Any: ...
313
+ def payload(self) -> object: ...
314
314
 
315
315
  @property
316
316
  def content(self) -> bytes: ...
@@ -370,7 +370,7 @@ class StorageUploadSessionReference:
370
370
  @dataclass(frozen=True, slots=True)
371
371
  class _GeneratedTransportResponse:
372
372
  status_code: int
373
- payload: Any
373
+ payload: object
374
374
  content: bytes
375
375
  headers: Mapping[str, str]
376
376
 
@@ -697,19 +697,25 @@ class AsyncDatabaseSelectTransport(Protocol):
697
697
  ) -> TransportResponse: ...
698
698
 
699
699
 
700
- def invoke(operation: Callable[..., Any], **kwargs: Any) -> Any:
700
+ _P = ParamSpec("_P")
701
+ _T = TypeVar("_T")
702
+
703
+
704
+ def invoke(operation: Callable[_P, _T], /, *args: _P.args, **kwargs: _P.kwargs) -> _T:
701
705
  try:
702
- return operation(**kwargs)
706
+ return operation(*args, **kwargs)
703
707
  except httpx.HTTPError as error:
704
708
  raise TransportError(str(error) or "Volcano transport failed") from error
705
709
 
706
710
 
707
711
  async def invoke_async(
708
- operation: Callable[..., Awaitable[Any]],
709
- **kwargs: Any,
710
- ) -> Any:
712
+ operation: Callable[_P, Awaitable[_T]],
713
+ /,
714
+ *args: _P.args,
715
+ **kwargs: _P.kwargs,
716
+ ) -> _T:
711
717
  try:
712
- return await operation(**kwargs)
718
+ return await operation(*args, **kwargs)
713
719
  except httpx.HTTPError as error:
714
720
  raise TransportError(str(error) or "Volcano transport failed") from error
715
721
 
@@ -732,7 +738,7 @@ def _error_type(status: int) -> type[VolcanoError]:
732
738
  return VolcanoError
733
739
 
734
740
 
735
- def response_payload(response: Any, expected_status: int) -> Any:
741
+ def response_payload(response: TransportResponse, expected_status: int) -> Any:
736
742
  status = int(response.status_code)
737
743
  if status != expected_status:
738
744
  payload: Mapping[str, object]
volcano_sdk/auth.py CHANGED
@@ -1457,16 +1457,14 @@ class Auth:
1457
1457
  ):
1458
1458
  if refresh_error is not None:
1459
1459
  raise refresh_error
1460
- if joined:
1461
- response_payload(response, 204)
1462
- return
1463
- refreshed = self._request_refreshed_session(session.refresh_token)
1464
- validate_refresh_identity(session, refreshed)
1465
- response = invoke(
1466
- transport.auth_delete_my_session,
1467
- authorization=refreshed.access_token,
1468
- session_id=session_id,
1469
- )
1460
+ if not joined:
1461
+ refreshed = self._request_refreshed_session(session.refresh_token)
1462
+ validate_refresh_identity(session, refreshed)
1463
+ response = invoke(
1464
+ transport.auth_delete_my_session,
1465
+ authorization=refreshed.access_token,
1466
+ session_id=session_id,
1467
+ )
1470
1468
  response_payload(response, 204)
1471
1469
 
1472
1470
 
volcano_sdk/database.py CHANGED
@@ -4,7 +4,9 @@ from __future__ import annotations
4
4
 
5
5
  from collections.abc import Mapping
6
6
  from dataclasses import dataclass, replace
7
- from typing import TYPE_CHECKING, Any, Protocol, Self, cast
7
+ from typing import TYPE_CHECKING, Any, Protocol, Self, TypedDict, cast
8
+
9
+ from typing_extensions import override
8
10
 
9
11
  if TYPE_CHECKING:
10
12
  from collections.abc import Sequence
@@ -46,12 +48,23 @@ class DatabaseContext(Protocol):
46
48
  def _session_token(self) -> str: ...
47
49
 
48
50
 
51
+ class _FilterCondition(TypedDict):
52
+ column: str
53
+ operator: str
54
+ value: object
55
+
56
+
57
+ class _OrderClause(TypedDict):
58
+ column: str
59
+ ascending: bool
60
+
61
+
49
62
  class FilterBuilder:
50
63
  """Shared immutable filters for database operations."""
51
64
 
52
- _filters: tuple[dict[str, Any], ...]
65
+ _filters: tuple[_FilterCondition, ...]
53
66
 
54
- def _with_filters(self, filters: tuple[dict[str, Any], ...]) -> Self:
67
+ def _with_filters(self, filters: tuple[_FilterCondition, ...]) -> Self:
55
68
  raise NotImplementedError
56
69
 
57
70
  def eq(self, column: str, value: object) -> Self:
@@ -165,7 +178,7 @@ class FilterBuilder:
165
178
  return self._filter(column, "in", list(values))
166
179
 
167
180
  def _filter(self, column: str, operator: str, value: object) -> Self:
168
- condition = {
181
+ condition: _FilterCondition = {
169
182
  "column": column,
170
183
  "operator": operator,
171
184
  "value": _snapshot_filter_value(value),
@@ -181,8 +194,8 @@ class QueryBuilder(FilterBuilder):
181
194
  _database_name: str
182
195
  _table: str
183
196
  _columns: tuple[str, ...] = ()
184
- _filters: tuple[dict[str, Any], ...] = ()
185
- _order: tuple[dict[str, Any], ...] = ()
197
+ _filters: tuple[_FilterCondition, ...] = ()
198
+ _order: tuple[_OrderClause, ...] = ()
186
199
  _limit: int | None = None
187
200
  _offset: int | None = None
188
201
 
@@ -255,7 +268,7 @@ class QueryBuilder(FilterBuilder):
255
268
  A new query with this ordering clause appended.
256
269
 
257
270
  """
258
- clause = {"column": column, "ascending": ascending}
271
+ clause: _OrderClause = {"column": column, "ascending": ascending}
259
272
  return replace(self, _order=(*self._order, clause))
260
273
 
261
274
  def limit(self, count: int) -> QueryBuilder:
@@ -280,7 +293,8 @@ class QueryBuilder(FilterBuilder):
280
293
  """
281
294
  return replace(self, _offset=count)
282
295
 
283
- def _with_filters(self, filters: tuple[dict[str, Any], ...]) -> QueryBuilder:
296
+ @override
297
+ def _with_filters(self, filters: tuple[_FilterCondition, ...]) -> QueryBuilder:
284
298
  return replace(self, _filters=filters)
285
299
 
286
300
  def _request_body(self) -> dict[str, object]:
@@ -360,9 +374,10 @@ class UpdateBuilder(FilterBuilder):
360
374
  _database_name: str
361
375
  _table: str
362
376
  _values: dict[str, JSONValue]
363
- _filters: tuple[dict[str, Any], ...] = ()
377
+ _filters: tuple[_FilterCondition, ...] = ()
364
378
 
365
- def _with_filters(self, filters: tuple[dict[str, Any], ...]) -> UpdateBuilder:
379
+ @override
380
+ def _with_filters(self, filters: tuple[_FilterCondition, ...]) -> UpdateBuilder:
366
381
  return replace(self, _filters=filters)
367
382
 
368
383
  def execute(self) -> list[dict[str, Any]]:
@@ -397,9 +412,10 @@ class DeleteBuilder(FilterBuilder):
397
412
  _client: DatabaseContext
398
413
  _database_name: str
399
414
  _table: str
400
- _filters: tuple[dict[str, Any], ...] = ()
415
+ _filters: tuple[_FilterCondition, ...] = ()
401
416
 
402
- def _with_filters(self, filters: tuple[dict[str, Any], ...]) -> DeleteBuilder:
417
+ @override
418
+ def _with_filters(self, filters: tuple[_FilterCondition, ...]) -> DeleteBuilder:
403
419
  return replace(self, _filters=filters)
404
420
 
405
421
  def execute(self) -> list[dict[str, Any]]:
volcano_sdk/durable.py CHANGED
@@ -309,9 +309,7 @@ def _execution_fields(payload: object) -> Mapping[str, object]:
309
309
 
310
310
  def _durable_execution(payload: object) -> DurableExecution:
311
311
  values = _execution_fields(payload)
312
- created_at = _datetime(values["created_at"])
313
- if created_at is None:
314
- raise TypeError(_INVALID_EXECUTION_PAYLOAD)
312
+ created_at = _parse_datetime(values["created_at"])
315
313
  result_expired = values.get("result_expired")
316
314
  if result_expired is not None and not isinstance(result_expired, bool):
317
315
  raise TypeError(_INVALID_EXECUTION_PAYLOAD)
@@ -378,6 +376,10 @@ def _datetime(value: object) -> datetime | None:
378
376
  return None
379
377
  if isinstance(value, datetime):
380
378
  return value
379
+ return _parse_datetime(value)
380
+
381
+
382
+ def _parse_datetime(value: object) -> datetime:
381
383
  if not isinstance(value, str) or not value:
382
384
  raise TypeError(_INVALID_EXECUTION_PAYLOAD)
383
385
  text = value.replace("Z", "+00:00") if value.endswith("Z") else value
@@ -41,7 +41,8 @@ Duration: TypeAlias = "str | int | dict[str, int]"
41
41
  # The second argument differs: the handler is given a durable context, and the
42
42
  # wrapper is given the invocation's own context.
43
43
  DurableHandler: TypeAlias = "Callable[[Any, DurableContext], Any]"
44
- FunctionHandler: TypeAlias = "Callable[[Any, Any], Any]"
44
+ # Invocation envelopes are distinct from the user handler's input and result.
45
+ FunctionHandler: TypeAlias = "Callable[[object, object], object]"
45
46
 
46
47
  # A deployed durable function gets the runtime from the build and needs no
47
48
  # extra. This is for running a handler in your own tests, which is the one place
@@ -673,19 +674,19 @@ class DurableContext:
673
674
 
674
675
 
675
676
  @overload
676
- def durable(handler: DurableHandler, *, logger: Any = ...) -> FunctionHandler: ...
677
+ def durable(handler: DurableHandler, *, logger: object = ...) -> FunctionHandler: ...
677
678
 
678
679
 
679
680
  @overload
680
681
  def durable(
681
- handler: None = ..., *, logger: Any = ...
682
+ handler: None = ..., *, logger: object = ...
682
683
  ) -> Callable[[DurableHandler], FunctionHandler]: ...
683
684
 
684
685
 
685
686
  def durable(
686
687
  handler: DurableHandler | None = None,
687
688
  *,
688
- logger: Any = None,
689
+ logger: object = None,
689
690
  ) -> FunctionHandler | Callable[[DurableHandler], FunctionHandler]:
690
691
  """Wrap a handler so Volcano runs it as a durable execution.
691
692
 
@@ -709,7 +710,7 @@ def durable(
709
710
  """
710
711
  if handler is None:
711
712
 
712
- def decorate(func: Callable[[Any, DurableContext], Any]) -> Any:
713
+ def decorate(func: DurableHandler) -> FunctionHandler:
713
714
  return durable(func, logger=logger)
714
715
 
715
716
  return decorate
@@ -719,22 +720,24 @@ def durable(
719
720
  return _wrap_durable(handler, logger)
720
721
 
721
722
 
722
- def _wrap_durable(handler: DurableHandler, logger: object) -> FunctionHandler:
723
+ def _wrap_durable(
724
+ handler: Callable[[T, DurableContext], object], logger: object
725
+ ) -> FunctionHandler:
723
726
  # Wrapped on the first invocation, not here: resolving the engine is what
724
727
  # fails when the runtime is absent, and a decorator that raises at import
725
728
  # time would break a module that merely mentions a durable handler.
726
- wrapped: list[Any] = []
729
+ wrapped: list[FunctionHandler] = []
727
730
 
728
731
  # functools.wraps rather than copying two attributes: __qualname__,
729
732
  # __module__, __dict__ and __wrapped__ matter to inspect.unwrap and to a
730
733
  # traceback, and leaving them pointing at this closure makes the SDK's
731
734
  # wrapper the thing a user sees when their handler fails.
732
735
  @functools.wraps(handler)
733
- def invoke(event: Any, function_context: Any) -> Any:
736
+ def invoke(event: object, function_context: object) -> object:
734
737
  if not wrapped:
735
738
  engine = _Engine.load()
736
739
 
737
- def run(input_value: Any, context: Any) -> Any:
740
+ def run(input_value: T, context: Any) -> object:
738
741
  if logger is not None:
739
742
  context.set_logger(logger)
740
743
  return handler(input_value, DurableContext(context, engine))
@@ -755,7 +758,7 @@ def _validate_wait_options(options: WaitUntilOptions) -> None:
755
758
 
756
759
 
757
760
  def _named(
758
- name: str | Callable[..., Any],
761
+ name: str | Callable[..., Any] | None,
759
762
  func: Callable[..., Any] | None,
760
763
  operation: str,
761
764
  ) -> tuple[str | None, Callable[..., Any]]:
volcano_sdk/functions.py CHANGED
@@ -183,7 +183,6 @@ class Functions:
183
183
  invoke_url=resolution.invoke_url,
184
184
  payload=payload,
185
185
  )
186
- response = cast("TransportResponse", response)
187
186
  if (
188
187
  response.status_code == _HTTP_UNAUTHORIZED
189
188
  and _header(response.headers, _FUNCTION_INVOKED_HEADER) is None
volcano_sdk/locks.py CHANGED
@@ -16,6 +16,8 @@ from .models import LockLease, LockState
16
16
  if TYPE_CHECKING:
17
17
  from collections.abc import Generator
18
18
 
19
+ from ._transport import TransportResponse
20
+
19
21
  _MIN_LOCK_TTL_SECONDS = 5
20
22
  _MAX_LOCK_TTL_SECONDS = 7_776_000
21
23
  _INVALID_LOCK_TTL = "ttl must be an integer between 5 seconds and 90 days"
@@ -39,7 +41,7 @@ class LockGetTransport(Protocol):
39
41
  authorization: str,
40
42
  key: str,
41
43
  request_id: str | None = None,
42
- ) -> object:
44
+ ) -> TransportResponse:
43
45
  """Get one project-scoped lock."""
44
46
  ...
45
47
 
@@ -55,7 +57,7 @@ class LockRenewTransport(Protocol):
55
57
  ttl: int,
56
58
  token: str,
57
59
  request_id: str | None = None,
58
- ) -> object:
60
+ ) -> TransportResponse:
59
61
  """Renew one project-scoped lock."""
60
62
  ...
61
63
 
@@ -69,7 +71,7 @@ class LockForceReleaseTransport(Protocol):
69
71
  authorization: str,
70
72
  key: str,
71
73
  request_id: str | None = None,
72
- ) -> object:
74
+ ) -> TransportResponse:
73
75
  """Force release one project-scoped lock."""
74
76
  ...
75
77
 
volcano_sdk/realtime.py CHANGED
@@ -11,9 +11,12 @@ from types import MappingProxyType
11
11
  from typing import TYPE_CHECKING, Any, Literal, Protocol, TypeAlias, cast
12
12
  from urllib.parse import quote, urlsplit, urlunsplit
13
13
 
14
+ from typing_extensions import override
15
+
14
16
  from ._realtime_fetch_worker import (
15
17
  PostgresFetchJob,
16
18
  PostgresFetchOutcome,
19
+ PostgresFetchRequest,
17
20
  PostgresFetchWorker,
18
21
  )
19
22
  from ._transport import (
@@ -30,6 +33,8 @@ if TYPE_CHECKING:
30
33
  from ._session_operations import SessionOperations
31
34
  from .models import Session
32
35
 
36
+ _PostgresFetchRequest: TypeAlias = PostgresFetchRequest
37
+
33
38
  MessageCallback = Callable[[Any], Any]
34
39
  RealtimeCallback = Callable[[Any], Any]
35
40
  UnsubscribeCallback = Callable[[], None]
@@ -187,14 +192,6 @@ def _filter_postgres_changes(
187
192
  return filtered
188
193
 
189
194
 
190
- @dataclass(frozen=True, slots=True)
191
- class _PostgresFetchRequest:
192
- database_name: str
193
- access_token: str
194
- table: str
195
- row_id: JSONValue
196
-
197
-
198
195
  @dataclass(frozen=True, slots=True)
199
196
  class _PostgresFetchConfig:
200
197
  enabled: bool
@@ -463,6 +460,7 @@ def _centrifuge_client(
463
460
 
464
461
 
465
462
  class _ProjectAwareSubscriptions(dict[str, Any]):
463
+ @override
466
464
  def get(self, key: str, default: Any = None) -> Any:
467
465
  subscription = super().get(key)
468
466
  if subscription is not None:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: volcano-sdk-python
3
- Version: 0.10.1
3
+ Version: 0.10.2
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
@@ -16,6 +16,7 @@ Requires-Python: >=3.11
16
16
  Requires-Dist: attrs>=26.1.0
17
17
  Requires-Dist: centrifuge-python<0.7.0,>=0.6.0
18
18
  Requires-Dist: httpx<0.29.0,>=0.28.1
19
+ Requires-Dist: typing-extensions>=4.4.0
19
20
  Provides-Extra: durable
20
21
  Requires-Dist: aws-durable-execution-sdk-python<3.0.0,>=2.0.0; extra == 'durable'
21
22
  Description-Content-Type: text/markdown
@@ -338,6 +339,10 @@ item when the execution resumes is not guaranteed, so a handler that branched on
338
339
  one would take a different path on the replay. `completion_reason` is how to tell
339
340
  why the batch ended.
340
341
 
342
+ `@durable` accepts typed handlers. The decorated function is the platform
343
+ entrypoint: its arguments and result are invocation envelopes, typed as `object`,
344
+ rather than the handler's application input and result.
345
+
341
346
  Durable operations are synchronous here — there is no `await`, and a step's own
342
347
  function is handed a scope carrying `log` and `attempt`. A wait is held by the
343
348
  platform rather than by your code, so an execution suspended for an hour costs
@@ -4,23 +4,23 @@ volcano_sdk/_lock_guard.py,sha256=YxuJDQOCxHYf7mk6Cp_CPE-mYKC9IoGt_Cxe7SVL8aw,59
4
4
  volcano_sdk/_lock_renewer.py,sha256=cgogAmoRM_An-PdymXumuGmxm62MGqOsQU4c_PYlUjg,1000
5
5
  volcano_sdk/_lock_worker.py,sha256=QhSmwR0WcwgT-7ueJMlbCogLqOakQ5eRAtyWV74fgV0,3057
6
6
  volcano_sdk/_log_response.py,sha256=wZaugr0eD0ZLSa-M6TUEwdIDsI_wblyDXucYEwNK14c,2523
7
- volcano_sdk/_realtime_fetch_worker.py,sha256=l745Ej_7aCv7ityknMWMUGtlxApbUSCP7ljUXgvXU0I,9882
7
+ volcano_sdk/_realtime_fetch_worker.py,sha256=f3mUPe5qZmGuhA9jfpqEUtpLZmvlJ1FhI1XkNz238FI,10406
8
8
  volcano_sdk/_session.py,sha256=w7B1rwbhH3yoE8kFA6gSpUpQb24rPwppotMu-IPHXdI,3025
9
9
  volcano_sdk/_session_operations.py,sha256=s_m-Gl-knjDrjUOFl6z2lJW4IGHAgCSCjRNA7LCB5j0,5727
10
- volcano_sdk/_transport.py,sha256=ACQYwf2MWRiDzAGlapVrN6fUvFHFWkgzRyxFt_PZC7M,61756
11
- volcano_sdk/auth.py,sha256=Ays0YcYdGq_4lfgefjDiGc2xnoAktR7acKeiol4HeWw,52263
10
+ volcano_sdk/_transport.py,sha256=hPciv-WJ7A7Siu6SNU6Ix6x_avPGTucxj8rzJePj43w,61904
11
+ volcano_sdk/auth.py,sha256=f1Dfyq6gpoIzajUKooiVGqgWgvVtCsF_dIG_uA23Hx0,52224
12
12
  volcano_sdk/client.py,sha256=MiwpMsAMyRebPZ7qhktx698TNmQR2y-Gytgf-FNG3TU,12643
13
13
  volcano_sdk/connection_string.py,sha256=p87P-fd9wVfia-LLRRsFTkW6tzx9FXn-GAy9OIOoDvE,2664
14
- volcano_sdk/database.py,sha256=ORc4rfcwXe8KjMKLc8pQiAHFD3IOxodK0lIJQCtxOxE,12563
15
- volcano_sdk/durable.py,sha256=_qowtjrFaa9cXFCj959XaJKfLdg3XtmyXsuEYdmPt14,13239
16
- volcano_sdk/durable_authoring.py,sha256=8uQB3iPFkg09Uz2otk4D_EC6UjMJWeNrzie7DGPEZRQ,33885
14
+ volcano_sdk/database.py,sha256=60Rt4oeaS_WHutC6wGPFunVvFHIo08rwaLiHGuFNRKM,12860
15
+ volcano_sdk/durable.py,sha256=3PuhxRNJy6m7IBhpc_Np6wnlRZVTmChdjjqVp_wLoxk,13250
16
+ volcano_sdk/durable_authoring.py,sha256=NqBY18LDqZoNsnChx_5XjT-1h9OoAniAu7Yu911ebKw,34029
17
17
  volcano_sdk/errors.py,sha256=ffs66FIN_jhyh5SMj7KdXb-zVV3DSgVK6xJEPNA5h9Y,1613
18
- volcano_sdk/functions.py,sha256=uUE3aEJ2sEXr0d-8NcwAcTYsnhPoD2SA7VguuN0OjoQ,13032
19
- volcano_sdk/locks.py,sha256=xnA25hywV2Usi9HiYbMoBBLxLA65wH9eO2FBL4s-Ocs,9842
18
+ volcano_sdk/functions.py,sha256=pdKdgDNo13MbEedeDYtAFazK4r7AZtzYQqrfz-xV5Pw,12977
19
+ volcano_sdk/locks.py,sha256=ptZq7CvXtlK0VU42Qq6DcQQMMjMqQl1zw9cxqdALMfU,9922
20
20
  volcano_sdk/logs.py,sha256=6gYFKLdPcG62KrVugpgw57Lqx0WcxKEZ-zosRz-DeW4,3943
21
21
  volcano_sdk/models.py,sha256=ZIo2YdZ-Ba7rCmHBo3eV9LGjpEftVTeVmrrfq-zGujs,11182
22
22
  volcano_sdk/py.typed,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
23
- volcano_sdk/realtime.py,sha256=TWp4RW8wpoyd9S1mzU2tHuvOc_4Ukt2JNqYcQgHkMUs,63103
23
+ volcano_sdk/realtime.py,sha256=k1vqSDZUfN-lO1zKSGd0lM-Cf9q2ebkdngu8oniLZF8,63091
24
24
  volcano_sdk/storage.py,sha256=fZKa2Jf0w_JVytQ3UjfFSUZRAJllwSHuqbH3ywzhb8Q,27617
25
25
  volcano_sdk/_generated/__init__.py,sha256=ZlB8wMYdN6EjpUS5W_lX3jsxq9G1kOcmUr-j3Fccfn8,164
26
26
  volcano_sdk/_generated/client.py,sha256=GDzw6T2uRmPlUb1DImpBGxwy0dNbhmBknfvvIQW0GuU,12387
@@ -809,7 +809,7 @@ volcano_sdk/_generated/models/usage_data_point.py,sha256=d4U08JbVTiIMM_l2BmRILL1
809
809
  volcano_sdk/_generated/models/variable.py,sha256=t8XaBfTwIH42L8RL4DFDtdqbhVfDEnRS5Axq2q6Tpvk,6405
810
810
  volcano_sdk/_generated/models/variable_deploy_source.py,sha256=G_aCiFHzQx8WhJFs0GvYZEFclfTrfkDvd3kT1e3vty8,463
811
811
  volcano_sdk/_generated/models/variable_status.py,sha256=THSsKBSiAYG4kAtBzGA4TZqxjgx8hdL0Q4qCdZfOj00,391
812
- volcano_sdk_python-0.10.1.dist-info/METADATA,sha256=33uuGzqSaPTW5h0AxsUTN0O22HKfshL4647WHV6J3Dk,45182
813
- volcano_sdk_python-0.10.1.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
814
- volcano_sdk_python-0.10.1.dist-info/licenses/LICENSE,sha256=AIHJy-5MFqM8fHfuGFTTb9a8rvJGypcVgN4aZ-7XTWw,11387
815
- volcano_sdk_python-0.10.1.dist-info/RECORD,,
812
+ volcano_sdk_python-0.10.2.dist-info/METADATA,sha256=iZ4NdMioMG22X20j6pclTVxcptrI5soVYsx_GYHfpqw,45435
813
+ volcano_sdk_python-0.10.2.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
814
+ volcano_sdk_python-0.10.2.dist-info/licenses/LICENSE,sha256=AIHJy-5MFqM8fHfuGFTTb9a8rvJGypcVgN4aZ-7XTWw,11387
815
+ volcano_sdk_python-0.10.2.dist-info/RECORD,,