volcano-sdk-python 0.13.2__py3-none-any.whl → 0.13.4__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/_session.py CHANGED
@@ -4,8 +4,8 @@ from __future__ import annotations
4
4
 
5
5
  import base64
6
6
  import json
7
- from collections.abc import Mapping
8
- from typing import TYPE_CHECKING, cast
7
+ from collections.abc import Callable, Mapping
8
+ from typing import TYPE_CHECKING, TypeGuard
9
9
  from uuid import UUID
10
10
 
11
11
  from .errors import AuthenticationError
@@ -17,6 +17,11 @@ _JWT_PARTS = 3
17
17
  _REFRESH_USER_MISMATCH = "Refreshed session belongs to a different user"
18
18
  _REFRESH_SESSION_MISMATCH = "Refreshed credentials belong to a different server session"
19
19
  _MISSING_SESSION_ID = "Cannot refresh supplied credentials without a session identifier"
20
+ _decode_json: Callable[[str], object] = json.loads
21
+
22
+
23
+ def _is_claim_mapping(value: object) -> TypeGuard[Mapping[object, object]]:
24
+ return isinstance(value, Mapping)
20
25
 
21
26
 
22
27
  def session_id_from_access_token(access_token: str) -> str | None:
@@ -33,15 +38,12 @@ def session_id_from_access_token(access_token: str) -> str | None:
33
38
  return None
34
39
  padding = "=" * (-len(parts[1]) % 4)
35
40
  try:
36
- payload = cast(
37
- "object", json.loads(base64.urlsafe_b64decode(parts[1] + padding).decode())
38
- )
41
+ payload = _decode_json(base64.urlsafe_b64decode(parts[1] + padding).decode())
39
42
  except (ValueError, UnicodeDecodeError, RecursionError):
40
43
  return None
41
- if not isinstance(payload, Mapping):
44
+ if not _is_claim_mapping(payload):
42
45
  return None
43
- values = cast("Mapping[object, object]", payload)
44
- return _normalized_session_id(values.get("session_id"))
46
+ return _normalized_session_id(payload.get("session_id"))
45
47
 
46
48
 
47
49
  def _normalized_session_id(session_id: object) -> str | None:
@@ -3,7 +3,7 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  from concurrent.futures import Future
6
- from threading import Lock
6
+ from threading import Lock, get_ident
7
7
  from typing import TYPE_CHECKING, TypeVar
8
8
 
9
9
  from .errors import SessionChangedError, VolcanoError
@@ -44,6 +44,8 @@ class SessionOperations:
44
44
  self._lock: LockType = Lock()
45
45
  self.refreshing: Future[Session] | None = None
46
46
  self.signing_out: Future[BaseException | None] | None = None
47
+ self._refresh_owner: int | None = None
48
+ self._sign_out_owner: int | None = None
47
49
  self._locally_cleared: bool = False
48
50
  self._verified_pair: tuple[str, str | None] | None = (
49
51
  (verified.access_token, verified.refresh_token)
@@ -91,11 +93,15 @@ class SessionOperations:
91
93
  future = self.refreshing
92
94
  if future is None or future.done():
93
95
  future = self.refreshing = Future()
94
- owner = True
96
+ claimed = future
97
+ self._refresh_owner = get_ident()
95
98
  else:
96
- owner = False
97
- if owner:
98
- self._complete(future, operation)
99
+ claimed = None
100
+ if claimed is None and self._refresh_owner == get_ident() and not future.done():
101
+ message = "Reentrant refresh"
102
+ raise RuntimeError(message)
103
+ if claimed is not None:
104
+ self._complete(claimed, operation)
99
105
  return future.result()
100
106
 
101
107
  def sign_out(
@@ -105,13 +111,21 @@ class SessionOperations:
105
111
  future = self.signing_out
106
112
  if future is None:
107
113
  future = self.signing_out = Future()
108
- owner = True
114
+ claimed = future
115
+ self._sign_out_owner = get_ident()
109
116
  else:
110
- owner = False
117
+ claimed = None
111
118
  preceding = self.refreshing
112
119
  pending = preceding is not None and not preceding.done()
113
- if owner:
114
- self._complete_revocation(future, operation, preceding, pending=pending)
120
+ if (
121
+ claimed is None
122
+ and self._sign_out_owner == get_ident()
123
+ and not future.done()
124
+ ):
125
+ message = "Reentrant sign-out"
126
+ raise RuntimeError(message)
127
+ if claimed is not None:
128
+ self._complete_revocation(claimed, operation, preceding, pending=pending)
115
129
  self._sign_out_result(future)
116
130
 
117
131
  def wait_for_sign_out(self) -> None:
volcano_sdk/_transport.py CHANGED
@@ -11,6 +11,7 @@ from typing import (
11
11
  TYPE_CHECKING,
12
12
  ParamSpec,
13
13
  Protocol,
14
+ TypeGuard,
14
15
  TypeVar,
15
16
  cast,
16
17
  overload,
@@ -288,6 +289,8 @@ HTTP_RATE_LIMITED = 429
288
289
  HTTP_OK = 200
289
290
  HTTP_SERVER_ERROR_MIN = 500
290
291
  HTTP_SERVER_ERROR_MAX = 599
292
+ _RETRY_AFTER_HEADER = "Retry-After"
293
+ _URL_TRAILING_SLASHES = "/"
291
294
  _MALFORMED_USER_PROFILE = "Expected a complete user profile"
292
295
  _MALFORMED_SESSION_PAGE = "Expected a complete session page"
293
296
  _MALFORMED_LINKED_OAUTH_PROVIDERS = "Expected complete linked OAuth providers"
@@ -351,6 +354,17 @@ class _ParsedHTTPResponse(_RawHTTPResponse, Protocol):
351
354
  def parsed(self) -> object: ...
352
355
 
353
356
 
357
+ class _JSONResponse(Protocol):
358
+ def json(self) -> object: ...
359
+
360
+
361
+ class _JSONDecoder(Protocol):
362
+ def __call__(self, document: bytes, /) -> object: ...
363
+
364
+
365
+ _decode_json: _JSONDecoder = json.loads
366
+
367
+
354
368
  @runtime_checkable
355
369
  class _ModelPayload(Protocol):
356
370
  def to_dict(self) -> Mapping[str, object]: ...
@@ -704,7 +718,7 @@ class Transport(Protocol):
704
718
  bucket_name: str,
705
719
  path: str,
706
720
  data: bytes,
707
- content_type: str = "application/octet-stream",
721
+ content_type: str,
708
722
  ) -> TransportResponse: ...
709
723
 
710
724
  def download_storage_object(
@@ -802,14 +816,21 @@ def _required_request_string(kwargs: Mapping[str, object], key: str) -> str:
802
816
  return value
803
817
 
804
818
 
819
+ def _is_object_mapping(value: object) -> TypeGuard[Mapping[object, object]]:
820
+ return isinstance(value, Mapping)
821
+
822
+
823
+ def _is_object_dict(value: object) -> TypeGuard[dict[object, object]]:
824
+ return isinstance(value, dict)
825
+
826
+
805
827
  def _request_headers(kwargs: Mapping[str, object]) -> dict[str, str]:
806
828
  raw_headers = kwargs.get("headers", {})
807
- if not isinstance(raw_headers, Mapping):
829
+ if not _is_object_mapping(raw_headers):
808
830
  field = "headers"
809
831
  raise _InvalidGeneratedRequestError(field)
810
- source = cast("Mapping[object, object]", raw_headers)
811
832
  headers: dict[str, str] = {}
812
- for key, value in source.items():
833
+ for key, value in raw_headers.items():
813
834
  if not isinstance(key, str) or not isinstance(value, str):
814
835
  field = "headers"
815
836
  raise _InvalidGeneratedRequestError(field)
@@ -823,12 +844,11 @@ def _request_params(
823
844
  raw_params = kwargs.get("params")
824
845
  if raw_params is None:
825
846
  return None
826
- if not isinstance(raw_params, Mapping):
847
+ if not _is_object_mapping(raw_params):
827
848
  field = "params"
828
849
  raise _InvalidGeneratedRequestError(field)
829
- source = cast("Mapping[object, object]", raw_params)
830
850
  params: dict[str, str | int | float | bool | None] = {}
831
- for key, value in source.items():
851
+ for key, value in raw_params.items():
832
852
  if not isinstance(key, str) or (
833
853
  value is not None and not isinstance(value, (str, int, float, bool))
834
854
  ):
@@ -853,14 +873,13 @@ def _generated_request(
853
873
  )
854
874
 
855
875
 
856
- def _json_object(response: httpx.Response) -> dict[str, object]:
857
- raw = cast("object", response.json())
858
- if not isinstance(raw, dict):
876
+ def _json_object(response: _JSONResponse) -> dict[str, object]:
877
+ raw = response.json()
878
+ if not _is_object_dict(raw):
859
879
  field = "response body"
860
880
  raise _InvalidGeneratedRequestError(field)
861
- source = cast("Mapping[object, object]", raw)
862
881
  payload: dict[str, object] = {}
863
- for key, value in source.items():
882
+ for key, value in raw.items():
864
883
  if not isinstance(key, str):
865
884
  field = "response body key"
866
885
  raise _InvalidGeneratedRequestError(field)
@@ -873,10 +892,7 @@ def response_payload(response: TransportResponse, expected_status: int) -> objec
873
892
  if status != expected_status:
874
893
  payload: Mapping[object, object]
875
894
  raw_payload = response.payload
876
- if isinstance(raw_payload, dict):
877
- payload = cast("Mapping[object, object]", raw_payload)
878
- else:
879
- payload = {}
895
+ payload = raw_payload if _is_object_dict(raw_payload) else {}
880
896
  message = str(
881
897
  payload.get("error") or payload.get("message") or "Volcano request failed"
882
898
  )
@@ -884,7 +900,7 @@ def response_payload(response: TransportResponse, expected_status: int) -> objec
884
900
  code = str(code_value) if code_value is not None else None
885
901
  retry_after = None
886
902
  if status == HTTP_RATE_LIMITED:
887
- retry_after_value = _header(response.headers, "Retry-After")
903
+ retry_after_value = _header(response.headers, _RETRY_AFTER_HEADER)
888
904
  try:
889
905
  retry_after = (
890
906
  int(retry_after_value) if retry_after_value is not None else None
@@ -908,7 +924,7 @@ class GeneratedTransport:
908
924
  timeout: float = 60.0,
909
925
  httpx_transport: httpx.BaseTransport | None = None,
910
926
  ) -> None:
911
- self._api_url: str = api_url.rstrip("/")
927
+ self._api_url: str = api_url.rstrip(_URL_TRAILING_SLASHES)
912
928
  self._timeout: float = timeout
913
929
  self._httpx_transport: httpx.BaseTransport | None = httpx_transport
914
930
 
@@ -932,7 +948,7 @@ class GeneratedTransport:
932
948
  payload = parsed
933
949
  else:
934
950
  try:
935
- raw = cast("object", json.loads(response.content))
951
+ raw = _decode_json(response.content)
936
952
  payload = raw
937
953
  except (json.JSONDecodeError, UnicodeDecodeError):
938
954
  payload = None
@@ -946,7 +962,7 @@ class GeneratedTransport:
946
962
  @staticmethod
947
963
  def _raw_response(response: _RawHTTPResponse) -> TransportResponse:
948
964
  try:
949
- payload = cast("object", json.loads(response.content))
965
+ payload = _decode_json(response.content)
950
966
  except (json.JSONDecodeError, UnicodeDecodeError):
951
967
  payload = None
952
968
  return _GeneratedTransportResponse(
@@ -1833,14 +1849,8 @@ class GeneratedTransport:
1833
1849
  # invoke contract's { payload } envelope.
1834
1850
  plain_payload = _plain_json(payload)
1835
1851
  with self._client(authorization) as client:
1836
- response = client.get_httpx_client().request(
1837
- method="POST",
1838
- url=invoke_url,
1839
- json={"payload": plain_payload},
1840
- headers={
1841
- "Authorization": f"Bearer {authorization}",
1842
- "Content-Type": "application/json",
1843
- },
1852
+ response = client.get_httpx_client().post(
1853
+ invoke_url, json={"payload": plain_payload}
1844
1854
  )
1845
1855
  return self._raw_response(response)
1846
1856
 
volcano_sdk/auth.py CHANGED
@@ -459,12 +459,22 @@ def _oauth_provider_token_status_from_payload(
459
459
  )
460
460
 
461
461
 
462
+ class _OAuthAPIData(Protocol):
463
+ @property
464
+ def data(self) -> object: ...
465
+
466
+
467
+ def _oauth_api_data(payload: _OAuthAPIData) -> object:
468
+ return payload.data
469
+
470
+
462
471
  def _oauth_api_data_from_payload(payload: object) -> JSONValue:
463
472
  if not isinstance(payload, CallOAuthProviderAPIResponse200):
464
473
  raise VolcanoError(_INVALID_OAUTH_API_RESPONSE)
465
- if not _is_json_value(payload.data):
474
+ data = _oauth_api_data(payload)
475
+ if not _is_json_value(data):
466
476
  raise VolcanoError(_INVALID_OAUTH_API_RESPONSE)
467
- return _freeze_json(payload.data)
477
+ return _freeze_json(data)
468
478
 
469
479
 
470
480
  class _SetSession(Protocol):
volcano_sdk/durable.py CHANGED
@@ -2,9 +2,10 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- from collections.abc import Mapping, Sequence
5
+ import math
6
+ from collections.abc import Mapping
6
7
  from datetime import datetime
7
- from typing import Protocol, cast, runtime_checkable
8
+ from typing import Protocol, TypeGuard, runtime_checkable
8
9
  from uuid import UUID
9
10
 
10
11
  from ._transport import (
@@ -46,6 +47,15 @@ _UUID_IDENTIFIERS = {
46
47
  }
47
48
  _HTTP_ACCEPTED = 202
48
49
  _HTTP_OK = 200
50
+ _EXECUTION_STATUSES: tuple[DurableExecutionStatus, ...] = (
51
+ "pending",
52
+ "running",
53
+ "succeeded",
54
+ "failed",
55
+ "timed_out",
56
+ "stopped",
57
+ "unknown",
58
+ )
49
59
 
50
60
 
51
61
  class DurableClientContext(Protocol):
@@ -318,13 +328,105 @@ def _identifier(value: object, field: str) -> str:
318
328
 
319
329
 
320
330
  def _execution_fields(payload: object) -> Mapping[str, object]:
321
- if not isinstance(payload, Mapping):
331
+ if not _is_object_mapping(payload):
322
332
  raise TypeError(_INVALID_EXECUTION_PAYLOAD)
323
- values = cast("Mapping[str, object]", payload)
324
333
  for required in ("id", "function_id", "name", "status", "region", "created_at"):
325
- if not isinstance(values.get(required), str) or not values[required]:
334
+ if not isinstance(payload.get(required), str) or not payload[required]:
326
335
  raise TypeError(_INVALID_EXECUTION_PAYLOAD)
327
- return values
336
+ return payload
337
+
338
+
339
+ def _is_object_mapping(value: object) -> TypeGuard[Mapping[str, object]]:
340
+ return _is_mapping(value) and all(isinstance(key, str) for key in value)
341
+
342
+
343
+ def _is_mapping(value: object) -> TypeGuard[Mapping[object, object]]:
344
+ return isinstance(value, Mapping)
345
+
346
+
347
+ def _is_sequence(value: object) -> TypeGuard[list[object] | tuple[object, ...]]:
348
+ return isinstance(value, (list, tuple))
349
+
350
+
351
+ def _execution_status(value: object) -> DurableExecutionStatus:
352
+ for status in _EXECUTION_STATUSES:
353
+ if value == status:
354
+ return status
355
+ raise TypeError(_INVALID_EXECUTION_PAYLOAD)
356
+
357
+
358
+ def _json_result(value: object) -> JSONValue:
359
+ try:
360
+ if _is_json_value(value, set()):
361
+ return value
362
+ except RecursionError as error:
363
+ raise TypeError(_INVALID_EXECUTION_PAYLOAD) from error
364
+ raise TypeError(_INVALID_EXECUTION_PAYLOAD)
365
+
366
+
367
+ def _is_json_value(value: object, active: set[int]) -> TypeGuard[JSONValue]:
368
+ if _is_json_scalar(value):
369
+ return True
370
+ if _is_mapping(value):
371
+ return _is_json_mapping(value, active)
372
+ if _is_sequence(value):
373
+ return _is_json_sequence(value, active)
374
+ return False
375
+
376
+
377
+ def _is_json_scalar(value: object) -> TypeGuard[str | int | float | bool | None]:
378
+ if value is None or isinstance(value, bool):
379
+ return True
380
+ if isinstance(value, str):
381
+ return _is_utf8(value)
382
+ if isinstance(value, int):
383
+ return _is_json_int(value)
384
+ if isinstance(value, float):
385
+ return math.isfinite(value)
386
+ return False
387
+
388
+
389
+ def _is_utf8(value: str) -> bool:
390
+ try:
391
+ _ = value.encode()
392
+ except UnicodeEncodeError:
393
+ return False
394
+ return True
395
+
396
+
397
+ def _is_json_int(value: int) -> bool:
398
+ try:
399
+ _ = int.__str__(value)
400
+ except ValueError:
401
+ return False
402
+ return True
403
+
404
+
405
+ def _is_json_mapping(value: Mapping[object, object], active: set[int]) -> bool:
406
+ marker = id(value)
407
+ if marker in active:
408
+ return False
409
+ active.add(marker)
410
+ try:
411
+ return all(
412
+ isinstance(key, str) and _is_utf8(key) and _is_json_value(item, active)
413
+ for key, item in value.items()
414
+ )
415
+ finally:
416
+ active.remove(marker)
417
+
418
+
419
+ def _is_json_sequence(
420
+ value: list[object] | tuple[object, ...], active: set[int]
421
+ ) -> bool:
422
+ marker = id(value)
423
+ if marker in active:
424
+ return False
425
+ active.add(marker)
426
+ try:
427
+ return all(_is_json_value(item, active) for item in value)
428
+ finally:
429
+ active.remove(marker)
328
430
 
329
431
 
330
432
  def _durable_execution(payload: object) -> DurableExecution:
@@ -337,10 +439,10 @@ def _durable_execution(payload: object) -> DurableExecution:
337
439
  id=str(values["id"]),
338
440
  function_id=str(values["function_id"]),
339
441
  name=str(values["name"]),
340
- status=cast("DurableExecutionStatus", str(values["status"])),
442
+ status=_execution_status(values["status"]),
341
443
  region=str(values["region"]),
342
444
  created_at=created_at,
343
- result=cast("JSONValue", values.get("result")),
445
+ result=_json_result(values.get("result")),
344
446
  result_expired=result_expired,
345
447
  error=_durable_error(values.get("error")),
346
448
  completed_at=_datetime(values.get("completed_at")),
@@ -350,11 +452,10 @@ def _durable_execution(payload: object) -> DurableExecution:
350
452
  def _durable_error(payload: object) -> DurableExecutionFailure | None:
351
453
  if payload is None:
352
454
  return None
353
- if not isinstance(payload, Mapping):
455
+ if not _is_object_mapping(payload):
354
456
  raise TypeError(_INVALID_EXECUTION_PAYLOAD)
355
- values = cast("Mapping[str, object]", payload)
356
- error_type = values.get("type")
357
- message = values.get("message")
457
+ error_type = payload.get("type")
458
+ message = payload.get("message")
358
459
  return DurableExecutionFailure(
359
460
  type=None if error_type is None else str(error_type),
360
461
  message=None if message is None else str(message),
@@ -362,23 +463,22 @@ def _durable_error(payload: object) -> DurableExecutionFailure | None:
362
463
 
363
464
 
364
465
  def _durable_execution_page(payload: object) -> DurableExecutionPage:
365
- if not isinstance(payload, Mapping):
466
+ if not _is_object_mapping(payload):
366
467
  raise TypeError(_INVALID_EXECUTION_PAGE)
367
- values = cast("Mapping[str, object]", payload)
368
- raw_data = values.get("data")
468
+ raw_data: object = payload.get("data")
369
469
  if raw_data is None:
370
- raw_data = []
371
- if not isinstance(raw_data, (list, tuple)):
470
+ raw_data = list[object]()
471
+ if not _is_sequence(raw_data):
372
472
  raise TypeError(_INVALID_EXECUTION_PAGE)
373
- data = tuple(cast("Sequence[object]", raw_data))
374
- has_more = values.get("has_more", False)
473
+ data = tuple(raw_data)
474
+ has_more = payload.get("has_more", False)
375
475
  if not isinstance(has_more, bool):
376
476
  raise TypeError(_INVALID_EXECUTION_PAGE)
377
477
  return DurableExecutionPage(
378
478
  executions=tuple(_durable_execution(entry) for entry in data),
379
- page=_count(values.get("page")),
380
- limit=_count(values.get("limit")),
381
- total=_count(values.get("total")),
479
+ page=_count(payload.get("page")),
480
+ limit=_count(payload.get("limit")),
481
+ total=_count(payload.get("total")),
382
482
  has_more=has_more,
383
483
  )
384
484
 
@@ -402,8 +502,7 @@ def _datetime(value: object) -> datetime | None:
402
502
  def _parse_datetime(value: object) -> datetime:
403
503
  if not isinstance(value, str) or not value:
404
504
  raise TypeError(_INVALID_EXECUTION_PAYLOAD)
405
- text = value.replace("Z", "+00:00") if value.endswith("Z") else value
406
505
  try:
407
- return datetime.fromisoformat(text)
506
+ return datetime.fromisoformat(value)
408
507
  except ValueError as error:
409
508
  raise TypeError(_INVALID_EXECUTION_PAYLOAD) from error
volcano_sdk/functions.py CHANGED
@@ -359,6 +359,13 @@ def _stale_mapping(response: TransportResponse) -> bool:
359
359
  )
360
360
 
361
361
 
362
+ class _JSONLoader(Protocol):
363
+ def loads(self, s: str, /, *, parse_constant: Callable[[str], None]) -> object: ...
364
+
365
+
366
+ _JSON_LOADER: _JSONLoader = json
367
+
368
+
362
369
  def _function_data(response: TransportResponse) -> JSONValue:
363
370
  if not response.content:
364
371
  return _json_value(response.payload)
@@ -369,7 +376,7 @@ def _function_data(response: TransportResponse) -> JSONValue:
369
376
  is_json = content_type is not None and "application/json" in content_type.lower()
370
377
  if is_json or text.startswith(("{", "[")):
371
378
  try:
372
- decoded: object = json.loads(text, parse_constant=_reject_json_constant)
379
+ decoded = _JSON_LOADER.loads(text, parse_constant=_reject_json_constant)
373
380
  return _json_value(decoded)
374
381
  except ValueError:
375
382
  pass
volcano_sdk/realtime.py CHANGED
@@ -10,7 +10,6 @@ from itertools import count
10
10
  from types import MappingProxyType
11
11
  from typing import (
12
12
  TYPE_CHECKING,
13
- Any,
14
13
  Literal,
15
14
  Protocol,
16
15
  TypeAlias,
@@ -47,9 +46,11 @@ if TYPE_CHECKING:
47
46
  _PostgresFetchRequest: TypeAlias = PostgresFetchRequest
48
47
  _SubscriptionT = TypeVar("_SubscriptionT")
49
48
  _DefaultT = TypeVar("_DefaultT")
49
+ _MessageT = TypeVar("_MessageT")
50
50
 
51
- MessageCallback = Callable[[Any], object]
52
- RealtimeCallback = Callable[[Any], object]
51
+ MessageCallback: TypeAlias = Callable[[_MessageT], object]
52
+ RealtimeCallback: TypeAlias = Callable[[_MessageT], object]
53
+ _StoredCallback = Callable[..., object]
53
54
  UnsubscribeCallback = Callable[[], None]
54
55
  ChannelType: TypeAlias = Literal["broadcast", "presence", "postgres"]
55
56
  PostgresEvent: TypeAlias = Literal["INSERT", "UPDATE", "DELETE"]
@@ -703,7 +704,7 @@ def _presence_info(info: object) -> RealtimePresenceInfo:
703
704
 
704
705
 
705
706
  async def _run_connection_callback(
706
- callback: RealtimeCallback,
707
+ callback: _StoredCallback,
707
708
  context: object,
708
709
  ) -> None:
709
710
  result = callback(context)
@@ -738,7 +739,7 @@ class Channel:
738
739
  self._name: str = name
739
740
  self._type: ChannelType = channel_type
740
741
  self._fetch_config: _PostgresFetchConfig = fetch_config
741
- self._callbacks: dict[str, list[MessageCallback]] = {}
742
+ self._callbacks: dict[str, list[_StoredCallback]] = {}
742
743
  self._presence_state: dict[str, RealtimePresenceInfo] = {}
743
744
  self._presence_events: list[tuple[str, RealtimePresenceInfo]] = []
744
745
  self._presence_syncing: bool = False
@@ -787,7 +788,7 @@ class Channel:
787
788
  """Canonical channel name sent to realtime."""
788
789
  return self._name
789
790
 
790
- def on(self, event: str, callback: MessageCallback) -> Channel:
791
+ def on(self, event: str, callback: Callable[[_MessageT], object]) -> Channel:
791
792
  """Register a callback for messages or presence events.
792
793
 
793
794
  Returns
@@ -852,7 +853,9 @@ class Channel:
852
853
 
853
854
  return unsubscribe
854
855
 
855
- def on_presence_sync(self, callback: MessageCallback) -> UnsubscribeCallback:
856
+ def on_presence_sync(
857
+ self, callback: Callable[[Mapping[str, RealtimePresenceInfo]], object]
858
+ ) -> UnsubscribeCallback:
856
859
  """Observe immutable snapshots of a presence channel's current state.
857
860
 
858
861
  Requires a presence channel.
@@ -1204,7 +1207,7 @@ class Channel:
1204
1207
 
1205
1208
  async def _run_callback(
1206
1209
  self,
1207
- callback: MessageCallback,
1210
+ callback: _StoredCallback,
1208
1211
  delivery: _CallbackDelivery,
1209
1212
  ) -> None:
1210
1213
  if not self._callback_delivery_is_current(delivery):
@@ -1406,7 +1409,7 @@ class Realtime:
1406
1409
  self._channels: dict[str, Channel] = {}
1407
1410
  self._callback_tasks: set[asyncio.Task[None]] = set()
1408
1411
  self._removing_channels: set[str] = set()
1409
- self._connection_callbacks: dict[str, dict[int, RealtimeCallback]] = {
1412
+ self._connection_callbacks: dict[str, dict[int, _StoredCallback]] = {
1410
1413
  "connect": {},
1411
1414
  "disconnect": {},
1412
1415
  "error": {},
@@ -1458,7 +1461,9 @@ class Realtime:
1458
1461
  for request in requests
1459
1462
  )
1460
1463
 
1461
- def on_connect(self, callback: RealtimeCallback) -> UnsubscribeCallback:
1464
+ def on_connect(
1465
+ self, callback: Callable[[RealtimeConnectContext], object]
1466
+ ) -> UnsubscribeCallback:
1462
1467
  """Register a connection callback.
1463
1468
 
1464
1469
  Returns
@@ -1469,7 +1474,9 @@ class Realtime:
1469
1474
  """
1470
1475
  return self._register_connection_callback("connect", callback)
1471
1476
 
1472
- def on_disconnect(self, callback: RealtimeCallback) -> UnsubscribeCallback:
1477
+ def on_disconnect(
1478
+ self, callback: Callable[[RealtimeDisconnectContext], object]
1479
+ ) -> UnsubscribeCallback:
1473
1480
  """Register a disconnection callback.
1474
1481
 
1475
1482
  Returns
@@ -1480,7 +1487,9 @@ class Realtime:
1480
1487
  """
1481
1488
  return self._register_connection_callback("disconnect", callback)
1482
1489
 
1483
- def on_error(self, callback: RealtimeCallback) -> UnsubscribeCallback:
1490
+ def on_error(
1491
+ self, callback: Callable[[RealtimeErrorContext], object]
1492
+ ) -> UnsubscribeCallback:
1484
1493
  """Register a transport-error callback.
1485
1494
 
1486
1495
  Returns
@@ -1494,7 +1503,7 @@ class Realtime:
1494
1503
  def _register_connection_callback(
1495
1504
  self,
1496
1505
  event: str,
1497
- callback: RealtimeCallback,
1506
+ callback: _StoredCallback,
1498
1507
  ) -> UnsubscribeCallback:
1499
1508
  require_callable(callback, CALLBACK_NOT_CALLABLE)
1500
1509
  callback_id = next(self._callback_ids)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: volcano-sdk-python
3
- Version: 0.13.2
3
+ Version: 0.13.4
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
@@ -6,22 +6,22 @@ volcano_sdk/_lock_renewer.py,sha256=cgogAmoRM_An-PdymXumuGmxm62MGqOsQU4c_PYlUjg,
6
6
  volcano_sdk/_lock_worker.py,sha256=IJ0TAFyKiLhS40j0LGLUcOnIrdaqCY3_ZVgETtXp0bc,3139
7
7
  volcano_sdk/_log_response.py,sha256=nSVV0Ir1szJftohnBj2hiP6-pf-rqsOv67brlJGdwQ8,3907
8
8
  volcano_sdk/_realtime_fetch_worker.py,sha256=f3mUPe5qZmGuhA9jfpqEUtpLZmvlJ1FhI1XkNz238FI,10406
9
- volcano_sdk/_session.py,sha256=qiuAXTxo5Yz3AM2T_gqPWBD7TmKl2KhvfWwpsmu7h9g,3033
10
- volcano_sdk/_session_operations.py,sha256=MdEWU_SrS56jmYCzyH81jqneb_pbIaJJI8vFb2WZsJo,5807
11
- volcano_sdk/_transport.py,sha256=WbU20NvfH5YPKApZE9bR4zRsCFNXBHTAbaCP8NiHqeY,65315
12
- volcano_sdk/auth.py,sha256=iwVAnTJV34q3yXWQbgLttD7rIziT3yIl9M3nB3fh-Uk,58253
9
+ volcano_sdk/_session.py,sha256=FiZD11mtOtXSL68WMQQOirxKLRmWk4NIh25zvJxRqek,3124
10
+ volcano_sdk/_session_operations.py,sha256=dS4seAcD-HNYNs2zOXTk66qA9HliqhXyQwEz91nhY-4,6446
11
+ volcano_sdk/_transport.py,sha256=j85miD43LvghpQZOEKJYbia4WbpGKz6a4W2gkzfQgWw,65308
12
+ volcano_sdk/auth.py,sha256=wO34z_fkwGscosLIPR9aQYyd6NsNMkqxk-8wGJQeJSU,58435
13
13
  volcano_sdk/client.py,sha256=HYvcsCD5_FGCkNv2Zo38hH9HYk1gEL8Cowy_U_K0rw8,13923
14
14
  volcano_sdk/connection_string.py,sha256=Ba-PrP8oeD1ILG-5zBLz1LPjUDEVoM_7tQwZ5EslB54,2712
15
15
  volcano_sdk/database.py,sha256=xZpn5V1zNdft9sgb2W6vO-Sfo61IOJ5bjLS5LpRZ8Sg,13887
16
- volcano_sdk/durable.py,sha256=zdLKsapHg7o5jgG1XUGKD2r-6rpeUs7wUN58i-gTmWU,13887
16
+ volcano_sdk/durable.py,sha256=D-bo3BR_u1G-0gUAFWO2NGSClgaTElJVFV7hk29KR0I,16307
17
17
  volcano_sdk/durable_authoring.py,sha256=DBB31rQqtJQqL7BtOiaEAsz75FeQ1tarUTG1ctftDN8,37322
18
18
  volcano_sdk/errors.py,sha256=ujND4F6E9OWr0-VLpuTdvnSZJPVhNUyHnvXJCDp39V0,1649
19
- volcano_sdk/functions.py,sha256=1H2O8bk_057x080gz7f62x9Etn1mtHvWMXktisJo5HA,16796
19
+ volcano_sdk/functions.py,sha256=AA-cJ1Pf5W2_Qx5NRKI1dgPg6wGxCuU8llfpOcsdGh4,16950
20
20
  volcano_sdk/locks.py,sha256=Zf2nBoolXK-zXeAJYdqbhmfkn3OiNUpX7XmmDhvGCmI,11779
21
21
  volcano_sdk/logs.py,sha256=VC2mM2kykvVcnFRd082HAAdVC4gn_2RXwvc998m8rK8,4692
22
22
  volcano_sdk/models.py,sha256=ZIo2YdZ-Ba7rCmHBo3eV9LGjpEftVTeVmrrfq-zGujs,11182
23
23
  volcano_sdk/py.typed,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
24
- volcano_sdk/realtime.py,sha256=-mUVBwaon5qPFWOsWVyW0jRCeBKbeCd110wBBxD_YcY,66800
24
+ volcano_sdk/realtime.py,sha256=HySI4IemLEsPCWxEhi_1THFWAtEzOoTwDunIqxbZDdk,67083
25
25
  volcano_sdk/storage.py,sha256=5RjF1LNZ_qEBBmBAfWB-NHvtqm2_Hyc7bSeOPt4xImI,33461
26
26
  volcano_sdk/_generated/__init__.py,sha256=ZlB8wMYdN6EjpUS5W_lX3jsxq9G1kOcmUr-j3Fccfn8,164
27
27
  volcano_sdk/_generated/client.py,sha256=GDzw6T2uRmPlUb1DImpBGxwy0dNbhmBknfvvIQW0GuU,12387
@@ -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.13.2.dist-info/METADATA,sha256=1h3743OIToGmex6Rb8-s_PAgPuTSJ7e5xrKM718o1Bs,46989
814
- volcano_sdk_python-0.13.2.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
815
- volcano_sdk_python-0.13.2.dist-info/licenses/LICENSE,sha256=AIHJy-5MFqM8fHfuGFTTb9a8rvJGypcVgN4aZ-7XTWw,11387
816
- volcano_sdk_python-0.13.2.dist-info/RECORD,,
813
+ volcano_sdk_python-0.13.4.dist-info/METADATA,sha256=_vQoCoKrlj5CAQ2fzMPXPf6tnP7hkCn98bJsDBDHOpI,46989
814
+ volcano_sdk_python-0.13.4.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
815
+ volcano_sdk_python-0.13.4.dist-info/licenses/LICENSE,sha256=AIHJy-5MFqM8fHfuGFTTb9a8rvJGypcVgN4aZ-7XTWw,11387
816
+ volcano_sdk_python-0.13.4.dist-info/RECORD,,