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.
- volcano_sdk/_realtime_fetch_worker.py +39 -23
- volcano_sdk/_transport.py +16 -10
- volcano_sdk/auth.py +8 -10
- volcano_sdk/database.py +28 -12
- volcano_sdk/durable.py +5 -3
- volcano_sdk/durable_authoring.py +13 -10
- volcano_sdk/functions.py +0 -1
- volcano_sdk/locks.py +5 -3
- volcano_sdk/realtime.py +6 -8
- {volcano_sdk_python-0.10.1.dist-info → volcano_sdk_python-0.10.2.dist-info}/METADATA +6 -1
- {volcano_sdk_python-0.10.1.dist-info → volcano_sdk_python-0.10.2.dist-info}/RECORD +13 -13
- {volcano_sdk_python-0.10.1.dist-info → volcano_sdk_python-0.10.2.dist-info}/WHEEL +0 -0
- {volcano_sdk_python-0.10.1.dist-info → volcano_sdk_python-0.10.2.dist-info}/licenses/LICENSE +0 -0
|
@@ -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,
|
|
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 =
|
|
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:
|
|
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[
|
|
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
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
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:
|
|
243
|
-
request:
|
|
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:
|
|
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) ->
|
|
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:
|
|
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
|
-
|
|
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[
|
|
709
|
-
|
|
710
|
-
|
|
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:
|
|
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
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
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[
|
|
65
|
+
_filters: tuple[_FilterCondition, ...]
|
|
53
66
|
|
|
54
|
-
def _with_filters(self, filters: tuple[
|
|
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[
|
|
185
|
-
_order: tuple[
|
|
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
|
-
|
|
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[
|
|
377
|
+
_filters: tuple[_FilterCondition, ...] = ()
|
|
364
378
|
|
|
365
|
-
|
|
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[
|
|
415
|
+
_filters: tuple[_FilterCondition, ...] = ()
|
|
401
416
|
|
|
402
|
-
|
|
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 =
|
|
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
|
volcano_sdk/durable_authoring.py
CHANGED
|
@@ -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
|
-
|
|
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:
|
|
677
|
+
def durable(handler: DurableHandler, *, logger: object = ...) -> FunctionHandler: ...
|
|
677
678
|
|
|
678
679
|
|
|
679
680
|
@overload
|
|
680
681
|
def durable(
|
|
681
|
-
handler: None = ..., *, logger:
|
|
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:
|
|
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:
|
|
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(
|
|
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[
|
|
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:
|
|
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:
|
|
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
|
-
) ->
|
|
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
|
-
) ->
|
|
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
|
-
) ->
|
|
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.
|
|
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=
|
|
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=
|
|
11
|
-
volcano_sdk/auth.py,sha256=
|
|
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=
|
|
15
|
-
volcano_sdk/durable.py,sha256=
|
|
16
|
-
volcano_sdk/durable_authoring.py,sha256=
|
|
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=
|
|
19
|
-
volcano_sdk/locks.py,sha256=
|
|
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=
|
|
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.
|
|
813
|
-
volcano_sdk_python-0.10.
|
|
814
|
-
volcano_sdk_python-0.10.
|
|
815
|
-
volcano_sdk_python-0.10.
|
|
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,,
|
|
File without changes
|
{volcano_sdk_python-0.10.1.dist-info → volcano_sdk_python-0.10.2.dist-info}/licenses/LICENSE
RENAMED
|
File without changes
|