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.
Files changed (37) hide show
  1. {altasigma-3.12.0.dev1/altasigma.egg-info → altasigma-3.13.0}/PKG-INFO +1 -1
  2. {altasigma-3.12.0.dev1 → altasigma-3.13.0}/altasigma/config/config.py +1 -4
  3. {altasigma-3.12.0.dev1 → altasigma-3.13.0}/altasigma/credentials/credential_utils.py +9 -74
  4. {altasigma-3.12.0.dev1 → altasigma-3.13.0}/altasigma/io/data_management.py +4 -4
  5. {altasigma-3.12.0.dev1 → altasigma-3.13.0}/altasigma/jobsupervisor/job_supervisor_http.py +4 -4
  6. {altasigma-3.12.0.dev1 → altasigma-3.13.0}/altasigma/spark_session/spark_session_util.py +1 -1
  7. {altasigma-3.12.0.dev1 → altasigma-3.13.0/altasigma.egg-info}/PKG-INFO +1 -1
  8. {altasigma-3.12.0.dev1 → altasigma-3.13.0}/setup.py +1 -1
  9. {altasigma-3.12.0.dev1 → altasigma-3.13.0}/LICENSE +0 -0
  10. {altasigma-3.12.0.dev1 → altasigma-3.13.0}/MANIFEST.in +0 -0
  11. {altasigma-3.12.0.dev1 → altasigma-3.13.0}/README_pypi.md +0 -0
  12. {altasigma-3.12.0.dev1 → altasigma-3.13.0}/altasigma/__init__.py +0 -0
  13. {altasigma-3.12.0.dev1 → altasigma-3.13.0}/altasigma/config/__init__.py +0 -0
  14. {altasigma-3.12.0.dev1 → altasigma-3.13.0}/altasigma/config/http_session.py +0 -0
  15. {altasigma-3.12.0.dev1 → altasigma-3.13.0}/altasigma/credentials/__init__.py +0 -0
  16. {altasigma-3.12.0.dev1 → altasigma-3.13.0}/altasigma/credentials/token_refresher.py +0 -0
  17. {altasigma-3.12.0.dev1 → altasigma-3.13.0}/altasigma/git_helper.py +0 -0
  18. {altasigma-3.12.0.dev1 → altasigma-3.13.0}/altasigma/initialize.py +0 -0
  19. {altasigma-3.12.0.dev1 → altasigma-3.13.0}/altasigma/io/__init__.py +0 -0
  20. {altasigma-3.12.0.dev1 → altasigma-3.13.0}/altasigma/io/augurdata.py +0 -0
  21. {altasigma-3.12.0.dev1 → altasigma-3.13.0}/altasigma/io/files.py +0 -0
  22. {altasigma-3.12.0.dev1 → altasigma-3.13.0}/altasigma/jobsupervisor/__init__.py +0 -0
  23. {altasigma-3.12.0.dev1 → altasigma-3.13.0}/altasigma/jobsupervisor/job_supervisor_abstract.py +0 -0
  24. {altasigma-3.12.0.dev1 → altasigma-3.13.0}/altasigma/jobsupervisor/job_supervisor_dev_mock.py +0 -0
  25. {altasigma-3.12.0.dev1 → altasigma-3.13.0}/altasigma/jobsupervisor/job_supervisor_helpers.py +0 -0
  26. {altasigma-3.12.0.dev1 → altasigma-3.13.0}/altasigma/jobsupervisor/reports.py +0 -0
  27. {altasigma-3.12.0.dev1 → altasigma-3.13.0}/altasigma/progress_reporter/__init__.py +0 -0
  28. {altasigma-3.12.0.dev1 → altasigma-3.13.0}/altasigma/progress_reporter/progress_reporter.py +0 -0
  29. {altasigma-3.12.0.dev1 → altasigma-3.13.0}/altasigma/spark_session/__init__.py +0 -0
  30. {altasigma-3.12.0.dev1 → altasigma-3.13.0}/altasigma/utils/__init__.py +0 -0
  31. {altasigma-3.12.0.dev1 → altasigma-3.13.0}/altasigma/utils/notebook.py +0 -0
  32. {altasigma-3.12.0.dev1 → altasigma-3.13.0}/altasigma.egg-info/SOURCES.txt +0 -0
  33. {altasigma-3.12.0.dev1 → altasigma-3.13.0}/altasigma.egg-info/dependency_links.txt +0 -0
  34. {altasigma-3.12.0.dev1 → altasigma-3.13.0}/altasigma.egg-info/entry_points.txt +0 -0
  35. {altasigma-3.12.0.dev1 → altasigma-3.13.0}/altasigma.egg-info/requires.txt +0 -0
  36. {altasigma-3.12.0.dev1 → altasigma-3.13.0}/altasigma.egg-info/top_level.txt +0 -0
  37. {altasigma-3.12.0.dev1 → altasigma-3.13.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: altasigma
3
- Version: 3.12.0.dev1
3
+ Version: 3.13.0
4
4
  Summary: Python helpers for data science and ML workflows on the AltaSigma AI platform
5
5
  Home-page: https://www.altasigma.com
6
6
  Author: AltaSigma GmbH
@@ -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
- # The job-token-manager sidecar owns the token lifecycle when present;
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/workbench/git-oauth2/access-token'
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
- str: The delegated user access token.
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()[0]
371
+ return self._device_auth_flow()
437
372
 
438
373
  try:
439
- return self._refresh_token(refresh_token)[0]
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)[0]
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()[0]
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}"})
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: altasigma
3
- Version: 3.12.0.dev1
3
+ Version: 3.13.0
4
4
  Summary: Python helpers for data science and ML workflows on the AltaSigma AI platform
5
5
  Home-page: https://www.altasigma.com
6
6
  Author: AltaSigma GmbH
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
2
2
 
3
3
  setup(
4
4
  name="altasigma",
5
- version="3.12.0-dev1",
5
+ version="3.13.0",
6
6
  author="AltaSigma GmbH",
7
7
  author_email="pypi@altasigma.com",
8
8
  packages=find_packages(exclude=['altasigma.test', 'altasigma.test.*']),
File without changes
File without changes
File without changes