volcano-sdk-python 0.13.2__py3-none-any.whl → 0.13.3__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 +10 -8
- volcano_sdk/_session_operations.py +23 -9
- volcano_sdk/_transport.py +38 -28
- {volcano_sdk_python-0.13.2.dist-info → volcano_sdk_python-0.13.3.dist-info}/METADATA +1 -1
- {volcano_sdk_python-0.13.2.dist-info → volcano_sdk_python-0.13.3.dist-info}/RECORD +7 -7
- {volcano_sdk_python-0.13.2.dist-info → volcano_sdk_python-0.13.3.dist-info}/WHEEL +0 -0
- {volcano_sdk_python-0.13.2.dist-info → volcano_sdk_python-0.13.3.dist-info}/licenses/LICENSE +0 -0
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,
|
|
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 =
|
|
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
|
|
44
|
+
if not _is_claim_mapping(payload):
|
|
42
45
|
return None
|
|
43
|
-
|
|
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
|
-
|
|
96
|
+
claimed = future
|
|
97
|
+
self._refresh_owner = get_ident()
|
|
95
98
|
else:
|
|
96
|
-
|
|
97
|
-
if
|
|
98
|
-
|
|
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
|
-
|
|
114
|
+
claimed = future
|
|
115
|
+
self._sign_out_owner = get_ident()
|
|
109
116
|
else:
|
|
110
|
-
|
|
117
|
+
claimed = None
|
|
111
118
|
preceding = self.refreshing
|
|
112
119
|
pending = preceding is not None and not preceding.done()
|
|
113
|
-
if
|
|
114
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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:
|
|
857
|
-
raw =
|
|
858
|
-
if not
|
|
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
|
|
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
|
|
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,
|
|
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 =
|
|
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 =
|
|
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().
|
|
1837
|
-
|
|
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
|
|
|
@@ -6,9 +6,9 @@ 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=
|
|
10
|
-
volcano_sdk/_session_operations.py,sha256=
|
|
11
|
-
volcano_sdk/_transport.py,sha256=
|
|
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
12
|
volcano_sdk/auth.py,sha256=iwVAnTJV34q3yXWQbgLttD7rIziT3yIl9M3nB3fh-Uk,58253
|
|
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
|
|
@@ -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.
|
|
814
|
-
volcano_sdk_python-0.13.
|
|
815
|
-
volcano_sdk_python-0.13.
|
|
816
|
-
volcano_sdk_python-0.13.
|
|
813
|
+
volcano_sdk_python-0.13.3.dist-info/METADATA,sha256=H4a4MssxuRv3OjhTbTgIWsRe3wXKxpyvyCrfN1bpNIc,46989
|
|
814
|
+
volcano_sdk_python-0.13.3.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
|
|
815
|
+
volcano_sdk_python-0.13.3.dist-info/licenses/LICENSE,sha256=AIHJy-5MFqM8fHfuGFTTb9a8rvJGypcVgN4aZ-7XTWw,11387
|
|
816
|
+
volcano_sdk_python-0.13.3.dist-info/RECORD,,
|
|
File without changes
|
{volcano_sdk_python-0.13.2.dist-info → volcano_sdk_python-0.13.3.dist-info}/licenses/LICENSE
RENAMED
|
File without changes
|