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.
Files changed (26) hide show
  1. {satisfactoscript-0.5.6 → satisfactoscript-0.5.7}/PKG-INFO +1 -1
  2. {satisfactoscript-0.5.6 → satisfactoscript-0.5.7}/pyproject.toml +1 -1
  3. {satisfactoscript-0.5.6 → satisfactoscript-0.5.7}/src/satisfactoscript/core/core.py +28 -9
  4. {satisfactoscript-0.5.6 → satisfactoscript-0.5.7}/src/satisfactoscript.egg-info/PKG-INFO +1 -1
  5. {satisfactoscript-0.5.6 → satisfactoscript-0.5.7}/src/satisfactoscript.egg-info/SOURCES.txt +1 -0
  6. satisfactoscript-0.5.7/tests/test_core_username.py +101 -0
  7. {satisfactoscript-0.5.6 → satisfactoscript-0.5.7}/README.md +0 -0
  8. {satisfactoscript-0.5.6 → satisfactoscript-0.5.7}/setup.cfg +0 -0
  9. {satisfactoscript-0.5.6 → satisfactoscript-0.5.7}/src/satisfactoscript/__init__.py +0 -0
  10. {satisfactoscript-0.5.6 → satisfactoscript-0.5.7}/src/satisfactoscript/agentic/__init__.py +0 -0
  11. {satisfactoscript-0.5.6 → satisfactoscript-0.5.7}/src/satisfactoscript/agentic/agent.py +0 -0
  12. {satisfactoscript-0.5.6 → satisfactoscript-0.5.7}/src/satisfactoscript/core/__init__.py +0 -0
  13. {satisfactoscript-0.5.6 → satisfactoscript-0.5.7}/src/satisfactoscript/core/config.py +0 -0
  14. {satisfactoscript-0.5.6 → satisfactoscript-0.5.7}/src/satisfactoscript/core/loaders.py +0 -0
  15. {satisfactoscript-0.5.6 → satisfactoscript-0.5.7}/src/satisfactoscript/core/registry.py +0 -0
  16. {satisfactoscript-0.5.6 → satisfactoscript-0.5.7}/src/satisfactoscript/semantic/__init__.py +0 -0
  17. {satisfactoscript-0.5.6 → satisfactoscript-0.5.7}/src/satisfactoscript/semantic/semantic.py +0 -0
  18. {satisfactoscript-0.5.6 → satisfactoscript-0.5.7}/src/satisfactoscript.egg-info/dependency_links.txt +0 -0
  19. {satisfactoscript-0.5.6 → satisfactoscript-0.5.7}/src/satisfactoscript.egg-info/requires.txt +0 -0
  20. {satisfactoscript-0.5.6 → satisfactoscript-0.5.7}/src/satisfactoscript.egg-info/top_level.txt +0 -0
  21. {satisfactoscript-0.5.6 → satisfactoscript-0.5.7}/tests/test_config.py +0 -0
  22. {satisfactoscript-0.5.6 → satisfactoscript-0.5.7}/tests/test_core.py +0 -0
  23. {satisfactoscript-0.5.6 → satisfactoscript-0.5.7}/tests/test_core_env_detection.py +0 -0
  24. {satisfactoscript-0.5.6 → satisfactoscript-0.5.7}/tests/test_dummy.py +0 -0
  25. {satisfactoscript-0.5.6 → satisfactoscript-0.5.7}/tests/test_loaders.py +0 -0
  26. {satisfactoscript-0.5.6 → satisfactoscript-0.5.7}/tests/test_registry.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: satisfactoscript
3
- Version: 0.5.6
3
+ Version: 0.5.7
4
4
  Summary: An Enterprise-Ready, Declarative Data Engineering Framework for Databricks Lakehouse.
5
5
  Author: julhouba
6
6
  Classifier: Programming Language :: Python :: 3
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "satisfactoscript"
7
- version = "0.5.6"
7
+ version = "0.5.7"
8
8
  description = "An Enterprise-Ready, Declarative Data Engineering Framework for Databricks Lakehouse."
9
9
  readme = "README.md"
10
10
  authors = [
@@ -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
- email = rows[0][0]
421
- return email.split('@')[0].replace('.', '_').replace('-', '_').lower()
422
- except:
426
+ if rows and rows[0][0]:
427
+ return _clean(rows[0][0])
428
+ except Exception:
423
429
  pass
424
430
 
425
- # Fallback Tags
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.split('@')[0].replace('.', '_').replace('-', '_').lower()
430
- except:
448
+ return _clean(email)
449
+ except Exception:
431
450
  return "unknown_user"
432
451
 
433
452
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: satisfactoscript
3
- Version: 0.5.6
3
+ Version: 0.5.7
4
4
  Summary: An Enterprise-Ready, Declarative Data Engineering Framework for Databricks Lakehouse.
5
5
  Author: julhouba
6
6
  Classifier: Programming Language :: Python :: 3
@@ -18,6 +18,7 @@ src/satisfactoscript/semantic/semantic.py
18
18
  tests/test_config.py
19
19
  tests/test_core.py
20
20
  tests/test_core_env_detection.py
21
+ tests/test_core_username.py
21
22
  tests/test_dummy.py
22
23
  tests/test_loaders.py
23
24
  tests/test_registry.py
@@ -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()