sibi-flux 2026.1.7__tar.gz → 2026.1.9__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 (128) hide show
  1. {sibi_flux-2026.1.7 → sibi_flux-2026.1.9}/PKG-INFO +1 -1
  2. {sibi_flux-2026.1.7 → sibi_flux-2026.1.9}/pyproject.toml +2 -1
  3. {sibi_flux-2026.1.7 → sibi_flux-2026.1.9}/src/sibi_flux/datacube/cli.py +173 -8
  4. {sibi_flux-2026.1.7 → sibi_flux-2026.1.9}/src/sibi_flux/datacube/generator.py +26 -0
  5. {sibi_flux-2026.1.7 → sibi_flux-2026.1.9}/src/sibi_flux/init/cube_proposer.py +7 -3
  6. {sibi_flux-2026.1.7 → sibi_flux-2026.1.9}/README.md +0 -0
  7. {sibi_flux-2026.1.7 → sibi_flux-2026.1.9}/src/sibi_dst/__init__.py +0 -0
  8. {sibi_flux-2026.1.7 → sibi_flux-2026.1.9}/src/sibi_flux/__init__.py +0 -0
  9. {sibi_flux-2026.1.7 → sibi_flux-2026.1.9}/src/sibi_flux/artifacts/__init__.py +0 -0
  10. {sibi_flux-2026.1.7 → sibi_flux-2026.1.9}/src/sibi_flux/artifacts/base.py +0 -0
  11. {sibi_flux-2026.1.7 → sibi_flux-2026.1.9}/src/sibi_flux/artifacts/parquet.py +0 -0
  12. {sibi_flux-2026.1.7 → sibi_flux-2026.1.9}/src/sibi_flux/artifacts/parquet_engine/__init__.py +0 -0
  13. {sibi_flux-2026.1.7 → sibi_flux-2026.1.9}/src/sibi_flux/artifacts/parquet_engine/executor.py +0 -0
  14. {sibi_flux-2026.1.7 → sibi_flux-2026.1.9}/src/sibi_flux/artifacts/parquet_engine/manifest.py +0 -0
  15. {sibi_flux-2026.1.7 → sibi_flux-2026.1.9}/src/sibi_flux/artifacts/parquet_engine/planner.py +0 -0
  16. {sibi_flux-2026.1.7 → sibi_flux-2026.1.9}/src/sibi_flux/cli.py +0 -0
  17. {sibi_flux-2026.1.7 → sibi_flux-2026.1.9}/src/sibi_flux/config/__init__.py +0 -0
  18. {sibi_flux-2026.1.7 → sibi_flux-2026.1.9}/src/sibi_flux/config/manager.py +0 -0
  19. {sibi_flux-2026.1.7 → sibi_flux-2026.1.9}/src/sibi_flux/config/settings.py +0 -0
  20. {sibi_flux-2026.1.7 → sibi_flux-2026.1.9}/src/sibi_flux/core/__init__.py +0 -0
  21. {sibi_flux-2026.1.7 → sibi_flux-2026.1.9}/src/sibi_flux/core/managed_resource/__init__.py +0 -0
  22. {sibi_flux-2026.1.7 → sibi_flux-2026.1.9}/src/sibi_flux/core/managed_resource/_managed_resource.py +0 -0
  23. {sibi_flux-2026.1.7 → sibi_flux-2026.1.9}/src/sibi_flux/core/type_maps/__init__.py +0 -0
  24. {sibi_flux-2026.1.7 → sibi_flux-2026.1.9}/src/sibi_flux/dask_cluster/__init__.py +0 -0
  25. {sibi_flux-2026.1.7 → sibi_flux-2026.1.9}/src/sibi_flux/dask_cluster/async_core.py +0 -0
  26. {sibi_flux-2026.1.7 → sibi_flux-2026.1.9}/src/sibi_flux/dask_cluster/client_manager.py +0 -0
  27. {sibi_flux-2026.1.7 → sibi_flux-2026.1.9}/src/sibi_flux/dask_cluster/core.py +0 -0
  28. {sibi_flux-2026.1.7 → sibi_flux-2026.1.9}/src/sibi_flux/dask_cluster/exceptions.py +0 -0
  29. {sibi_flux-2026.1.7 → sibi_flux-2026.1.9}/src/sibi_flux/dask_cluster/utils.py +0 -0
  30. {sibi_flux-2026.1.7 → sibi_flux-2026.1.9}/src/sibi_flux/datacube/__init__.py +0 -0
  31. {sibi_flux-2026.1.7 → sibi_flux-2026.1.9}/src/sibi_flux/datacube/_data_cube.py +0 -0
  32. {sibi_flux-2026.1.7 → sibi_flux-2026.1.9}/src/sibi_flux/datacube/config_engine.py +0 -0
  33. {sibi_flux-2026.1.7 → sibi_flux-2026.1.9}/src/sibi_flux/datacube/field_factory.py +0 -0
  34. {sibi_flux-2026.1.7 → sibi_flux-2026.1.9}/src/sibi_flux/datacube/field_mapper.py +0 -0
  35. {sibi_flux-2026.1.7 → sibi_flux-2026.1.9}/src/sibi_flux/datacube/field_registry.py +0 -0
  36. {sibi_flux-2026.1.7 → sibi_flux-2026.1.9}/src/sibi_flux/datacube/orchestrator.py +0 -0
  37. {sibi_flux-2026.1.7 → sibi_flux-2026.1.9}/src/sibi_flux/datacube/router.py +0 -0
  38. {sibi_flux-2026.1.7 → sibi_flux-2026.1.9}/src/sibi_flux/dataset/__init__.py +0 -0
  39. {sibi_flux-2026.1.7 → sibi_flux-2026.1.9}/src/sibi_flux/dataset/_dataset.py +0 -0
  40. {sibi_flux-2026.1.7 → sibi_flux-2026.1.9}/src/sibi_flux/dataset/hybrid_loader.py +0 -0
  41. {sibi_flux-2026.1.7 → sibi_flux-2026.1.9}/src/sibi_flux/df_enricher/__init__.py +0 -0
  42. {sibi_flux-2026.1.7 → sibi_flux-2026.1.9}/src/sibi_flux/df_enricher/async_enricher.py +0 -0
  43. {sibi_flux-2026.1.7 → sibi_flux-2026.1.9}/src/sibi_flux/df_enricher/attacher.py +0 -0
  44. {sibi_flux-2026.1.7 → sibi_flux-2026.1.9}/src/sibi_flux/df_enricher/merger.py +0 -0
  45. {sibi_flux-2026.1.7 → sibi_flux-2026.1.9}/src/sibi_flux/df_enricher/specs.py +0 -0
  46. {sibi_flux-2026.1.7 → sibi_flux-2026.1.9}/src/sibi_flux/df_enricher/types.py +0 -0
  47. {sibi_flux-2026.1.7 → sibi_flux-2026.1.9}/src/sibi_flux/df_helper/__init__.py +0 -0
  48. {sibi_flux-2026.1.7 → sibi_flux-2026.1.9}/src/sibi_flux/df_helper/_df_helper.py +0 -0
  49. {sibi_flux-2026.1.7 → sibi_flux-2026.1.9}/src/sibi_flux/df_helper/backends/__init__.py +0 -0
  50. {sibi_flux-2026.1.7 → sibi_flux-2026.1.9}/src/sibi_flux/df_helper/backends/_params.py +0 -0
  51. {sibi_flux-2026.1.7 → sibi_flux-2026.1.9}/src/sibi_flux/df_helper/backends/_strategies.py +0 -0
  52. {sibi_flux-2026.1.7 → sibi_flux-2026.1.9}/src/sibi_flux/df_helper/backends/http/__init__.py +0 -0
  53. {sibi_flux-2026.1.7 → sibi_flux-2026.1.9}/src/sibi_flux/df_helper/backends/http/_http_config.py +0 -0
  54. {sibi_flux-2026.1.7 → sibi_flux-2026.1.9}/src/sibi_flux/df_helper/backends/parquet/__init__.py +0 -0
  55. {sibi_flux-2026.1.7 → sibi_flux-2026.1.9}/src/sibi_flux/df_helper/backends/parquet/_parquet_options.py +0 -0
  56. {sibi_flux-2026.1.7 → sibi_flux-2026.1.9}/src/sibi_flux/df_helper/backends/sqlalchemy/__init__.py +0 -0
  57. {sibi_flux-2026.1.7 → sibi_flux-2026.1.9}/src/sibi_flux/df_helper/backends/sqlalchemy/_db_connection.py +0 -0
  58. {sibi_flux-2026.1.7 → sibi_flux-2026.1.9}/src/sibi_flux/df_helper/backends/sqlalchemy/_db_gatekeeper.py +0 -0
  59. {sibi_flux-2026.1.7 → sibi_flux-2026.1.9}/src/sibi_flux/df_helper/backends/sqlalchemy/_io_dask.py +0 -0
  60. {sibi_flux-2026.1.7 → sibi_flux-2026.1.9}/src/sibi_flux/df_helper/backends/sqlalchemy/_load_from_db.py +0 -0
  61. {sibi_flux-2026.1.7 → sibi_flux-2026.1.9}/src/sibi_flux/df_helper/backends/sqlalchemy/_model_registry.py +0 -0
  62. {sibi_flux-2026.1.7 → sibi_flux-2026.1.9}/src/sibi_flux/df_helper/backends/sqlalchemy/_sql_model_builder.py +0 -0
  63. {sibi_flux-2026.1.7 → sibi_flux-2026.1.9}/src/sibi_flux/df_helper/backends/utils.py +0 -0
  64. {sibi_flux-2026.1.7 → sibi_flux-2026.1.9}/src/sibi_flux/df_helper/core/__init__.py +0 -0
  65. {sibi_flux-2026.1.7 → sibi_flux-2026.1.9}/src/sibi_flux/df_helper/core/_defaults.py +0 -0
  66. {sibi_flux-2026.1.7 → sibi_flux-2026.1.9}/src/sibi_flux/df_helper/core/_filter_handler.py +0 -0
  67. {sibi_flux-2026.1.7 → sibi_flux-2026.1.9}/src/sibi_flux/df_helper/core/_params_config.py +0 -0
  68. {sibi_flux-2026.1.7 → sibi_flux-2026.1.9}/src/sibi_flux/df_helper/core/_query_config.py +0 -0
  69. {sibi_flux-2026.1.7 → sibi_flux-2026.1.9}/src/sibi_flux/df_validator/__init__.py +0 -0
  70. {sibi_flux-2026.1.7 → sibi_flux-2026.1.9}/src/sibi_flux/df_validator/_df_validator.py +0 -0
  71. {sibi_flux-2026.1.7 → sibi_flux-2026.1.9}/src/sibi_flux/init/__init__.py +0 -0
  72. {sibi_flux-2026.1.7 → sibi_flux-2026.1.9}/src/sibi_flux/init/app.py +0 -0
  73. {sibi_flux-2026.1.7 → sibi_flux-2026.1.9}/src/sibi_flux/init/core.py +0 -0
  74. {sibi_flux-2026.1.7 → sibi_flux-2026.1.9}/src/sibi_flux/init/cube_extender.py +0 -0
  75. {sibi_flux-2026.1.7 → sibi_flux-2026.1.9}/src/sibi_flux/init/discovery_updater.py +0 -0
  76. {sibi_flux-2026.1.7 → sibi_flux-2026.1.9}/src/sibi_flux/init/env.py +0 -0
  77. {sibi_flux-2026.1.7 → sibi_flux-2026.1.9}/src/sibi_flux/init/env_engine.py +0 -0
  78. {sibi_flux-2026.1.7 → sibi_flux-2026.1.9}/src/sibi_flux/init/env_generator.py +0 -0
  79. {sibi_flux-2026.1.7 → sibi_flux-2026.1.9}/src/sibi_flux/init/rule_generator.py +0 -0
  80. {sibi_flux-2026.1.7 → sibi_flux-2026.1.9}/src/sibi_flux/init/templates/__init__.py +0 -0
  81. {sibi_flux-2026.1.7 → sibi_flux-2026.1.9}/src/sibi_flux/init/templates/discovery_params.yaml +0 -0
  82. {sibi_flux-2026.1.7 → sibi_flux-2026.1.9}/src/sibi_flux/init/templates/gen_dc.py +0 -0
  83. {sibi_flux-2026.1.7 → sibi_flux-2026.1.9}/src/sibi_flux/init/templates/property_template.yaml +0 -0
  84. {sibi_flux-2026.1.7 → sibi_flux-2026.1.9}/src/sibi_flux/logger/__init__.py +0 -0
  85. {sibi_flux-2026.1.7 → sibi_flux-2026.1.9}/src/sibi_flux/logger/_logger.py +0 -0
  86. {sibi_flux-2026.1.7 → sibi_flux-2026.1.9}/src/sibi_flux/mcp/__init__.py +0 -0
  87. {sibi_flux-2026.1.7 → sibi_flux-2026.1.9}/src/sibi_flux/mcp/client.py +0 -0
  88. {sibi_flux-2026.1.7 → sibi_flux-2026.1.9}/src/sibi_flux/mcp/router.py +0 -0
  89. {sibi_flux-2026.1.7 → sibi_flux-2026.1.9}/src/sibi_flux/orchestration/__init__.py +0 -0
  90. {sibi_flux-2026.1.7 → sibi_flux-2026.1.9}/src/sibi_flux/orchestration/_artifact_orchestrator.py +0 -0
  91. {sibi_flux-2026.1.7 → sibi_flux-2026.1.9}/src/sibi_flux/orchestration/_pipeline_executor.py +0 -0
  92. {sibi_flux-2026.1.7 → sibi_flux-2026.1.9}/src/sibi_flux/osmnx_helper/__init__.py +0 -0
  93. {sibi_flux-2026.1.7 → sibi_flux-2026.1.9}/src/sibi_flux/osmnx_helper/_pbf_handler.py +0 -0
  94. {sibi_flux-2026.1.7 → sibi_flux-2026.1.9}/src/sibi_flux/osmnx_helper/graph_loader.py +0 -0
  95. {sibi_flux-2026.1.7 → sibi_flux-2026.1.9}/src/sibi_flux/osmnx_helper/utils.py +0 -0
  96. {sibi_flux-2026.1.7 → sibi_flux-2026.1.9}/src/sibi_flux/parquet/__init__.py +0 -0
  97. {sibi_flux-2026.1.7 → sibi_flux-2026.1.9}/src/sibi_flux/parquet/readers/__init__.py +0 -0
  98. {sibi_flux-2026.1.7 → sibi_flux-2026.1.9}/src/sibi_flux/parquet/readers/base.py +0 -0
  99. {sibi_flux-2026.1.7 → sibi_flux-2026.1.9}/src/sibi_flux/parquet/readers/parquet.py +0 -0
  100. {sibi_flux-2026.1.7 → sibi_flux-2026.1.9}/src/sibi_flux/parquet/saver/__init__.py +0 -0
  101. {sibi_flux-2026.1.7 → sibi_flux-2026.1.9}/src/sibi_flux/parquet/saver/_parquet_saver.py +0 -0
  102. {sibi_flux-2026.1.7 → sibi_flux-2026.1.9}/src/sibi_flux/parquet/saver/_write_gatekeeper.py +0 -0
  103. {sibi_flux-2026.1.7 → sibi_flux-2026.1.9}/src/sibi_flux/pipelines/__init__.py +0 -0
  104. {sibi_flux-2026.1.7 → sibi_flux-2026.1.9}/src/sibi_flux/pipelines/base.py +0 -0
  105. {sibi_flux-2026.1.7 → sibi_flux-2026.1.9}/src/sibi_flux/pipelines/template.py +0 -0
  106. {sibi_flux-2026.1.7 → sibi_flux-2026.1.9}/src/sibi_flux/py.typed +0 -0
  107. {sibi_flux-2026.1.7 → sibi_flux-2026.1.9}/src/sibi_flux/readers/__init__.py +0 -0
  108. {sibi_flux-2026.1.7 → sibi_flux-2026.1.9}/src/sibi_flux/readers/base.py +0 -0
  109. {sibi_flux-2026.1.7 → sibi_flux-2026.1.9}/src/sibi_flux/storage/__init__.py +0 -0
  110. {sibi_flux-2026.1.7 → sibi_flux-2026.1.9}/src/sibi_flux/storage/_fs_registry.py +0 -0
  111. {sibi_flux-2026.1.7 → sibi_flux-2026.1.9}/src/sibi_flux/storage/_storage_manager.py +0 -0
  112. {sibi_flux-2026.1.7 → sibi_flux-2026.1.9}/src/sibi_flux/storage/factory.py +0 -0
  113. {sibi_flux-2026.1.7 → sibi_flux-2026.1.9}/src/sibi_flux/utils/__init__.py +0 -0
  114. {sibi_flux-2026.1.7 → sibi_flux-2026.1.9}/src/sibi_flux/utils/clickhouse_writer/__init__.py +0 -0
  115. {sibi_flux-2026.1.7 → sibi_flux-2026.1.9}/src/sibi_flux/utils/clickhouse_writer/_clickhouse_writer.py +0 -0
  116. {sibi_flux-2026.1.7 → sibi_flux-2026.1.9}/src/sibi_flux/utils/common.py +0 -0
  117. {sibi_flux-2026.1.7 → sibi_flux-2026.1.9}/src/sibi_flux/utils/dask_utils.py +0 -0
  118. {sibi_flux-2026.1.7 → sibi_flux-2026.1.9}/src/sibi_flux/utils/data_utils/__init__.py +0 -0
  119. {sibi_flux-2026.1.7 → sibi_flux-2026.1.9}/src/sibi_flux/utils/data_utils/_data_utils.py +0 -0
  120. {sibi_flux-2026.1.7 → sibi_flux-2026.1.9}/src/sibi_flux/utils/dataframe_utils.py +0 -0
  121. {sibi_flux-2026.1.7 → sibi_flux-2026.1.9}/src/sibi_flux/utils/date_utils/__init__.py +0 -0
  122. {sibi_flux-2026.1.7 → sibi_flux-2026.1.9}/src/sibi_flux/utils/date_utils/_business_days.py +0 -0
  123. {sibi_flux-2026.1.7 → sibi_flux-2026.1.9}/src/sibi_flux/utils/date_utils/_date_utils.py +0 -0
  124. {sibi_flux-2026.1.7 → sibi_flux-2026.1.9}/src/sibi_flux/utils/date_utils/_file_age_checker.py +0 -0
  125. {sibi_flux-2026.1.7 → sibi_flux-2026.1.9}/src/sibi_flux/utils/file_utils.py +0 -0
  126. {sibi_flux-2026.1.7 → sibi_flux-2026.1.9}/src/sibi_flux/utils/filepath_generator/__init__.py +0 -0
  127. {sibi_flux-2026.1.7 → sibi_flux-2026.1.9}/src/sibi_flux/utils/filepath_generator/_filepath_generator.py +0 -0
  128. {sibi_flux-2026.1.7 → sibi_flux-2026.1.9}/src/sibi_flux/utils/retry.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: sibi-flux
3
- Version: 2026.1.7
3
+ Version: 2026.1.9
4
4
  Summary: Sibi Toolkit: A collection of tools for Data Analysis/Engineering.
5
5
  Author: Luis Valverde
6
6
  Author-email: Luis Valverde <lvalverdeb@gmail.com>
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "sibi-flux"
3
- version = "2026.1.7"
3
+ version = "2026.1.9"
4
4
  description = "Sibi Toolkit: A collection of tools for Data Analysis/Engineering."
5
5
  readme = "README.md"
6
6
  authors = [
@@ -97,6 +97,7 @@ members = [
97
97
  "test-prj",
98
98
  "test_prj",
99
99
  "test_prj_verify",
100
+ "clean-test-prj",
100
101
  ]
101
102
 
102
103
  [tool.pytest.ini_options]
@@ -203,7 +203,7 @@ def set_context_defaults(
203
203
 
204
204
 
205
205
  def _get_db_url_callback(
206
- registry: DatacubeRegistry, db_url_map: Optional[str]
206
+ registry: DatacubeRegistry, db_url_map: Optional[str], params: Optional[Dict[str, Any]] = None
207
207
  ) -> Callable[[str], str]:
208
208
  """Helper to create a callback that resolves DB URLs from CLI overrides or registry."""
209
209
  cli_urls = json.loads(db_url_map) if db_url_map else {}
@@ -212,8 +212,24 @@ def _get_db_url_callback(
212
212
  # 1. CLI Override
213
213
  if conf_name in cli_urls:
214
214
  return cli_urls[conf_name]
215
+
215
216
  # 2. Dynamic Resolution
216
- url = resolve_db_url(conf_name, registry.global_imports)
217
+ imports = registry.global_imports
218
+
219
+ # Check specific import_spec from params
220
+ if params and "databases" in params:
221
+ for db in params.get("databases", []):
222
+ ref = db.get("connection_ref") or db.get("connection_obj")
223
+ if ref == conf_name:
224
+ spec = db.get("import_spec")
225
+ if spec and isinstance(spec, dict):
226
+ # Prepend specific module
227
+ imports = [spec.get("module")] + imports
228
+ elif db.get("global_import"):
229
+ imports = [db.get("global_import")] + imports
230
+ break
231
+
232
+ url = resolve_db_url(conf_name, imports)
217
233
  if url:
218
234
  return url
219
235
  raise ValueError(
@@ -283,9 +299,16 @@ def sync(
283
299
 
284
300
  flat_tables = {}
285
301
  for grp, tbls in existing_reg_data.items():
286
- if isinstance(tbls, dict):
302
+ if grp == "tables" and isinstance(tbls, dict):
303
+ # Modern format: tables are in 'tables' dict
304
+ for t, t_meta in tbls.items():
305
+ flat_tables[t] = t_meta
306
+ continue
307
+
308
+ if isinstance(tbls, dict) and grp != "global_imports":
309
+ # Legacy Scoped Format (grp is connection_obj)
287
310
  for t, t_meta in tbls.items():
288
- # Inject the config object (group key) so DatacubeRegistry knows the connection
311
+ # Inject/Overwrite connection_obj only if implied by scope
289
312
  t_meta["connection_obj"] = grp
290
313
  flat_tables[t] = t_meta
291
314
 
@@ -295,6 +318,11 @@ def sync(
295
318
  console.print(f"[yellow]Warning: Could not load existing registry: {e}[/yellow]")
296
319
 
297
320
  registry = DatacubeRegistry(config_data, params=context.params)
321
+
322
+ # Fix 2 Re-applied: Ensure global_imports are populated from params if registry loaded from file didn't have them
323
+ # This is critical for Clean Project + Force flows where registry might be partial or JIT
324
+ if not registry.global_imports and context.params.get("global_imports"):
325
+ registry.global_imports = context.params.get("global_imports")
298
326
 
299
327
  # --- Aggregation Phase ---
300
328
  # --- Aggregation Phase ---
@@ -453,7 +481,7 @@ def sync(
453
481
  registry.valid_paths = list(valid_paths)
454
482
  registry.valid_fieldmap_paths = context.valid_fieldmap_paths
455
483
 
456
- get_url = _get_db_url_callback(registry, db_url_map)
484
+ get_url = _get_db_url_callback(registry, db_url_map, params=context.params)
457
485
 
458
486
  # Group tables by target file
459
487
  file_groups = registry.group_tables_by_file()
@@ -770,8 +798,22 @@ def discover(
770
798
  if not wl_filename:
771
799
  # Default convention: discovery_whitelist_<db_name>.yaml
772
800
  wl_filename = f"discovery_whitelist_{conn_obj}.yaml"
801
+
773
802
  whitelist_path = config_path.parent / wl_filename
774
803
 
804
+ # Fallback 1: Auto-generated per-db (from scan/map fallback logic)
805
+ if not whitelist_path.exists():
806
+ fb = config_path.parent / f"discovery_whitelist_{conn_obj}.yaml"
807
+ if fb.exists():
808
+ whitelist_path = fb
809
+
810
+ # Fallback 2: Global whitelist.yaml (Generated by 'whitelist' command)
811
+ if not whitelist_path.exists():
812
+ global_wl = config_path.parent / "whitelist.yaml"
813
+ if global_wl.exists():
814
+ console.print(f"[dim]Fallback: Using global {global_wl.name} for {db_name}[/dim]")
815
+ whitelist_path = global_wl
816
+
775
817
  # Determine rules path
776
818
  rules_filename = db_config.get("rules_file")
777
819
  if not rules_filename:
@@ -1014,8 +1056,21 @@ def scan(
1014
1056
  raise typer.Exit(code=1)
1015
1057
 
1016
1058
  # Resolve global output file
1017
- global_tables_file = params.get("all_tables_file") or "all_tables.yaml"
1018
- global_tables_path = config_path.parent / global_tables_file
1059
+ # First check nested discovery block (standard), then root (fallback)
1060
+ discovery_conf = params.get("discovery", {})
1061
+ global_tables_file = (
1062
+ discovery_conf.get("all_tables_file")
1063
+ or params.get("all_tables_file")
1064
+ or "all_tables.yaml"
1065
+ )
1066
+
1067
+ # If using absolute path (resolved from context), use it directly
1068
+ # Otherwise treat as relative to config file location
1069
+ path_obj = Path(global_tables_file)
1070
+ if path_obj.is_absolute():
1071
+ global_tables_path = path_obj
1072
+ else:
1073
+ global_tables_path = config_path.parent / global_tables_file
1019
1074
 
1020
1075
  # Load existing data to preserve config for DBs not being scanned
1021
1076
  all_tables_data = {}
@@ -1089,7 +1144,7 @@ def drift(
1089
1144
  config_data = _load_and_resolve_config(config_path)
1090
1145
 
1091
1146
  registry = DatacubeRegistry(config_data)
1092
- get_url = _get_db_url_callback(registry, db_url_map)
1147
+ get_url = _get_db_url_callback(registry, db_url_map, params=config_data)
1093
1148
  cli_urls = json.loads(db_url_map) if db_url_map else {}
1094
1149
 
1095
1150
  drift_table = Table(title="Schema Drift Analysis")
@@ -1493,6 +1548,116 @@ def map(
1493
1548
  return
1494
1549
 
1495
1550
 
1551
+ @app.command()
1552
+ def workflow(
1553
+ config_file: Optional[Path] = typer.Option(None, "--config"),
1554
+ db_url_map: Optional[str] = typer.Option(None, "--db-urls"),
1555
+ env_file: Optional[Path] = typer.Option(None, "--env-file", "-e"),
1556
+ db_name: Optional[str] = typer.Option(
1557
+ None, "--db", help="Target specific database (filtering map/sync)"
1558
+ ),
1559
+ app_name: Optional[str] = typer.Option(
1560
+ None, "--app-name", help="Run create_cubes for the specified application"
1561
+ ),
1562
+ force: bool = typer.Option(False, "--force", "-f", help="Force rebuild/overwrite"),
1563
+ dry_run: bool = typer.Option(
1564
+ False, "--dry-run", help="Preview changes without executing"
1565
+ ),
1566
+ ) -> None:
1567
+ """
1568
+ Executes the full Datacube workflow: Scan -> Propose -> Discover -> Map -> Sync -> (Optional) Create Cubes.
1569
+ """
1570
+ console.rule("[bold magenta]Starting Datacube Workflow[/]")
1571
+
1572
+ # Resolve env_file: CLI > Params > Default (Consistent with discover/sync)
1573
+ if env_file:
1574
+ env_path = env_file
1575
+ elif context.params and context.params.get("defaults", {}).get("env_file"):
1576
+ env_path = Path(context.params.get("defaults", {})["env_file"])
1577
+ else:
1578
+ env_path = Path(".env.linux")
1579
+ load_environment(env_path, logger=console.print)
1580
+
1581
+ # Ensure context is configured with params from the config file
1582
+ config_path = config_file or context.default_config
1583
+ if config_path and config_path.exists():
1584
+ # Load and resolve config to ensure context.params is populated
1585
+ # This is critical for 'discover' to find 'databases' list
1586
+ try:
1587
+ config_data = _load_and_resolve_config(config_path)
1588
+ context.params = config_data
1589
+ context.default_config = config_path
1590
+ # We don't have easy access to other set_context_defaults args here,
1591
+ # but params is what 'discover' needs most.
1592
+ console.print(f"[dim]Loaded workflow context from {config_path}[/dim]")
1593
+ except Exception as e:
1594
+ console.print(f"[yellow]Warning: Failed to load context from {config_path}: {e}[/yellow]")
1595
+
1596
+ # Step 0: Scan & Propose Rules (Critical for new projects/drift)
1597
+ console.print(f"\n[bold cyan]Step 0a: Scan (Introspection)[/]")
1598
+ scan(
1599
+ config_file=config_file,
1600
+ db_url_map=db_url_map,
1601
+ env_file=env_file,
1602
+ db_name=db_name,
1603
+ )
1604
+
1605
+ console.print(f"\n[bold cyan]Step 0b: Propose Rules[/]")
1606
+ propose_rules(
1607
+ config_file=config_file,
1608
+ dry_run=dry_run, # Preview rules if dry-running
1609
+ )
1610
+
1611
+ console.print(f"\n[bold cyan]Step 0c: Generate Whitelist[/]")
1612
+ whitelist(
1613
+ config_file=config_file,
1614
+ db_name=db_name,
1615
+ db_url_map=db_url_map,
1616
+ env_file=env_file,
1617
+ force=force,
1618
+ )
1619
+
1620
+ # Step 1: Discover
1621
+ # Note: Discover currently runs for all DBs in config.
1622
+ console.print(f"\n[bold cyan]Step 1: Discovery[/]")
1623
+ discover(
1624
+ config_file=config_file,
1625
+ db_conf="replica_db_conf", # Pass explicit string to avoid Typer OptionInfo default
1626
+ db_url_map=db_url_map,
1627
+ env_file=env_file,
1628
+ update=True, # Always update registry to enable chaining
1629
+ prune=False, # Safer default? Or should we expose prune?
1630
+ run_sync=False, # We handle sync explicitly later
1631
+ dry_run=dry_run,
1632
+ generate_fields=False, # We use map command for this
1633
+ force=False, # Discovery force is about fields usually?
1634
+ )
1635
+
1636
+ # Step 2: Map (Skipped - Handled by Sync)
1637
+ # console.print(f"\n[bold cyan]Step 2: Map Generation (Integrated into Sync)[/]")
1638
+
1639
+ # Step 3: Sync -> Generates Code + Field Maps
1640
+ console.print(f"\n[bold cyan]Step 3: Sync (Code Generation)[/]")
1641
+ sync(
1642
+ config_file=config_file,
1643
+ db_url_map=db_url_map,
1644
+ env_file=env_file, # sync command has env_file
1645
+ force=force,
1646
+ dry_run=dry_run,
1647
+ )
1648
+
1649
+ # Step 4: Create Cubes (Optional)
1650
+ if app_name:
1651
+ console.print(f"\n[bold cyan]Step 4: Application Cubes ({app_name})[/]")
1652
+ if dry_run:
1653
+ console.print("[yellow]Skipping create_cubes in dry-run mode.[/yellow]")
1654
+ else:
1655
+ from sibi_flux.init.cube_extender import create_cubes
1656
+ create_cubes(app_name)
1657
+
1658
+ console.rule("[bold magenta]Workflow Completed[/]")
1659
+
1660
+
1496
1661
  @app.command()
1497
1662
  def init(
1498
1663
  config_file: Optional[Path] = typer.Option(None, "--config"),
@@ -778,6 +778,32 @@ def generate_datacube_module_code(
778
778
  else:
779
779
  imports.add(base_imp)
780
780
 
781
+ # Config Object Import
782
+ # We need to resolve import for conf_obj (e.g. clickhouse_conf)
783
+ # Using registry.params which contains databases list with import_spec
784
+ params = registry.params
785
+ dbs = params.get("databases", [])
786
+ for db in dbs:
787
+ ref = db.get("connection_ref") or db.get("connection_obj")
788
+ if ref == conf_obj:
789
+ spec = db.get("import_spec")
790
+ if spec and isinstance(spec, dict):
791
+ mod = spec.get("module")
792
+ sym = spec.get("symbol")
793
+ if mod and sym:
794
+ imports.add(f"from {mod} import {sym}")
795
+ elif db.get("global_import"):
796
+ # Legacy or simple string import
797
+ imp_str = db.get("global_import")
798
+ if imp_str and sym and sym in imp_str:
799
+ imports.add(imp_str)
800
+ elif imp_str: # Fallback to add specific object if simple import
801
+ # Try to parse or just add "from ... import conf_obj" logic?
802
+ # If global_import is "from conf.credentials import *" -> hard to know
803
+ # If it is "from conf.credentials import replica_conf" -> easy
804
+ imports.add(imp_str)
805
+ break
806
+
781
807
  details = registry.get_table_details(table_name)
782
808
  field_map_str = details.get("field_map")
783
809
  sticky_filters = details.get("sticky_filters")
@@ -56,6 +56,8 @@ def propose_cubes(db_domain: str, app_name: str) -> None:
56
56
 
57
57
  # Registry Structure: {conf_obj: {table_name: {class_name: ..., path: ...}}}
58
58
  for conf_obj, tables in global_data.items():
59
+ if not isinstance(tables, dict):
60
+ continue
59
61
  for table_name, meta in tables.items():
60
62
  path_str = meta.get("path", "")
61
63
  class_name = meta.get("class_name")
@@ -100,14 +102,16 @@ def propose_cubes(db_domain: str, app_name: str) -> None:
100
102
 
101
103
  # 4. Update App Registry
102
104
 
103
- # Load existing
104
105
  current_config = {"cubes": []}
105
106
  if registry_path.exists():
106
107
  try:
107
108
  with open(registry_path, "r") as f:
108
109
  loaded = yaml.safe_load(f)
109
- if loaded and "cubes" in loaded:
110
- current_config = loaded
110
+ if loaded:
111
+ current_config = loaded
112
+ if "cubes" not in current_config or current_config["cubes"] is None:
113
+ current_config["cubes"] = []
114
+
111
115
  except Exception:
112
116
  pass
113
117
 
File without changes