volcano-sdk-python 0.9.2__py3-none-any.whl → 0.9.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/_function_resolution.py +15 -2
- volcano_sdk/_transport.py +6 -5
- volcano_sdk/functions.py +91 -19
- {volcano_sdk_python-0.9.2.dist-info → volcano_sdk_python-0.9.4.dist-info}/METADATA +56 -29
- {volcano_sdk_python-0.9.2.dist-info → volcano_sdk_python-0.9.4.dist-info}/RECORD +7 -7
- {volcano_sdk_python-0.9.2.dist-info → volcano_sdk_python-0.9.4.dist-info}/WHEEL +0 -0
- {volcano_sdk_python-0.9.2.dist-info → volcano_sdk_python-0.9.4.dist-info}/licenses/LICENSE +0 -0
|
@@ -12,8 +12,12 @@ from __future__ import annotations
|
|
|
12
12
|
import threading
|
|
13
13
|
import time
|
|
14
14
|
from dataclasses import dataclass
|
|
15
|
+
from typing import TYPE_CHECKING
|
|
15
16
|
from urllib.parse import urlsplit
|
|
16
17
|
|
|
18
|
+
if TYPE_CHECKING:
|
|
19
|
+
from .errors import NotFoundError
|
|
20
|
+
|
|
17
21
|
MAX_ENTRIES = 1024
|
|
18
22
|
NEGATIVE_TTL_SECONDS = 30.0
|
|
19
23
|
|
|
@@ -41,6 +45,9 @@ class CachedOutcome:
|
|
|
41
45
|
"""A cached resolve result: a resolution, or a remembered miss when None."""
|
|
42
46
|
|
|
43
47
|
resolution: FunctionResolution | None
|
|
48
|
+
message: str = "Function was not found"
|
|
49
|
+
code: str | None = None
|
|
50
|
+
retry_after: int | None = None
|
|
44
51
|
|
|
45
52
|
|
|
46
53
|
@dataclass(frozen=True, slots=True)
|
|
@@ -122,13 +129,19 @@ def store(
|
|
|
122
129
|
_store((api_url, authorization, name), CachedOutcome(resolution), ttl_seconds)
|
|
123
130
|
|
|
124
131
|
|
|
125
|
-
def store_missing(
|
|
132
|
+
def store_missing(
|
|
133
|
+
api_url: str, authorization: str, name: str, error: NotFoundError
|
|
134
|
+
) -> None:
|
|
126
135
|
"""Remember briefly that a name does not resolve.
|
|
127
136
|
|
|
128
137
|
A caller retrying an unknown name in a loop would otherwise re-ask the
|
|
129
138
|
server on every attempt.
|
|
130
139
|
"""
|
|
131
|
-
_store(
|
|
140
|
+
_store(
|
|
141
|
+
(api_url, authorization, name),
|
|
142
|
+
CachedOutcome(None, str(error), error.code, error.retry_after),
|
|
143
|
+
NEGATIVE_TTL_SECONDS,
|
|
144
|
+
)
|
|
132
145
|
|
|
133
146
|
|
|
134
147
|
def _store(
|
volcano_sdk/_transport.py
CHANGED
|
@@ -99,10 +99,12 @@ from ._generated.api.database_queries.query_database_update import (
|
|
|
99
99
|
from ._generated.api.database_queries.query_database_update import (
|
|
100
100
|
_get_kwargs as database_update_kwargs,
|
|
101
101
|
)
|
|
102
|
-
from ._generated.api.functions import resolve_function_for_invocation
|
|
103
102
|
from ._generated.api.functions.invoke_function import (
|
|
104
103
|
_get_kwargs as invoke_function_kwargs,
|
|
105
104
|
)
|
|
105
|
+
from ._generated.api.functions.resolve_function_for_invocation import (
|
|
106
|
+
_get_kwargs as resolve_function_kwargs,
|
|
107
|
+
)
|
|
106
108
|
from ._generated.api.locks import (
|
|
107
109
|
force_release_project_lock,
|
|
108
110
|
get_project_lock,
|
|
@@ -1643,11 +1645,10 @@ class GeneratedTransport:
|
|
|
1643
1645
|
name: str,
|
|
1644
1646
|
) -> TransportResponse:
|
|
1645
1647
|
with self._client(authorization) as client:
|
|
1646
|
-
response =
|
|
1647
|
-
|
|
1648
|
-
name=name,
|
|
1648
|
+
response = client.get_httpx_client().request(
|
|
1649
|
+
**resolve_function_kwargs(name=name)
|
|
1649
1650
|
)
|
|
1650
|
-
return self.
|
|
1651
|
+
return self._raw_response(response)
|
|
1651
1652
|
|
|
1652
1653
|
def invoke_function(
|
|
1653
1654
|
self,
|
volcano_sdk/functions.py
CHANGED
|
@@ -4,14 +4,19 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import json
|
|
6
6
|
import re
|
|
7
|
-
from collections.abc import Mapping
|
|
8
|
-
from typing import TYPE_CHECKING, Protocol, cast
|
|
7
|
+
from collections.abc import Callable, Mapping
|
|
8
|
+
from typing import TYPE_CHECKING, Protocol, TypeVar, cast
|
|
9
9
|
|
|
10
10
|
from . import _function_resolution
|
|
11
11
|
from ._function_resolution import FunctionResolution
|
|
12
12
|
from ._transport import TransportResponse, invoke, response_payload
|
|
13
|
-
from .errors import
|
|
14
|
-
|
|
13
|
+
from .errors import (
|
|
14
|
+
AuthenticationError,
|
|
15
|
+
NotFoundError,
|
|
16
|
+
SessionChangedError,
|
|
17
|
+
VolcanoError,
|
|
18
|
+
)
|
|
19
|
+
from .models import FunctionResponse, JSONValue, _freeze_json
|
|
15
20
|
|
|
16
21
|
if TYPE_CHECKING:
|
|
17
22
|
from .client import VolcanoClient
|
|
@@ -23,14 +28,60 @@ _INVALID_FUNCTION_NAME = (
|
|
|
23
28
|
)
|
|
24
29
|
_INVALID_FUNCTION_RESPONSE = "Expected a complete function response"
|
|
25
30
|
_INVALID_FUNCTION_PAYLOAD = "Function payload must be a mapping"
|
|
26
|
-
_UNKNOWN_FUNCTION = "Function was not found"
|
|
27
31
|
_HTTP_SUCCESS_MIN = 200
|
|
28
32
|
_HTTP_SUCCESS_MAX = 300
|
|
29
33
|
_HTTP_NOT_FOUND = 404
|
|
34
|
+
_HTTP_UNAUTHORIZED = 401
|
|
30
35
|
# Present only once the platform has dispatched to the function.
|
|
31
36
|
_FUNCTION_INVOKED_HEADER = "X-Volcano-Function-Invoked"
|
|
32
37
|
|
|
33
38
|
|
|
39
|
+
_Result = TypeVar("_Result")
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class _FunctionAuth:
|
|
43
|
+
def __init__(self, client: VolcanoClient) -> None:
|
|
44
|
+
self._client = client
|
|
45
|
+
self._binding = client._capture_session_binding()
|
|
46
|
+
self._fallback_token = client._function_token()
|
|
47
|
+
|
|
48
|
+
def run(self, operation: Callable[[str], _Result]) -> _Result:
|
|
49
|
+
if self._binding[2] is not None:
|
|
50
|
+
self._binding = self._client.auth._owned_refresh_session(self._binding)
|
|
51
|
+
try:
|
|
52
|
+
return self._run(operation)
|
|
53
|
+
finally:
|
|
54
|
+
if self._binding[2] is not None:
|
|
55
|
+
self._client.auth._validate_read_failure(self._binding)
|
|
56
|
+
elif self._client._capture_session_binding()[1] != self._binding[1]:
|
|
57
|
+
raise SessionChangedError
|
|
58
|
+
|
|
59
|
+
def _run(self, operation: Callable[[str], _Result]) -> _Result:
|
|
60
|
+
try:
|
|
61
|
+
return operation(self._token())
|
|
62
|
+
except AuthenticationError as original:
|
|
63
|
+
if self._binding[2] is None or original.status != _HTTP_UNAUTHORIZED:
|
|
64
|
+
raise
|
|
65
|
+
try:
|
|
66
|
+
# Resolve has released its cache lock before refresh callbacks run.
|
|
67
|
+
self._client.auth._refresh_session_for_binding(self._binding)
|
|
68
|
+
except SessionChangedError:
|
|
69
|
+
raise
|
|
70
|
+
except VolcanoError:
|
|
71
|
+
raise original from None
|
|
72
|
+
return operation(self._token())
|
|
73
|
+
|
|
74
|
+
def _token(self) -> str:
|
|
75
|
+
if self._binding[2] is None:
|
|
76
|
+
if self._client._capture_session_binding()[1] != self._binding[1]:
|
|
77
|
+
raise SessionChangedError
|
|
78
|
+
return self._fallback_token
|
|
79
|
+
session = self._client.auth._owned_refresh_session(self._binding)[2]
|
|
80
|
+
if session is None:
|
|
81
|
+
raise SessionChangedError
|
|
82
|
+
return session.access_token
|
|
83
|
+
|
|
84
|
+
|
|
34
85
|
class FunctionsTransport(Protocol):
|
|
35
86
|
"""Transport operations required by the functions facade."""
|
|
36
87
|
|
|
@@ -77,13 +128,17 @@ class Functions:
|
|
|
77
128
|
payload: Mapping[str, JSONValue] | None = None,
|
|
78
129
|
) -> FunctionResponse:
|
|
79
130
|
"""Resolve and invoke a function with an optional JSON object payload."""
|
|
131
|
+
auth = _FunctionAuth(self._client)
|
|
80
132
|
name = _function_name(name)
|
|
81
133
|
request_payload = _function_payload(payload)
|
|
82
|
-
authorization = self._client._function_token()
|
|
83
134
|
transport = cast("FunctionsTransport", self._client._transport)
|
|
84
|
-
resolution =
|
|
85
|
-
|
|
86
|
-
|
|
135
|
+
authorization, resolution = auth.run(
|
|
136
|
+
lambda token: (token, self._resolve(transport, token, name))
|
|
137
|
+
)
|
|
138
|
+
response = auth.run(
|
|
139
|
+
lambda token: self._invoke_resolved(
|
|
140
|
+
transport, token, resolution, request_payload
|
|
141
|
+
)
|
|
87
142
|
)
|
|
88
143
|
if _stale_mapping(response):
|
|
89
144
|
# The function was deleted and recreated, so the cached identity no
|
|
@@ -91,9 +146,13 @@ class Functions:
|
|
|
91
146
|
_function_resolution.forget(
|
|
92
147
|
self._client._api_base_url(), authorization, name
|
|
93
148
|
)
|
|
94
|
-
resolution =
|
|
95
|
-
|
|
96
|
-
|
|
149
|
+
_, resolution = auth.run(
|
|
150
|
+
lambda token: (token, self._resolve(transport, token, name))
|
|
151
|
+
)
|
|
152
|
+
response = auth.run(
|
|
153
|
+
lambda token: self._invoke_resolved(
|
|
154
|
+
transport, token, resolution, request_payload
|
|
155
|
+
)
|
|
97
156
|
)
|
|
98
157
|
return self._response(response)
|
|
99
158
|
|
|
@@ -118,7 +177,13 @@ class Functions:
|
|
|
118
177
|
invoke_url=resolution.invoke_url,
|
|
119
178
|
payload=payload,
|
|
120
179
|
)
|
|
121
|
-
|
|
180
|
+
response = cast("TransportResponse", response)
|
|
181
|
+
if (
|
|
182
|
+
response.status_code == _HTTP_UNAUTHORIZED
|
|
183
|
+
and _header(response.headers, _FUNCTION_INVOKED_HEADER) is None
|
|
184
|
+
):
|
|
185
|
+
response_payload(response, _HTTP_SUCCESS_MIN)
|
|
186
|
+
return response
|
|
122
187
|
|
|
123
188
|
def _resolve(
|
|
124
189
|
self,
|
|
@@ -148,7 +213,12 @@ class Functions:
|
|
|
148
213
|
if cached is None:
|
|
149
214
|
return None
|
|
150
215
|
if cached.resolution is None:
|
|
151
|
-
raise NotFoundError(
|
|
216
|
+
raise NotFoundError(
|
|
217
|
+
cached.message,
|
|
218
|
+
status=_HTTP_NOT_FOUND,
|
|
219
|
+
code=cached.code,
|
|
220
|
+
retry_after=cached.retry_after,
|
|
221
|
+
)
|
|
152
222
|
return cached.resolution
|
|
153
223
|
|
|
154
224
|
def _resolve_uncached(
|
|
@@ -163,9 +233,11 @@ class Functions:
|
|
|
163
233
|
authorization=authorization,
|
|
164
234
|
name=name,
|
|
165
235
|
)
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
236
|
+
try:
|
|
237
|
+
payload = response_payload(resolved, _HTTP_SUCCESS_MIN)
|
|
238
|
+
except NotFoundError as error:
|
|
239
|
+
_function_resolution.store_missing(api_url, authorization, name, error)
|
|
240
|
+
raise
|
|
169
241
|
resolution = self._resolution(payload, api_url)
|
|
170
242
|
_function_resolution.store(
|
|
171
243
|
api_url, authorization, name, resolution, self._cache_ttl(payload)
|
|
@@ -269,9 +341,9 @@ def _function_name(value: object) -> str:
|
|
|
269
341
|
return value
|
|
270
342
|
|
|
271
343
|
|
|
272
|
-
def _function_payload(value: object) ->
|
|
344
|
+
def _function_payload(value: object) -> Mapping[str, JSONValue]:
|
|
273
345
|
if value is None:
|
|
274
346
|
return {}
|
|
275
347
|
if not isinstance(value, Mapping):
|
|
276
348
|
raise TypeError(_INVALID_FUNCTION_PAYLOAD)
|
|
277
|
-
return
|
|
349
|
+
return cast("Mapping[str, JSONValue]", _freeze_json(cast("JSONValue", value)))
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: volcano-sdk-python
|
|
3
|
-
Version: 0.9.
|
|
3
|
+
Version: 0.9.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
|
|
@@ -26,6 +26,9 @@ Use the Volcano Python SDK to access authentication, databases, storage,
|
|
|
26
26
|
functions, locks, logs, and realtime events. Requires Python 3.11 or later.
|
|
27
27
|
|
|
28
28
|
Start with the [Python quickstart](https://github.com/Kong/volcano-sdk-python/blob/main/docs/README.md).
|
|
29
|
+
See the [functions guide](https://github.com/Kong/volcano-sdk-python/blob/main/docs/functions.md) for invocation identity and response handling.
|
|
30
|
+
|
|
31
|
+
See [Authentication](https://github.com/Kong/volcano-sdk-python/blob/main/docs/authentication.md) for account, session, email, and OAuth workflows.
|
|
29
32
|
|
|
30
33
|
## Install
|
|
31
34
|
|
|
@@ -54,13 +57,15 @@ client = VolcanoClient(
|
|
|
54
57
|
|
|
55
58
|
sign_up = client.auth.sign_up(
|
|
56
59
|
email="new-user@example.com",
|
|
57
|
-
password="
|
|
60
|
+
password="correct-horse-battery-staple",
|
|
58
61
|
metadata={"display_name": "New User"},
|
|
59
62
|
)
|
|
60
63
|
if sign_up.confirmation_required:
|
|
61
64
|
print(sign_up.message)
|
|
62
65
|
|
|
63
|
-
session = client.auth.sign_in(
|
|
66
|
+
session = client.auth.sign_in(
|
|
67
|
+
email="user@example.com", password="correct-horse-battery-staple"
|
|
68
|
+
)
|
|
64
69
|
current_session = client.auth.get_session()
|
|
65
70
|
assert current_session == session
|
|
66
71
|
|
|
@@ -68,7 +73,7 @@ user = client.auth.get_user()
|
|
|
68
73
|
assert user.id == session.user_id
|
|
69
74
|
|
|
70
75
|
updated_user = client.auth.update_user(
|
|
71
|
-
password="new-
|
|
76
|
+
password="new-correct-horse-battery-staple",
|
|
72
77
|
metadata={"display_name": "Grace", "avatar": None},
|
|
73
78
|
)
|
|
74
79
|
assert updated_user.id == session.user_id
|
|
@@ -218,14 +223,21 @@ The request goes to the function's own domain rather than to `api_url`, so an
|
|
|
218
223
|
egress rule that allows only the API host will block it; the resolved endpoint
|
|
219
224
|
is cached for the lifetime the platform gives it. Deployments with no public
|
|
220
225
|
function domain invoke through the API host instead.
|
|
221
|
-
It uses the
|
|
226
|
+
It uses the current session token, including a supplied `access_token`, then a configured service key,
|
|
222
227
|
then the anonymous key. An anonymous key can invoke a public function without a
|
|
223
228
|
user session; the function receives no user identity. The immutable result
|
|
224
229
|
includes the response body, status, headers, and `X-Volcano-Version`. The body
|
|
225
230
|
can be a JSON object, array, scalar, or text; an empty body returns `None`.
|
|
226
231
|
JSON arrays become immutable tuples. Invalid JSON is returned as text. A
|
|
227
|
-
function's own non-2xx response is returned when
|
|
228
|
-
|
|
232
|
+
function's own non-2xx response is returned when Volcano confirms it ran;
|
|
233
|
+
non-success platform HTTP responses raise typed SDK errors.
|
|
234
|
+
|
|
235
|
+
Function resolution and invocation recover from a platform HTTP 401 before dispatch:
|
|
236
|
+
the SDK refreshes the captured session and retries the rejected request once.
|
|
237
|
+
Concurrent calls share successful recovery. Replacing or signing out that session
|
|
238
|
+
prevents replay under another identity. The call preserves its original payload values.
|
|
239
|
+
A function's own response, HTTP 403, or a network failure never triggers this retry.
|
|
240
|
+
Anonymous and service keys do not refresh.
|
|
229
241
|
|
|
230
242
|
`logs.search()` returns an immutable page of retained runtime or deployment log
|
|
231
243
|
events. Pass `next_cursor` back as `cursor` to continue a search. `logs.activity()`
|
|
@@ -318,7 +330,9 @@ The signup acknowledgement is identical for new and existing email addresses. Pa
|
|
|
318
330
|
|
|
319
331
|
```python
|
|
320
332
|
result = client.auth.sign_up(
|
|
321
|
-
email="new-user@example.com",
|
|
333
|
+
email="new-user@example.com",
|
|
334
|
+
password="correct-horse-battery-staple",
|
|
335
|
+
sign_in_when_allowed=True,
|
|
322
336
|
)
|
|
323
337
|
session = result.session # None when no follow-up sign-in ran.
|
|
324
338
|
```
|
|
@@ -334,21 +348,23 @@ request. It is cached data, not proof of authentication; use `auth.get_user()`
|
|
|
334
348
|
to fetch the server-validated profile. Existing three-field `Session` construction
|
|
335
349
|
still works, with `user=None`. An adopted snapshot must have the same user ID.
|
|
336
350
|
Successful `get_user()`, `update_user()`, `convert_anonymous()`, and
|
|
337
|
-
`confirm_email_change()` calls update that local snapshot
|
|
338
|
-
|
|
351
|
+
`confirm_email_change()` calls update that local snapshot. Automatic HTTP 401 recovery
|
|
352
|
+
can rotate credentials and emit `TOKEN_REFRESHED`; the profile update itself does not. Previously returned sessions remain immutable.
|
|
339
353
|
Profile identity checks compare UUID values; the session retains its original
|
|
340
354
|
user ID spelling, including in the cached snapshot.
|
|
341
355
|
|
|
342
356
|
`get_user()` sends the active access token to Volcano and returns an immutable, server-validated
|
|
343
357
|
profile with the complete public AuthUser fields. Profile timestamps are timezone-aware `datetime`
|
|
344
358
|
values, and nested user and application metadata are immutable. The request updates the cached
|
|
345
|
-
profile without changing credentials
|
|
359
|
+
profile without changing credentials unless HTTP 401 recovery requires a refresh.
|
|
360
|
+
Successful recovery rotates credentials and emits `TOKEN_REFRESHED`. If another authentication operation replaces the session
|
|
346
361
|
while the request is in flight, `get_user()` raises `SessionChangedError` instead of returning a
|
|
347
362
|
profile for stale credentials.
|
|
348
363
|
|
|
349
364
|
`update_user()` updates the current user's password, metadata, or both. Metadata is a shallow patch:
|
|
350
365
|
omitted keys remain unchanged, and setting a key to `None` removes it. The method returns the same
|
|
351
|
-
immutable profile type as `get_user()` and updates the cached profile without changing credentials.
|
|
366
|
+
immutable profile type as `get_user()` and updates the cached profile without changing credentials unless HTTP 401 recovery requires a refresh.
|
|
367
|
+
Successful recovery rotates credentials and emits `TOKEN_REFRESHED`.
|
|
352
368
|
It also rejects a response if another authentication operation replaces the session while the
|
|
353
369
|
update is in flight.
|
|
354
370
|
|
|
@@ -436,7 +452,9 @@ hosted_url = client.auth.get_hosted_auth_url(
|
|
|
436
452
|
```
|
|
437
453
|
|
|
438
454
|
Store `hosted_state` in the user's signed server-side session before redirecting to `hosted_url`.
|
|
439
|
-
|
|
455
|
+
In the callback, atomically fetch and delete the stored state before validation,
|
|
456
|
+
even if validation or adoption fails. Reject a missing or already-consumed state.
|
|
457
|
+
After parsing the returned fragment into a `Session`, validate and adopt it:
|
|
440
458
|
|
|
441
459
|
```python
|
|
442
460
|
session = client.auth.adopt_hosted_auth_session(
|
|
@@ -465,8 +483,9 @@ authorization_url = client.auth.sign_in_with_oauth(
|
|
|
465
483
|
```
|
|
466
484
|
|
|
467
485
|
Store `oauth_state` in the user's signed server-side session, then redirect the user to the returned
|
|
468
|
-
URL. In the callback,
|
|
469
|
-
|
|
486
|
+
URL. In the callback, atomically fetch and delete the stored nonce as `stored_oauth_state`;
|
|
487
|
+
reject a missing or already-consumed nonce. Pass the returned and consumed states to
|
|
488
|
+
the SDK so it rejects login CSRF before exchanging the one-time code:
|
|
470
489
|
|
|
471
490
|
```python
|
|
472
491
|
session = client.auth.exchange_oauth_code(
|
|
@@ -560,9 +579,11 @@ Revoke one session by ID:
|
|
|
560
579
|
client.auth.delete_session(session_id="00000000-0000-4000-8000-000000000099")
|
|
561
580
|
```
|
|
562
581
|
|
|
563
|
-
The request uses the current access token.
|
|
564
|
-
credentials
|
|
565
|
-
|
|
582
|
+
The request uses the current access token. When its JWT contains a readable UUID `session_id`,
|
|
583
|
+
deleting that session clears local credentials even if the request outcome is uncertain.
|
|
584
|
+
Without that identifier, the SDK cannot recognize self-deletion. Deleting another session does not
|
|
585
|
+
itself clear local state. HTTP 401 recovery can rotate credentials and emit `TOKEN_REFRESHED`;
|
|
586
|
+
a server-rejected refresh clears the captured session before the operation raises. If another authentication operation replaces the session before deletion finishes, the method
|
|
566
587
|
raises `SessionChangedError` instead of clearing the replacement or acknowledging a stale result.
|
|
567
588
|
|
|
568
589
|
Create an anonymous account and make its tokens the current session:
|
|
@@ -574,12 +595,12 @@ session = client.auth.sign_in_anonymously(metadata={"device": "mobile"})
|
|
|
574
595
|
Anonymous sign-ins must be enabled for the project. Convert the account before signing out if the
|
|
575
596
|
user needs to recover it later.
|
|
576
597
|
|
|
577
|
-
Attach email credentials
|
|
598
|
+
Attach email credentials while preserving the anonymous user's ID:
|
|
578
599
|
|
|
579
600
|
```python
|
|
580
601
|
user = client.auth.convert_anonymous(
|
|
581
602
|
email="user@example.com",
|
|
582
|
-
password="
|
|
603
|
+
password="a-long-example-password-2026",
|
|
583
604
|
metadata={"display_name": "Ada"},
|
|
584
605
|
)
|
|
585
606
|
```
|
|
@@ -591,7 +612,7 @@ Set a new password with the recovery token from that email:
|
|
|
591
612
|
```python
|
|
592
613
|
client.auth.reset_password(
|
|
593
614
|
token="recovery-token",
|
|
594
|
-
new_password="new-
|
|
615
|
+
new_password="new-correct-horse-battery-staple",
|
|
595
616
|
)
|
|
596
617
|
```
|
|
597
618
|
|
|
@@ -602,10 +623,13 @@ when the reset flow completes.
|
|
|
602
623
|
To start with only a supplied user access token, pass `access_token` to
|
|
603
624
|
`VolcanoClient`. Construction makes no request and leaves `refresh_token`,
|
|
604
625
|
`user_id`, and `user` as `None` until supplied or validated by the server.
|
|
605
|
-
`get_user()` validates and caches the profile without changing credentials.
|
|
626
|
+
`get_user()` validates and caches the profile without changing credentials unless HTTP 401 recovery requires a refresh.
|
|
627
|
+
Successful recovery rotates credentials and emits `TOKEN_REFRESHED`.
|
|
606
628
|
Without a refresh token, `refresh_session()` raises `AuthenticationError` and
|
|
607
|
-
`sign_out()` revokes the server session
|
|
608
|
-
|
|
629
|
+
`sign_out()` clears local state and revokes the server session when the access JWT
|
|
630
|
+
contains a readable UUID `session_id`.
|
|
631
|
+
Supplied credentials require both a refresh token and an access JWT with a readable UUID
|
|
632
|
+
`session_id` to enable refresh. See the [token bootstrap example](https://github.com/Kong/volcano-sdk-python/blob/main/docs/README.md#use-a-supplied-access-token).
|
|
609
633
|
|
|
610
634
|
Copy a complete native session into another client's memory:
|
|
611
635
|
|
|
@@ -630,7 +654,8 @@ assert client.auth.get_session() is refreshed
|
|
|
630
654
|
```
|
|
631
655
|
|
|
632
656
|
On success, `refresh_session()` replaces the in-memory session and returns the immutable new
|
|
633
|
-
snapshot. An authentication
|
|
657
|
+
snapshot. An authentication rejection from the refresh endpoint clears the captured session.
|
|
658
|
+
Missing refresh credentials, failed session-continuity checks, server errors, and
|
|
634
659
|
transport failures preserve it, and a late response never replaces a newer session. The SDK does
|
|
635
660
|
not persist sessions.
|
|
636
661
|
|
|
@@ -652,8 +677,8 @@ subscription.unsubscribe()
|
|
|
652
677
|
Registration queues `INITIAL_SESSION`. It normally arrives before registration returns, but an
|
|
653
678
|
existing notification dispatch may deliver it afterward. Successful session creation, refresh, and
|
|
654
679
|
local clearing emit `SIGNED_IN`, `TOKEN_REFRESHED`, and `SIGNED_OUT`. Callbacks are delivered locally
|
|
655
|
-
in transition order after the state lock is released
|
|
656
|
-
|
|
680
|
+
in transition order after the state lock is released. Ordinary callback `Exception` failures are
|
|
681
|
+
isolated; exceptions such as `KeyboardInterrupt` propagate after the session transition has committed. Unsubscribing prevents queued and future delivery; a callback already selected for
|
|
657
682
|
delivery may finish after `unsubscribe()` returns. The SDK does not broadcast between processes or
|
|
658
683
|
persist sessions.
|
|
659
684
|
|
|
@@ -665,8 +690,10 @@ assert client.auth.get_session() is None
|
|
|
665
690
|
```
|
|
666
691
|
|
|
667
692
|
Sign-out uses the refresh token directly when the SDK received both credentials together from
|
|
668
|
-
sign-in or a validated refresh. Supplied credentials use the access-token session
|
|
669
|
-
the SDK can refresh once and revoke that
|
|
693
|
+
sign-in or a validated refresh. Supplied credentials use the access-token session when its JWT
|
|
694
|
+
contains a readable UUID `session_id`; on HTTP 401, the SDK can refresh once and revoke that
|
|
695
|
+
same session without adopting the renewed credentials. Without that identifier, sign-out uses
|
|
696
|
+
the supplied refresh token, or only clears local state if no refresh token is available.
|
|
670
697
|
Calling `sign_out()` without a session succeeds without a request. A revocation failure is raised
|
|
671
698
|
after the captured local session is cleared. Sign-out waits for an already-running refresh and uses its validated credentials.
|
|
672
699
|
Later refresh attempts raise `SessionChangedError` without a request. Concurrent sign-out calls
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
volcano_sdk/__init__.py,sha256=rPSFMLjFbxeQvSRkL1Ee8MsNWArIhs8fgNFgtDuMNzI,1954
|
|
2
|
-
volcano_sdk/_function_resolution.py,sha256=
|
|
2
|
+
volcano_sdk/_function_resolution.py,sha256=g04npZTP6h2ePEAnpJryP889l5erCfFkcLloBV84eTs,5387
|
|
3
3
|
volcano_sdk/_lock_guard.py,sha256=VhHTgBc4e5EDj8j15vVIhdBOTp6fvD628x3hVBeF39o,5794
|
|
4
4
|
volcano_sdk/_lock_renewer.py,sha256=tFo1ZeRiPKB5VSbW61uoMpNR67ZDYyMEe-dCjpAM3As,841
|
|
5
5
|
volcano_sdk/_lock_worker.py,sha256=QhSmwR0WcwgT-7ueJMlbCogLqOakQ5eRAtyWV74fgV0,3057
|
|
6
6
|
volcano_sdk/_realtime_fetch_worker.py,sha256=zQTgFF4cIUBeAL4J2OpWAlz_sIynv2BLfu588EogAdo,9269
|
|
7
7
|
volcano_sdk/_session.py,sha256=LYCv2NCDlL5Slh2YLEG8VQ8z-kDaKDYWW4vGdMvUf1U,2474
|
|
8
8
|
volcano_sdk/_session_operations.py,sha256=1vI9gtwjxG2eUGT-nIYnMEgRldJqgIj2lRLukBbG64E,5518
|
|
9
|
-
volcano_sdk/_transport.py,sha256=
|
|
9
|
+
volcano_sdk/_transport.py,sha256=ox_wKKEW42gm2307uK9vIiQFB5l8pzb0ifWXxkzPiHQ,58331
|
|
10
10
|
volcano_sdk/auth.py,sha256=iKq8o0K8geVKXrnLzhvOjAQoX_Nd0yQDJqDH4bemWaM,47366
|
|
11
11
|
volcano_sdk/client.py,sha256=CJJnf_iTcXnyY2Y57LXJVAn39mTUsepblUg9op-Thzo,12283
|
|
12
12
|
volcano_sdk/connection_string.py,sha256=LlTZigsrCF4IfAqQ8SDTWJv35hnnzNu1MfeVv5JW-Kk,2331
|
|
13
13
|
volcano_sdk/database.py,sha256=p_5OaqlBjKAtIUfKQCflXM_CGqokrVmpOMsp6KHiJ8A,9493
|
|
14
14
|
volcano_sdk/errors.py,sha256=ffs66FIN_jhyh5SMj7KdXb-zVV3DSgVK6xJEPNA5h9Y,1613
|
|
15
|
-
volcano_sdk/functions.py,sha256=
|
|
15
|
+
volcano_sdk/functions.py,sha256=31OWRkzBAFjvgynErRFfgjhvzA_sIfv73gz7gYX3G-0,12645
|
|
16
16
|
volcano_sdk/locks.py,sha256=eSkidfHQ_wml5-1LT2hBtHxeALP6E1cGYK0FzHjbrNk,9241
|
|
17
17
|
volcano_sdk/logs.py,sha256=lxORRQBvsnJ0BdMau19MXrIeig5_mEucQSyJ4F1CZxU,4666
|
|
18
18
|
volcano_sdk/models.py,sha256=n5EdzkWzmOstXsuFlvfGOILRemQAqG2P4VCLMelW8SA,8690
|
|
@@ -775,7 +775,7 @@ volcano_sdk/_generated/models/usage_data_point.py,sha256=d4U08JbVTiIMM_l2BmRILL1
|
|
|
775
775
|
volcano_sdk/_generated/models/variable.py,sha256=tvGDOMv77gW6j5skufnfrlNAhzCzA1F7nuPCQWx79yY,5926
|
|
776
776
|
volcano_sdk/_generated/models/variable_deploy_source.py,sha256=G_aCiFHzQx8WhJFs0GvYZEFclfTrfkDvd3kT1e3vty8,463
|
|
777
777
|
volcano_sdk/_generated/models/variable_status.py,sha256=THSsKBSiAYG4kAtBzGA4TZqxjgx8hdL0Q4qCdZfOj00,391
|
|
778
|
-
volcano_sdk_python-0.9.
|
|
779
|
-
volcano_sdk_python-0.9.
|
|
780
|
-
volcano_sdk_python-0.9.
|
|
781
|
-
volcano_sdk_python-0.9.
|
|
778
|
+
volcano_sdk_python-0.9.4.dist-info/METADATA,sha256=RxgSj08eiYMbgWjVj-CXOia39awfCLKsyjRmytGlwmY,37824
|
|
779
|
+
volcano_sdk_python-0.9.4.dist-info/WHEEL,sha256=THafob7ofN-NsuMN7Mg4qZyHaQI7KkD-QlcQatYhXPo,87
|
|
780
|
+
volcano_sdk_python-0.9.4.dist-info/licenses/LICENSE,sha256=AIHJy-5MFqM8fHfuGFTTb9a8rvJGypcVgN4aZ-7XTWw,11387
|
|
781
|
+
volcano_sdk_python-0.9.4.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|