altasigma 3.12.0.dev0__tar.gz → 3.12.0.dev2__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.dev0/altasigma.egg-info → altasigma-3.12.0.dev2}/PKG-INFO +1 -1
  2. {altasigma-3.12.0.dev0 → altasigma-3.12.0.dev2}/altasigma/config/config.py +4 -1
  3. {altasigma-3.12.0.dev0 → altasigma-3.12.0.dev2}/altasigma/credentials/credential_utils.py +190 -43
  4. {altasigma-3.12.0.dev0 → altasigma-3.12.0.dev2}/altasigma/git_helper.py +19 -3
  5. {altasigma-3.12.0.dev0 → altasigma-3.12.0.dev2}/altasigma/io/data_management.py +4 -4
  6. {altasigma-3.12.0.dev0 → altasigma-3.12.0.dev2}/altasigma/jobsupervisor/job_supervisor_http.py +4 -4
  7. {altasigma-3.12.0.dev0 → altasigma-3.12.0.dev2}/altasigma/spark_session/spark_session_util.py +1 -1
  8. {altasigma-3.12.0.dev0 → altasigma-3.12.0.dev2}/altasigma/utils/notebook.py +3 -1
  9. {altasigma-3.12.0.dev0 → altasigma-3.12.0.dev2/altasigma.egg-info}/PKG-INFO +1 -1
  10. {altasigma-3.12.0.dev0 → altasigma-3.12.0.dev2}/setup.py +1 -1
  11. {altasigma-3.12.0.dev0 → altasigma-3.12.0.dev2}/LICENSE +0 -0
  12. {altasigma-3.12.0.dev0 → altasigma-3.12.0.dev2}/MANIFEST.in +0 -0
  13. {altasigma-3.12.0.dev0 → altasigma-3.12.0.dev2}/README_pypi.md +0 -0
  14. {altasigma-3.12.0.dev0 → altasigma-3.12.0.dev2}/altasigma/__init__.py +0 -0
  15. {altasigma-3.12.0.dev0 → altasigma-3.12.0.dev2}/altasigma/config/__init__.py +0 -0
  16. {altasigma-3.12.0.dev0 → altasigma-3.12.0.dev2}/altasigma/config/http_session.py +0 -0
  17. {altasigma-3.12.0.dev0 → altasigma-3.12.0.dev2}/altasigma/credentials/__init__.py +0 -0
  18. {altasigma-3.12.0.dev0 → altasigma-3.12.0.dev2}/altasigma/credentials/token_refresher.py +0 -0
  19. {altasigma-3.12.0.dev0 → altasigma-3.12.0.dev2}/altasigma/initialize.py +0 -0
  20. {altasigma-3.12.0.dev0 → altasigma-3.12.0.dev2}/altasigma/io/__init__.py +0 -0
  21. {altasigma-3.12.0.dev0 → altasigma-3.12.0.dev2}/altasigma/io/augurdata.py +0 -0
  22. {altasigma-3.12.0.dev0 → altasigma-3.12.0.dev2}/altasigma/io/files.py +0 -0
  23. {altasigma-3.12.0.dev0 → altasigma-3.12.0.dev2}/altasigma/jobsupervisor/__init__.py +0 -0
  24. {altasigma-3.12.0.dev0 → altasigma-3.12.0.dev2}/altasigma/jobsupervisor/job_supervisor_abstract.py +0 -0
  25. {altasigma-3.12.0.dev0 → altasigma-3.12.0.dev2}/altasigma/jobsupervisor/job_supervisor_dev_mock.py +0 -0
  26. {altasigma-3.12.0.dev0 → altasigma-3.12.0.dev2}/altasigma/jobsupervisor/job_supervisor_helpers.py +0 -0
  27. {altasigma-3.12.0.dev0 → altasigma-3.12.0.dev2}/altasigma/jobsupervisor/reports.py +0 -0
  28. {altasigma-3.12.0.dev0 → altasigma-3.12.0.dev2}/altasigma/progress_reporter/__init__.py +0 -0
  29. {altasigma-3.12.0.dev0 → altasigma-3.12.0.dev2}/altasigma/progress_reporter/progress_reporter.py +0 -0
  30. {altasigma-3.12.0.dev0 → altasigma-3.12.0.dev2}/altasigma/spark_session/__init__.py +0 -0
  31. {altasigma-3.12.0.dev0 → altasigma-3.12.0.dev2}/altasigma/utils/__init__.py +0 -0
  32. {altasigma-3.12.0.dev0 → altasigma-3.12.0.dev2}/altasigma.egg-info/SOURCES.txt +0 -0
  33. {altasigma-3.12.0.dev0 → altasigma-3.12.0.dev2}/altasigma.egg-info/dependency_links.txt +0 -0
  34. {altasigma-3.12.0.dev0 → altasigma-3.12.0.dev2}/altasigma.egg-info/entry_points.txt +0 -0
  35. {altasigma-3.12.0.dev0 → altasigma-3.12.0.dev2}/altasigma.egg-info/requires.txt +0 -0
  36. {altasigma-3.12.0.dev0 → altasigma-3.12.0.dev2}/altasigma.egg-info/top_level.txt +0 -0
  37. {altasigma-3.12.0.dev0 → altasigma-3.12.0.dev2}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: altasigma
3
- Version: 3.12.0.dev0
3
+ Version: 3.12.0.dev2
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,7 +273,10 @@ class ModuleConfig:
273
273
  except Exception as e:
274
274
  logger.warning(f"Unexpected error deleting environment variable CLIENT_SECRET: {e}")
275
275
 
276
- TokenRefresher().schedule_refresh()
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()
277
280
 
278
281
 
279
282
  module_config = None
@@ -7,9 +7,11 @@ 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
10
11
  import json
11
12
  import logging
12
13
  import os
14
+ import socket
13
15
  from pathlib import Path
14
16
  from time import sleep
15
17
 
@@ -21,6 +23,56 @@ from ..config.http_session import _http_session
21
23
 
22
24
  logger = logging.getLogger(__name__)
23
25
 
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
+
24
76
 
25
77
  def _decode_jwt_azp(token: str) -> str:
26
78
  """
@@ -55,6 +107,95 @@ def _decode_jwt_azp(token: str) -> str:
55
107
  return ''
56
108
 
57
109
 
110
+ class ReauthRequired(Exception):
111
+ """The dashboard responded that the user must (re)complete the git OAuth flow.
112
+
113
+ Headless callers (e.g. the notebook-git-api sidecar via the
114
+ ``ALTASIGMA_ACCESS_TOKEN`` env var) cannot perform that flow themselves —
115
+ they should propagate this exception to the layer that owns the user's UI
116
+ so it can surface ``authorize_url``. The interactive notebook path catches
117
+ this exception and polls until the user completes the flow in the browser.
118
+ """
119
+
120
+ def __init__(self, authorize_url: str):
121
+ self.authorize_url = authorize_url
122
+ super().__init__(
123
+ f"git OAuth required; complete the flow at {authorize_url}"
124
+ )
125
+
126
+
127
+ def _fetch_git_credentials(host_url: str, access_token: str) -> tuple[str, str]:
128
+ """
129
+ Mint a fresh per-user git access token via the dashboard's git-oauth2 endpoint
130
+ and shape it into the (username, password) pair git's credential helper
131
+ protocol expects.
132
+
133
+ Pure HTTP — never blocks, never polls, never prints. The interactive
134
+ polling-on-reauth loop lives in ``_await_git_oauth_completion`` (used by the
135
+ notebook flow in ``CredentialUtils.get_git_credentials``); headless callers
136
+ receive ``ReauthRequired`` and decide for themselves how to surface it.
137
+
138
+ Consumers: ``CredentialUtils.get_git_credentials`` (this package, notebook
139
+ path) and the ``notebook-git-api`` sidecar (imports this symbol directly).
140
+
141
+ Args:
142
+ host_url (str): The AltaSigma installation host URL (no trailing slash needed).
143
+ access_token (str): A Keycloak bearer authorised for the dashboard.
144
+
145
+ Returns:
146
+ tuple: (username, password)
147
+
148
+ Raises:
149
+ ReauthRequired: User has no usable grant; carries the authorize URL.
150
+ Exception: Any other non-200 response.
151
+ """
152
+ headers = {'Authorization': f'Bearer {access_token}'}
153
+ url = f'{host_url}/api/git/oauth/access-token'
154
+ response = _http_session().get(url, headers=headers)
155
+
156
+ if _is_reauth_required(response):
157
+ # User has no usable grant (never connected, revoked, or expired refresh).
158
+ raise ReauthRequired(response.json()["details"]["authorizeUrl"])
159
+
160
+ if response.status_code == 200:
161
+ # Gitea-only for now: token goes in the password field, username can be
162
+ # anything non-empty. When other providers (GitLab, Bitbucket, ...) are
163
+ # added, the dashboard should return both fields and the helper should
164
+ # just forward them.
165
+ return "oauth2", response.json()["accessToken"]
166
+
167
+ raise Exception(
168
+ f"ERROR: Unexpected response from dashboard: {response.status_code}, {response.text}")
169
+
170
+
171
+ def _is_reauth_required(response) -> bool:
172
+ """True if the response is the dashboard's 409 reauth-required signal."""
173
+ if response.status_code != 409:
174
+ return False
175
+ try:
176
+ return response.json().get("details", {}).get("reason") == "reauth_required"
177
+ except ValueError:
178
+ return False
179
+
180
+
181
+ def _await_git_oauth_completion(host_url: str, access_token: str, authorize_url: str) -> tuple[str, str]:
182
+ """Interactive notebook helper: show the OAuth authorize URL once, then
183
+ poll the dashboard until the user completes the flow in the browser."""
184
+ # Plain URL — OSC 8 escapes show as garbage in Jupyter `!` cell output.
185
+ display_or_print(
186
+ msg_notebook=HTML(
187
+ f'Please authorize git access: '
188
+ f'<a href="{authorize_url}" target="_blank">{authorize_url}</a>'),
189
+ msg_other=f"Please authorize git access:\n{authorize_url}")
190
+
191
+ while True:
192
+ sleep(2)
193
+ try:
194
+ return _fetch_git_credentials(host_url, access_token)
195
+ except ReauthRequired:
196
+ continue
197
+
198
+
58
199
  class CredentialUtils:
59
200
  """
60
201
  Manages authentication and data source credential access.
@@ -114,7 +255,7 @@ class CredentialUtils:
114
255
  - (accessKey, secretKey, expiresAt) for key-based sources
115
256
  - (username, password, expiresAt) for password-based sources
116
257
  """
117
- (access_token, refresh_token) = self._existing_token()
258
+ access_token = self._existing_token()
118
259
  header_get_cred = {'Authorization': f'Bearer {access_token}'}
119
260
  data_sources_request = _http_session().get(f'{self.dashboard_api_host}/dataman/datasources',
120
261
  headers=header_get_cred)
@@ -146,43 +287,32 @@ class CredentialUtils:
146
287
  raise Exception(
147
288
  f"ERROR: Unexpected response from dataman: {data_sources_request.status_code}, {data_sources_request.text}")
148
289
 
149
- def get_git_credentials(self, host: str) -> tuple[str, str, str]:
290
+ def get_git_credentials(self) -> tuple[str, str]:
150
291
  """
151
- Retrieve Gitea credentials for a given host from the AltaSigma dashboard.
292
+ Retrieve git credentials for the user's connected provider from the AltaSigma dashboard.
152
293
 
153
294
  Acquires a Keycloak bearer via the same token flow as `get_credentials`,
154
- then asks the dashboard for the per-user Gitea username/password bound to
155
- the requested host.
156
-
157
- Args:
158
- host (str): The git host the user wants credentials for (e.g. "gitea.altasigma.internal").
295
+ then asks the dashboard to mint a fresh per-user git access token for the
296
+ single dashboard-configured provider. If the user hasn't completed the
297
+ git OAuth flow yet (or their grant has expired), prints the authorize URL
298
+ and polls the dashboard until they complete the flow in the browser.
159
299
 
160
300
  Returns:
161
- tuple: (username, password, expiresAt)
301
+ tuple: (username, password)
162
302
 
163
303
  Raises:
164
- Exception: If the user has not connected the given git host in the dashboard (404),
165
- or if the dashboard returns any other non-2xx status.
304
+ Exception: If the dashboard returns any non-200 / non-reauth status.
166
305
  """
167
- (access_token, _refresh_token) = self._existing_token()
168
- headers = {'Authorization': f'Bearer {access_token}'}
169
-
170
- url = f'{self.dashboard_api_host}/git/{host}/credentials'
171
- response = _http_session().get(url, headers=headers)
172
-
173
- if response.status_code == 200:
174
- res = response.json()
175
- display_or_print(
176
- msg_notebook=HTML("Successfully obtained git credentials"),
177
- msg_other="Successfully obtained git credentials")
178
- return res["username"], res["password"], res["expiresAt"]
179
- elif response.status_code == 404:
180
- raise Exception(
181
- f"Git host '{host}' is not connected in the AltaSigma dashboard. "
182
- f"Connect Gitea under Account → Integrations and retry.")
183
- else:
184
- raise Exception(
185
- f"ERROR: Unexpected response from dashboard: {response.status_code}, {response.text}")
306
+ access_token = self._existing_token()
307
+ try:
308
+ username, password = _fetch_git_credentials(self.dashboard_api_host, access_token)
309
+ except ReauthRequired as e:
310
+ username, password = _await_git_oauth_completion(
311
+ self.dashboard_api_host, access_token, e.authorize_url)
312
+ display_or_print(
313
+ msg_notebook=HTML("Successfully obtained git credentials"),
314
+ msg_other="Successfully obtained git credentials")
315
+ return username, password
186
316
 
187
317
  def _refresh_token(self, refresh_token: str) -> tuple[str, str]:
188
318
  """
@@ -242,7 +372,7 @@ class CredentialUtils:
242
372
  else:
243
373
  raise Exception(f"Could not obtain a new refresh token. {token_response}")
244
374
 
245
- def _existing_token(self) -> tuple[str, str]:
375
+ def _existing_token(self) -> str:
246
376
  """
247
377
  Check for and validate existing authentication tokens.
248
378
 
@@ -255,11 +385,24 @@ class CredentialUtils:
255
385
  before refreshing to reduce unnecessary API calls.
256
386
 
257
387
  Returns:
258
- tuple: A tuple containing (access_token, refresh_token)
388
+ str: The delegated user access token.
259
389
 
260
390
  Raises:
261
391
  Exception: If token retrieval or refresh fails, or if device auth cannot proceed
262
392
  """
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()
263
406
  current_client_id = os.environ['CLIENT_ID']
264
407
  # check if there are any saved tokens
265
408
  # For a deployment restart we expect to find a current token from the previous container (realtime module only). Normal jobs don't restart
@@ -290,10 +433,10 @@ class CredentialUtils:
290
433
  os.remove(self._TMP_FILE_PATH)
291
434
  if current_client_id == "altasigma-frontend":
292
435
  raise RuntimeError("Device auth flow cannot be initiated with client ID 'altasigma-frontend'")
293
- return self._device_auth_flow()
436
+ return self._device_auth_flow()[0]
294
437
 
295
438
  try:
296
- return self._refresh_token(refresh_token)
439
+ return self._refresh_token(refresh_token)[0]
297
440
  except Exception as e:
298
441
  os.remove(self._TMP_FILE_PATH)
299
442
  raise e
@@ -309,11 +452,11 @@ class CredentialUtils:
309
452
  # This is unexpected and a recovery is unlikely. Since we expect it to be a read-only mounted file, we can't delete it.
310
453
  raise Exception(f"Refresh token is missing from {self._SECRET_FILE_PATH}")
311
454
  else:
312
- return self._refresh_token(refresh_token)
455
+ return self._refresh_token(refresh_token)[0]
313
456
  # Expected case for the first call from the workbench with the device flow auth
314
457
  if current_client_id == "altasigma-frontend":
315
458
  raise RuntimeError("Device auth flow cannot be initiated with client ID 'altasigma-frontend'")
316
- return self._device_auth_flow()
459
+ return self._device_auth_flow()[0]
317
460
 
318
461
  def _write_tokens_to_file(self, access_token: str, refresh_token: str):
319
462
  """
@@ -370,10 +513,11 @@ class CredentialUtils:
370
513
  device_response = response.json()
371
514
  device_code = device_response["device_code"]
372
515
  authorization_url = device_response["verification_uri_complete"]
516
+ # URL on its own line so terminals don't soft-wrap it mid-token.
373
517
  display_or_print(
374
518
  msg_notebook=HTML(
375
519
  f'Please authenticate using the following link: <a href="{authorization_url}" target="_blank">{authorization_url}</a>'),
376
- msg_other=f'Please authenticate using the following link: {authorization_url}')
520
+ msg_other=f'Please authenticate using the following link:\n{authorization_url}')
377
521
  else:
378
522
  raise Exception(f"ERROR: Unexpected response {response.status_code}, {response.text}")
379
523
 
@@ -401,13 +545,16 @@ class CredentialUtils:
401
545
  break
402
546
  elif response.status_code == 400 and response.json().get('error') == 'authorization_pending':
403
547
  if is_in_notebook():
548
+ # Re-render URL + animated dots so the cell stays compact
549
+ # (single output, not appended on every poll).
404
550
  clear_output(wait=True)
405
- display_or_print(msg_notebook=HTML(
406
- f'Please authenticate using the following link: <a href="{authorization_url}" target="_blank">{authorization_url}</a>'),
407
- msg_other=f'Please authenticate using the following link: {authorization_url}')
408
- display_or_print(
409
- msg_notebook=HTML(f"Authorization pending {'.' * i}"),
410
- msg_other=f"Authorization pending {'.' * i}")
551
+ display_or_print(msg_notebook=HTML(
552
+ f'Please authenticate using the following link: <a href="{authorization_url}" target="_blank">{authorization_url}</a>'),
553
+ msg_other=f'Please authenticate using the following link: {authorization_url}')
554
+ display_or_print(
555
+ msg_notebook=HTML(f"Authorization pending {'.' * i}"),
556
+ msg_other=f"Authorization pending {'.' * i}")
557
+ # Non-notebook: URL was printed once before the polling loop; stay quiet here.
411
558
  i = (i + 1) % 4
412
559
  else:
413
560
  raise Exception(
@@ -9,6 +9,14 @@ Installed as the console script `git-credential-altasigma`. Once a user runs
9
9
  `git config credential.helper altasigma`, git will resolve that to this script
10
10
  on PATH and invoke it with `get`, `store`, or `erase` subcommands.
11
11
 
12
+ The helper resolves the dashboard bearer through ``CredentialUtils``
13
+ (refresh-token cache + Keycloak refresh / device flow), so it is intended for
14
+ interactive (notebook) callers where the device-auth URL can be shown to a
15
+ human. Headless callers (e.g. the notebook-git-api sidecar) should call
16
+ ``altasigma.credentials.credential_utils._fetch_git_credentials`` directly from
17
+ Python with the inbound bearer so ``ReauthRequired`` can be surfaced as a
18
+ structured error.
19
+
12
20
  Stdout is reserved exclusively for the two `username=...` / `password=...` lines
13
21
  that git expects on success. Everything else (logs, errors) goes to stderr, and
14
22
  any failure exits non-zero with empty stdout so git treats it as "no credentials"
@@ -67,14 +75,22 @@ def main() -> int:
67
75
  logger.error("git credential request missing required 'host' field")
68
76
  return 1
69
77
 
78
+ # Redirect stdout -> stderr while fetching credentials so that any user-facing
79
+ # prints from CredentialUtils (e.g. the device-flow URL, "Authorization pending")
80
+ # don't pollute git's credential-helper input. Git parses each line on the
81
+ # helper's stdout as `key=value`; anything else makes git warn and close the pipe.
82
+ real_stdout = sys.stdout
83
+ sys.stdout = sys.stderr
70
84
  try:
71
- username, password, _expires_at = CredentialUtils().get_git_credentials(host)
85
+ username, password = CredentialUtils().get_git_credentials()
72
86
  except Exception as exc:
73
87
  logger.error("failed to obtain git credentials for host %s: %s", host, exc)
74
88
  return 1
89
+ finally:
90
+ sys.stdout = real_stdout
75
91
 
76
- sys.stdout.write(f"username={username}\n")
77
- sys.stdout.write(f"password={password}\n")
92
+ real_stdout.write(f"username={username}\n")
93
+ real_stdout.write(f"password={password}\n")
78
94
  return 0
79
95
 
80
96
 
@@ -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}"})
@@ -61,4 +61,6 @@ def display_or_print(msg_notebook, msg_other):
61
61
  if is_in_notebook():
62
62
  display(msg_notebook)
63
63
  else:
64
- print(msg_other)
64
+ # flush=True so output reaches the parent (e.g. git invoking the credential helper)
65
+ # immediately, instead of sitting in Python's pipe buffer until process exit.
66
+ print(msg_other, flush=True)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: altasigma
3
- Version: 3.12.0.dev0
3
+ Version: 3.12.0.dev2
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-dev0",
5
+ version="3.12.0-dev2",
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