rowan-python 3.1.10__py3-none-any.whl → 3.1.11__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.
- rowan/api_keys.py +54 -0
- {rowan_python-3.1.10.dist-info → rowan_python-3.1.11.dist-info}/METADATA +1 -1
- {rowan_python-3.1.10.dist-info → rowan_python-3.1.11.dist-info}/RECORD +5 -5
- {rowan_python-3.1.10.dist-info → rowan_python-3.1.11.dist-info}/WHEEL +0 -0
- {rowan_python-3.1.10.dist-info → rowan_python-3.1.11.dist-info}/licenses/LICENSE +0 -0
rowan/api_keys.py
CHANGED
|
@@ -21,6 +21,8 @@ class APIKey(BaseModel):
|
|
|
21
21
|
:ivar is_revoked: Whether the key has been revoked.
|
|
22
22
|
:ivar scope: Permission scope ("read", "read_write", or "read_write_delete").
|
|
23
23
|
:ivar can_manage_api_keys: Whether this key can create/list/revoke other API keys.
|
|
24
|
+
:ivar budget: Maximum credits this key may spend. ``None`` means unlimited.
|
|
25
|
+
:ivar credits_used: Credits spent by this key so far.
|
|
24
26
|
:ivar scoped_project_uuid: If set, the key can only access this project.
|
|
25
27
|
:ivar created_by_key_uuid: UUID of the API key used to create this one (if any).
|
|
26
28
|
:ivar revoked_at: When the key was revoked, if applicable.
|
|
@@ -35,6 +37,8 @@ class APIKey(BaseModel):
|
|
|
35
37
|
is_revoked: bool
|
|
36
38
|
scope: str
|
|
37
39
|
can_manage_api_keys: bool
|
|
40
|
+
budget: float | None = None
|
|
41
|
+
credits_used: float = 0
|
|
38
42
|
scoped_project_uuid: str | None = None
|
|
39
43
|
created_by_key_uuid: str | None = None
|
|
40
44
|
revoked_at: datetime | None = None
|
|
@@ -57,6 +61,27 @@ class APIKey(BaseModel):
|
|
|
57
61
|
response.raise_for_status()
|
|
58
62
|
return type(self)(**response.json())
|
|
59
63
|
|
|
64
|
+
def refresh(self, in_place: bool = True) -> Self:
|
|
65
|
+
"""
|
|
66
|
+
Reload this key's metadata (e.g. ``credits_used``) from the server.
|
|
67
|
+
|
|
68
|
+
:param in_place: If True, update this instance in-place. If False, return new instance.
|
|
69
|
+
:returns: Updated APIKey object.
|
|
70
|
+
:raises ValueError: If this key is no longer present in the account's key list.
|
|
71
|
+
"""
|
|
72
|
+
matches = [key for key in list_api_keys(active=None) if key.uuid == self.uuid]
|
|
73
|
+
if not matches:
|
|
74
|
+
raise ValueError(f"API key {self.uuid!r} not found.")
|
|
75
|
+
updated = matches[0]
|
|
76
|
+
|
|
77
|
+
if not in_place:
|
|
78
|
+
return type(self)(**updated.model_dump())
|
|
79
|
+
|
|
80
|
+
for field_name in type(self).model_fields:
|
|
81
|
+
setattr(self, field_name, getattr(updated, field_name))
|
|
82
|
+
|
|
83
|
+
return self
|
|
84
|
+
|
|
60
85
|
|
|
61
86
|
class CreatedAPIKey(BaseModel):
|
|
62
87
|
"""
|
|
@@ -78,6 +103,7 @@ def create_api_key(
|
|
|
78
103
|
scope: APIKeyScope = "read_write",
|
|
79
104
|
valid_days: int = 365,
|
|
80
105
|
scoped_project_uuid: str | None = None,
|
|
106
|
+
budget: float | None = None,
|
|
81
107
|
) -> CreatedAPIKey:
|
|
82
108
|
"""
|
|
83
109
|
Create a new API key.
|
|
@@ -89,6 +115,8 @@ def create_api_key(
|
|
|
89
115
|
:param scope: Permission scope. One of "read", "read_write", "read_write_delete".
|
|
90
116
|
:param valid_days: Number of days until the key expires.
|
|
91
117
|
:param scoped_project_uuid: If provided, restrict the key to a single project.
|
|
118
|
+
:param budget: Maximum credits the key may spend. If not provided, the key has no
|
|
119
|
+
spending limit.
|
|
92
120
|
:returns: plaintext key together with its metadata; the plaintext key is
|
|
93
121
|
only returned once — store it immediately.
|
|
94
122
|
"""
|
|
@@ -99,6 +127,7 @@ def create_api_key(
|
|
|
99
127
|
"scope": scope,
|
|
100
128
|
"valid_days": valid_days,
|
|
101
129
|
"scoped_project_uuid": scoped_project_uuid,
|
|
130
|
+
"budget": budget,
|
|
102
131
|
}
|
|
103
132
|
with api_client() as client:
|
|
104
133
|
response = client.post("/api_key", json=payload)
|
|
@@ -142,3 +171,28 @@ def revoke_api_key(uuid: str) -> APIKey:
|
|
|
142
171
|
response = client.post(f"/api_key/{uuid}/revoke")
|
|
143
172
|
response.raise_for_status()
|
|
144
173
|
return APIKey(**response.json())
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def update_api_key_budget(uuid: str, budget: float | None) -> APIKey:
|
|
177
|
+
"""
|
|
178
|
+
Update the spending budget of an API key.
|
|
179
|
+
|
|
180
|
+
The caller must currently authenticate with an unscoped key that has
|
|
181
|
+
``can_manage_api_keys`` permission.
|
|
182
|
+
|
|
183
|
+
:param uuid: UUID of the key to update.
|
|
184
|
+
:param budget: New maximum credits the key may spend. Pass ``None`` to remove
|
|
185
|
+
the spending limit.
|
|
186
|
+
:returns: Updated APIKey object.
|
|
187
|
+
"""
|
|
188
|
+
with api_client() as client:
|
|
189
|
+
response = client.patch(f"/api_key/{uuid}", json={"budget": budget})
|
|
190
|
+
if response.status_code == 403:
|
|
191
|
+
raise PermissionError(
|
|
192
|
+
"API key update rejected by the server (403). The key you are "
|
|
193
|
+
"authenticating with must be unscoped and have `can_manage_api_keys` "
|
|
194
|
+
"permission. Create a manager key from the Rowan web UI "
|
|
195
|
+
"(Account → API keys) and retry with that key."
|
|
196
|
+
)
|
|
197
|
+
response.raise_for_status()
|
|
198
|
+
return APIKey(**response.json())
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
rowan/__init__.py,sha256=MhgIDJjrk8M_O9NtBL9I2_e5iTHCcc7Ju3XbBi2wtUA,1788
|
|
2
|
-
rowan/api_keys.py,sha256=
|
|
2
|
+
rowan/api_keys.py,sha256=Sv-CYivYUxf32u2TAculXuni422mB3jmHtTxm2cDRRA,6991
|
|
3
3
|
rowan/calculation.py,sha256=nuUtLTtn9D-FCM9TJl3h5euBgIYqa5fr5r_PSNBS7Xo,3809
|
|
4
4
|
rowan/config.py,sha256=TejQKSxnzNKKTNL9-2bCLq6RvAh54oVA5Ivl1p_ZT8Q,20899
|
|
5
5
|
rowan/constants.py,sha256=emCH4m9OL2Hm5E-6mJGM_FgzrK_JrZT-FiKJ6pMNQ4Y,84
|
|
@@ -53,7 +53,7 @@ rowan/workflows/solvent_dependent_conformers.py,sha256=Z2xESmcM8WcTsR2TSTUa--Rrh
|
|
|
53
53
|
rowan/workflows/spin_states.py,sha256=rjkgie2-XVNIN7O6P93yn6EPv9Ogjy8iTj3ufZIsUgY,9331
|
|
54
54
|
rowan/workflows/strain.py,sha256=kCW_BlX__sdQG1JVbFZuB-57rkpnxa_JCw3h4uKdGDk,6425
|
|
55
55
|
rowan/workflows/tautomer_search.py,sha256=mbRl0ZJ7wibueRF8c8_idhXJ1rtwg7LBt--5QwQ_Cck,5767
|
|
56
|
-
rowan_python-3.1.
|
|
57
|
-
rowan_python-3.1.
|
|
58
|
-
rowan_python-3.1.
|
|
59
|
-
rowan_python-3.1.
|
|
56
|
+
rowan_python-3.1.11.dist-info/METADATA,sha256=pmlTh_uskn9-91pN4XYHT4irvEF2MVo7bBhiumUPZaY,2446
|
|
57
|
+
rowan_python-3.1.11.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
|
|
58
|
+
rowan_python-3.1.11.dist-info/licenses/LICENSE,sha256=i05z7xEhyrg6f8j0lR3XYjShnF-MJGFQ-DnpsZ8yiVI,1084
|
|
59
|
+
rowan_python-3.1.11.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|