satisfactoscript 0.5.6__tar.gz → 0.5.7__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.
- {satisfactoscript-0.5.6 → satisfactoscript-0.5.7}/PKG-INFO +1 -1
- {satisfactoscript-0.5.6 → satisfactoscript-0.5.7}/pyproject.toml +1 -1
- {satisfactoscript-0.5.6 → satisfactoscript-0.5.7}/src/satisfactoscript/core/core.py +28 -9
- {satisfactoscript-0.5.6 → satisfactoscript-0.5.7}/src/satisfactoscript.egg-info/PKG-INFO +1 -1
- {satisfactoscript-0.5.6 → satisfactoscript-0.5.7}/src/satisfactoscript.egg-info/SOURCES.txt +1 -0
- satisfactoscript-0.5.7/tests/test_core_username.py +101 -0
- {satisfactoscript-0.5.6 → satisfactoscript-0.5.7}/README.md +0 -0
- {satisfactoscript-0.5.6 → satisfactoscript-0.5.7}/setup.cfg +0 -0
- {satisfactoscript-0.5.6 → satisfactoscript-0.5.7}/src/satisfactoscript/__init__.py +0 -0
- {satisfactoscript-0.5.6 → satisfactoscript-0.5.7}/src/satisfactoscript/agentic/__init__.py +0 -0
- {satisfactoscript-0.5.6 → satisfactoscript-0.5.7}/src/satisfactoscript/agentic/agent.py +0 -0
- {satisfactoscript-0.5.6 → satisfactoscript-0.5.7}/src/satisfactoscript/core/__init__.py +0 -0
- {satisfactoscript-0.5.6 → satisfactoscript-0.5.7}/src/satisfactoscript/core/config.py +0 -0
- {satisfactoscript-0.5.6 → satisfactoscript-0.5.7}/src/satisfactoscript/core/loaders.py +0 -0
- {satisfactoscript-0.5.6 → satisfactoscript-0.5.7}/src/satisfactoscript/core/registry.py +0 -0
- {satisfactoscript-0.5.6 → satisfactoscript-0.5.7}/src/satisfactoscript/semantic/__init__.py +0 -0
- {satisfactoscript-0.5.6 → satisfactoscript-0.5.7}/src/satisfactoscript/semantic/semantic.py +0 -0
- {satisfactoscript-0.5.6 → satisfactoscript-0.5.7}/src/satisfactoscript.egg-info/dependency_links.txt +0 -0
- {satisfactoscript-0.5.6 → satisfactoscript-0.5.7}/src/satisfactoscript.egg-info/requires.txt +0 -0
- {satisfactoscript-0.5.6 → satisfactoscript-0.5.7}/src/satisfactoscript.egg-info/top_level.txt +0 -0
- {satisfactoscript-0.5.6 → satisfactoscript-0.5.7}/tests/test_config.py +0 -0
- {satisfactoscript-0.5.6 → satisfactoscript-0.5.7}/tests/test_core.py +0 -0
- {satisfactoscript-0.5.6 → satisfactoscript-0.5.7}/tests/test_core_env_detection.py +0 -0
- {satisfactoscript-0.5.6 → satisfactoscript-0.5.7}/tests/test_dummy.py +0 -0
- {satisfactoscript-0.5.6 → satisfactoscript-0.5.7}/tests/test_loaders.py +0 -0
- {satisfactoscript-0.5.6 → satisfactoscript-0.5.7}/tests/test_registry.py +0 -0
|
@@ -409,25 +409,44 @@ class SatisfactoEngine:
|
|
|
409
409
|
def _get_clean_username(self):
|
|
410
410
|
"""
|
|
411
411
|
Récupère le user courant (ex: 'julien_hou').
|
|
412
|
-
|
|
412
|
+
Three strategies in order:
|
|
413
|
+
1. Spark SQL current_user() — works natively on Databricks.
|
|
414
|
+
2. Databricks SDK REST API — works locally when gRPC DDL fails.
|
|
415
|
+
3. DBUtils notebook context tags — fallback for classic clusters.
|
|
416
|
+
|
|
413
417
|
Returns:
|
|
414
418
|
str: The cleaned username of the current execution context.
|
|
415
419
|
"""
|
|
420
|
+
def _clean(email):
|
|
421
|
+
return email.split('@')[0].replace('.', '_').replace('-', '_').lower()
|
|
422
|
+
|
|
423
|
+
# Strategy 1: Spark SQL (works natively on Databricks)
|
|
416
424
|
try:
|
|
417
|
-
# Priorité à Spark SQL (compatible VS Code & Notebooks)
|
|
418
425
|
rows = self.spark.sql("SELECT current_user()").collect()
|
|
419
|
-
if rows:
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
except:
|
|
426
|
+
if rows and rows[0][0]:
|
|
427
|
+
return _clean(rows[0][0])
|
|
428
|
+
except Exception:
|
|
423
429
|
pass
|
|
424
430
|
|
|
425
|
-
#
|
|
431
|
+
# Strategy 2: Databricks SDK REST API (works locally when gRPC fails)
|
|
432
|
+
try:
|
|
433
|
+
from databricks.sdk import WorkspaceClient
|
|
434
|
+
host = os.getenv("DATABRICKS_HOST")
|
|
435
|
+
token = os.getenv("DATABRICKS_TOKEN")
|
|
436
|
+
if host and token:
|
|
437
|
+
w = WorkspaceClient(host=host, token=token)
|
|
438
|
+
email = w.current_user.me().user_name
|
|
439
|
+
if email:
|
|
440
|
+
return _clean(email)
|
|
441
|
+
except Exception:
|
|
442
|
+
pass
|
|
443
|
+
|
|
444
|
+
# Strategy 3: DBUtils notebook context tags (classic clusters / notebooks)
|
|
426
445
|
try:
|
|
427
446
|
context = self.dbutils.notebook.entry_point.getDbutils().notebook().getContext()
|
|
428
447
|
email = context.tags().apply('user')
|
|
429
|
-
return email
|
|
430
|
-
except:
|
|
448
|
+
return _clean(email)
|
|
449
|
+
except Exception:
|
|
431
450
|
return "unknown_user"
|
|
432
451
|
|
|
433
452
|
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Tests for SatisfactoEngine._get_clean_username.
|
|
3
|
+
Covers: Spark SQL, Databricks SDK REST API, DBUtils fallback, unknown_user.
|
|
4
|
+
"""
|
|
5
|
+
import sys
|
|
6
|
+
import pytest
|
|
7
|
+
from unittest.mock import MagicMock, patch
|
|
8
|
+
from satisfactoscript.core.core import SatisfactoEngine
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _make_engine(spark=None, dbutils=None):
|
|
12
|
+
engine = object.__new__(SatisfactoEngine)
|
|
13
|
+
engine.spark = spark or MagicMock()
|
|
14
|
+
engine.dbutils = dbutils or MagicMock()
|
|
15
|
+
return engine
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class TestGetCleanUsername:
|
|
19
|
+
|
|
20
|
+
def test_returns_username_from_spark_sql(self):
|
|
21
|
+
spark = MagicMock()
|
|
22
|
+
spark.sql.return_value.collect.return_value = [("julien.houba@company.com",)]
|
|
23
|
+
engine = _make_engine(spark=spark)
|
|
24
|
+
|
|
25
|
+
assert engine._get_clean_username() == "julien_houba"
|
|
26
|
+
|
|
27
|
+
def test_cleans_dots_and_dashes(self):
|
|
28
|
+
spark = MagicMock()
|
|
29
|
+
spark.sql.return_value.collect.return_value = [("jean-pierre.dupont@company.com",)]
|
|
30
|
+
engine = _make_engine(spark=spark)
|
|
31
|
+
|
|
32
|
+
assert engine._get_clean_username() == "jean_pierre_dupont"
|
|
33
|
+
|
|
34
|
+
def test_falls_back_to_sdk_when_spark_sql_fails(self):
|
|
35
|
+
spark = MagicMock()
|
|
36
|
+
spark.sql.side_effect = Exception("gRPC: Missing UserContext")
|
|
37
|
+
engine = _make_engine(spark=spark)
|
|
38
|
+
|
|
39
|
+
mock_me = MagicMock()
|
|
40
|
+
mock_me.user_name = "hqhoujul@company.com"
|
|
41
|
+
mock_wc = MagicMock()
|
|
42
|
+
mock_wc.current_user.me.return_value = mock_me
|
|
43
|
+
|
|
44
|
+
fake_sdk = MagicMock()
|
|
45
|
+
fake_sdk.WorkspaceClient.return_value = mock_wc
|
|
46
|
+
sys.modules["databricks.sdk"] = fake_sdk
|
|
47
|
+
|
|
48
|
+
with patch.dict("os.environ", {"DATABRICKS_HOST": "https://adb.net", "DATABRICKS_TOKEN": "dapi123"}):
|
|
49
|
+
result = engine._get_clean_username()
|
|
50
|
+
|
|
51
|
+
assert result == "hqhoujul"
|
|
52
|
+
|
|
53
|
+
def test_falls_back_to_dbutils_when_sql_and_sdk_fail(self):
|
|
54
|
+
spark = MagicMock()
|
|
55
|
+
spark.sql.side_effect = Exception("SQL error")
|
|
56
|
+
engine = _make_engine(spark=spark)
|
|
57
|
+
|
|
58
|
+
fake_sdk = MagicMock()
|
|
59
|
+
fake_sdk.WorkspaceClient.side_effect = Exception("SDK error")
|
|
60
|
+
sys.modules["databricks.sdk"] = fake_sdk
|
|
61
|
+
|
|
62
|
+
context = MagicMock()
|
|
63
|
+
context.tags.return_value.apply.return_value = "fallback.user@company.com"
|
|
64
|
+
engine.dbutils.notebook.entry_point.getDbutils.return_value.notebook.return_value.getContext.return_value = context
|
|
65
|
+
|
|
66
|
+
with patch.dict("os.environ", {"DATABRICKS_HOST": "https://adb.net", "DATABRICKS_TOKEN": "dapi123"}):
|
|
67
|
+
result = engine._get_clean_username()
|
|
68
|
+
|
|
69
|
+
assert result == "fallback_user"
|
|
70
|
+
|
|
71
|
+
def test_returns_unknown_user_when_all_strategies_fail(self):
|
|
72
|
+
spark = MagicMock()
|
|
73
|
+
spark.sql.side_effect = Exception("SQL error")
|
|
74
|
+
engine = _make_engine(spark=spark)
|
|
75
|
+
|
|
76
|
+
fake_sdk = MagicMock()
|
|
77
|
+
fake_sdk.WorkspaceClient.side_effect = Exception("SDK error")
|
|
78
|
+
sys.modules["databricks.sdk"] = fake_sdk
|
|
79
|
+
|
|
80
|
+
engine.dbutils.notebook.entry_point.getDbutils.side_effect = Exception("no context")
|
|
81
|
+
|
|
82
|
+
with patch.dict("os.environ", {}, clear=True):
|
|
83
|
+
result = engine._get_clean_username()
|
|
84
|
+
|
|
85
|
+
assert result == "unknown_user"
|
|
86
|
+
|
|
87
|
+
def test_sdk_not_called_when_no_credentials(self):
|
|
88
|
+
spark = MagicMock()
|
|
89
|
+
spark.sql.side_effect = Exception("SQL error")
|
|
90
|
+
engine = _make_engine(spark=spark)
|
|
91
|
+
|
|
92
|
+
fake_sdk = MagicMock()
|
|
93
|
+
sys.modules["databricks.sdk"] = fake_sdk
|
|
94
|
+
|
|
95
|
+
engine.dbutils.notebook.entry_point.getDbutils.side_effect = Exception("no context")
|
|
96
|
+
|
|
97
|
+
with patch.dict("os.environ", {}, clear=True):
|
|
98
|
+
engine._get_clean_username()
|
|
99
|
+
|
|
100
|
+
# WorkspaceClient must not be called without credentials
|
|
101
|
+
fake_sdk.WorkspaceClient.assert_not_called()
|
|
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
|
{satisfactoscript-0.5.6 → satisfactoscript-0.5.7}/src/satisfactoscript.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{satisfactoscript-0.5.6 → satisfactoscript-0.5.7}/src/satisfactoscript.egg-info/requires.txt
RENAMED
|
File without changes
|
{satisfactoscript-0.5.6 → satisfactoscript-0.5.7}/src/satisfactoscript.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|