satisfactoscript 0.6.2__tar.gz → 0.6.5__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 (33) hide show
  1. {satisfactoscript-0.6.2 → satisfactoscript-0.6.5}/PKG-INFO +1 -1
  2. {satisfactoscript-0.6.2 → satisfactoscript-0.6.5}/pyproject.toml +1 -1
  3. {satisfactoscript-0.6.2 → satisfactoscript-0.6.5}/src/satisfactoscript/core/__init__.py +1 -0
  4. {satisfactoscript-0.6.2 → satisfactoscript-0.6.5}/src/satisfactoscript/core/core.py +37 -0
  5. {satisfactoscript-0.6.2 → satisfactoscript-0.6.5}/src/satisfactoscript.egg-info/PKG-INFO +1 -1
  6. {satisfactoscript-0.6.2 → satisfactoscript-0.6.5}/tests/test_core.py +10 -9
  7. satisfactoscript-0.6.5/tests/test_registry_import_paths.py +51 -0
  8. satisfactoscript-0.6.2/tests/test_registry_import_paths.py +0 -26
  9. {satisfactoscript-0.6.2 → satisfactoscript-0.6.5}/README.md +0 -0
  10. {satisfactoscript-0.6.2 → satisfactoscript-0.6.5}/setup.cfg +0 -0
  11. {satisfactoscript-0.6.2 → satisfactoscript-0.6.5}/src/satisfactoscript/__init__.py +0 -0
  12. {satisfactoscript-0.6.2 → satisfactoscript-0.6.5}/src/satisfactoscript/agentic/__init__.py +0 -0
  13. {satisfactoscript-0.6.2 → satisfactoscript-0.6.5}/src/satisfactoscript/agentic/agent.py +0 -0
  14. {satisfactoscript-0.6.2 → satisfactoscript-0.6.5}/src/satisfactoscript/core/config.py +0 -0
  15. {satisfactoscript-0.6.2 → satisfactoscript-0.6.5}/src/satisfactoscript/core/loaders.py +0 -0
  16. {satisfactoscript-0.6.2 → satisfactoscript-0.6.5}/src/satisfactoscript/core/registry.py +0 -0
  17. {satisfactoscript-0.6.2 → satisfactoscript-0.6.5}/src/satisfactoscript/registry.py +0 -0
  18. {satisfactoscript-0.6.2 → satisfactoscript-0.6.5}/src/satisfactoscript/semantic/__init__.py +0 -0
  19. {satisfactoscript-0.6.2 → satisfactoscript-0.6.5}/src/satisfactoscript/semantic/semantic.py +0 -0
  20. {satisfactoscript-0.6.2 → satisfactoscript-0.6.5}/src/satisfactoscript/utils.py +0 -0
  21. {satisfactoscript-0.6.2 → satisfactoscript-0.6.5}/src/satisfactoscript.egg-info/SOURCES.txt +0 -0
  22. {satisfactoscript-0.6.2 → satisfactoscript-0.6.5}/src/satisfactoscript.egg-info/dependency_links.txt +0 -0
  23. {satisfactoscript-0.6.2 → satisfactoscript-0.6.5}/src/satisfactoscript.egg-info/requires.txt +0 -0
  24. {satisfactoscript-0.6.2 → satisfactoscript-0.6.5}/src/satisfactoscript.egg-info/top_level.txt +0 -0
  25. {satisfactoscript-0.6.2 → satisfactoscript-0.6.5}/tests/test_config.py +0 -0
  26. {satisfactoscript-0.6.2 → satisfactoscript-0.6.5}/tests/test_core_connect_patch.py +0 -0
  27. {satisfactoscript-0.6.2 → satisfactoscript-0.6.5}/tests/test_core_env_detection.py +0 -0
  28. {satisfactoscript-0.6.2 → satisfactoscript-0.6.5}/tests/test_core_join.py +0 -0
  29. {satisfactoscript-0.6.2 → satisfactoscript-0.6.5}/tests/test_core_username.py +0 -0
  30. {satisfactoscript-0.6.2 → satisfactoscript-0.6.5}/tests/test_dummy.py +0 -0
  31. {satisfactoscript-0.6.2 → satisfactoscript-0.6.5}/tests/test_loaders.py +0 -0
  32. {satisfactoscript-0.6.2 → satisfactoscript-0.6.5}/tests/test_registry.py +0 -0
  33. {satisfactoscript-0.6.2 → satisfactoscript-0.6.5}/tests/test_utils_safe_columns.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: satisfactoscript
3
- Version: 0.6.2
3
+ Version: 0.6.5
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.6.2"
7
+ version = "0.6.5"
8
8
  description = "An Enterprise-Ready, Declarative Data Engineering Framework for Databricks Lakehouse."
9
9
  readme = "README.md"
10
10
  authors = [
@@ -1,5 +1,6 @@
1
1
  from .core import SatisfactoEngine
2
2
  from .registry import RuleRegistry
3
3
  from .config import ConfigurationManager
4
+ from . import loaders # noqa: F401 — triggers loader registration via decorators
4
5
 
5
6
  __all__ = ["SatisfactoEngine", "RuleRegistry", "ConfigurationManager"]
@@ -210,6 +210,7 @@ class SatisfactoEngine:
210
210
  raise RuntimeError("CRITICAL: Failed to initialize Spark Session.")
211
211
 
212
212
  self._patch_connect_debugging()
213
+ self._patch_connect_user_context()
213
214
  self.dbutils = DBUtils(self.spark)
214
215
 
215
216
  # ======================================================================
@@ -279,6 +280,42 @@ class SatisfactoEngine:
279
280
  except Exception:
280
281
  pass
281
282
 
283
+ def _patch_connect_user_context(self):
284
+ """
285
+ Patches SparkConnectClient._user_id when it is empty or missing.
286
+
287
+ Root cause: on Databricks Connect v2 from local environments (Windows/PyCharm),
288
+ the gRPC session is created successfully but _user_id is not populated from the
289
+ SDK config. PySpark's execute_command only injects user_context.user_id when
290
+ self._user_id is truthy — so every gRPC execution call (saveAsTable, collect,
291
+ execute_command for DDL) fails immediately with:
292
+ 'Missing required field UserContext'
293
+
294
+ Fix: after session creation, if _user_id is absent on the SparkConnectClient,
295
+ fetch the current user's email via the Databricks SDK REST API (which is not
296
+ affected by the gRPC issue) and inject it directly into the client object.
297
+ This is a one-time init patch with no functional side effects.
298
+ """
299
+ try:
300
+ client = getattr(self.spark, 'client', None)
301
+ if client is None:
302
+ return # Standard SparkSession (not a Connect session) — nothing to patch
303
+
304
+ if getattr(client, '_user_id', None):
305
+ return # Already properly set — no patch needed
306
+
307
+ from databricks.sdk import WorkspaceClient
308
+ host = os.getenv("DATABRICKS_HOST")
309
+ token = os.getenv("DATABRICKS_TOKEN")
310
+ if host and token:
311
+ w = WorkspaceClient(host=host, token=token)
312
+ email = w.current_user.me().user_name
313
+ if email:
314
+ client._user_id = email
315
+ print(f" [Init] ⚙️ Applied UserContext patch: user_id injected ('{email}').")
316
+ except Exception:
317
+ pass
318
+
282
319
  def _find_file_upwards(self, filename):
283
320
  """
284
321
  Searches for a file starting from current working directory and moving up.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: satisfactoscript
3
- Version: 0.6.2
3
+ Version: 0.6.5
4
4
  Summary: An Enterprise-Ready, Declarative Data Engineering Framework for Databricks Lakehouse.
5
5
  Author: julhouba
6
6
  Classifier: Programming Language :: Python :: 3
@@ -250,9 +250,9 @@ def test_process_schema_raises_error_on_unknown_rule(mock_engine):
250
250
  mock_engine.spark.catalog.dropTempView("some_table")
251
251
 
252
252
 
253
- def test_run_split_to_org(mock_engine, mocker, spark):
253
+ def test_run_process_and_split(mock_engine, mocker, spark):
254
254
  """
255
- Tests the run_split_to_org runner. It spies on the _write_dataframe method
255
+ Tests the run_process_and_split runner. It spies on the _write_dataframe method
256
256
  to ensure it's called correctly without actually writing any data.
257
257
  """
258
258
  # 0. Create the schema required for the test before running the engine
@@ -271,19 +271,20 @@ def test_run_split_to_org(mock_engine, mocker, spark):
271
271
  source_df = spark.createDataFrame(source_data, ["id", "sales_org_code"])
272
272
  source_df.createOrReplaceTempView("source_for_split")
273
273
 
274
- # 3. Define schema and org list
274
+ # 3. Define schema and split values
275
275
  schema_dict = {"tables": [{"name": "source_for_split", "alias": "t"}]}
276
- org_list = [
277
- {"label": "france", "org_code": "FR"},
278
- {"label": "germany", "org_code": "DE"}
276
+ split_values = [
277
+ {"label": "france", "value": "FR"},
278
+ {"label": "germany", "value": "DE"}
279
279
  ]
280
280
 
281
281
  # 4. Execute the runner
282
- mock_engine.run_split_to_org(
282
+ mock_engine.run_process_and_split(
283
283
  schema_dict=schema_dict,
284
- org_list=org_list,
284
+ split_values=split_values,
285
285
  target_layer="gold",
286
- target_base_name="sales"
286
+ target_base_name="sales",
287
+ split_column="sales_org_code"
287
288
  )
288
289
 
289
290
  # 5. Assertions on the spy
@@ -0,0 +1,51 @@
1
+ """
2
+ Tests for RuleRegistry import path compatibility and core __init__ exports.
3
+ All import paths must resolve to the same singleton class.
4
+ """
5
+ from satisfactoscript.core.registry import RuleRegistry as RR_core
6
+ from satisfactoscript.registry import RuleRegistry as RR_shim
7
+ from satisfactoscript import RuleRegistry as RR_top
8
+
9
+
10
+ def test_all_import_paths_resolve_to_same_class():
11
+ """All three import paths must return the exact same class (same singleton)."""
12
+ assert RR_core is RR_shim
13
+ assert RR_core is RR_top
14
+
15
+
16
+ def test_rule_registered_via_shim_is_visible_everywhere():
17
+ """A rule registered using the shim import must be retrievable from all paths."""
18
+ @RR_shim.register_rule(name="__test_shim_rule__")
19
+ def _my_rule(df):
20
+ return df
21
+
22
+ assert RR_core.get_rule("__test_shim_rule__") is _my_rule
23
+ assert RR_top.get_rule("__test_shim_rule__") is _my_rule
24
+
25
+ # Cleanup
26
+ RR_core._rules.pop("__test_shim_rule__", None)
27
+
28
+
29
+ # ==============================================================================
30
+ # TESTS FOR core/__init__.py EXPORTS AND LOADER REGISTRATION
31
+ # ==============================================================================
32
+
33
+ def test_core_init_exports_public_api():
34
+ """Importing satisfactoscript.core must expose SatisfactoEngine, RuleRegistry, ConfigurationManager."""
35
+ import satisfactoscript.core as core_pkg
36
+
37
+ assert hasattr(core_pkg, "SatisfactoEngine"), "SatisfactoEngine missing from satisfactoscript.core"
38
+ assert hasattr(core_pkg, "RuleRegistry"), "RuleRegistry missing from satisfactoscript.core"
39
+ assert hasattr(core_pkg, "ConfigurationManager"), "ConfigurationManager missing from satisfactoscript.core"
40
+
41
+
42
+ def test_core_init_triggers_loader_registration():
43
+ """Importing satisfactoscript.core must register built-in loaders via the loaders side-effect import."""
44
+ import satisfactoscript.core # noqa: F401 — ensures the __init__ has been executed
45
+
46
+ assert "load_and_union_tables" in RR_core._loaders, (
47
+ "load_and_union_tables not registered — loaders side-effect import may be broken"
48
+ )
49
+ assert "load_generic_explode_union" in RR_core._loaders, (
50
+ "load_generic_explode_union not registered — loaders side-effect import may be broken"
51
+ )
@@ -1,26 +0,0 @@
1
- """
2
- Tests for RuleRegistry import path compatibility.
3
- All import paths must resolve to the same singleton class.
4
- """
5
- from satisfactoscript.core.registry import RuleRegistry as RR_core
6
- from satisfactoscript.registry import RuleRegistry as RR_shim
7
- from satisfactoscript import RuleRegistry as RR_top
8
-
9
-
10
- def test_all_import_paths_resolve_to_same_class():
11
- """All three import paths must return the exact same class (same singleton)."""
12
- assert RR_core is RR_shim
13
- assert RR_core is RR_top
14
-
15
-
16
- def test_rule_registered_via_shim_is_visible_everywhere():
17
- """A rule registered using the shim import must be retrievable from all paths."""
18
- @RR_shim.register_rule(name="__test_shim_rule__")
19
- def _my_rule(df):
20
- return df
21
-
22
- assert RR_core.get_rule("__test_shim_rule__") is _my_rule
23
- assert RR_top.get_rule("__test_shim_rule__") is _my_rule
24
-
25
- # Cleanup
26
- RR_core._rules.pop("__test_shim_rule__", None)