volcano-sdk-python 0.6.7__py3-none-any.whl → 0.7.0__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/auth.py CHANGED
@@ -262,7 +262,7 @@ def _email_change_result_from_payload(payload: object) -> EmailChangeResult:
262
262
  )
263
263
 
264
264
 
265
- def _user_from_payload(payload: object) -> User:
265
+ def _user_from_payload(payload: object) -> tuple[User, Mapping[str, JSONValue]]:
266
266
  if not isinstance(
267
267
  payload,
268
268
  (
@@ -277,7 +277,7 @@ def _user_from_payload(payload: object) -> User:
277
277
  project_id = _none_if_unset(user.project_id)
278
278
  user_metadata = _none_if_unset(user.user_metadata)
279
279
  app_metadata = _none_if_unset(user.app_metadata)
280
- return User(
280
+ profile = User(
281
281
  id=str(user.id),
282
282
  email=user.email,
283
283
  status=user.status,
@@ -299,6 +299,7 @@ def _user_from_payload(payload: object) -> User:
299
299
  created_at=_none_if_unset(user.created_at),
300
300
  updated_at=_none_if_unset(user.updated_at),
301
301
  )
302
+ return profile, cast("Mapping[str, JSONValue]", user.to_dict())
302
303
 
303
304
 
304
305
  def _none_if_unset(value: _T | Unset) -> _T | None:
@@ -468,6 +469,10 @@ class AuthContext(Protocol):
468
469
 
469
470
  def _capture_session_binding(self) -> tuple[int, int, Session | None]: ...
470
471
 
472
+ def _update_session_user_if_current(
473
+ self, user: Mapping[str, JSONValue], generation: int
474
+ ) -> bool: ...
475
+
471
476
  def _set_session_if_current(
472
477
  self,
473
478
  session: Session,
@@ -580,10 +585,7 @@ class Auth:
580
585
  password=password,
581
586
  metadata=dict(metadata or {}),
582
587
  )
583
- user = _user_from_payload(response_payload(response, 200))
584
- if self._client._capture_session()[0] != generation:
585
- raise SessionChangedError
586
- return user
588
+ return self._update_current_user(response_payload(response, 200), generation)
587
589
 
588
590
  def reset_password_for_email(self, *, email: str) -> None:
589
591
  """Request a reset email without revealing whether the account exists."""
@@ -636,10 +638,7 @@ class Auth:
636
638
  authorization=current.access_token,
637
639
  token=token,
638
640
  )
639
- user = _user_from_payload(response_payload(response, 200))
640
- if self._client._capture_session()[0] != generation:
641
- raise SessionChangedError
642
- return user
641
+ return self._update_current_user(response_payload(response, 200), generation)
643
642
 
644
643
  def delete_all_other_sessions(self) -> None:
645
644
  """Delete every other session while preserving the current session."""
@@ -945,6 +944,12 @@ class Auth:
945
944
  )
946
945
  response_payload(response, 200)
947
946
 
947
+ def _update_current_user(self, payload: object, generation: int) -> User:
948
+ user, snapshot = _user_from_payload(payload)
949
+ if not self._client._update_session_user_if_current(snapshot, generation):
950
+ raise SessionChangedError
951
+ return user
952
+
948
953
  def get_user(self) -> User:
949
954
  """Load a server-validated profile for the current session."""
950
955
  generation, current = self._client._capture_session()
@@ -952,10 +957,7 @@ class Auth:
952
957
  raise AuthenticationError(_NO_ACTIVE_SESSION)
953
958
  transport = cast("AuthGetUserTransport", self._client._transport)
954
959
  response = invoke(transport.auth_get_user, authorization=current.access_token)
955
- user = _user_from_payload(response_payload(response, 200))
956
- if self._client._capture_session()[0] != generation:
957
- raise SessionChangedError
958
- return user
960
+ return self._update_current_user(response_payload(response, 200), generation)
959
961
 
960
962
  def update_user(
961
963
  self,
@@ -974,10 +976,7 @@ class Auth:
974
976
  password=password,
975
977
  metadata=None if metadata is None else dict(metadata),
976
978
  )
977
- user = _user_from_payload(response_payload(response, 200))
978
- if self._client._capture_session()[0] != generation:
979
- raise SessionChangedError
980
- return user
979
+ return self._update_current_user(response_payload(response, 200), generation)
981
980
 
982
981
  def sign_in(self, *, email: str, password: str) -> Session:
983
982
  """Sign in a user and store the returned session."""
volcano_sdk/client.py CHANGED
@@ -4,11 +4,14 @@ from __future__ import annotations
4
4
 
5
5
  import threading
6
6
  from collections import deque
7
+ from dataclasses import replace
7
8
  from typing import TYPE_CHECKING
9
+ from uuid import UUID
8
10
 
9
11
  from ._transport import GeneratedTransport, Transport
10
12
  from .auth import Auth
11
13
  from .database import Database
14
+ from .errors import AuthenticationError
12
15
  from .functions import Functions
13
16
  from .locks import Locks
14
17
  from .logs import Logs
@@ -16,17 +19,19 @@ from .models import (
16
19
  AuthChangeEvent,
17
20
  AuthStateCallback,
18
21
  AuthSubscription,
22
+ JSONValue,
19
23
  Session,
20
24
  )
21
25
  from .realtime import CentrifugeFactory, Realtime
22
26
  from .storage import Storage
23
27
 
24
28
  if TYPE_CHECKING:
25
- from collections.abc import Callable
29
+ from collections.abc import Callable, Mapping
26
30
  from types import TracebackType
27
31
 
28
32
  _NO_ACTIVE_SESSION = "No active session"
29
33
  _NO_SERVICE_KEY = "No service key configured"
34
+ _PROFILE_USER_MISMATCH = "Profile user does not match the active session"
30
35
 
31
36
 
32
37
  class _CallbackOutcome:
@@ -161,6 +166,25 @@ class VolcanoClient:
161
166
  self._current_session,
162
167
  )
163
168
 
169
+ def _update_session_user_if_current(
170
+ self, user: Mapping[str, JSONValue], generation: int
171
+ ) -> bool:
172
+ with self._session_lock:
173
+ current = self._current_session
174
+ if generation != self._session_generation or current is None:
175
+ return False
176
+ try:
177
+ same_user = UUID(str(user["id"])) == UUID(current.user_id)
178
+ except ValueError:
179
+ same_user = False
180
+ if not same_user:
181
+ raise AuthenticationError(_PROFILE_USER_MISMATCH)
182
+ # Profile updates do not replace credentials or invalidate other requests.
183
+ self._current_session = replace(
184
+ current, user={**user, "id": current.user_id}
185
+ )
186
+ return True
187
+
164
188
  def _set_session_if_current(
165
189
  self,
166
190
  session: Session,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: volcano-sdk-python
3
- Version: 0.6.7
3
+ Version: 0.7.0
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
@@ -315,18 +315,24 @@ including metadata. The snapshot is deeply immutable and available without a
315
315
  request. It is cached data, not proof of authentication; use `auth.get_user()`
316
316
  to fetch the server-validated profile. Existing three-field `Session` construction
317
317
  still works, with `user=None`. An adopted snapshot must have the same user ID.
318
+ Successful `get_user()`, `update_user()`, `convert_anonymous()`, and
319
+ `confirm_email_change()` calls update that local snapshot without changing tokens
320
+ or emitting an auth-state event. Previously returned sessions remain immutable.
321
+ Profile identity checks compare UUID values; the session retains its original
322
+ user ID spelling, including in the cached snapshot.
318
323
 
319
324
  `get_user()` sends the active access token to Volcano and returns an immutable, server-validated
320
325
  profile with the complete public AuthUser fields. Profile timestamps are timezone-aware `datetime`
321
- values, and nested user and application metadata are immutable. The request does not replace the
322
- session or cache the profile. If another authentication operation replaces the session while the
323
- request is in flight, `get_user()` raises `SessionChangedError` instead of returning a profile for
324
- stale credentials.
326
+ values, and nested user and application metadata are immutable. The request updates the cached
327
+ profile without changing credentials. If another authentication operation replaces the session
328
+ while the request is in flight, `get_user()` raises `SessionChangedError` instead of returning a
329
+ profile for stale credentials.
325
330
 
326
331
  `update_user()` updates the current user's password, metadata, or both. Metadata is a shallow patch:
327
332
  omitted keys remain unchanged, and setting a key to `None` removes it. The method returns the same
328
- immutable profile type as `get_user()` and does not replace the active session. It also rejects a
329
- response if another authentication operation replaces the session while the update is in flight.
333
+ immutable profile type as `get_user()` and updates the cached profile without changing credentials.
334
+ It also rejects a response if another authentication operation replaces the session while the
335
+ update is in flight.
330
336
 
331
337
  Request a password reset email without creating or changing a session:
332
338
 
@@ -5,8 +5,8 @@ volcano_sdk/_lock_renewer.py,sha256=tFo1ZeRiPKB5VSbW61uoMpNR67ZDYyMEe-dCjpAM3As,
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/_transport.py,sha256=apXZLuT9W17vO-7Ag2E289o2B-uL-Rndv_wRPm69x0I,55481
8
- volcano_sdk/auth.py,sha256=8CkF7nrZJ1jFPW3DllYs8v096DezGdpxFE8jHwMx3e4,42164
9
- volcano_sdk/client.py,sha256=MFNhfh4o_M4GuVE7wQOTjdwprDgRisnCWxyM-Los5nQ,9395
8
+ volcano_sdk/auth.py,sha256=6TXpCZ0mPqluPh-nJSYUbjcryJTwZBz03zDSHuP1eZA,42262
9
+ volcano_sdk/client.py,sha256=KutjBUOEJyK73OibKilrcWazpWeXKEZYUehSAH8qxUE,10366
10
10
  volcano_sdk/connection_string.py,sha256=LlTZigsrCF4IfAqQ8SDTWJv35hnnzNu1MfeVv5JW-Kk,2331
11
11
  volcano_sdk/database.py,sha256=p_5OaqlBjKAtIUfKQCflXM_CGqokrVmpOMsp6KHiJ8A,9493
12
12
  volcano_sdk/errors.py,sha256=ffs66FIN_jhyh5SMj7KdXb-zVV3DSgVK6xJEPNA5h9Y,1613
@@ -772,7 +772,7 @@ volcano_sdk/_generated/models/usage_data_point.py,sha256=d4U08JbVTiIMM_l2BmRILL1
772
772
  volcano_sdk/_generated/models/variable.py,sha256=tvGDOMv77gW6j5skufnfrlNAhzCzA1F7nuPCQWx79yY,5926
773
773
  volcano_sdk/_generated/models/variable_deploy_source.py,sha256=G_aCiFHzQx8WhJFs0GvYZEFclfTrfkDvd3kT1e3vty8,463
774
774
  volcano_sdk/_generated/models/variable_status.py,sha256=THSsKBSiAYG4kAtBzGA4TZqxjgx8hdL0Q4qCdZfOj00,391
775
- volcano_sdk_python-0.6.7.dist-info/METADATA,sha256=LXWuhSliyw_ftVThjDPwI_OyXwZufBa9oFfUWt30yzI,32886
776
- volcano_sdk_python-0.6.7.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
777
- volcano_sdk_python-0.6.7.dist-info/licenses/LICENSE,sha256=AIHJy-5MFqM8fHfuGFTTb9a8rvJGypcVgN4aZ-7XTWw,11387
778
- volcano_sdk_python-0.6.7.dist-info/RECORD,,
775
+ volcano_sdk_python-0.7.0.dist-info/METADATA,sha256=UvxuStKj4B3uX6BtRlrXlfjXNQw4GaOe6vDVN6nKF9c,33273
776
+ volcano_sdk_python-0.7.0.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
777
+ volcano_sdk_python-0.7.0.dist-info/licenses/LICENSE,sha256=AIHJy-5MFqM8fHfuGFTTb9a8rvJGypcVgN4aZ-7XTWw,11387
778
+ volcano_sdk_python-0.7.0.dist-info/RECORD,,