altasigma 3.12.0.dev1__tar.gz → 3.13.0__tar.gz
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.
- {altasigma-3.12.0.dev1/altasigma.egg-info → altasigma-3.13.0}/PKG-INFO +1 -1
- {altasigma-3.12.0.dev1 → altasigma-3.13.0}/altasigma/config/config.py +1 -4
- {altasigma-3.12.0.dev1 → altasigma-3.13.0}/altasigma/credentials/credential_utils.py +9 -74
- {altasigma-3.12.0.dev1 → altasigma-3.13.0}/altasigma/io/data_management.py +4 -4
- {altasigma-3.12.0.dev1 → altasigma-3.13.0}/altasigma/jobsupervisor/job_supervisor_http.py +4 -4
- {altasigma-3.12.0.dev1 → altasigma-3.13.0}/altasigma/spark_session/spark_session_util.py +1 -1
- {altasigma-3.12.0.dev1 → altasigma-3.13.0/altasigma.egg-info}/PKG-INFO +1 -1
- {altasigma-3.12.0.dev1 → altasigma-3.13.0}/setup.py +1 -1
- {altasigma-3.12.0.dev1 → altasigma-3.13.0}/LICENSE +0 -0
- {altasigma-3.12.0.dev1 → altasigma-3.13.0}/MANIFEST.in +0 -0
- {altasigma-3.12.0.dev1 → altasigma-3.13.0}/README_pypi.md +0 -0
- {altasigma-3.12.0.dev1 → altasigma-3.13.0}/altasigma/__init__.py +0 -0
- {altasigma-3.12.0.dev1 → altasigma-3.13.0}/altasigma/config/__init__.py +0 -0
- {altasigma-3.12.0.dev1 → altasigma-3.13.0}/altasigma/config/http_session.py +0 -0
- {altasigma-3.12.0.dev1 → altasigma-3.13.0}/altasigma/credentials/__init__.py +0 -0
- {altasigma-3.12.0.dev1 → altasigma-3.13.0}/altasigma/credentials/token_refresher.py +0 -0
- {altasigma-3.12.0.dev1 → altasigma-3.13.0}/altasigma/git_helper.py +0 -0
- {altasigma-3.12.0.dev1 → altasigma-3.13.0}/altasigma/initialize.py +0 -0
- {altasigma-3.12.0.dev1 → altasigma-3.13.0}/altasigma/io/__init__.py +0 -0
- {altasigma-3.12.0.dev1 → altasigma-3.13.0}/altasigma/io/augurdata.py +0 -0
- {altasigma-3.12.0.dev1 → altasigma-3.13.0}/altasigma/io/files.py +0 -0
- {altasigma-3.12.0.dev1 → altasigma-3.13.0}/altasigma/jobsupervisor/__init__.py +0 -0
- {altasigma-3.12.0.dev1 → altasigma-3.13.0}/altasigma/jobsupervisor/job_supervisor_abstract.py +0 -0
- {altasigma-3.12.0.dev1 → altasigma-3.13.0}/altasigma/jobsupervisor/job_supervisor_dev_mock.py +0 -0
- {altasigma-3.12.0.dev1 → altasigma-3.13.0}/altasigma/jobsupervisor/job_supervisor_helpers.py +0 -0
- {altasigma-3.12.0.dev1 → altasigma-3.13.0}/altasigma/jobsupervisor/reports.py +0 -0
- {altasigma-3.12.0.dev1 → altasigma-3.13.0}/altasigma/progress_reporter/__init__.py +0 -0
- {altasigma-3.12.0.dev1 → altasigma-3.13.0}/altasigma/progress_reporter/progress_reporter.py +0 -0
- {altasigma-3.12.0.dev1 → altasigma-3.13.0}/altasigma/spark_session/__init__.py +0 -0
- {altasigma-3.12.0.dev1 → altasigma-3.13.0}/altasigma/utils/__init__.py +0 -0
- {altasigma-3.12.0.dev1 → altasigma-3.13.0}/altasigma/utils/notebook.py +0 -0
- {altasigma-3.12.0.dev1 → altasigma-3.13.0}/altasigma.egg-info/SOURCES.txt +0 -0
- {altasigma-3.12.0.dev1 → altasigma-3.13.0}/altasigma.egg-info/dependency_links.txt +0 -0
- {altasigma-3.12.0.dev1 → altasigma-3.13.0}/altasigma.egg-info/entry_points.txt +0 -0
- {altasigma-3.12.0.dev1 → altasigma-3.13.0}/altasigma.egg-info/requires.txt +0 -0
- {altasigma-3.12.0.dev1 → altasigma-3.13.0}/altasigma.egg-info/top_level.txt +0 -0
- {altasigma-3.12.0.dev1 → altasigma-3.13.0}/setup.cfg +0 -0
|
@@ -273,10 +273,7 @@ class ModuleConfig:
|
|
|
273
273
|
except Exception as e:
|
|
274
274
|
logger.warning(f"Unexpected error deleting environment variable CLIENT_SECRET: {e}")
|
|
275
275
|
|
|
276
|
-
|
|
277
|
-
# only keep the mounted refresh_token alive when we're the refresher.
|
|
278
|
-
if not os.environ.get("AS_TOKEN_SOCKET"):
|
|
279
|
-
TokenRefresher().schedule_refresh()
|
|
276
|
+
TokenRefresher().schedule_refresh()
|
|
280
277
|
|
|
281
278
|
|
|
282
279
|
module_config = None
|
|
@@ -7,11 +7,9 @@ It supports both interactive authentication flows for notebooks and automated
|
|
|
7
7
|
authentication for production environments.
|
|
8
8
|
"""
|
|
9
9
|
import base64
|
|
10
|
-
import http.client
|
|
11
10
|
import json
|
|
12
11
|
import logging
|
|
13
12
|
import os
|
|
14
|
-
import socket
|
|
15
13
|
from pathlib import Path
|
|
16
14
|
from time import sleep
|
|
17
15
|
|
|
@@ -23,56 +21,6 @@ from ..config.http_session import _http_session
|
|
|
23
21
|
|
|
24
22
|
logger = logging.getLogger(__name__)
|
|
25
23
|
|
|
26
|
-
# When set, the job runs alongside the job-token-manager sidecar, which performs the
|
|
27
|
-
# broker token exchange. The package reads the delegated user access token from the
|
|
28
|
-
# sidecar over this Unix domain socket; git credentials are fetched from the dashboard
|
|
29
|
-
# with that token.
|
|
30
|
-
_AS_TOKEN_SOCKET_ENV = "AS_TOKEN_SOCKET"
|
|
31
|
-
_SIDECAR_TIMEOUT_SECONDS = 15
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
class _UDSConnection(http.client.HTTPConnection):
|
|
35
|
-
"""HTTPConnection that dials a Unix domain socket instead of a TCP host.
|
|
36
|
-
|
|
37
|
-
Lets us speak plain HTTP to the token sidecar (which listens on a local UDS)
|
|
38
|
-
using only the standard library — no extra dependency, and no TCP port is
|
|
39
|
-
ever opened.
|
|
40
|
-
"""
|
|
41
|
-
|
|
42
|
-
def __init__(self, socket_path: str):
|
|
43
|
-
super().__init__("localhost", timeout=_SIDECAR_TIMEOUT_SECONDS)
|
|
44
|
-
self._socket_path = socket_path
|
|
45
|
-
|
|
46
|
-
def connect(self):
|
|
47
|
-
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
|
48
|
-
sock.settimeout(self.timeout)
|
|
49
|
-
sock.connect(self._socket_path)
|
|
50
|
-
self.sock = sock
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
def _sidecar_get(path: str) -> dict:
|
|
54
|
-
"""GET a JSON document from the token sidecar over the UDS."""
|
|
55
|
-
conn = _UDSConnection(os.environ[_AS_TOKEN_SOCKET_ENV])
|
|
56
|
-
try:
|
|
57
|
-
conn.request("GET", path)
|
|
58
|
-
response = conn.getresponse()
|
|
59
|
-
body = response.read()
|
|
60
|
-
if response.status != 200:
|
|
61
|
-
raise RuntimeError(
|
|
62
|
-
f"token sidecar returned {response.status} for {path}: "
|
|
63
|
-
f"{body.decode('utf-8', 'replace')}")
|
|
64
|
-
return json.loads(body)
|
|
65
|
-
finally:
|
|
66
|
-
conn.close()
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
def _sidecar_access_token() -> str:
|
|
70
|
-
"""Fetch the delegated user access token from the token sidecar."""
|
|
71
|
-
access_token = _sidecar_get("/user-token").get("access_token")
|
|
72
|
-
if not isinstance(access_token, str) or not access_token:
|
|
73
|
-
raise RuntimeError("token sidecar returned an empty or malformed access_token")
|
|
74
|
-
return access_token
|
|
75
|
-
|
|
76
24
|
|
|
77
25
|
def _decode_jwt_azp(token: str) -> str:
|
|
78
26
|
"""
|
|
@@ -150,7 +98,7 @@ def _fetch_git_credentials(host_url: str, access_token: str) -> tuple[str, str]:
|
|
|
150
98
|
Exception: Any other non-200 response.
|
|
151
99
|
"""
|
|
152
100
|
headers = {'Authorization': f'Bearer {access_token}'}
|
|
153
|
-
url = f'{host_url}/api/
|
|
101
|
+
url = f'{host_url}/api/git/oauth/access-token'
|
|
154
102
|
response = _http_session().get(url, headers=headers)
|
|
155
103
|
|
|
156
104
|
if _is_reauth_required(response):
|
|
@@ -255,7 +203,7 @@ class CredentialUtils:
|
|
|
255
203
|
- (accessKey, secretKey, expiresAt) for key-based sources
|
|
256
204
|
- (username, password, expiresAt) for password-based sources
|
|
257
205
|
"""
|
|
258
|
-
access_token = self._existing_token()
|
|
206
|
+
(access_token, refresh_token) = self._existing_token()
|
|
259
207
|
header_get_cred = {'Authorization': f'Bearer {access_token}'}
|
|
260
208
|
data_sources_request = _http_session().get(f'{self.dashboard_api_host}/dataman/datasources',
|
|
261
209
|
headers=header_get_cred)
|
|
@@ -303,7 +251,7 @@ class CredentialUtils:
|
|
|
303
251
|
Raises:
|
|
304
252
|
Exception: If the dashboard returns any non-200 / non-reauth status.
|
|
305
253
|
"""
|
|
306
|
-
access_token = self._existing_token()
|
|
254
|
+
(access_token, _refresh_token) = self._existing_token()
|
|
307
255
|
try:
|
|
308
256
|
username, password = _fetch_git_credentials(self.dashboard_api_host, access_token)
|
|
309
257
|
except ReauthRequired as e:
|
|
@@ -372,7 +320,7 @@ class CredentialUtils:
|
|
|
372
320
|
else:
|
|
373
321
|
raise Exception(f"Could not obtain a new refresh token. {token_response}")
|
|
374
322
|
|
|
375
|
-
def _existing_token(self) -> str:
|
|
323
|
+
def _existing_token(self) -> tuple[str, str]:
|
|
376
324
|
"""
|
|
377
325
|
Check for and validate existing authentication tokens.
|
|
378
326
|
|
|
@@ -385,24 +333,11 @@ class CredentialUtils:
|
|
|
385
333
|
before refreshing to reduce unnecessary API calls.
|
|
386
334
|
|
|
387
335
|
Returns:
|
|
388
|
-
|
|
336
|
+
tuple: A tuple containing (access_token, refresh_token)
|
|
389
337
|
|
|
390
338
|
Raises:
|
|
391
339
|
Exception: If token retrieval or refresh fails, or if device auth cannot proceed
|
|
392
340
|
"""
|
|
393
|
-
# When running as a job with the token sidecar, the sidecar owns the
|
|
394
|
-
# token exchange and refresh. Read the delegated user token from it and
|
|
395
|
-
# never touch the mounted refresh token; the sidecar refreshes upstream.
|
|
396
|
-
#
|
|
397
|
-
# TODO: if the workbench ever runs the job-token-manager sidecar too, then
|
|
398
|
-
# AS_TOKEN_SOCKET is always set and the self-managed *token* machinery below
|
|
399
|
-
# becomes dead code: the device-auth flow, the mounted-refresh-token path,
|
|
400
|
-
# `_refresh_token`, and the `TokenRefresher` thread. At that point this
|
|
401
|
-
# function collapses to the sidecar read. (Git credentials are unaffected —
|
|
402
|
-
# `_fetch_git_credentials` fetches them from the dashboard with the token,
|
|
403
|
-
# whether or not the sidecar is present.)
|
|
404
|
-
if os.environ.get(_AS_TOKEN_SOCKET_ENV):
|
|
405
|
-
return _sidecar_access_token()
|
|
406
341
|
current_client_id = os.environ['CLIENT_ID']
|
|
407
342
|
# check if there are any saved tokens
|
|
408
343
|
# For a deployment restart we expect to find a current token from the previous container (realtime module only). Normal jobs don't restart
|
|
@@ -433,10 +368,10 @@ class CredentialUtils:
|
|
|
433
368
|
os.remove(self._TMP_FILE_PATH)
|
|
434
369
|
if current_client_id == "altasigma-frontend":
|
|
435
370
|
raise RuntimeError("Device auth flow cannot be initiated with client ID 'altasigma-frontend'")
|
|
436
|
-
return self._device_auth_flow()
|
|
371
|
+
return self._device_auth_flow()
|
|
437
372
|
|
|
438
373
|
try:
|
|
439
|
-
return self._refresh_token(refresh_token)
|
|
374
|
+
return self._refresh_token(refresh_token)
|
|
440
375
|
except Exception as e:
|
|
441
376
|
os.remove(self._TMP_FILE_PATH)
|
|
442
377
|
raise e
|
|
@@ -452,11 +387,11 @@ class CredentialUtils:
|
|
|
452
387
|
# This is unexpected and a recovery is unlikely. Since we expect it to be a read-only mounted file, we can't delete it.
|
|
453
388
|
raise Exception(f"Refresh token is missing from {self._SECRET_FILE_PATH}")
|
|
454
389
|
else:
|
|
455
|
-
return self._refresh_token(refresh_token)
|
|
390
|
+
return self._refresh_token(refresh_token)
|
|
456
391
|
# Expected case for the first call from the workbench with the device flow auth
|
|
457
392
|
if current_client_id == "altasigma-frontend":
|
|
458
393
|
raise RuntimeError("Device auth flow cannot be initiated with client ID 'altasigma-frontend'")
|
|
459
|
-
return self._device_auth_flow()
|
|
394
|
+
return self._device_auth_flow()
|
|
460
395
|
|
|
461
396
|
def _write_tokens_to_file(self, access_token: str, refresh_token: str):
|
|
462
397
|
"""
|
|
@@ -232,7 +232,7 @@ def get_datasource_by_code(datasource_code: str) -> DataSource:
|
|
|
232
232
|
Raises:
|
|
233
233
|
HTTPError: If the API request fails.
|
|
234
234
|
"""
|
|
235
|
-
access_token = _credential_utils()._existing_token()
|
|
235
|
+
access_token, _ = _credential_utils()._existing_token()
|
|
236
236
|
|
|
237
237
|
response = _http_session().get(
|
|
238
238
|
f"http://{_data_man_config().api_host}:{_data_man_config().api_port}/dataman/datasource/{datasource_code}",
|
|
@@ -255,7 +255,7 @@ def get_datasource(datasource_name: str) -> DataSource:
|
|
|
255
255
|
ValueError: If no data source with the specified name exists.
|
|
256
256
|
RuntimeError: If the API request fails.
|
|
257
257
|
"""
|
|
258
|
-
access_token = _credential_utils()._existing_token()
|
|
258
|
+
access_token, _ = _credential_utils()._existing_token()
|
|
259
259
|
|
|
260
260
|
data_sources_request = _http_session().get(
|
|
261
261
|
f"http://{_data_man_config().api_host}:{_data_man_config().api_port}/dataman/datasources",
|
|
@@ -287,7 +287,7 @@ def get_s3_credentials(datasource_code: str) -> S3Credentials:
|
|
|
287
287
|
Raises:
|
|
288
288
|
HTTPError: If the credential request fails.
|
|
289
289
|
"""
|
|
290
|
-
access_token = _credential_utils()._existing_token()
|
|
290
|
+
access_token, _ = _credential_utils()._existing_token()
|
|
291
291
|
response = _http_session().get(
|
|
292
292
|
f"http://{_data_man_config().api_host}:{_data_man_config().api_port}/dataman/s3/{datasource_code}/credentials",
|
|
293
293
|
headers={"Authorization": f"Bearer {access_token}"})
|
|
@@ -311,7 +311,7 @@ def get_cassandra_credentials(datasource_code: str) -> CassandraCredentials:
|
|
|
311
311
|
HTTPError: If the credential request fails.
|
|
312
312
|
Exception: If the response contains errors.
|
|
313
313
|
"""
|
|
314
|
-
access_token = _credential_utils()._existing_token()
|
|
314
|
+
access_token, _ = _credential_utils()._existing_token()
|
|
315
315
|
response = _http_session().get(
|
|
316
316
|
f"http://{_data_man_config().api_host}:{_data_man_config().api_port}/dataman/cassandra/{datasource_code}/credentials",
|
|
317
317
|
headers={"Authorization": f"Bearer {access_token}"})
|
|
@@ -102,7 +102,7 @@ class JobSupervisorHTTP(JobSupervisorAbstract):
|
|
|
102
102
|
}
|
|
103
103
|
data = json.dumps(data_dict)
|
|
104
104
|
logger.debug(f"Request body: {data}")
|
|
105
|
-
access_token = _credential_utils()._existing_token()
|
|
105
|
+
access_token, _ = _credential_utils()._existing_token()
|
|
106
106
|
response = _http_session().post(
|
|
107
107
|
url, data=data, headers={"Authorization": f"Bearer {access_token}", "Content-Type": "application/json"}
|
|
108
108
|
)
|
|
@@ -164,7 +164,7 @@ class JobSupervisorHTTP(JobSupervisorAbstract):
|
|
|
164
164
|
"message": reason,
|
|
165
165
|
}
|
|
166
166
|
data = json.dumps(data_dict)
|
|
167
|
-
access_token = _credential_utils()._existing_token()
|
|
167
|
+
access_token, _ = _credential_utils()._existing_token()
|
|
168
168
|
response = _http_session().post(
|
|
169
169
|
url, data=data, headers={"Authorization": f"Bearer {access_token}", "Content-Type": "application/json"}
|
|
170
170
|
)
|
|
@@ -213,7 +213,7 @@ class JobSupervisorHTTP(JobSupervisorAbstract):
|
|
|
213
213
|
data_dict, default=lambda x: x.__dict__
|
|
214
214
|
) # To decode objects as dicts
|
|
215
215
|
logger.debug(f"Learning report data string: {data}")
|
|
216
|
-
access_token = _credential_utils()._existing_token()
|
|
216
|
+
access_token, _ = _credential_utils()._existing_token()
|
|
217
217
|
response = _http_session().post(
|
|
218
218
|
url, data=data, headers={"Authorization": f"Bearer {access_token}", "Content-Type": "application/json"}
|
|
219
219
|
)
|
|
@@ -259,7 +259,7 @@ class JobSupervisorHTTP(JobSupervisorAbstract):
|
|
|
259
259
|
data = json.dumps(data_dict)
|
|
260
260
|
logger.debug(f"Request body: {data}")
|
|
261
261
|
|
|
262
|
-
access_token = _credential_utils()._existing_token()
|
|
262
|
+
access_token, _ = _credential_utils()._existing_token()
|
|
263
263
|
response = _http_session().post(
|
|
264
264
|
url, data=data, headers={"Authorization": f"Bearer {access_token}", "Content-Type": "application/json"}
|
|
265
265
|
)
|
|
@@ -36,7 +36,7 @@ def _get_datasource_info(data_source_name: str):
|
|
|
36
36
|
ValueError: If the datasource cannot be retrieved or does not exist.
|
|
37
37
|
"""
|
|
38
38
|
host_url = os.environ['HOST_URL']
|
|
39
|
-
access_token = _credential_utils()._existing_token()
|
|
39
|
+
access_token, _ = _credential_utils()._existing_token()
|
|
40
40
|
|
|
41
41
|
data_sources_request = _http_session().get(f"{host_url}/dataman/datasources",
|
|
42
42
|
headers={"Authorization": f"Bearer {access_token}"})
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{altasigma-3.12.0.dev1 → altasigma-3.13.0}/altasigma/jobsupervisor/job_supervisor_abstract.py
RENAMED
|
File without changes
|
{altasigma-3.12.0.dev1 → altasigma-3.13.0}/altasigma/jobsupervisor/job_supervisor_dev_mock.py
RENAMED
|
File without changes
|
{altasigma-3.12.0.dev1 → altasigma-3.13.0}/altasigma/jobsupervisor/job_supervisor_helpers.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|