rapidata 1.6.1__py3-none-any.whl → 1.6.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.
Potentially problematic release.
This version of rapidata might be problematic. Click here for more details.
- rapidata/rapidata_client/rapidata_client.py +3 -3
- rapidata/service/credential_manager.py +232 -0
- rapidata/service/openapi_service.py +17 -56
- rapidata/service/token_manager.py +175 -0
- {rapidata-1.6.1.dist-info → rapidata-1.6.3.dist-info}/METADATA +1 -1
- {rapidata-1.6.1.dist-info → rapidata-1.6.3.dist-info}/RECORD +8 -6
- {rapidata-1.6.1.dist-info → rapidata-1.6.3.dist-info}/LICENSE +0 -0
- {rapidata-1.6.1.dist-info → rapidata-1.6.3.dist-info}/WHEEL +0 -0
|
@@ -27,10 +27,10 @@ class RapidataClient:
|
|
|
27
27
|
|
|
28
28
|
def __init__(
|
|
29
29
|
self,
|
|
30
|
-
client_id: str,
|
|
31
|
-
client_secret: str,
|
|
30
|
+
client_id: str | None = None,
|
|
31
|
+
client_secret: str | None = None,
|
|
32
32
|
endpoint: str = "https://api.rapidata.ai",
|
|
33
|
-
token_url: str = "https://auth.rapidata.ai
|
|
33
|
+
token_url: str = "https://auth.rapidata.ai",
|
|
34
34
|
oauth_scope: str = "openid",
|
|
35
35
|
cert_path: str | None = None,
|
|
36
36
|
):
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import os
|
|
3
|
+
import time
|
|
4
|
+
import webbrowser
|
|
5
|
+
from datetime import datetime, timezone
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from socket import gethostname
|
|
8
|
+
from typing import Dict, List, Optional, Tuple
|
|
9
|
+
|
|
10
|
+
import requests
|
|
11
|
+
from pydantic import BaseModel
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ClientCredential(BaseModel):
|
|
15
|
+
display_name: str
|
|
16
|
+
client_id: str
|
|
17
|
+
client_secret: str
|
|
18
|
+
endpoint: str
|
|
19
|
+
created_at: datetime
|
|
20
|
+
last_used: datetime
|
|
21
|
+
|
|
22
|
+
@classmethod
|
|
23
|
+
def from_dict(cls, data: Dict):
|
|
24
|
+
return cls(
|
|
25
|
+
display_name=data["display_name"],
|
|
26
|
+
client_id=data["client_id"],
|
|
27
|
+
client_secret=data["client_secret"],
|
|
28
|
+
endpoint=data["endpoint"],
|
|
29
|
+
created_at=datetime.fromisoformat(data["created_at"]),
|
|
30
|
+
last_used=datetime.fromisoformat(data["last_used"]),
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
def to_dict(self) -> Dict:
|
|
34
|
+
return {
|
|
35
|
+
"display_name": self.display_name,
|
|
36
|
+
"client_id": self.client_id,
|
|
37
|
+
"client_secret": self.client_secret,
|
|
38
|
+
"endpoint": self.endpoint,
|
|
39
|
+
"created_at": self.created_at.isoformat(),
|
|
40
|
+
"last_used": self.last_used.isoformat(),
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
def get_display_string(self):
|
|
44
|
+
return f"{self.display_name} - Client ID: {self.client_id} (Created: {self.created_at.strftime('%Y-%m-%d %H:%M:%S')})"
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class BridgeToken(BaseModel):
|
|
48
|
+
read_key: str
|
|
49
|
+
write_key: str
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class CredentialManager:
|
|
53
|
+
def __init__(
|
|
54
|
+
self,
|
|
55
|
+
endpoint: str,
|
|
56
|
+
cert_path: str | None = None,
|
|
57
|
+
poll_timeout: int = 60,
|
|
58
|
+
poll_interval: int = 1,
|
|
59
|
+
):
|
|
60
|
+
self.endpoint = endpoint
|
|
61
|
+
self.cert_path = cert_path
|
|
62
|
+
self.poll_timeout = poll_timeout
|
|
63
|
+
self.poll_interval = poll_interval
|
|
64
|
+
|
|
65
|
+
self.config_dir = Path.home() / ".config" / "rapidata"
|
|
66
|
+
self.config_path = self.config_dir / "credentials.json"
|
|
67
|
+
|
|
68
|
+
# Ensure config directory exists
|
|
69
|
+
self.config_dir.mkdir(parents=True, exist_ok=True)
|
|
70
|
+
|
|
71
|
+
def _read_credentials(self) -> Dict[str, List[ClientCredential]]:
|
|
72
|
+
"""Read all stored credentials from the config file."""
|
|
73
|
+
if not self.config_path.exists():
|
|
74
|
+
return {}
|
|
75
|
+
|
|
76
|
+
try:
|
|
77
|
+
with open(self.config_path, "r") as f:
|
|
78
|
+
data = json.load(f)
|
|
79
|
+
return {
|
|
80
|
+
env: [ClientCredential.from_dict(cred) for cred in creds]
|
|
81
|
+
for env, creds in data.items()
|
|
82
|
+
}
|
|
83
|
+
except json.JSONDecodeError:
|
|
84
|
+
return {}
|
|
85
|
+
|
|
86
|
+
def _write_credentials(
|
|
87
|
+
self, credentials: Dict[str, List[ClientCredential]]
|
|
88
|
+
) -> None:
|
|
89
|
+
data = {
|
|
90
|
+
env: [cred.to_dict() for cred in creds]
|
|
91
|
+
for env, creds in credentials.items()
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
with open(self.config_path, "w") as f:
|
|
95
|
+
json.dump(data, f, indent=2)
|
|
96
|
+
|
|
97
|
+
# Ensure file is only readable by the user
|
|
98
|
+
os.chmod(self.config_path, 0o600)
|
|
99
|
+
|
|
100
|
+
def _store_credential(self, credential: ClientCredential) -> None:
|
|
101
|
+
credentials = self._read_credentials()
|
|
102
|
+
|
|
103
|
+
if credential.endpoint not in credentials:
|
|
104
|
+
credentials[credential.endpoint] = []
|
|
105
|
+
|
|
106
|
+
credentials[credential.endpoint].append(credential)
|
|
107
|
+
self._write_credentials(credentials)
|
|
108
|
+
|
|
109
|
+
@staticmethod
|
|
110
|
+
def _select_credential(
|
|
111
|
+
credentials: List[ClientCredential],
|
|
112
|
+
) -> Optional[ClientCredential]:
|
|
113
|
+
if not credentials:
|
|
114
|
+
return None
|
|
115
|
+
|
|
116
|
+
if len(credentials) == 1:
|
|
117
|
+
return credentials[0]
|
|
118
|
+
|
|
119
|
+
return max(credentials, key=lambda c: c.last_used)
|
|
120
|
+
|
|
121
|
+
def get_client_credentials(self) -> Optional[ClientCredential]:
|
|
122
|
+
"""Gets stored client credentials or create new ones via browser auth."""
|
|
123
|
+
credentials = self._read_credentials()
|
|
124
|
+
env_credentials = credentials.get(self.endpoint, [])
|
|
125
|
+
|
|
126
|
+
if env_credentials:
|
|
127
|
+
credential = self._select_credential(env_credentials)
|
|
128
|
+
if credential:
|
|
129
|
+
credential.last_used = datetime.now(timezone.utc)
|
|
130
|
+
self._write_credentials(credentials)
|
|
131
|
+
return credential
|
|
132
|
+
|
|
133
|
+
return self._create_new_credentials()
|
|
134
|
+
|
|
135
|
+
def _get_bridge_tokens(self) -> Optional[BridgeToken]:
|
|
136
|
+
"""Get bridge tokens from the identity endpoint."""
|
|
137
|
+
try:
|
|
138
|
+
bridge_endpoint = (
|
|
139
|
+
f"{self.endpoint}/Identity/CreateBridgeToken?clientId=rapidata-cli"
|
|
140
|
+
)
|
|
141
|
+
response = requests.post(bridge_endpoint, verify=self.cert_path)
|
|
142
|
+
if not response.ok:
|
|
143
|
+
print(f"Failed to get bridge tokens: {response.status_code}")
|
|
144
|
+
return None
|
|
145
|
+
|
|
146
|
+
data = response.json()
|
|
147
|
+
return BridgeToken(read_key=data["readKey"], write_key=data["writeKey"])
|
|
148
|
+
except requests.RequestException as e:
|
|
149
|
+
print(f"Failed to get bridge tokens: {e}")
|
|
150
|
+
return None
|
|
151
|
+
|
|
152
|
+
def _poll_read_key(self, read_key: str) -> Optional[str]:
|
|
153
|
+
"""Poll the read key endpoint until we get an access token."""
|
|
154
|
+
read_endpoint = f"{self.endpoint}/Identity/ReadBridgeToken"
|
|
155
|
+
start_time = time.time()
|
|
156
|
+
|
|
157
|
+
while time.time() - start_time < self.poll_timeout:
|
|
158
|
+
try:
|
|
159
|
+
response = requests.get(
|
|
160
|
+
read_endpoint, params={"readKey": read_key}, verify=self.cert_path
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
if response.status_code == 200:
|
|
164
|
+
return response.json().get("accessToken")
|
|
165
|
+
elif response.status_code == 202:
|
|
166
|
+
# Still processing
|
|
167
|
+
time.sleep(self.poll_interval)
|
|
168
|
+
continue
|
|
169
|
+
else:
|
|
170
|
+
# Error occurred
|
|
171
|
+
print(f"Error polling read key: {response.status_code}")
|
|
172
|
+
return None
|
|
173
|
+
|
|
174
|
+
except requests.RequestException as e:
|
|
175
|
+
print(f"Error polling read key: {e}")
|
|
176
|
+
return None
|
|
177
|
+
|
|
178
|
+
print("Polling timed out")
|
|
179
|
+
return None
|
|
180
|
+
|
|
181
|
+
def _create_client(self, access_token: str) -> Optional[Tuple[str, str, str]]:
|
|
182
|
+
"""Create a new client using the access token."""
|
|
183
|
+
try:
|
|
184
|
+
# set the display name to the hostname
|
|
185
|
+
display_name = f"{gethostname()} - CLI"
|
|
186
|
+
response = requests.post(
|
|
187
|
+
f"{self.endpoint}/Client",
|
|
188
|
+
headers={
|
|
189
|
+
"Authorization": f"Bearer {access_token}",
|
|
190
|
+
"Content-Type": "application/json",
|
|
191
|
+
"Accept": "*/*",
|
|
192
|
+
},
|
|
193
|
+
json={"displayName": display_name},
|
|
194
|
+
verify=self.cert_path,
|
|
195
|
+
)
|
|
196
|
+
response.raise_for_status()
|
|
197
|
+
data = response.json()
|
|
198
|
+
return data.get("clientId"), data.get("clientSecret"), display_name
|
|
199
|
+
except requests.RequestException as e:
|
|
200
|
+
print(f"Failed to create client: {e}")
|
|
201
|
+
return None
|
|
202
|
+
|
|
203
|
+
def _create_new_credentials(self) -> Optional[ClientCredential]:
|
|
204
|
+
bridge_endpoint = self._get_bridge_tokens()
|
|
205
|
+
if not bridge_endpoint:
|
|
206
|
+
return None
|
|
207
|
+
|
|
208
|
+
auth_url = f"{self.endpoint}/connect/authorize/external?clientId=rapidata-cli&scope=openid profile email roles&writeKey={bridge_endpoint.write_key}"
|
|
209
|
+
webbrowser.open(auth_url)
|
|
210
|
+
|
|
211
|
+
access_token = self._poll_read_key(bridge_endpoint.read_key)
|
|
212
|
+
if not access_token:
|
|
213
|
+
return None
|
|
214
|
+
|
|
215
|
+
client_state = self._create_client(access_token)
|
|
216
|
+
|
|
217
|
+
if not client_state:
|
|
218
|
+
raise ValueError("Failed to create client")
|
|
219
|
+
|
|
220
|
+
client_id, client_secret, display_name = client_state
|
|
221
|
+
|
|
222
|
+
credential = ClientCredential(
|
|
223
|
+
client_id=client_id,
|
|
224
|
+
client_secret=client_secret,
|
|
225
|
+
display_name=display_name,
|
|
226
|
+
endpoint=self.endpoint,
|
|
227
|
+
created_at=datetime.now(timezone.utc),
|
|
228
|
+
last_used=datetime.now(timezone.utc),
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
self._store_credential(credential)
|
|
232
|
+
return credential
|
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
import json
|
|
2
|
-
import time
|
|
3
|
-
import requests
|
|
4
|
-
import threading
|
|
5
1
|
from rapidata.api_client.api.campaign_api import CampaignApi
|
|
6
2
|
from rapidata.api_client.api.dataset_api import DatasetApi
|
|
7
3
|
from rapidata.api_client.api.order_api import OrderApi
|
|
@@ -11,61 +7,44 @@ from rapidata.api_client.api.validation_api import ValidationApi
|
|
|
11
7
|
from rapidata.api_client.api.workflow_api import WorkflowApi
|
|
12
8
|
from rapidata.api_client.api_client import ApiClient
|
|
13
9
|
from rapidata.api_client.configuration import Configuration
|
|
10
|
+
from rapidata.service.token_manager import TokenManager, TokenInfo
|
|
14
11
|
|
|
15
12
|
|
|
16
13
|
class OpenAPIService:
|
|
17
|
-
|
|
18
|
-
_TOKEN_EXPIRATION_MINUTES = 30
|
|
19
|
-
|
|
20
14
|
def __init__(
|
|
21
15
|
self,
|
|
22
|
-
client_id: str,
|
|
23
|
-
client_secret: str,
|
|
16
|
+
client_id: str | None,
|
|
17
|
+
client_secret: str | None,
|
|
24
18
|
endpoint: str,
|
|
25
19
|
token_url: str,
|
|
26
20
|
oauth_scope: str,
|
|
27
|
-
cert_path: str | None = None
|
|
21
|
+
cert_path: str | None = None,
|
|
28
22
|
):
|
|
23
|
+
token_manager = TokenManager(
|
|
24
|
+
client_id=client_id,
|
|
25
|
+
client_secret=client_secret,
|
|
26
|
+
endpoint=token_url,
|
|
27
|
+
oauth_scope=oauth_scope,
|
|
28
|
+
cert_path=cert_path
|
|
29
|
+
)
|
|
29
30
|
client_configuration = Configuration(host=endpoint, ssl_ca_cert=cert_path)
|
|
30
31
|
self.api_client = ApiClient(configuration=client_configuration)
|
|
31
32
|
|
|
32
33
|
self._client_id = client_id
|
|
33
34
|
self._client_secret = client_secret
|
|
34
35
|
self._oauth_scope = oauth_scope
|
|
35
|
-
self._token_url = token_url
|
|
36
|
+
self._token_url = f"{token_url}/connect/token"
|
|
36
37
|
self._cert_path = cert_path
|
|
37
38
|
|
|
38
|
-
self.
|
|
39
|
-
self._order_api = OrderApi(self.api_client)
|
|
40
|
-
self._dataset_api = DatasetApi(self.api_client)
|
|
41
|
-
|
|
42
|
-
api_token = self.__fetch_token(
|
|
43
|
-
self._client_id, self._client_secret, self._oauth_scope, self._token_url, self._cert_path
|
|
44
|
-
)
|
|
45
|
-
self.api_client.configuration.api_key["bearer"] = f"Bearer {api_token}"
|
|
46
|
-
|
|
47
|
-
refresh_thread = threading.Thread(
|
|
48
|
-
target=lambda: self.__refresh_token_periodically(self._TOKEN_EXPIRATION_MINUTES - 1)
|
|
49
|
-
)
|
|
50
|
-
refresh_thread.daemon = True
|
|
51
|
-
refresh_thread.start()
|
|
52
|
-
|
|
53
|
-
def __refresh_token_periodically(self, refresh_interval):
|
|
54
|
-
while True:
|
|
55
|
-
new_token = self.__fetch_token(
|
|
56
|
-
self._client_id, self._client_secret, self._oauth_scope, self._token_url, self._cert_path
|
|
57
|
-
)
|
|
58
|
-
self.api_client.configuration.api_key["bearer"] = f"Bearer {new_token}"
|
|
59
|
-
|
|
60
|
-
time.sleep(refresh_interval)
|
|
39
|
+
token_manager.start_token_refresh(token_callback=self._set_token)
|
|
61
40
|
|
|
62
41
|
@property
|
|
63
42
|
def order_api(self) -> OrderApi:
|
|
64
|
-
return self.
|
|
43
|
+
return OrderApi(self.api_client)
|
|
65
44
|
|
|
66
45
|
@property
|
|
67
46
|
def dataset_api(self) -> DatasetApi:
|
|
68
|
-
return self.
|
|
47
|
+
return DatasetApi(self.api_client)
|
|
69
48
|
|
|
70
49
|
@property
|
|
71
50
|
def validation_api(self) -> ValidationApi:
|
|
@@ -87,23 +66,5 @@ class OpenAPIService:
|
|
|
87
66
|
def workflow_api(self) -> WorkflowApi:
|
|
88
67
|
return WorkflowApi(self.api_client)
|
|
89
68
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
try:
|
|
93
|
-
return requests.post(
|
|
94
|
-
token_url,
|
|
95
|
-
data={
|
|
96
|
-
'grant_type': 'client_credentials',
|
|
97
|
-
'client_id': client_id,
|
|
98
|
-
'client_secret': client_secret,
|
|
99
|
-
'scope': scope,
|
|
100
|
-
},
|
|
101
|
-
verify=cert_path
|
|
102
|
-
).json()['access_token']
|
|
103
|
-
except requests.RequestException as e:
|
|
104
|
-
raise Exception(f"Failed to fetch token: {e}")
|
|
105
|
-
except json.JSONDecodeError as e:
|
|
106
|
-
raise Exception(f"Failed to parse token response: {e}")
|
|
107
|
-
except KeyError as e:
|
|
108
|
-
raise Exception(f"Failed to extract token from response: {e}")
|
|
109
|
-
|
|
69
|
+
def _set_token(self, token: TokenInfo):
|
|
70
|
+
self.api_client.configuration.api_key["bearer"] = f"Bearer {token.access_token}"
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import logging
|
|
3
|
+
import threading
|
|
4
|
+
from datetime import datetime, timedelta
|
|
5
|
+
from typing import Optional, Callable
|
|
6
|
+
|
|
7
|
+
import requests
|
|
8
|
+
from pydantic import BaseModel
|
|
9
|
+
|
|
10
|
+
from rapidata.service.credential_manager import CredentialManager
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class TokenInfo(BaseModel):
|
|
16
|
+
access_token: str
|
|
17
|
+
expires_in: int
|
|
18
|
+
issued_at: datetime
|
|
19
|
+
token_type: str = "Bearer"
|
|
20
|
+
|
|
21
|
+
@property
|
|
22
|
+
def auth_header(self):
|
|
23
|
+
return f"{self.token_type} {self.access_token}"
|
|
24
|
+
|
|
25
|
+
@property
|
|
26
|
+
def time_remaining(self):
|
|
27
|
+
remaining = (
|
|
28
|
+
(self.issued_at + timedelta(seconds=self.expires_in)) - datetime.now()
|
|
29
|
+
).total_seconds()
|
|
30
|
+
return max(0.0, remaining)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class TokenManager:
|
|
34
|
+
def __init__(
|
|
35
|
+
self,
|
|
36
|
+
client_id: str | None = None,
|
|
37
|
+
client_secret: str | None = None,
|
|
38
|
+
endpoint: str = "https://auth.rapidata.ai",
|
|
39
|
+
oauth_scope: str = "openid profile email",
|
|
40
|
+
cert_path: str | None = None,
|
|
41
|
+
refresh_threshold: float = 0.8,
|
|
42
|
+
max_sleep_time: float = 30,
|
|
43
|
+
):
|
|
44
|
+
self._client_id = client_id
|
|
45
|
+
self._client_secret = client_secret
|
|
46
|
+
|
|
47
|
+
if not client_id or not client_secret:
|
|
48
|
+
credential_manager = CredentialManager(
|
|
49
|
+
endpoint=endpoint, cert_path=cert_path
|
|
50
|
+
)
|
|
51
|
+
credentials = credential_manager.get_client_credentials()
|
|
52
|
+
if not credentials:
|
|
53
|
+
raise ValueError("Failed to fetch client credentials")
|
|
54
|
+
self._client_id = credentials.client_id
|
|
55
|
+
self._client_secret = credentials.client_secret
|
|
56
|
+
|
|
57
|
+
self._endpoint = endpoint
|
|
58
|
+
self._oauth_scope = oauth_scope
|
|
59
|
+
self._cert_path = cert_path
|
|
60
|
+
self._refresh_threshold = refresh_threshold
|
|
61
|
+
self._max_sleep_time = max_sleep_time
|
|
62
|
+
|
|
63
|
+
self._token_lock = threading.Lock()
|
|
64
|
+
self._current_token: Optional[TokenInfo] = None
|
|
65
|
+
self._refresh_thread: Optional[threading.Thread] = None
|
|
66
|
+
self._should_stop = threading.Event()
|
|
67
|
+
|
|
68
|
+
def fetch_token(self):
|
|
69
|
+
try:
|
|
70
|
+
response = requests.post(
|
|
71
|
+
f"{self._endpoint}/connect/token",
|
|
72
|
+
data={
|
|
73
|
+
"grant_type": "client_credentials",
|
|
74
|
+
"client_id": self._client_id,
|
|
75
|
+
"client_secret": self._client_secret,
|
|
76
|
+
"scope": self._oauth_scope,
|
|
77
|
+
},
|
|
78
|
+
verify=self._cert_path,
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
if response.ok:
|
|
82
|
+
data = response.json()
|
|
83
|
+
return TokenInfo(
|
|
84
|
+
access_token=data["access_token"],
|
|
85
|
+
token_type=data["token_type"],
|
|
86
|
+
expires_in=data["expires_in"],
|
|
87
|
+
issued_at=datetime.now(),
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
else:
|
|
91
|
+
data = response.text
|
|
92
|
+
error_description = "An unknown error occurred"
|
|
93
|
+
if "error_description" in data:
|
|
94
|
+
error_description = (
|
|
95
|
+
data.split("error_description")[1].split("\n")[0].strip()
|
|
96
|
+
)
|
|
97
|
+
raise ValueError(f"Failed to fetch token: {error_description}")
|
|
98
|
+
except requests.RequestException as e:
|
|
99
|
+
raise ValueError(f"Failed to fetch token: {e}")
|
|
100
|
+
except json.JSONDecodeError as e:
|
|
101
|
+
raise ValueError(f"Failed to parse token response: {e}")
|
|
102
|
+
except KeyError as e:
|
|
103
|
+
raise ValueError(f"Failed to extract token from response: {e}")
|
|
104
|
+
|
|
105
|
+
def start_token_refresh(self, token_callback: Callable[[TokenInfo], None]) -> None:
|
|
106
|
+
if self._refresh_thread and self._refresh_thread.is_alive():
|
|
107
|
+
logger.error("Token refresh thread is already running")
|
|
108
|
+
return
|
|
109
|
+
|
|
110
|
+
def refresh_loop():
|
|
111
|
+
while not self._should_stop.is_set():
|
|
112
|
+
try:
|
|
113
|
+
with self._token_lock:
|
|
114
|
+
if self._should_refresh_token(self._current_token):
|
|
115
|
+
logger.debug("Refreshing token")
|
|
116
|
+
self._current_token = self.fetch_token()
|
|
117
|
+
token_callback(self._current_token)
|
|
118
|
+
|
|
119
|
+
if self._current_token:
|
|
120
|
+
time_until_refresh_threshold = (
|
|
121
|
+
self._current_token.time_remaining
|
|
122
|
+
- (
|
|
123
|
+
self._current_token.expires_in
|
|
124
|
+
* (1 - self._refresh_threshold)
|
|
125
|
+
)
|
|
126
|
+
)
|
|
127
|
+
logger.debug("Time until refresh threshold: %s", time_until_refresh_threshold)
|
|
128
|
+
sleep_time = min(
|
|
129
|
+
self._max_sleep_time, time_until_refresh_threshold
|
|
130
|
+
)
|
|
131
|
+
logger.debug(
|
|
132
|
+
f"Sleeping for {sleep_time} until checking the token again"
|
|
133
|
+
)
|
|
134
|
+
self._should_stop.wait(timeout=max(1.0, sleep_time))
|
|
135
|
+
else:
|
|
136
|
+
self._should_stop.wait(timeout=self._max_sleep_time)
|
|
137
|
+
except Exception as e:
|
|
138
|
+
logger.error("Failed to refresh token: %s", e)
|
|
139
|
+
self._should_stop.wait(timeout=5)
|
|
140
|
+
|
|
141
|
+
self._should_stop.clear()
|
|
142
|
+
self._refresh_thread = threading.Thread(target=refresh_loop, daemon=True)
|
|
143
|
+
self._refresh_thread.start()
|
|
144
|
+
|
|
145
|
+
def stop_token_refresh(self):
|
|
146
|
+
self._should_stop.set()
|
|
147
|
+
if self._refresh_thread:
|
|
148
|
+
self._refresh_thread.join(timeout=1)
|
|
149
|
+
self._refresh_thread = None
|
|
150
|
+
|
|
151
|
+
def get_current_token(self) -> Optional[TokenInfo]:
|
|
152
|
+
with self._token_lock:
|
|
153
|
+
return self._current_token
|
|
154
|
+
|
|
155
|
+
def _should_refresh_token(self, token: TokenInfo | None) -> bool:
|
|
156
|
+
if not token:
|
|
157
|
+
return True
|
|
158
|
+
|
|
159
|
+
limit = token.expires_in * (1 - self._refresh_threshold)
|
|
160
|
+
|
|
161
|
+
logger.debug(
|
|
162
|
+
"The token was issued at %s, it expires in %s. It has %s seconds remaining and we refresh the token when it has %s seconds remaining",
|
|
163
|
+
token.issued_at,
|
|
164
|
+
token.expires_in,
|
|
165
|
+
token.time_remaining,
|
|
166
|
+
limit,
|
|
167
|
+
)
|
|
168
|
+
return token.time_remaining < limit
|
|
169
|
+
|
|
170
|
+
def __enter__(self):
|
|
171
|
+
return self
|
|
172
|
+
|
|
173
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
174
|
+
self.stop_token_refresh()
|
|
175
|
+
return False
|
|
@@ -345,7 +345,7 @@ rapidata/rapidata_client/metadata/transcription_metadata.py,sha256=THtDEVCON4Ulc
|
|
|
345
345
|
rapidata/rapidata_client/order/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
346
346
|
rapidata/rapidata_client/order/rapidata_order.py,sha256=t5ddz_6Dk2DOpsDQ2EiZHJJB1RzU-etKwBL0RErEsSY,4567
|
|
347
347
|
rapidata/rapidata_client/order/rapidata_order_builder.py,sha256=b--9byhsiAW1fL0mVSPzGJT0X4MQ-tCC0BNjIm2vu-Q,16406
|
|
348
|
-
rapidata/rapidata_client/rapidata_client.py,sha256=
|
|
348
|
+
rapidata/rapidata_client/rapidata_client.py,sha256=a_OZBd3sldws3ElqZt_eyqrSzdUNLxynjG5XCNLmmnY,8032
|
|
349
349
|
rapidata/rapidata_client/referee/__init__.py,sha256=E1VODxTjoQRnxzdgMh3aRlDLouxe1nWuvozEHXD2gq4,150
|
|
350
350
|
rapidata/rapidata_client/referee/base_referee.py,sha256=bMy7cw0a-pGNbFu6u_1_Jplu0A483Ubj4oDQzh8vu8k,493
|
|
351
351
|
rapidata/rapidata_client/referee/early_stopping_referee.py,sha256=Dg2Kk7OiLBtS3kknsLxyJIlS27xmPvsikFR6g4xlbTE,1862
|
|
@@ -371,9 +371,11 @@ rapidata/rapidata_client/workflow/evaluation_workflow.py,sha256=IBQoVFxOaeCDIBfa
|
|
|
371
371
|
rapidata/rapidata_client/workflow/free_text_workflow.py,sha256=VaypoG3yKgsbtVyqxta3W28eDwdnGebCy2xDWPCBMyo,1566
|
|
372
372
|
rapidata/rapidata_client/workflow/transcription_workflow.py,sha256=_KDtGCdRhauJm3jQHpwhY-Hq79CLg5I8q2RgOz5lo1g,1404
|
|
373
373
|
rapidata/service/__init__.py,sha256=s9bS1AJZaWIhLtJX_ZA40_CK39rAAkwdAmymTMbeWl4,68
|
|
374
|
+
rapidata/service/credential_manager.py,sha256=CNr7jasOR3vN2XCYtCk4EI38KV2ggSVAg1w_Hhp5LII,7929
|
|
374
375
|
rapidata/service/local_file_service.py,sha256=pgorvlWcx52Uh3cEG6VrdMK_t__7dacQ_5AnfY14BW8,877
|
|
375
|
-
rapidata/service/openapi_service.py,sha256=
|
|
376
|
-
rapidata
|
|
377
|
-
rapidata-1.6.
|
|
378
|
-
rapidata-1.6.
|
|
379
|
-
rapidata-1.6.
|
|
376
|
+
rapidata/service/openapi_service.py,sha256=ejAIilSjRweGcR4nN4txqZNDuoI-WIJEuagdN2oSd58,2335
|
|
377
|
+
rapidata/service/token_manager.py,sha256=JZ5YbR5Di8dO3H4kK11d0kzWlrXxjgCmeNkHA4AapCM,6425
|
|
378
|
+
rapidata-1.6.3.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
379
|
+
rapidata-1.6.3.dist-info/METADATA,sha256=q-e7zziNADs-DUkyxagIGXd1wh9TRiPAs_ELku0_LEs,1056
|
|
380
|
+
rapidata-1.6.3.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
|
|
381
|
+
rapidata-1.6.3.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|