volcano-sdk-python 0.7.2__py3-none-any.whl → 0.8.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/_lock_guard.py +4 -1
- volcano_sdk/_transport.py +78 -20
- volcano_sdk/auth.py +49 -29
- volcano_sdk/locks.py +87 -20
- {volcano_sdk_python-0.7.2.dist-info → volcano_sdk_python-0.8.0.dist-info}/METADATA +10 -1
- {volcano_sdk_python-0.7.2.dist-info → volcano_sdk_python-0.8.0.dist-info}/RECORD +8 -8
- {volcano_sdk_python-0.7.2.dist-info → volcano_sdk_python-0.8.0.dist-info}/WHEEL +1 -1
- {volcano_sdk_python-0.7.2.dist-info → volcano_sdk_python-0.8.0.dist-info}/licenses/LICENSE +0 -0
volcano_sdk/_lock_guard.py
CHANGED
|
@@ -54,13 +54,16 @@ class LockGuard:
|
|
|
54
54
|
*,
|
|
55
55
|
ttl: int,
|
|
56
56
|
started_at: float,
|
|
57
|
+
lease_started_at: float | None = None,
|
|
57
58
|
) -> None:
|
|
58
59
|
"""Track one acquired lease against its local monotonic deadline."""
|
|
59
60
|
self._state_lock = threading.Lock()
|
|
60
61
|
self._lease = lease
|
|
61
62
|
self._ttl = ttl
|
|
62
63
|
self._absolute_deadline = started_at + MAX_LOCK_LIFETIME_SECONDS
|
|
63
|
-
|
|
64
|
+
# A successful retry renews the TTL, not the maximum ownership lifetime.
|
|
65
|
+
lease_started_at = started_at if lease_started_at is None else lease_started_at
|
|
66
|
+
self._lease_deadline = min(lease_started_at + ttl, self._absolute_deadline)
|
|
64
67
|
self._failure: Exception | None = None
|
|
65
68
|
self._lost = threading.Event()
|
|
66
69
|
|
volcano_sdk/_transport.py
CHANGED
|
@@ -13,14 +13,10 @@ from uuid import UUID, uuid4
|
|
|
13
13
|
import httpx
|
|
14
14
|
|
|
15
15
|
from ._generated.api.authentication import (
|
|
16
|
-
auth_confirm_email_change,
|
|
17
|
-
auth_convert_anonymous,
|
|
18
|
-
auth_get_user,
|
|
19
16
|
auth_logout,
|
|
20
17
|
auth_refresh,
|
|
21
18
|
auth_signin,
|
|
22
19
|
auth_signup,
|
|
23
|
-
auth_update_user,
|
|
24
20
|
)
|
|
25
21
|
from ._generated.api.authentication.auth_cancel_email_change import (
|
|
26
22
|
_get_kwargs as cancel_email_change_kwargs,
|
|
@@ -28,6 +24,18 @@ from ._generated.api.authentication.auth_cancel_email_change import (
|
|
|
28
24
|
from ._generated.api.authentication.auth_confirm_email import (
|
|
29
25
|
_get_kwargs as confirm_email_kwargs,
|
|
30
26
|
)
|
|
27
|
+
from ._generated.api.authentication.auth_confirm_email_change import (
|
|
28
|
+
_build_response as build_auth_confirm_email_change_response,
|
|
29
|
+
)
|
|
30
|
+
from ._generated.api.authentication.auth_confirm_email_change import (
|
|
31
|
+
_get_kwargs as auth_confirm_email_change_kwargs,
|
|
32
|
+
)
|
|
33
|
+
from ._generated.api.authentication.auth_convert_anonymous import (
|
|
34
|
+
_build_response as build_auth_convert_anonymous_response,
|
|
35
|
+
)
|
|
36
|
+
from ._generated.api.authentication.auth_convert_anonymous import (
|
|
37
|
+
_get_kwargs as auth_convert_anonymous_kwargs,
|
|
38
|
+
)
|
|
31
39
|
from ._generated.api.authentication.auth_delete_all_my_sessions import (
|
|
32
40
|
_get_kwargs as delete_all_my_sessions_kwargs,
|
|
33
41
|
)
|
|
@@ -40,6 +48,12 @@ from ._generated.api.authentication.auth_forgot_password import (
|
|
|
40
48
|
from ._generated.api.authentication.auth_get_my_sessions import (
|
|
41
49
|
_get_kwargs as get_my_sessions_kwargs,
|
|
42
50
|
)
|
|
51
|
+
from ._generated.api.authentication.auth_get_user import (
|
|
52
|
+
_build_response as build_auth_get_user_response,
|
|
53
|
+
)
|
|
54
|
+
from ._generated.api.authentication.auth_get_user import (
|
|
55
|
+
_get_kwargs as auth_get_user_kwargs,
|
|
56
|
+
)
|
|
43
57
|
from ._generated.api.authentication.auth_request_email_change import (
|
|
44
58
|
_get_kwargs as request_email_change_kwargs,
|
|
45
59
|
)
|
|
@@ -52,6 +66,12 @@ from ._generated.api.authentication.auth_reset_password import (
|
|
|
52
66
|
from ._generated.api.authentication.auth_signup_anonymous import (
|
|
53
67
|
_get_kwargs as signup_anonymous_kwargs,
|
|
54
68
|
)
|
|
69
|
+
from ._generated.api.authentication.auth_update_user import (
|
|
70
|
+
_build_response as build_auth_update_user_response,
|
|
71
|
+
)
|
|
72
|
+
from ._generated.api.authentication.auth_update_user import (
|
|
73
|
+
_get_kwargs as auth_update_user_kwargs,
|
|
74
|
+
)
|
|
55
75
|
from ._generated.api.database_queries import (
|
|
56
76
|
query_database_select,
|
|
57
77
|
)
|
|
@@ -84,12 +104,17 @@ from ._generated.api.functions.invoke_function import (
|
|
|
84
104
|
_get_kwargs as invoke_function_kwargs,
|
|
85
105
|
)
|
|
86
106
|
from ._generated.api.locks import (
|
|
87
|
-
acquire_project_lock,
|
|
88
107
|
force_release_project_lock,
|
|
89
108
|
get_project_lock,
|
|
90
109
|
release_project_lock,
|
|
91
110
|
renew_project_lock,
|
|
92
111
|
)
|
|
112
|
+
from ._generated.api.locks.acquire_project_lock import (
|
|
113
|
+
_build_response as build_lock_acquire_response,
|
|
114
|
+
)
|
|
115
|
+
from ._generated.api.locks.acquire_project_lock import (
|
|
116
|
+
_get_kwargs as lock_acquire_kwargs,
|
|
117
|
+
)
|
|
93
118
|
from ._generated.api.logs.get_project_log_activity import (
|
|
94
119
|
_build_response as build_log_activity_response,
|
|
95
120
|
)
|
|
@@ -233,6 +258,7 @@ if TYPE_CHECKING:
|
|
|
233
258
|
)
|
|
234
259
|
from .models import JSONValue
|
|
235
260
|
|
|
261
|
+
HTTP_CREATED = 201
|
|
236
262
|
HTTP_UNAUTHORIZED = 401
|
|
237
263
|
HTTP_NOT_FOUND = 404
|
|
238
264
|
HTTP_CONFLICT = 409
|
|
@@ -623,6 +649,7 @@ class Transport(Protocol):
|
|
|
623
649
|
key: str,
|
|
624
650
|
ttl: int,
|
|
625
651
|
token: str,
|
|
652
|
+
request_id: str | None = None,
|
|
626
653
|
) -> TransportResponse: ...
|
|
627
654
|
|
|
628
655
|
def release_project_lock(
|
|
@@ -631,6 +658,7 @@ class Transport(Protocol):
|
|
|
631
658
|
authorization: str,
|
|
632
659
|
key: str,
|
|
633
660
|
token: str,
|
|
661
|
+
request_id: str | None = None,
|
|
634
662
|
) -> TransportResponse: ...
|
|
635
663
|
|
|
636
664
|
|
|
@@ -829,9 +857,13 @@ class GeneratedTransport:
|
|
|
829
857
|
)
|
|
830
858
|
try:
|
|
831
859
|
with self._client(authorization) as client:
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
860
|
+
raw_response = client.get_httpx_client().request(
|
|
861
|
+
**auth_convert_anonymous_kwargs(body=body)
|
|
862
|
+
)
|
|
863
|
+
if raw_response.status_code == HTTP_UNAUTHORIZED:
|
|
864
|
+
return self._raw_response(raw_response)
|
|
865
|
+
response = build_auth_convert_anonymous_response(
|
|
866
|
+
client=client, response=raw_response
|
|
835
867
|
)
|
|
836
868
|
except (
|
|
837
869
|
AttributeError,
|
|
@@ -928,9 +960,13 @@ class GeneratedTransport:
|
|
|
928
960
|
body = AuthConfirmEmailChangeBody(email_change_token=token)
|
|
929
961
|
try:
|
|
930
962
|
with self._client(authorization) as client:
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
963
|
+
raw_response = client.get_httpx_client().request(
|
|
964
|
+
**auth_confirm_email_change_kwargs(body=body)
|
|
965
|
+
)
|
|
966
|
+
if raw_response.status_code == HTTP_UNAUTHORIZED:
|
|
967
|
+
return self._raw_response(raw_response)
|
|
968
|
+
response = build_auth_confirm_email_change_response(
|
|
969
|
+
client=client, response=raw_response
|
|
934
970
|
)
|
|
935
971
|
except (
|
|
936
972
|
AttributeError,
|
|
@@ -1204,7 +1240,14 @@ class GeneratedTransport:
|
|
|
1204
1240
|
def auth_get_user(self, *, authorization: str) -> TransportResponse:
|
|
1205
1241
|
try:
|
|
1206
1242
|
with self._client(authorization) as client:
|
|
1207
|
-
|
|
1243
|
+
raw_response = client.get_httpx_client().request(
|
|
1244
|
+
**auth_get_user_kwargs()
|
|
1245
|
+
)
|
|
1246
|
+
if raw_response.status_code == HTTP_UNAUTHORIZED:
|
|
1247
|
+
return self._raw_response(raw_response)
|
|
1248
|
+
response = build_auth_get_user_response(
|
|
1249
|
+
client=client, response=raw_response
|
|
1250
|
+
)
|
|
1208
1251
|
except (
|
|
1209
1252
|
AttributeError,
|
|
1210
1253
|
KeyError,
|
|
@@ -1239,7 +1282,14 @@ class GeneratedTransport:
|
|
|
1239
1282
|
)
|
|
1240
1283
|
try:
|
|
1241
1284
|
with self._client(authorization) as client:
|
|
1242
|
-
|
|
1285
|
+
raw_response = client.get_httpx_client().request(
|
|
1286
|
+
**auth_update_user_kwargs(body=body)
|
|
1287
|
+
)
|
|
1288
|
+
if raw_response.status_code == HTTP_UNAUTHORIZED:
|
|
1289
|
+
return self._raw_response(raw_response)
|
|
1290
|
+
response = build_auth_update_user_response(
|
|
1291
|
+
client=client, response=raw_response
|
|
1292
|
+
)
|
|
1243
1293
|
except (
|
|
1244
1294
|
AttributeError,
|
|
1245
1295
|
KeyError,
|
|
@@ -1693,15 +1743,19 @@ class GeneratedTransport:
|
|
|
1693
1743
|
key: str,
|
|
1694
1744
|
ttl: int,
|
|
1695
1745
|
token: str,
|
|
1746
|
+
request_id: str | None = None,
|
|
1696
1747
|
) -> TransportResponse:
|
|
1697
1748
|
with self._client(authorization) as client:
|
|
1698
|
-
|
|
1749
|
+
request_kwargs = lock_acquire_kwargs(
|
|
1699
1750
|
key,
|
|
1700
|
-
client=client,
|
|
1701
1751
|
body=ProjectLockLeaseRequest(ttl_seconds=ttl),
|
|
1702
1752
|
x_volcano_lock_token=cast("UUID", token),
|
|
1703
|
-
x_volcano_request_id=cast("UUID", str(uuid4())),
|
|
1753
|
+
x_volcano_request_id=cast("UUID", request_id or str(uuid4())),
|
|
1704
1754
|
)
|
|
1755
|
+
raw_response = client.get_httpx_client().request(**request_kwargs)
|
|
1756
|
+
if raw_response.status_code != HTTP_CREATED:
|
|
1757
|
+
return self._raw_response(raw_response)
|
|
1758
|
+
response = build_lock_acquire_response(client=client, response=raw_response)
|
|
1705
1759
|
return self._response(response)
|
|
1706
1760
|
|
|
1707
1761
|
def get_project_lock(
|
|
@@ -1709,12 +1763,13 @@ class GeneratedTransport:
|
|
|
1709
1763
|
*,
|
|
1710
1764
|
authorization: str,
|
|
1711
1765
|
key: str,
|
|
1766
|
+
request_id: str | None = None,
|
|
1712
1767
|
) -> TransportResponse:
|
|
1713
1768
|
with self._client(authorization) as client:
|
|
1714
1769
|
response = get_project_lock.sync_detailed(
|
|
1715
1770
|
key,
|
|
1716
1771
|
client=client,
|
|
1717
|
-
x_volcano_request_id=cast("UUID", str(uuid4())),
|
|
1772
|
+
x_volcano_request_id=cast("UUID", request_id or str(uuid4())),
|
|
1718
1773
|
)
|
|
1719
1774
|
return self._response(response)
|
|
1720
1775
|
|
|
@@ -1723,12 +1778,13 @@ class GeneratedTransport:
|
|
|
1723
1778
|
*,
|
|
1724
1779
|
authorization: str,
|
|
1725
1780
|
key: str,
|
|
1781
|
+
request_id: str | None = None,
|
|
1726
1782
|
) -> TransportResponse:
|
|
1727
1783
|
with self._client(authorization) as client:
|
|
1728
1784
|
response = force_release_project_lock.sync_detailed(
|
|
1729
1785
|
key,
|
|
1730
1786
|
client=client,
|
|
1731
|
-
x_volcano_request_id=cast("UUID", str(uuid4())),
|
|
1787
|
+
x_volcano_request_id=cast("UUID", request_id or str(uuid4())),
|
|
1732
1788
|
)
|
|
1733
1789
|
return self._response(response)
|
|
1734
1790
|
|
|
@@ -1739,6 +1795,7 @@ class GeneratedTransport:
|
|
|
1739
1795
|
key: str,
|
|
1740
1796
|
ttl: int,
|
|
1741
1797
|
token: str,
|
|
1798
|
+
request_id: str | None = None,
|
|
1742
1799
|
) -> TransportResponse:
|
|
1743
1800
|
with self._client(authorization) as client:
|
|
1744
1801
|
response = renew_project_lock.sync_detailed(
|
|
@@ -1746,7 +1803,7 @@ class GeneratedTransport:
|
|
|
1746
1803
|
client=client,
|
|
1747
1804
|
body=ProjectLockLeaseRequest(ttl_seconds=ttl),
|
|
1748
1805
|
x_volcano_lock_token=cast("UUID", token),
|
|
1749
|
-
x_volcano_request_id=cast("UUID", str(uuid4())),
|
|
1806
|
+
x_volcano_request_id=cast("UUID", request_id or str(uuid4())),
|
|
1750
1807
|
)
|
|
1751
1808
|
return self._response(response)
|
|
1752
1809
|
|
|
@@ -1756,12 +1813,13 @@ class GeneratedTransport:
|
|
|
1756
1813
|
authorization: str,
|
|
1757
1814
|
key: str,
|
|
1758
1815
|
token: str,
|
|
1816
|
+
request_id: str | None = None,
|
|
1759
1817
|
) -> TransportResponse:
|
|
1760
1818
|
with self._client(authorization) as client:
|
|
1761
1819
|
response = release_project_lock.sync_detailed(
|
|
1762
1820
|
key,
|
|
1763
1821
|
client=client,
|
|
1764
1822
|
x_volcano_lock_token=cast("UUID", token),
|
|
1765
|
-
x_volcano_request_id=cast("UUID", str(uuid4())),
|
|
1823
|
+
x_volcano_request_id=cast("UUID", request_id or str(uuid4())),
|
|
1766
1824
|
)
|
|
1767
1825
|
return self._response(response)
|
volcano_sdk/auth.py
CHANGED
|
@@ -9,6 +9,7 @@ import secrets
|
|
|
9
9
|
import threading
|
|
10
10
|
from collections.abc import Mapping
|
|
11
11
|
from contextlib import suppress
|
|
12
|
+
from copy import deepcopy
|
|
12
13
|
from dataclasses import replace
|
|
13
14
|
from datetime import datetime
|
|
14
15
|
from http import HTTPStatus
|
|
@@ -574,18 +575,22 @@ class Auth:
|
|
|
574
575
|
metadata: Mapping[str, object] | None = None,
|
|
575
576
|
) -> User:
|
|
576
577
|
"""Attach email credentials to the current anonymous account."""
|
|
577
|
-
|
|
578
|
-
if
|
|
578
|
+
binding = self._client._capture_session_binding()
|
|
579
|
+
if binding[2] is None:
|
|
579
580
|
raise AuthenticationError(_NO_ACTIVE_SESSION)
|
|
580
581
|
transport = cast("AuthConvertAnonymousTransport", self._client._transport)
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
582
|
+
request_metadata = deepcopy(dict(metadata or {}))
|
|
583
|
+
response = self._session_request(
|
|
584
|
+
lambda access_token: invoke(
|
|
585
|
+
transport.auth_convert_anonymous,
|
|
586
|
+
authorization=access_token,
|
|
587
|
+
email=email,
|
|
588
|
+
password=password,
|
|
589
|
+
metadata=request_metadata,
|
|
590
|
+
),
|
|
591
|
+
binding=binding,
|
|
587
592
|
)
|
|
588
|
-
return self._update_current_user(response_payload(response, 200),
|
|
593
|
+
return self._update_current_user(response_payload(response, 200), binding)
|
|
589
594
|
|
|
590
595
|
def reset_password_for_email(self, *, email: str) -> None:
|
|
591
596
|
"""Request a reset email without revealing whether the account exists."""
|
|
@@ -629,16 +634,19 @@ class Auth:
|
|
|
629
634
|
|
|
630
635
|
def confirm_email_change(self, *, token: str) -> User:
|
|
631
636
|
"""Confirm a pending email change and return the updated user."""
|
|
632
|
-
|
|
633
|
-
if
|
|
637
|
+
binding = self._client._capture_session_binding()
|
|
638
|
+
if binding[2] is None:
|
|
634
639
|
raise AuthenticationError(_NO_ACTIVE_SESSION)
|
|
635
640
|
transport = cast("AuthConfirmEmailChangeTransport", self._client._transport)
|
|
636
|
-
response =
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
641
|
+
response = self._session_request(
|
|
642
|
+
lambda access_token: invoke(
|
|
643
|
+
transport.auth_confirm_email_change,
|
|
644
|
+
authorization=access_token,
|
|
645
|
+
token=token,
|
|
646
|
+
),
|
|
647
|
+
binding=binding,
|
|
640
648
|
)
|
|
641
|
-
return self._update_current_user(response_payload(response, 200),
|
|
649
|
+
return self._update_current_user(response_payload(response, 200), binding)
|
|
642
650
|
|
|
643
651
|
def delete_all_other_sessions(self) -> None:
|
|
644
652
|
"""Delete every other session while preserving the current session."""
|
|
@@ -944,7 +952,10 @@ class Auth:
|
|
|
944
952
|
)
|
|
945
953
|
response_payload(response, 200)
|
|
946
954
|
|
|
947
|
-
def _update_current_user(
|
|
955
|
+
def _update_current_user(
|
|
956
|
+
self, payload: object, binding: tuple[int, int, Session | None]
|
|
957
|
+
) -> User:
|
|
958
|
+
generation = self._owned_refresh_session(binding)[0]
|
|
948
959
|
user, snapshot = _user_from_payload(payload)
|
|
949
960
|
if not self._client._update_session_user_if_current(snapshot, generation):
|
|
950
961
|
raise SessionChangedError
|
|
@@ -952,12 +963,17 @@ class Auth:
|
|
|
952
963
|
|
|
953
964
|
def get_user(self) -> User:
|
|
954
965
|
"""Load a server-validated profile for the current session."""
|
|
955
|
-
|
|
956
|
-
if
|
|
966
|
+
binding = self._client._capture_session_binding()
|
|
967
|
+
if binding[2] is None:
|
|
957
968
|
raise AuthenticationError(_NO_ACTIVE_SESSION)
|
|
958
969
|
transport = cast("AuthGetUserTransport", self._client._transport)
|
|
959
|
-
response =
|
|
960
|
-
|
|
970
|
+
response = self._session_request(
|
|
971
|
+
lambda access_token: invoke(
|
|
972
|
+
transport.auth_get_user, authorization=access_token
|
|
973
|
+
),
|
|
974
|
+
binding=binding,
|
|
975
|
+
)
|
|
976
|
+
return self._update_current_user(response_payload(response, 200), binding)
|
|
961
977
|
|
|
962
978
|
def update_user(
|
|
963
979
|
self,
|
|
@@ -966,17 +982,21 @@ class Auth:
|
|
|
966
982
|
metadata: Mapping[str, object] | None = None,
|
|
967
983
|
) -> User:
|
|
968
984
|
"""Update and return the current user's server-validated profile."""
|
|
969
|
-
|
|
970
|
-
if
|
|
985
|
+
binding = self._client._capture_session_binding()
|
|
986
|
+
if binding[2] is None:
|
|
971
987
|
raise AuthenticationError(_NO_ACTIVE_SESSION)
|
|
972
988
|
transport = cast("AuthUpdateUserTransport", self._client._transport)
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
989
|
+
request_metadata = None if metadata is None else deepcopy(dict(metadata))
|
|
990
|
+
response = self._session_request(
|
|
991
|
+
lambda access_token: invoke(
|
|
992
|
+
transport.auth_update_user,
|
|
993
|
+
authorization=access_token,
|
|
994
|
+
password=password,
|
|
995
|
+
metadata=request_metadata,
|
|
996
|
+
),
|
|
997
|
+
binding=binding,
|
|
978
998
|
)
|
|
979
|
-
return self._update_current_user(response_payload(response, 200),
|
|
999
|
+
return self._update_current_user(response_payload(response, 200), binding)
|
|
980
1000
|
|
|
981
1001
|
def sign_in(self, *, email: str, password: str) -> Session:
|
|
982
1002
|
"""Sign in a user and store the returned session."""
|
volcano_sdk/locks.py
CHANGED
|
@@ -5,11 +5,12 @@ from __future__ import annotations
|
|
|
5
5
|
from contextlib import contextmanager, suppress
|
|
6
6
|
from datetime import datetime
|
|
7
7
|
from typing import TYPE_CHECKING, Protocol, cast
|
|
8
|
-
from uuid import uuid4
|
|
8
|
+
from uuid import UUID, uuid4
|
|
9
9
|
|
|
10
10
|
from ._lock_guard import LockGuard, _lease_now
|
|
11
11
|
from ._lock_worker import LockRenewer
|
|
12
12
|
from ._transport import Transport, invoke, response_payload
|
|
13
|
+
from .errors import ServerError, TransportError
|
|
13
14
|
from .models import LockLease, LockState
|
|
14
15
|
|
|
15
16
|
if TYPE_CHECKING:
|
|
@@ -37,6 +38,7 @@ class LockGetTransport(Protocol):
|
|
|
37
38
|
*,
|
|
38
39
|
authorization: str,
|
|
39
40
|
key: str,
|
|
41
|
+
request_id: str | None = None,
|
|
40
42
|
) -> object:
|
|
41
43
|
"""Get one project-scoped lock."""
|
|
42
44
|
...
|
|
@@ -52,6 +54,7 @@ class LockRenewTransport(Protocol):
|
|
|
52
54
|
key: str,
|
|
53
55
|
ttl: int,
|
|
54
56
|
token: str,
|
|
57
|
+
request_id: str | None = None,
|
|
55
58
|
) -> object:
|
|
56
59
|
"""Renew one project-scoped lock."""
|
|
57
60
|
...
|
|
@@ -65,6 +68,7 @@ class LockForceReleaseTransport(Protocol):
|
|
|
65
68
|
*,
|
|
66
69
|
authorization: str,
|
|
67
70
|
key: str,
|
|
71
|
+
request_id: str | None = None,
|
|
68
72
|
) -> object:
|
|
69
73
|
"""Force release one project-scoped lock."""
|
|
70
74
|
...
|
|
@@ -85,6 +89,17 @@ def _validate_ttl(ttl: object) -> None:
|
|
|
85
89
|
raise ValueError(_INVALID_LOCK_TTL)
|
|
86
90
|
|
|
87
91
|
|
|
92
|
+
def _request_uuid(value: str | None, name: str) -> str:
|
|
93
|
+
if value is None:
|
|
94
|
+
return str(uuid4())
|
|
95
|
+
try:
|
|
96
|
+
UUID(value)
|
|
97
|
+
except (AttributeError, ValueError) as error:
|
|
98
|
+
message = f"{name} must be a UUID string"
|
|
99
|
+
raise ValueError(message) from error
|
|
100
|
+
return value
|
|
101
|
+
|
|
102
|
+
|
|
88
103
|
class Locks:
|
|
89
104
|
"""Acquire and release project-scoped distributed locks."""
|
|
90
105
|
|
|
@@ -92,13 +107,14 @@ class Locks:
|
|
|
92
107
|
"""Create a lock facade backed by a client."""
|
|
93
108
|
self._client = client
|
|
94
109
|
|
|
95
|
-
def get(self, key: str) -> LockState:
|
|
110
|
+
def get(self, key: str, *, request_id: str | None = None) -> LockState:
|
|
96
111
|
"""Return the current state of a project-scoped lock."""
|
|
97
112
|
transport = cast("LockGetTransport", self._client._transport)
|
|
98
113
|
response = invoke(
|
|
99
114
|
transport.get_project_lock,
|
|
100
115
|
authorization=self._client._service_token(),
|
|
101
116
|
key=key,
|
|
117
|
+
request_id=_request_uuid(request_id, "request_id"),
|
|
102
118
|
)
|
|
103
119
|
payload = response_payload(response, 200)
|
|
104
120
|
return LockState(
|
|
@@ -107,26 +123,64 @@ class Locks:
|
|
|
107
123
|
fencing_token=payload.get("fencing_token"),
|
|
108
124
|
)
|
|
109
125
|
|
|
110
|
-
def acquire(
|
|
111
|
-
|
|
126
|
+
def acquire(
|
|
127
|
+
self,
|
|
128
|
+
key: str,
|
|
129
|
+
*,
|
|
130
|
+
ttl: int,
|
|
131
|
+
token: str | None = None,
|
|
132
|
+
request_id: str | None = None,
|
|
133
|
+
) -> LockLease:
|
|
134
|
+
"""Acquire with one bounded retry using the same ownership token."""
|
|
135
|
+
lease, _ = self._acquire_with_start(
|
|
136
|
+
key, ttl=ttl, token=token, request_id=request_id
|
|
137
|
+
)
|
|
138
|
+
return lease
|
|
139
|
+
|
|
140
|
+
def _acquire_with_start(
|
|
141
|
+
self,
|
|
142
|
+
key: str,
|
|
143
|
+
*,
|
|
144
|
+
ttl: int,
|
|
145
|
+
token: str | None,
|
|
146
|
+
request_id: str | None,
|
|
147
|
+
) -> tuple[LockLease, float]:
|
|
112
148
|
_validate_ttl(ttl)
|
|
113
|
-
token =
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
149
|
+
token = _request_uuid(token, "token")
|
|
150
|
+
request_id = _request_uuid(request_id, "request_id")
|
|
151
|
+
authorization = self._client._service_token()
|
|
152
|
+
started_at = _lease_now()
|
|
153
|
+
try:
|
|
154
|
+
payload = self._acquire_payload(key, ttl, token, request_id, authorization)
|
|
155
|
+
except (TransportError, ServerError) as error:
|
|
156
|
+
if error.status not in (None, 503):
|
|
157
|
+
raise
|
|
158
|
+
started_at = _lease_now()
|
|
159
|
+
payload = self._acquire_payload(key, ttl, token, request_id, authorization)
|
|
160
|
+
lease = LockLease(
|
|
117
161
|
key=key,
|
|
118
|
-
ttl=ttl,
|
|
119
162
|
token=token,
|
|
163
|
+
expires_at=_parse_datetime(payload.get("expires_at")),
|
|
164
|
+
fencing_token=cast("int | None", payload.get("fencing_token")),
|
|
120
165
|
)
|
|
121
|
-
|
|
122
|
-
|
|
166
|
+
return lease, started_at
|
|
167
|
+
|
|
168
|
+
def _acquire_payload(
|
|
169
|
+
self, key: str, ttl: int, token: str, request_id: str, authorization: str
|
|
170
|
+
) -> dict[str, object]:
|
|
171
|
+
response = invoke(
|
|
172
|
+
self._client._transport.acquire_project_lock,
|
|
173
|
+
authorization=authorization,
|
|
123
174
|
key=key,
|
|
175
|
+
ttl=ttl,
|
|
124
176
|
token=token,
|
|
125
|
-
|
|
126
|
-
fencing_token=payload.get("fencing_token"),
|
|
177
|
+
request_id=request_id,
|
|
127
178
|
)
|
|
179
|
+
return cast("dict[str, object]", response_payload(response, 201))
|
|
128
180
|
|
|
129
|
-
def renew(
|
|
181
|
+
def renew(
|
|
182
|
+
self, key: str, lease: LockLease, *, ttl: int, request_id: str | None = None
|
|
183
|
+
) -> LockLease:
|
|
130
184
|
"""Renew a lock lease and return its immutable replacement."""
|
|
131
185
|
_validate_ttl(ttl)
|
|
132
186
|
transport = cast("LockRenewTransport", self._client._transport)
|
|
@@ -134,6 +188,7 @@ class Locks:
|
|
|
134
188
|
transport.renew_project_lock,
|
|
135
189
|
authorization=self._client._service_token(),
|
|
136
190
|
key=key,
|
|
191
|
+
request_id=_request_uuid(request_id, "request_id"),
|
|
137
192
|
ttl=ttl,
|
|
138
193
|
token=lease.token,
|
|
139
194
|
)
|
|
@@ -145,35 +200,47 @@ class Locks:
|
|
|
145
200
|
fencing_token=payload.get("fencing_token"),
|
|
146
201
|
)
|
|
147
202
|
|
|
148
|
-
def release(
|
|
203
|
+
def release(
|
|
204
|
+
self, key: str, lease: LockLease, *, request_id: str | None = None
|
|
205
|
+
) -> None:
|
|
149
206
|
"""Release a lock lease."""
|
|
150
207
|
response = invoke(
|
|
151
208
|
self._client._transport.release_project_lock,
|
|
152
209
|
authorization=self._client._service_token(),
|
|
153
210
|
key=key,
|
|
211
|
+
request_id=_request_uuid(request_id, "request_id"),
|
|
154
212
|
token=lease.token,
|
|
155
213
|
)
|
|
156
214
|
response_payload(response, 204)
|
|
157
215
|
|
|
158
|
-
def force_release(self, key: str) -> None:
|
|
216
|
+
def force_release(self, key: str, *, request_id: str | None = None) -> None:
|
|
159
217
|
"""Release a lock regardless of which token owns it."""
|
|
160
218
|
transport = cast("LockForceReleaseTransport", self._client._transport)
|
|
161
219
|
response = invoke(
|
|
162
220
|
transport.force_release_project_lock,
|
|
163
221
|
authorization=self._client._service_token(),
|
|
164
222
|
key=key,
|
|
223
|
+
request_id=_request_uuid(request_id, "request_id"),
|
|
165
224
|
)
|
|
166
225
|
response_payload(response, 204)
|
|
167
226
|
|
|
168
227
|
@contextmanager
|
|
169
|
-
def with_lock(
|
|
228
|
+
def with_lock(
|
|
229
|
+
self,
|
|
230
|
+
key: str,
|
|
231
|
+
*,
|
|
232
|
+
ttl: int,
|
|
233
|
+
token: str | None = None,
|
|
234
|
+
request_id: str | None = None,
|
|
235
|
+
) -> Generator[LockGuard, None, None]:
|
|
170
236
|
"""Hold and automatically renew a lock for the context's lifetime."""
|
|
171
237
|
_validate_ttl(ttl)
|
|
172
238
|
started_at = _lease_now()
|
|
239
|
+
lease, lease_started_at = self._acquire_with_start(
|
|
240
|
+
key, ttl=ttl, token=token, request_id=request_id
|
|
241
|
+
)
|
|
173
242
|
guard = LockGuard(
|
|
174
|
-
|
|
175
|
-
ttl=ttl,
|
|
176
|
-
started_at=started_at,
|
|
243
|
+
lease, ttl=ttl, started_at=started_at, lease_started_at=lease_started_at
|
|
177
244
|
)
|
|
178
245
|
renewer = LockRenewer(self, key, guard, ttl=ttl)
|
|
179
246
|
renewer_started = False
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: volcano-sdk-python
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.8.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
|
|
@@ -25,6 +25,8 @@ Description-Content-Type: text/markdown
|
|
|
25
25
|
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
|
+
Start with the [Python quickstart](https://github.com/Kong/volcano-sdk-python/blob/main/docs/README.md).
|
|
29
|
+
|
|
28
30
|
## Install
|
|
29
31
|
|
|
30
32
|
```shell
|
|
@@ -180,6 +182,13 @@ original error. `on_progress` runs after each successful part with cumulative
|
|
|
180
182
|
uploaded bytes and the total size.
|
|
181
183
|
`upload_part()` returns immutable part metadata and can safely retry the same
|
|
182
184
|
part number to replace that part.
|
|
185
|
+
Acquisition accepts caller-owned UUID `token` and `request_id` values and retries
|
|
186
|
+
an ambiguous transport failure or HTTP 503 once with the same request and credential.
|
|
187
|
+
Retain those IDs to recover an uncertain acquisition. Other lock methods accept
|
|
188
|
+
`request_id`; block-scoped helpers forward initial IDs only to acquisition.
|
|
189
|
+
See the [lock guide](https://github.com/Kong/volcano-sdk-python/blob/main/docs/locks.md)
|
|
190
|
+
for examples and fencing requirements.
|
|
191
|
+
|
|
183
192
|
`locks.get()` returns immutable lock availability, expiry, and fencing-token
|
|
184
193
|
state without acquiring the lock.
|
|
185
194
|
Lock acquisition and renewal require an integer TTL from 5 seconds through 90 days.
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
volcano_sdk/__init__.py,sha256=rPSFMLjFbxeQvSRkL1Ee8MsNWArIhs8fgNFgtDuMNzI,1954
|
|
2
2
|
volcano_sdk/_function_resolution.py,sha256=_gC3LY6x71NtJc_o9d9eq3N27Z-T_Un3p2-XRu2-Cqs,5088
|
|
3
|
-
volcano_sdk/_lock_guard.py,sha256=
|
|
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
|
-
volcano_sdk/_transport.py,sha256=
|
|
8
|
-
volcano_sdk/auth.py,sha256=
|
|
7
|
+
volcano_sdk/_transport.py,sha256=AFd47C3X1WAHwq3f9V2ux0WNzwGIn4NBxrwgaIN-xl0,58296
|
|
8
|
+
volcano_sdk/auth.py,sha256=Z5UCrqdvmHBS6n6ZleXya04hYV94rH4SRV1GW2yimXo,43185
|
|
9
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
|
|
13
13
|
volcano_sdk/functions.py,sha256=zDeAKyCieNnI6wzb9iZMlg9_cbjaYWXYbXHb7jzCle4,10126
|
|
14
|
-
volcano_sdk/locks.py,sha256=
|
|
14
|
+
volcano_sdk/locks.py,sha256=eSkidfHQ_wml5-1LT2hBtHxeALP6E1cGYK0FzHjbrNk,9241
|
|
15
15
|
volcano_sdk/logs.py,sha256=lxORRQBvsnJ0BdMau19MXrIeig5_mEucQSyJ4F1CZxU,4666
|
|
16
16
|
volcano_sdk/models.py,sha256=43zM6LQ9eJ3Hn6q9PiVSozSmOzPSr4m4OL8G8KVb-gg,8619
|
|
17
17
|
volcano_sdk/py.typed,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
@@ -773,7 +773,7 @@ volcano_sdk/_generated/models/usage_data_point.py,sha256=d4U08JbVTiIMM_l2BmRILL1
|
|
|
773
773
|
volcano_sdk/_generated/models/variable.py,sha256=tvGDOMv77gW6j5skufnfrlNAhzCzA1F7nuPCQWx79yY,5926
|
|
774
774
|
volcano_sdk/_generated/models/variable_deploy_source.py,sha256=G_aCiFHzQx8WhJFs0GvYZEFclfTrfkDvd3kT1e3vty8,463
|
|
775
775
|
volcano_sdk/_generated/models/variable_status.py,sha256=THSsKBSiAYG4kAtBzGA4TZqxjgx8hdL0Q4qCdZfOj00,391
|
|
776
|
-
volcano_sdk_python-0.
|
|
777
|
-
volcano_sdk_python-0.
|
|
778
|
-
volcano_sdk_python-0.
|
|
779
|
-
volcano_sdk_python-0.
|
|
776
|
+
volcano_sdk_python-0.8.0.dist-info/METADATA,sha256=lJ43KIGGSaMGAsRRM_EqJYI9AJXnEibMrg9CqElZU78,34010
|
|
777
|
+
volcano_sdk_python-0.8.0.dist-info/WHEEL,sha256=THafob7ofN-NsuMN7Mg4qZyHaQI7KkD-QlcQatYhXPo,87
|
|
778
|
+
volcano_sdk_python-0.8.0.dist-info/licenses/LICENSE,sha256=AIHJy-5MFqM8fHfuGFTTb9a8rvJGypcVgN4aZ-7XTWw,11387
|
|
779
|
+
volcano_sdk_python-0.8.0.dist-info/RECORD,,
|
|
File without changes
|