sibi-flux 2026.1.1__tar.gz → 2026.1.2__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 (124) hide show
  1. {sibi_flux-2026.1.1 → sibi_flux-2026.1.2}/PKG-INFO +42 -1
  2. {sibi_flux-2026.1.1 → sibi_flux-2026.1.2}/README.md +40 -0
  3. {sibi_flux-2026.1.1 → sibi_flux-2026.1.2}/pyproject.toml +34 -1
  4. sibi_flux-2026.1.2/src/sibi_flux/cli.py +45 -0
  5. {sibi_flux-2026.1.1 → sibi_flux-2026.1.2}/src/sibi_flux/config/settings.py +7 -7
  6. sibi_flux-2026.1.2/src/sibi_flux/init/core.py +159 -0
  7. sibi_flux-2026.1.2/src/sibi_flux/init/discovery_updater.py +99 -0
  8. sibi_flux-2026.1.2/src/sibi_flux/init/env.py +86 -0
  9. sibi_flux-2026.1.2/src/sibi_flux/init/env_engine.py +151 -0
  10. sibi_flux-2026.1.2/src/sibi_flux/init/env_generator.py +554 -0
  11. sibi_flux-2026.1.2/src/sibi_flux/init/templates/__init__.py +0 -0
  12. sibi_flux-2026.1.2/src/sibi_flux/init/templates/discovery_params.yaml +45 -0
  13. sibi_flux-2026.1.2/src/sibi_flux/init/templates/gen_dc.py +137 -0
  14. sibi_flux-2026.1.2/src/sibi_flux/init/templates/property_template.yaml +10 -0
  15. sibi_flux-2026.1.2/src/sibi_flux/py.typed +0 -0
  16. {sibi_flux-2026.1.1 → sibi_flux-2026.1.2}/src/sibi_dst/__init__.py +0 -0
  17. {sibi_flux-2026.1.1 → sibi_flux-2026.1.2}/src/sibi_flux/__init__.py +0 -0
  18. {sibi_flux-2026.1.1 → sibi_flux-2026.1.2}/src/sibi_flux/artifacts/__init__.py +0 -0
  19. {sibi_flux-2026.1.1 → sibi_flux-2026.1.2}/src/sibi_flux/artifacts/base.py +0 -0
  20. {sibi_flux-2026.1.1 → sibi_flux-2026.1.2}/src/sibi_flux/artifacts/parquet.py +0 -0
  21. {sibi_flux-2026.1.1 → sibi_flux-2026.1.2}/src/sibi_flux/artifacts/parquet_engine/__init__.py +0 -0
  22. {sibi_flux-2026.1.1 → sibi_flux-2026.1.2}/src/sibi_flux/artifacts/parquet_engine/executor.py +0 -0
  23. {sibi_flux-2026.1.1 → sibi_flux-2026.1.2}/src/sibi_flux/artifacts/parquet_engine/manifest.py +0 -0
  24. {sibi_flux-2026.1.1 → sibi_flux-2026.1.2}/src/sibi_flux/artifacts/parquet_engine/planner.py +0 -0
  25. {sibi_flux-2026.1.1 → sibi_flux-2026.1.2}/src/sibi_flux/config/__init__.py +0 -0
  26. {sibi_flux-2026.1.1 → sibi_flux-2026.1.2}/src/sibi_flux/config/manager.py +0 -0
  27. {sibi_flux-2026.1.1 → sibi_flux-2026.1.2}/src/sibi_flux/core/__init__.py +0 -0
  28. {sibi_flux-2026.1.1 → sibi_flux-2026.1.2}/src/sibi_flux/core/managed_resource/__init__.py +0 -0
  29. {sibi_flux-2026.1.1 → sibi_flux-2026.1.2}/src/sibi_flux/core/managed_resource/_managed_resource.py +0 -0
  30. {sibi_flux-2026.1.1 → sibi_flux-2026.1.2}/src/sibi_flux/core/type_maps/__init__.py +0 -0
  31. {sibi_flux-2026.1.1 → sibi_flux-2026.1.2}/src/sibi_flux/dask_cluster/__init__.py +0 -0
  32. {sibi_flux-2026.1.1 → sibi_flux-2026.1.2}/src/sibi_flux/dask_cluster/async_core.py +0 -0
  33. {sibi_flux-2026.1.1 → sibi_flux-2026.1.2}/src/sibi_flux/dask_cluster/client_manager.py +0 -0
  34. {sibi_flux-2026.1.1 → sibi_flux-2026.1.2}/src/sibi_flux/dask_cluster/core.py +0 -0
  35. {sibi_flux-2026.1.1 → sibi_flux-2026.1.2}/src/sibi_flux/dask_cluster/exceptions.py +0 -0
  36. {sibi_flux-2026.1.1 → sibi_flux-2026.1.2}/src/sibi_flux/dask_cluster/utils.py +0 -0
  37. {sibi_flux-2026.1.1 → sibi_flux-2026.1.2}/src/sibi_flux/datacube/__init__.py +0 -0
  38. {sibi_flux-2026.1.1 → sibi_flux-2026.1.2}/src/sibi_flux/datacube/_data_cube.py +0 -0
  39. {sibi_flux-2026.1.1 → sibi_flux-2026.1.2}/src/sibi_flux/datacube/cli.py +0 -0
  40. {sibi_flux-2026.1.1 → sibi_flux-2026.1.2}/src/sibi_flux/datacube/config_engine.py +0 -0
  41. {sibi_flux-2026.1.1 → sibi_flux-2026.1.2}/src/sibi_flux/datacube/field_factory.py +0 -0
  42. {sibi_flux-2026.1.1 → sibi_flux-2026.1.2}/src/sibi_flux/datacube/field_mapper.py +0 -0
  43. {sibi_flux-2026.1.1 → sibi_flux-2026.1.2}/src/sibi_flux/datacube/field_registry.py +0 -0
  44. {sibi_flux-2026.1.1 → sibi_flux-2026.1.2}/src/sibi_flux/datacube/generator.py +0 -0
  45. {sibi_flux-2026.1.1 → sibi_flux-2026.1.2}/src/sibi_flux/datacube/orchestrator.py +0 -0
  46. {sibi_flux-2026.1.1 → sibi_flux-2026.1.2}/src/sibi_flux/datacube/router.py +0 -0
  47. {sibi_flux-2026.1.1 → sibi_flux-2026.1.2}/src/sibi_flux/dataset/__init__.py +0 -0
  48. {sibi_flux-2026.1.1 → sibi_flux-2026.1.2}/src/sibi_flux/dataset/_dataset.py +0 -0
  49. {sibi_flux-2026.1.1 → sibi_flux-2026.1.2}/src/sibi_flux/dataset/hybrid_loader.py +0 -0
  50. {sibi_flux-2026.1.1 → sibi_flux-2026.1.2}/src/sibi_flux/df_enricher/__init__.py +0 -0
  51. {sibi_flux-2026.1.1 → sibi_flux-2026.1.2}/src/sibi_flux/df_enricher/async_enricher.py +0 -0
  52. {sibi_flux-2026.1.1 → sibi_flux-2026.1.2}/src/sibi_flux/df_enricher/attacher.py +0 -0
  53. {sibi_flux-2026.1.1 → sibi_flux-2026.1.2}/src/sibi_flux/df_enricher/merger.py +0 -0
  54. {sibi_flux-2026.1.1 → sibi_flux-2026.1.2}/src/sibi_flux/df_enricher/specs.py +0 -0
  55. {sibi_flux-2026.1.1 → sibi_flux-2026.1.2}/src/sibi_flux/df_enricher/types.py +0 -0
  56. {sibi_flux-2026.1.1 → sibi_flux-2026.1.2}/src/sibi_flux/df_helper/__init__.py +0 -0
  57. {sibi_flux-2026.1.1 → sibi_flux-2026.1.2}/src/sibi_flux/df_helper/_df_helper.py +0 -0
  58. {sibi_flux-2026.1.1 → sibi_flux-2026.1.2}/src/sibi_flux/df_helper/backends/__init__.py +0 -0
  59. {sibi_flux-2026.1.1 → sibi_flux-2026.1.2}/src/sibi_flux/df_helper/backends/_params.py +0 -0
  60. {sibi_flux-2026.1.1 → sibi_flux-2026.1.2}/src/sibi_flux/df_helper/backends/_strategies.py +0 -0
  61. {sibi_flux-2026.1.1 → sibi_flux-2026.1.2}/src/sibi_flux/df_helper/backends/http/__init__.py +0 -0
  62. {sibi_flux-2026.1.1 → sibi_flux-2026.1.2}/src/sibi_flux/df_helper/backends/http/_http_config.py +0 -0
  63. {sibi_flux-2026.1.1 → sibi_flux-2026.1.2}/src/sibi_flux/df_helper/backends/parquet/__init__.py +0 -0
  64. {sibi_flux-2026.1.1 → sibi_flux-2026.1.2}/src/sibi_flux/df_helper/backends/parquet/_parquet_options.py +0 -0
  65. {sibi_flux-2026.1.1 → sibi_flux-2026.1.2}/src/sibi_flux/df_helper/backends/sqlalchemy/__init__.py +0 -0
  66. {sibi_flux-2026.1.1 → sibi_flux-2026.1.2}/src/sibi_flux/df_helper/backends/sqlalchemy/_db_connection.py +0 -0
  67. {sibi_flux-2026.1.1 → sibi_flux-2026.1.2}/src/sibi_flux/df_helper/backends/sqlalchemy/_db_gatekeeper.py +0 -0
  68. {sibi_flux-2026.1.1 → sibi_flux-2026.1.2}/src/sibi_flux/df_helper/backends/sqlalchemy/_io_dask.py +0 -0
  69. {sibi_flux-2026.1.1 → sibi_flux-2026.1.2}/src/sibi_flux/df_helper/backends/sqlalchemy/_load_from_db.py +0 -0
  70. {sibi_flux-2026.1.1 → sibi_flux-2026.1.2}/src/sibi_flux/df_helper/backends/sqlalchemy/_model_registry.py +0 -0
  71. {sibi_flux-2026.1.1 → sibi_flux-2026.1.2}/src/sibi_flux/df_helper/backends/sqlalchemy/_sql_model_builder.py +0 -0
  72. {sibi_flux-2026.1.1 → sibi_flux-2026.1.2}/src/sibi_flux/df_helper/backends/utils.py +0 -0
  73. {sibi_flux-2026.1.1 → sibi_flux-2026.1.2}/src/sibi_flux/df_helper/core/__init__.py +0 -0
  74. {sibi_flux-2026.1.1 → sibi_flux-2026.1.2}/src/sibi_flux/df_helper/core/_defaults.py +0 -0
  75. {sibi_flux-2026.1.1 → sibi_flux-2026.1.2}/src/sibi_flux/df_helper/core/_filter_handler.py +0 -0
  76. {sibi_flux-2026.1.1 → sibi_flux-2026.1.2}/src/sibi_flux/df_helper/core/_params_config.py +0 -0
  77. {sibi_flux-2026.1.1 → sibi_flux-2026.1.2}/src/sibi_flux/df_helper/core/_query_config.py +0 -0
  78. {sibi_flux-2026.1.1 → sibi_flux-2026.1.2}/src/sibi_flux/df_validator/__init__.py +0 -0
  79. {sibi_flux-2026.1.1 → sibi_flux-2026.1.2}/src/sibi_flux/df_validator/_df_validator.py +0 -0
  80. /sibi_flux-2026.1.1/src/sibi_flux/py.typed → /sibi_flux-2026.1.2/src/sibi_flux/init/__init__.py +0 -0
  81. {sibi_flux-2026.1.1 → sibi_flux-2026.1.2}/src/sibi_flux/logger/__init__.py +0 -0
  82. {sibi_flux-2026.1.1 → sibi_flux-2026.1.2}/src/sibi_flux/logger/_logger.py +0 -0
  83. {sibi_flux-2026.1.1 → sibi_flux-2026.1.2}/src/sibi_flux/mcp/__init__.py +0 -0
  84. {sibi_flux-2026.1.1 → sibi_flux-2026.1.2}/src/sibi_flux/mcp/client.py +0 -0
  85. {sibi_flux-2026.1.1 → sibi_flux-2026.1.2}/src/sibi_flux/mcp/router.py +0 -0
  86. {sibi_flux-2026.1.1 → sibi_flux-2026.1.2}/src/sibi_flux/orchestration/__init__.py +0 -0
  87. {sibi_flux-2026.1.1 → sibi_flux-2026.1.2}/src/sibi_flux/orchestration/_artifact_orchestrator.py +0 -0
  88. {sibi_flux-2026.1.1 → sibi_flux-2026.1.2}/src/sibi_flux/orchestration/_pipeline_executor.py +0 -0
  89. {sibi_flux-2026.1.1 → sibi_flux-2026.1.2}/src/sibi_flux/osmnx_helper/__init__.py +0 -0
  90. {sibi_flux-2026.1.1 → sibi_flux-2026.1.2}/src/sibi_flux/osmnx_helper/_pbf_handler.py +0 -0
  91. {sibi_flux-2026.1.1 → sibi_flux-2026.1.2}/src/sibi_flux/osmnx_helper/graph_loader.py +0 -0
  92. {sibi_flux-2026.1.1 → sibi_flux-2026.1.2}/src/sibi_flux/osmnx_helper/utils.py +0 -0
  93. {sibi_flux-2026.1.1 → sibi_flux-2026.1.2}/src/sibi_flux/parquet/__init__.py +0 -0
  94. {sibi_flux-2026.1.1 → sibi_flux-2026.1.2}/src/sibi_flux/parquet/readers/__init__.py +0 -0
  95. {sibi_flux-2026.1.1 → sibi_flux-2026.1.2}/src/sibi_flux/parquet/readers/base.py +0 -0
  96. {sibi_flux-2026.1.1 → sibi_flux-2026.1.2}/src/sibi_flux/parquet/readers/parquet.py +0 -0
  97. {sibi_flux-2026.1.1 → sibi_flux-2026.1.2}/src/sibi_flux/parquet/saver/__init__.py +0 -0
  98. {sibi_flux-2026.1.1 → sibi_flux-2026.1.2}/src/sibi_flux/parquet/saver/_parquet_saver.py +0 -0
  99. {sibi_flux-2026.1.1 → sibi_flux-2026.1.2}/src/sibi_flux/parquet/saver/_write_gatekeeper.py +0 -0
  100. {sibi_flux-2026.1.1 → sibi_flux-2026.1.2}/src/sibi_flux/pipelines/__init__.py +0 -0
  101. {sibi_flux-2026.1.1 → sibi_flux-2026.1.2}/src/sibi_flux/pipelines/base.py +0 -0
  102. {sibi_flux-2026.1.1 → sibi_flux-2026.1.2}/src/sibi_flux/pipelines/template.py +0 -0
  103. {sibi_flux-2026.1.1 → sibi_flux-2026.1.2}/src/sibi_flux/readers/__init__.py +0 -0
  104. {sibi_flux-2026.1.1 → sibi_flux-2026.1.2}/src/sibi_flux/readers/base.py +0 -0
  105. {sibi_flux-2026.1.1 → sibi_flux-2026.1.2}/src/sibi_flux/storage/__init__.py +0 -0
  106. {sibi_flux-2026.1.1 → sibi_flux-2026.1.2}/src/sibi_flux/storage/_fs_registry.py +0 -0
  107. {sibi_flux-2026.1.1 → sibi_flux-2026.1.2}/src/sibi_flux/storage/_storage_manager.py +0 -0
  108. {sibi_flux-2026.1.1 → sibi_flux-2026.1.2}/src/sibi_flux/storage/factory.py +0 -0
  109. {sibi_flux-2026.1.1 → sibi_flux-2026.1.2}/src/sibi_flux/utils/__init__.py +0 -0
  110. {sibi_flux-2026.1.1 → sibi_flux-2026.1.2}/src/sibi_flux/utils/clickhouse_writer/__init__.py +0 -0
  111. {sibi_flux-2026.1.1 → sibi_flux-2026.1.2}/src/sibi_flux/utils/clickhouse_writer/_clickhouse_writer.py +0 -0
  112. {sibi_flux-2026.1.1 → sibi_flux-2026.1.2}/src/sibi_flux/utils/common.py +0 -0
  113. {sibi_flux-2026.1.1 → sibi_flux-2026.1.2}/src/sibi_flux/utils/dask_utils.py +0 -0
  114. {sibi_flux-2026.1.1 → sibi_flux-2026.1.2}/src/sibi_flux/utils/data_utils/__init__.py +0 -0
  115. {sibi_flux-2026.1.1 → sibi_flux-2026.1.2}/src/sibi_flux/utils/data_utils/_data_utils.py +0 -0
  116. {sibi_flux-2026.1.1 → sibi_flux-2026.1.2}/src/sibi_flux/utils/dataframe_utils.py +0 -0
  117. {sibi_flux-2026.1.1 → sibi_flux-2026.1.2}/src/sibi_flux/utils/date_utils/__init__.py +0 -0
  118. {sibi_flux-2026.1.1 → sibi_flux-2026.1.2}/src/sibi_flux/utils/date_utils/_business_days.py +0 -0
  119. {sibi_flux-2026.1.1 → sibi_flux-2026.1.2}/src/sibi_flux/utils/date_utils/_date_utils.py +0 -0
  120. {sibi_flux-2026.1.1 → sibi_flux-2026.1.2}/src/sibi_flux/utils/date_utils/_file_age_checker.py +0 -0
  121. {sibi_flux-2026.1.1 → sibi_flux-2026.1.2}/src/sibi_flux/utils/file_utils.py +0 -0
  122. {sibi_flux-2026.1.1 → sibi_flux-2026.1.2}/src/sibi_flux/utils/filepath_generator/__init__.py +0 -0
  123. {sibi_flux-2026.1.1 → sibi_flux-2026.1.2}/src/sibi_flux/utils/filepath_generator/_filepath_generator.py +0 -0
  124. {sibi_flux-2026.1.1 → sibi_flux-2026.1.2}/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.1
3
+ Version: 2026.1.2
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>
@@ -28,6 +28,7 @@ Requires-Dist: opentelemetry-api>=1.38.0
28
28
  Requires-Dist: opentelemetry-exporter-otlp>=1.38.0
29
29
  Requires-Dist: opentelemetry-sdk>=1.38.0
30
30
  Requires-Dist: deep-translator>=1.11.4
31
+ Requires-Dist: pyyaml>=6.0.3
31
32
  Requires-Dist: sibi-flux[distributed,geospatial,mcp] ; extra == 'complete'
32
33
  Requires-Dist: distributed>=2025.11.0 ; extra == 'distributed'
33
34
  Requires-Dist: osmnx>=2.0.7 ; extra == 'geospatial'
@@ -282,3 +283,43 @@ async with GenericMcpClient(url="http://localhost:8000/sse") as client:
282
283
  # Call a tool
283
284
  vat = await client.call_tool("calculate_vat", arguments={"amount": 100.0})
284
285
  ```
286
+
287
+ ### 8. Datacube Generation (`gen_dc.py`)
288
+
289
+ Automate the creation of Datacube classes and Field Maps from your database schema.
290
+
291
+ **Configuration (`discovery_params.yaml`)**
292
+
293
+ Define your generation rules in a hierarchical configuration file:
294
+
295
+ ```yaml
296
+ defaults:
297
+ backend: sqlalchemy
298
+ class_suffix: Dc
299
+
300
+ discovery:
301
+ whitelist_file: whitelist.yaml
302
+ rules_file: discovery_rules.yaml
303
+
304
+ generation:
305
+ enable_field_maps: true
306
+ ```
307
+
308
+ **Commands**
309
+
310
+ 1. **Discover**: Introspects the database and updates your whitelist/registry.
311
+ ```bash
312
+ uv run poe dc-discover
313
+ ```
314
+ * **Whitelist**: Explicitly define tables to generate. Support `custom_name` to override class names.
315
+ * **Rules**: Regex-based patterns to match tables.
316
+
317
+ 2. **Sync**: Generates Python code (Datacubes and Field Maps) based on the registry.
318
+ ```bash
319
+ uv run poe dc-sync --force
320
+ ```
321
+
322
+ **Key Features**
323
+ - **Custom Naming**: Add `custom_name: MyCube` to `whitelist.yaml` to override generated names.
324
+ - **Hierarchical Config**: Strict validation of generation parameters.
325
+ - **Field Maps**: Auto-generates type-safe mapping files for every table.
@@ -231,3 +231,43 @@ async with GenericMcpClient(url="http://localhost:8000/sse") as client:
231
231
  # Call a tool
232
232
  vat = await client.call_tool("calculate_vat", arguments={"amount": 100.0})
233
233
  ```
234
+
235
+ ### 8. Datacube Generation (`gen_dc.py`)
236
+
237
+ Automate the creation of Datacube classes and Field Maps from your database schema.
238
+
239
+ **Configuration (`discovery_params.yaml`)**
240
+
241
+ Define your generation rules in a hierarchical configuration file:
242
+
243
+ ```yaml
244
+ defaults:
245
+ backend: sqlalchemy
246
+ class_suffix: Dc
247
+
248
+ discovery:
249
+ whitelist_file: whitelist.yaml
250
+ rules_file: discovery_rules.yaml
251
+
252
+ generation:
253
+ enable_field_maps: true
254
+ ```
255
+
256
+ **Commands**
257
+
258
+ 1. **Discover**: Introspects the database and updates your whitelist/registry.
259
+ ```bash
260
+ uv run poe dc-discover
261
+ ```
262
+ * **Whitelist**: Explicitly define tables to generate. Support `custom_name` to override class names.
263
+ * **Rules**: Regex-based patterns to match tables.
264
+
265
+ 2. **Sync**: Generates Python code (Datacubes and Field Maps) based on the registry.
266
+ ```bash
267
+ uv run poe dc-sync --force
268
+ ```
269
+
270
+ **Key Features**
271
+ - **Custom Naming**: Add `custom_name: MyCube` to `whitelist.yaml` to override generated names.
272
+ - **Hierarchical Config**: Strict validation of generation parameters.
273
+ - **Field Maps**: Auto-generates type-safe mapping files for every table.
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "sibi-flux"
3
- version = "2026.1.1"
3
+ version = "2026.1.2"
4
4
  description = "Sibi Toolkit: A collection of tools for Data Analysis/Engineering."
5
5
  readme = "README.md"
6
6
  authors = [
@@ -34,6 +34,7 @@ dependencies = [
34
34
  "opentelemetry-exporter-otlp>=1.38.0",
35
35
  "opentelemetry-sdk>=1.38.0",
36
36
  "deep-translator>=1.11.4",
37
+ "pyyaml>=6.0.3",
37
38
  ]
38
39
 
39
40
  [project.optional-dependencies]
@@ -60,6 +61,9 @@ complete = [
60
61
  "sibi-flux[distributed,geospatial,mcp]"
61
62
  ]
62
63
 
64
+ [project.scripts]
65
+ sibi-flux = "sibi_flux.cli:app"
66
+
63
67
 
64
68
  [dependency-groups]
65
69
  dev = [
@@ -90,6 +94,35 @@ build-backend = "uv_build"
90
94
  module-root = "src"
91
95
  module-name = ["sibi_flux", "sibi_dst"]
92
96
 
97
+ [tool.uv.workspace]
98
+ members = [
99
+ "test-project-alpha",
100
+ "test-project-beta",
101
+ "test-project-gamma",
102
+ "test-project-delta",
103
+ "test-project-epsilon",
104
+ "test-project-zeta",
105
+ "test-project-eta",
106
+ "test-project-theta",
107
+ "test-project-iota",
108
+ "test-project-kappa",
109
+ "test-project-lambda",
110
+ "test-project-alpha/latest",
111
+ "test-project-mu",
112
+ "test-project-mu/latest",
113
+ "test-project-xi",
114
+ "test-project-xi/latest",
115
+ "test-project-omicron",
116
+ "test-project-omicron/latest",
117
+ "test-project-pi",
118
+ "test-project-pi/latest",
119
+ "test-project-rho",
120
+ "test-project-rho/latest",
121
+ "test-project-sigma",
122
+ "test-project-sigma/latest",
123
+ "test-project-tau",
124
+ ]
125
+
93
126
  [tool.pytest.ini_options]
94
127
  pythonpath = ["src", "."]
95
128
  testpaths = ["tests"]
@@ -0,0 +1,45 @@
1
+ import typer
2
+ from typing import Optional
3
+ from pathlib import Path
4
+ from rich.console import Console
5
+ from sibi_flux.init.core import initialize_project
6
+
7
+ app = typer.Typer(help="Sibi Flux CLI")
8
+ console = Console()
9
+
10
+ @app.callback()
11
+ def callback():
12
+ """
13
+ Sibi Flux CLI
14
+ """
15
+
16
+ @app.command()
17
+ def init(
18
+ project_name: str = typer.Argument(..., help="Name of the project to create"),
19
+ lib: bool = typer.Option(False, "--lib", help="Initialize as a library project (passed to uv init)"),
20
+ app: bool = typer.Option(False, "--app", help="Initialize as an application project (passed to uv init)")
21
+ ):
22
+ """
23
+ Initialize a new Sibi Flux project.
24
+
25
+ Creates a new directory <project_name>, initializes it with 'uv',
26
+ and adds 'sibi-flux' as a dependency.
27
+ """
28
+ initialize_project(project_name, lib, app)
29
+
30
+ @app.command()
31
+ def env(
32
+ project_path: Path = typer.Argument(Path("."), help="Project root directory"),
33
+ env_file: Optional[Path] = typer.Option(None, "--env-file", "-e", help="Path to environment file (defaults to .env)"),
34
+ cleanup: bool = typer.Option(False, "--cleanup", help="Remove existing configuration files"),
35
+ production: bool = typer.Option(False, "--production", "-p", help="Generate production skeleton (no hardcoded values)"),
36
+ ):
37
+ """
38
+ Initialize configuration files (settings.py, credentials) based on .env
39
+ """
40
+ from sibi_flux.init.env import init_env
41
+ init_env(project_path, env_file, cleanup=cleanup, production_mode=production)
42
+
43
+
44
+ if __name__ == "__main__":
45
+ app()
@@ -1,4 +1,4 @@
1
- from typing import Optional, Any
1
+ from typing import Optional, Any, ClassVar
2
2
  from pydantic import SecretStr
3
3
  from pydantic_settings import BaseSettings, SettingsConfigDict
4
4
 
@@ -9,6 +9,8 @@ class SibiBaseSettings(BaseSettings):
9
9
  model_config = SettingsConfigDict(
10
10
  env_file=".env", env_file_encoding="utf-8", extra="ignore"
11
11
  )
12
+
13
+ conf_name: ClassVar[str] = ""
12
14
 
13
15
 
14
16
  class FsSettings(SibiBaseSettings):
@@ -84,13 +86,8 @@ class DatabaseSettings(SibiBaseSettings):
84
86
  """Generic SQL Database settings."""
85
87
 
86
88
  db_url: str = "sqlite:///:memory:"
87
-
88
-
89
- class ClickhouseBaseSettings(SibiBaseSettings):
90
- """Base settings for ClickHouse connection."""
91
-
92
89
  host: str = "localhost"
93
- port: int = 8123
90
+ port: int = 5432
94
91
  database: str = "default"
95
92
  user: str = "default"
96
93
  password: SecretStr = SecretStr("secret")
@@ -102,9 +99,12 @@ class ClickhouseBaseSettings(SibiBaseSettings):
102
99
  "dbname": self.database,
103
100
  "user": self.user,
104
101
  "password": self.password.get_secret_value() if self.password else None,
102
+ "db_url": self.db_url,
105
103
  }
106
104
 
107
105
 
106
+
107
+
108
108
  class RedisBaseSettings(SibiBaseSettings):
109
109
  """Base settings for Redis connection."""
110
110
 
@@ -0,0 +1,159 @@
1
+ import subprocess
2
+ import os
3
+ import sys
4
+ from pathlib import Path
5
+ from rich.console import Console
6
+ import typer
7
+
8
+ console = Console()
9
+
10
+ def initialize_project(
11
+ project_name: str,
12
+ lib: bool = False,
13
+ app: bool = False,
14
+ ) -> None:
15
+ """
16
+ Initialize a new Sibi Flux project with the standard directory structure and configuration.
17
+
18
+ Args:
19
+ project_name: The name of the project directory to create.
20
+ lib: If True, initialize as a library project via `uv init --lib`.
21
+ app: If True, initialize as an application project via `uv init --app`.
22
+ Note: If `lib` is False, the default behavior will be to include `--app`
23
+ unless `app` is explicitly False. (Aligned with CLI defaults).
24
+ """
25
+ project_path = Path(os.getcwd()) / project_name
26
+
27
+ if project_path.exists():
28
+ console.print(f"[red]Error: Directory '{project_name}' already exists.[/red]")
29
+ raise typer.Exit(code=1)
30
+
31
+ console.print(f"[bold blue]Initializing Sibi Flux project: {project_name}[/bold blue]")
32
+
33
+ # 1. Create Directory
34
+ try:
35
+ # Run `uv init`
36
+ # format: uv init <project_name> [--lib] [--app]
37
+ cmd = ["uv", "init", project_name]
38
+
39
+ if lib:
40
+ cmd.append("--lib")
41
+ else:
42
+ # Default behavior: behave like --app unless explicitly disabled?
43
+ # From previous steps, user wanted default to be --app.
44
+ # If lib is False, we default to app.
45
+ cmd.append("--app")
46
+
47
+ console.print(f"Running: {' '.join(cmd)}")
48
+ subprocess.check_call(cmd)
49
+
50
+ # 2. Add sibi-flux dependency
51
+ console.print(f"Adding sibi-flux dependency...")
52
+ try:
53
+ from importlib.metadata import version
54
+ ver = version("sibi-flux")
55
+ pkg_spec = f"sibi-flux>={ver}"
56
+ except Exception:
57
+ pkg_spec = "sibi-flux@latest"
58
+
59
+ subprocess.check_call(["uv", "add", pkg_spec], cwd=project_path)
60
+
61
+ # 2b. Add dev dependencies
62
+ dev_deps = ["black", "notebook", "pytest", "ruff", "httpx", "poethepoet", "PyYAML"]
63
+ console.print(f"Adding dev dependencies: {', '.join(dev_deps)}...")
64
+ subprocess.check_call(["uv", "add", "--dev"] + dev_deps, cwd=project_path)
65
+
66
+ # 3. Create Scaffolding Folders (conf, dataobjects, generators)
67
+ (project_path / "conf").mkdir(exist_ok=True)
68
+ (project_path / "dataobjects").mkdir(exist_ok=True)
69
+ (project_path / "generators" / "datacubes").mkdir(parents=True, exist_ok=True)
70
+
71
+ # Add basic .gitkeep
72
+ (project_path / "conf" / ".gitkeep").touch()
73
+ (project_path / "dataobjects" / ".gitkeep").touch()
74
+ (project_path / "generators" / ".gitkeep").touch()
75
+ # (project_path / "generators" / "datacubes" / ".gitkeep").touch() # Not needed if we write params
76
+
77
+ # 4. Write discovery_params.yaml templates
78
+ try:
79
+ from importlib import resources as importlib_resources
80
+ pass
81
+ except ImportError:
82
+ import importlib_resources # type: ignore
83
+
84
+ # Access the template resource from sibi_flux.init.templates
85
+ try:
86
+ # Modern way
87
+ ref = importlib_resources.files("sibi_flux.init.templates").joinpath("discovery_params.yaml")
88
+ template_content = ref.read_text()
89
+ except AttributeError:
90
+ # Python < 3.9
91
+ template_content = importlib_resources.read_text("sibi_flux.init.templates", "discovery_params.yaml")
92
+
93
+ # Refactor paths for the new project structure
94
+ template_content = template_content.replace("solutions/dataobjects/", "dataobjects/")
95
+ template_content = template_content.replace("solutions.conf", "conf")
96
+ template_content = template_content.replace(".env.linux", ".env.local")
97
+
98
+ (project_path / "generators" / "datacubes" / "discovery_params.yaml").write_text(template_content)
99
+
100
+ # Write property_template.yaml
101
+ try:
102
+ ref_prop = importlib_resources.files("sibi_flux.init.templates").joinpath("property_template.yaml")
103
+ prop_content = ref_prop.read_text()
104
+ except AttributeError:
105
+ prop_content = importlib_resources.read_text("sibi_flux.init.templates", "property_template.yaml")
106
+
107
+ (project_path / "generators" / "datacubes" / "property_template.yaml").write_text(prop_content)
108
+
109
+ # Write gen_dc.py
110
+ try:
111
+ ref_gen = importlib_resources.files("sibi_flux.init.templates").joinpath("gen_dc.py")
112
+ gen_content = ref_gen.read_text()
113
+ except AttributeError:
114
+ gen_content = importlib_resources.read_text("sibi_flux.init.templates", "gen_dc.py")
115
+
116
+ (project_path / "generators" / "datacubes" / "gen_dc.py").write_text(gen_content)
117
+
118
+ # 5. Create .env.local
119
+ (project_path / ".env.local").touch()
120
+
121
+ # 6. Create/Update .gitignore
122
+ gitignore_path = project_path / ".gitignore"
123
+ gitignore_content = ""
124
+ if gitignore_path.exists():
125
+ gitignore_content = gitignore_path.read_text()
126
+
127
+ # Ensure .env is ignored
128
+ if ".env" not in gitignore_content:
129
+ with open(gitignore_path, "a") as f:
130
+ if gitignore_content and not gitignore_content.endswith("\n"):
131
+ f.write("\n")
132
+ f.write("\n# Environment variables\n.env\n.env.local\n.env.*\n")
133
+
134
+ if not gitignore_path.exists():
135
+ gitignore_path.write_text("# Sibi Flux\n.env\n.env.local\n.env.*\n__pycache__/\n*.pyc\n.DS_Store\n")
136
+
137
+ # 7. Configure poe tasks in pyproject.toml
138
+ pyproject_path = project_path / "pyproject.toml"
139
+ if pyproject_path.exists():
140
+ with open(pyproject_path, "a") as f:
141
+ f.write("\n\n[tool.poe.tasks]\n")
142
+ f.write('dc-sync = "python generators/datacubes/gen_dc.py sync"\n')
143
+ f.write('dc-init = "python generators/datacubes/gen_dc.py init"\n')
144
+ f.write('dc-discover = "python generators/datacubes/gen_dc.py discover"\n')
145
+ f.write('dc-scan = "python generators/datacubes/gen_dc.py scan"\n')
146
+ f.write('dc-match = "python generators/datacubes/gen_dc.py match"\n')
147
+ f.write('dc-map = "python generators/datacubes/gen_dc.py map"\n')
148
+
149
+ console.print(f"[bold green]Successfully initialized {project_name}![/bold green]")
150
+ console.print(f"Created directories: conf/, dataobjects/, generators/datacubes/")
151
+ console.print(f"Created files: .env.local, .gitignore, generators/datacubes/[discovery_params.yaml, property_template.yaml, gen_dc.py]")
152
+ console.print(f"cd {project_name}")
153
+
154
+ except subprocess.CalledProcessError as e:
155
+ console.print(f"[red]Command failed with exit code {e.returncode}[/red]")
156
+ raise typer.Exit(code=e.returncode)
157
+ except FileNotFoundError:
158
+ console.print("[red]Error: 'uv' command not found. Please ensure uv is installed and in your PATH.[/red]")
159
+ raise typer.Exit(code=1)
@@ -0,0 +1,99 @@
1
+ import yaml
2
+ from pathlib import Path
3
+ from typing import List, Dict, Any
4
+ from sibi_flux.init.env_engine import EnvFile, EnvSection
5
+ from rich.console import Console
6
+
7
+ console = Console()
8
+
9
+ class DiscoveryParamsUpdater:
10
+ """
11
+ Updates generators/datacubes/discovery_params.yaml to include
12
+ database connections found in the generated env configuration.
13
+ """
14
+
15
+ @staticmethod
16
+ def _is_database(section: EnvSection) -> bool:
17
+ return section.type.upper() in ["POSTGRES", "MYSQL", "CLICKHOUSE", "SQLALCHEMY_DATABASE"]
18
+
19
+ @staticmethod
20
+ def update(project_path: Path, env_data: EnvFile) -> None:
21
+ params_path = project_path / "generators" / "datacubes" / "discovery_params.yaml"
22
+ if not params_path.exists():
23
+ console.print(f"[yellow]Warning: {params_path} not found. Skipping discovery params update.[/yellow]")
24
+ return
25
+
26
+ try:
27
+ # 1. Read existing YAML
28
+ with open(params_path, "r") as f:
29
+ data = yaml.safe_load(f) or {}
30
+
31
+ # 2. Extract DBs from Env
32
+ db_sections = [s for s in env_data.sections if DiscoveryParamsUpdater._is_database(s)]
33
+
34
+ if not db_sections:
35
+ if "databases" in data:
36
+ del data["databases"]
37
+ with open(params_path, "w") as f:
38
+ yaml.dump(data, f, sort_keys=False, default_flow_style=False)
39
+ console.print(f"[yellow]Removed databases section from discovery_params.yaml (no databases found in env).[/yellow]")
40
+ return
41
+
42
+ # Ensure databases list exists
43
+ if "databases" not in data:
44
+ data["databases"] = []
45
+
46
+ # Map existing entries by ID to avoid duplicates (upsert behavior)
47
+ existing_dbs = {db.get("id"): db for db in data["databases"]}
48
+
49
+ changes_made = False
50
+
51
+ for section in db_sections:
52
+ db_id = section.name.lower()
53
+
54
+ # Check exclusion? No, default to include all.
55
+
56
+ # Generate entry
57
+ conf_name = f"{section.name.lower()}_conf"
58
+ if section.type.upper() == "CLICKHOUSE":
59
+ conf_name = "clickhouse_config"
60
+
61
+ # Extract DB Name for db_domain
62
+ db_domain = db_id # Fallback
63
+ for var in section.vars:
64
+ # Check for common DB name keys.
65
+ # Note: Keys in EnvSection are fully qualified (e.g. REPLICA_DATABASE)
66
+ # We check if the suffix matches DATABASE
67
+ if var.key.endswith("_DATABASE") or var.key == "DATABASE":
68
+ db_domain = var.value
69
+ break
70
+
71
+ new_entry = {
72
+ "id": db_id,
73
+ "connection_ref": conf_name,
74
+ "db_domain": db_domain,
75
+ "import_spec": {
76
+ "module": "conf.credentials",
77
+ "symbol": conf_name
78
+ }
79
+ }
80
+
81
+ # Upsert
82
+ if db_id in existing_dbs:
83
+ # Update existing? Only if significantly different?
84
+ # For now, let's update strict fields but preserve extra if any.
85
+ # Or simpler: Overwrite specific keys.
86
+ existing_dbs[db_id].update(new_entry)
87
+ else:
88
+ data["databases"].append(new_entry)
89
+ changes_made = True
90
+
91
+ # 3. Write back
92
+ # Use sort_keys=False to preserve order roughly
93
+ with open(params_path, "w") as f:
94
+ yaml.dump(data, f, sort_keys=False, default_flow_style=False)
95
+
96
+ console.print(f"[green]Updated discovery_params.yaml with {len(db_sections)} databases.[/green]")
97
+
98
+ except Exception as e:
99
+ console.print(f"[red]Failed to update discovery_params.yaml: {e}[/red]")
@@ -0,0 +1,86 @@
1
+ from pathlib import Path
2
+ from rich.console import Console
3
+ from typing import Optional
4
+
5
+ from sibi_flux.init.env_engine import EnvParser
6
+ from sibi_flux.init.env_generator import EnvGenerator
7
+
8
+ console = Console()
9
+
10
+ def init_env(project_path: Path, env_file: Optional[Path] = None, cleanup: bool = False, production_mode: bool = False):
11
+ """
12
+ Initializes configuration files for the project.
13
+ Parses .env file (or defaults) and dynamically generates settings.py and credentials.
14
+ If cleanup is True, removes generated configuration files.
15
+ If production_mode is True, generates a 'skeleton' settings.py without hardcoded values.
16
+ """
17
+ conf_dir = project_path / "conf"
18
+
19
+ if cleanup:
20
+ settings_path = conf_dir / "settings.py"
21
+ creds_path = conf_dir / "credentials" / "__init__.py"
22
+
23
+ removed = []
24
+ if settings_path.exists():
25
+ settings_path.unlink()
26
+ removed.append("conf/settings.py")
27
+
28
+ if creds_path.exists():
29
+ creds_path.unlink()
30
+ removed.append("conf/credentials/__init__.py")
31
+
32
+ if removed:
33
+ console.print(f"[green]Removed: {', '.join(removed)}[/green]")
34
+ else:
35
+ console.print("[yellow]No configuration files found to remove.[/yellow]")
36
+ return
37
+
38
+ # Parse Environment
39
+ if env_file:
40
+ if not env_file.exists():
41
+ console.print(f"[red]Error: Environment file {env_file} does not exist.[/red]")
42
+ return
43
+
44
+ content = env_file.read_text().strip()
45
+ if not content or all(line.strip().startswith('#') for line in content.splitlines() if line.strip()):
46
+ console.print(f"[yellow]Environment file {env_file} is empty or contains only comments. No action taken.[/yellow]")
47
+ return
48
+
49
+ env_data = EnvParser.parse(content)
50
+ env_filename = env_file.name
51
+ else:
52
+ # Implicit default: try to find .env, but if not found or empty, maybe generate skeleton?
53
+ # User request implies: "if an .env file is empty, message that nothing will be done"
54
+ # But if NO file passed, we assume .env in project root.
55
+
56
+ default_env = project_path / ".env"
57
+ if not default_env.exists():
58
+ console.print(f"[yellow]No .env file found at {default_env}. Skipping generation.[/yellow]")
59
+ return
60
+
61
+ content = default_env.read_text().strip()
62
+ if not content or all(line.strip().startswith('#') for line in content.splitlines() if line.strip()):
63
+ console.print(f"[yellow]Environment file .env is empty or contains only comments. No action taken.[/yellow]")
64
+ return
65
+
66
+ env_data = EnvParser.parse(content)
67
+ env_filename = ".env"
68
+
69
+ conf_dir.mkdir(exist_ok=True)
70
+
71
+ # 1. settings.py
72
+ settings_code = EnvGenerator.generate_pydantic_code(env_data, env_file_name=env_filename, production_mode=production_mode)
73
+ (conf_dir / "settings.py").write_text(settings_code)
74
+ console.print(f"[green]Created conf/settings.py (parsed {len(env_data.sections)} sections)[/green]")
75
+
76
+ # 2. credentials/__init__.py
77
+ creds_dir = conf_dir / "credentials"
78
+ creds_dir.mkdir(exist_ok=True)
79
+
80
+ creds_code = EnvGenerator.generate_credentials_code(env_data)
81
+ (creds_dir / "__init__.py").write_text(creds_code)
82
+ console.print(f"[green]Created conf/credentials/__init__.py[/green]")
83
+
84
+ # 3. Update discovery_params.yaml
85
+ from sibi_flux.init.discovery_updater import DiscoveryParamsUpdater
86
+ DiscoveryParamsUpdater.update(project_path, env_data)