etlplus 0.4.6__py3-none-any.whl → 0.7.0__py3-none-any.whl

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.
@@ -0,0 +1,128 @@
1
+ {# ---------- helpers ---------- #}
2
+ {% macro qid(name) -%}[{{ name | replace(']', ']]') }}]{%- endmacro %}
3
+ {% macro qname(schema, name) -%}{{ qid(schema) }}.{{ qid(name) }}{%- endmacro %}
4
+ {% macro df_name(table, col) -%}DF_{{ table }}_{{ col }}{%- endmacro %}
5
+ {% macro ck_name(table, col) -%}CK_{{ table }}_{{ col }}{%- endmacro %}
6
+
7
+ {# ---------- create schema (optional) ---------- #}
8
+ {% if spec.create_schema | default(true) %}
9
+ IF
10
+ NOT EXISTS (
11
+ SELECT 1
12
+ FROM sys.schemas
13
+ WHERE name = N'{{ spec.schema }}'
14
+ )
15
+ EXEC ('CREATE SCHEMA {{ qid(spec.schema) }}');
16
+ GO
17
+ {% endif %}
18
+
19
+ {# ---------- create table guarded ---------- #}
20
+ {% set fqtn = qname(spec.schema, spec.table) %}
21
+ IF OBJECT_ID(N'{{ spec.schema }}.{{ spec.table }}', N'U') IS NULL
22
+ BEGIN
23
+ CREATE TABLE {{ fqtn }} (
24
+ {% set ns = namespace(maxlen=0) %}
25
+ {% for c in spec.columns %}
26
+ {% set nlen = (c.name|string)|length %}
27
+ {% if nlen > ns.maxlen %}{% set ns.maxlen = nlen %}{% endif %}
28
+ {% endfor %}
29
+ {% set indent_cols = ' ' %}
30
+ {% for c in spec.columns %}
31
+ {# Map-safe lookups so StrictUndefined never bites on missing keys #}
32
+ {%- set ctype = c.get('type') -%}
33
+ {%- set ident = c.get('identity') -%}
34
+ {%- set computed = c.get('computed') -%}
35
+ {%- set nullable = c.get('nullable', true) -%}
36
+ {%- set defv = c.get('default') -%}
37
+ {%- set checkv = c.get('check') -%}
38
+ {{- indent_cols -}}{{ qid(c.name) }} {{ ctype }}
39
+ {%- if computed %} AS ({{ computed }}) PERSISTED{%- endif -%}
40
+ {%- if nullable %} NULL{%- else %} NOT NULL{%- endif -%}
41
+ {%- if ident %} IDENTITY({{ ident.get('seed', 1) }}, {{ ident.get('increment', 1) }}){%- endif -%}
42
+ {%- if defv %} CONSTRAINT {{ qid(df_name(spec.table, c.name)) }} DEFAULT ({{ defv }}){%- endif -%}
43
+ {%- if checkv %} CONSTRAINT {{ qid(ck_name(spec.table, c.name)) }} CHECK ({{ checkv }}){%- endif -%}
44
+ {{ "," if not loop.last }}
45
+ {% endfor %}
46
+
47
+ {%- if spec.primary_key is defined and spec.primary_key %}
48
+ , CONSTRAINT {{ qid(spec.primary_key.name
49
+ | default('PK_' ~ spec.table)) }}
50
+ PRIMARY KEY CLUSTERED (
51
+ {%- for col in spec.primary_key.columns -%}
52
+ {{ qid(col) }}{{ ", " if not loop.last }}
53
+ {%- endfor -%}
54
+ )
55
+ {%- endif %}
56
+
57
+ {%- for uq in spec.unique_constraints | default([]) %}
58
+ , CONSTRAINT {{ qid(uq.name) }} UNIQUE (
59
+ {%- for col in uq.columns -%}
60
+ {{ qid(col) }}{{ ", " if not loop.last }}
61
+ {%- endfor -%}
62
+ )
63
+ {%- endfor %}
64
+ );
65
+ END
66
+ GO
67
+
68
+ {# ---------- indexes ---------- #}
69
+ {% for ix in spec.indexes | default([]) %}
70
+ {%- set ix_unique = ix.get('unique', false) -%}
71
+ {%- set ix_include = ix.get('include') -%}
72
+ {%- set ix_where = ix.get('where') -%}
73
+ IF
74
+ NOT EXISTS (
75
+ SELECT 1
76
+ FROM sys.indexes
77
+ WHERE name = N'{{ ix.name }}'
78
+ AND object_id = OBJECT_ID(N'{{ spec.schema }}.{{ spec.table }}')
79
+ )
80
+ BEGIN
81
+ CREATE{% if ix_unique %} UNIQUE{% endif %} INDEX {{ qid(ix.name) }}
82
+ ON {{ fqtn }} (
83
+ {%- for col in ix.columns -%}
84
+ {{ qid(col) }}{{ ", " if not loop.last }}
85
+ {%- endfor -%}
86
+ )
87
+ {%- if ix_include %}
88
+ INCLUDE (
89
+ {%- for col in ix_include -%}
90
+ {{ qid(col) }}{{ ", " if not loop.last }}
91
+ {%- endfor -%}
92
+ )
93
+ {%- endif -%}
94
+ {%- if ix_where %} WHERE {{ ix_where }}{% endif %};
95
+ END
96
+ GO
97
+ {% endfor %}
98
+
99
+ {# ---------- foreign keys ---------- #}
100
+ {% for fk in spec.foreign_keys | default([]) %}
101
+ IF
102
+ NOT EXISTS (
103
+ SELECT 1
104
+ FROM sys.foreign_keys
105
+ WHERE
106
+ name = N'{{ fk.name }}'
107
+ AND parent_object_id = OBJECT_ID(N'{{ spec.schema }}.{{ spec.table }}'
108
+ )
109
+ )
110
+ BEGIN
111
+ ALTER TABLE {{ fqtn }} WITH CHECK
112
+ ADD CONSTRAINT {{ qid(fk.name) }} FOREIGN KEY (
113
+ {%- for col in fk.columns -%}
114
+ {{ qid(col) }}{{ ", " if not loop.last }}
115
+ {%- endfor -%}
116
+ ) REFERENCES
117
+ {{ qname(fk.ref_schema | default('dbo'), fk.ref_table) }} (
118
+ {%- for col in fk.ref_columns -%}
119
+ {{ qid(col) }}{{ ", " if not loop.last }}
120
+ {%- endfor -%}
121
+ )
122
+ {%- set on_upd = fk.get('on_update') -%}
123
+ {%- set on_del = fk.get('on_delete') -%}
124
+ {%- if on_upd %} ON UPDATE {{ on_upd }}{% endif -%}
125
+ {%- if on_del %} ON DELETE {{ on_del }}{% endif %};
126
+ END
127
+ GO
128
+ {% endfor %}
@@ -0,0 +1,69 @@
1
+ {# -- Helpers -- #}
2
+ {% macro qid(name) -%}
3
+ [{{ name | replace(']', ']]') }}]
4
+ {%- endmacro %}
5
+
6
+ {% macro qname(schema, name) -%}
7
+ {{ qid(schema) }}.{{ qid(name) }}
8
+ {%- endmacro %}
9
+
10
+ {# Convert any expression to nvarchar(MAX) with N'' for NULLs #}
11
+ {% macro to_str(expr) -%}
12
+ ISNULL(CONVERT(NVARCHAR(MAX), {{ expr }}), N'')
13
+ {%- endmacro %}
14
+
15
+ {# Normalize truthy values to 'TRUE' / 'FALSE' #}
16
+ {% macro truthy(expr) -%}
17
+ IIF(CONVERT(VARCHAR(5), {{ expr }}) IN ('TRUE', '1', 'YES'), 'TRUE', 'FALSE')
18
+ {%- endmacro %}
19
+
20
+ {# -- Create schema (optional) -- #}
21
+ {% if spec.create_schema | default(true) %}
22
+ IF
23
+ NOT EXISTS (
24
+ SELECT 1
25
+ FROM sys.schemas
26
+ WHERE name = N'{{ spec.schema }}'
27
+ )
28
+ EXEC ('CREATE SCHEMA {{ qid(spec.schema) }}');
29
+ GO
30
+ {% endif %}
31
+
32
+ {# -- SET options (match your source view) -- #}
33
+ SET QUOTED_IDENTIFIER ON
34
+ SET ANSI_NULLS ON
35
+ GO
36
+
37
+ {# -- Derive names with fallbacks so table-style specs also work -- #}
38
+ {% set view_name = spec.view if spec.view is defined else ('vw_' ~ spec.table) %}
39
+ {% set src_schema = (spec.from_schema if spec.from_schema is defined else (spec.schema if spec.schema is defined else 'dbo')) %}
40
+ {% set src_table = (
41
+ spec.from_table if spec.from_table is defined else
42
+ (spec.source_table if spec.source_table is defined else spec.table)
43
+ ) %}
44
+ {% set indent_cols = ' ' %}
45
+
46
+ {# -- Create view -- #}
47
+ CREATE VIEW {{ qname(spec.schema | default('dbo'), view_name) }} AS
48
+ SELECT
49
+ {% if spec.projections is defined and spec.projections %}
50
+ {% for p in spec.projections %}
51
+ {%- if p.type is defined and (p.type | lower)[:8] == 'nvarchar' -%}
52
+ {{- indent_cols -}}{{ to_str(p.expr) }} AS {{ qid(p.alias) }}
53
+ {%- else -%}
54
+ {{- indent_cols -}}{{ p.expr }}
55
+ {%- endif -%}
56
+ {{ "," if not loop.last }}
57
+ {% endfor %}
58
+ {% else %}
59
+ {% for c in spec.columns | default([]) %}
60
+ {%- if c.type is defined and (c.type | lower)[:8] == 'nvarchar' -%}
61
+ {{- indent_cols -}}{{ to_str(qid(c.name)) }} AS {{ qid(c.name) }}
62
+ {%- else -%}
63
+ {{- indent_cols -}}{{ qid(c.name) }}
64
+ {%- endif -%}
65
+ {{ "," if not loop.last }}
66
+ {% endfor %}
67
+ {%- endif %}
68
+ FROM {{ qname(src_schema, src_table) }};
69
+ GO
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: etlplus
3
- Version: 0.4.6
3
+ Version: 0.7.0
4
4
  Summary: A Swiss Army knife for simple ETL operations
5
5
  Home-page: https://github.com/Dagitali/ETLPlus
6
6
  Author: ETLPlus Team
@@ -21,7 +21,10 @@ Requires-Dist: jinja2>=3.1.6
21
21
  Requires-Dist: pyodbc>=5.3.0
22
22
  Requires-Dist: python-dotenv>=1.2.1
23
23
  Requires-Dist: pandas>=2.3.3
24
+ Requires-Dist: pydantic>=2.12.5
25
+ Requires-Dist: PyYAML>=6.0.3
24
26
  Requires-Dist: requests>=2.32.5
27
+ Requires-Dist: SQLAlchemy>=2.0.45
25
28
  Requires-Dist: typer>=0.21.0
26
29
  Provides-Extra: dev
27
30
  Requires-Dist: black>=25.9.0; extra == "dev"
@@ -61,6 +64,8 @@ package and command-line interface for data extraction, validation, transformati
61
64
  - [Quickstart](#quickstart)
62
65
  - [Usage](#usage)
63
66
  - [Command Line Interface](#command-line-interface)
67
+ - [Inspect Pipelines](#inspect-pipelines)
68
+ - [Render SQL DDL](#render-sql-ddl)
64
69
  - [Extract Data](#extract-data)
65
70
  - [Validate Data](#validate-data)
66
71
  - [Transform Data](#transform-data)
@@ -88,6 +93,14 @@ package and command-line interface for data extraction, validation, transformati
88
93
 
89
94
  ## Features
90
95
 
96
+ - **Check** data pipeline definitions before running them:
97
+ - Summarize jobs, sources, targets, and transforms
98
+ - Confirm configuration changes by printing focused sections on demand
99
+
100
+ - **Render** SQL DDL from shared table specs:
101
+ - Generate CREATE TABLE or view statements
102
+ - Swap templates or direct output to files for database migrations
103
+
91
104
  - **Extract** data from multiple sources:
92
105
  - Files (CSV, JSON, XML, YAML)
93
106
  - Databases (connection string support)
@@ -169,6 +182,44 @@ etlplus --help
169
182
  etlplus --version
170
183
  ```
171
184
 
185
+ #### Check Pipelines
186
+
187
+ Use `etlplus check` to explore pipeline YAML definitions without running them. The command can print
188
+ job names, summarize configured sources and targets, or drill into specific sections.
189
+
190
+ List jobs and show a pipeline summary:
191
+ ```bash
192
+ etlplus check --config examples/configs/pipeline.yml --jobs
193
+ etlplus check --config examples/configs/pipeline.yml --summary
194
+ ```
195
+
196
+ Show sources or transforms for troubleshooting:
197
+ ```bash
198
+ etlplus check --config examples/configs/pipeline.yml --sources
199
+ etlplus check --config examples/configs/pipeline.yml --transforms
200
+ ```
201
+
202
+ #### Render SQL DDL
203
+
204
+ Use `etlplus render` to turn table schema specs into ready-to-run SQL. Render from a pipeline config
205
+ or from a standalone schema file, and choose the built-in `ddl` or `view` templates (or provide your
206
+ own).
207
+
208
+ Render all tables defined in a pipeline:
209
+ ```bash
210
+ etlplus render --config examples/configs/pipeline.yml --template ddl
211
+ ```
212
+
213
+ Render a single table in that pipeline:
214
+ ```bash
215
+ etlplus render --config examples/configs/pipeline.yml --table customers --template view
216
+ ```
217
+
218
+ Render from a standalone table spec to a file:
219
+ ```bash
220
+ etlplus render --spec schemas/customer.yml --template view -o temp/customer_view.sql
221
+ ```
222
+
172
223
  #### Extract Data
173
224
 
174
225
  Note: For file sources, the format is normally inferred from the filename extension. Use
@@ -306,6 +357,19 @@ For YAML-driven pipelines executed end-to-end (extract → validate → transfor
306
357
  - Authoring: [`docs/pipeline-guide.md`](docs/pipeline-guide.md)
307
358
  - Runner API and internals: [`docs/run-module.md`](docs/run-module.md)
308
359
 
360
+ CLI quick reference for pipelines:
361
+
362
+ ```bash
363
+ # List jobs or show a pipeline summary
364
+ etlplus check --config examples/configs/pipeline.yml --jobs
365
+ etlplus check --config examples/configs/pipeline.yml --summary
366
+
367
+ # Run a job
368
+ etlplus run --config examples/configs/pipeline.yml --job file_to_file_customers
369
+
370
+ # Deprecated shim (will be removed): etlplus pipeline
371
+ ```
372
+
309
373
  ### Complete ETL Pipeline Example
310
374
 
311
375
  ```bash
@@ -7,7 +7,7 @@ etlplus/file.py,sha256=RxIAsGDN4f_vNA2B5-ct88JNd_ISAyYbooIRE5DstS8,17972
7
7
  etlplus/load.py,sha256=BwF3gT4gIr-5CvNMz_aLTCl-w2ihWSTxNVd4X92XFwI,8737
8
8
  etlplus/mixins.py,sha256=ifGpHwWv7U00yqGf-kN93vJax2IiK4jaGtTsPsO3Oak,1350
9
9
  etlplus/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
- etlplus/run.py,sha256=zl_Yx35spcgaa9Xx7-kcJEb1CAYyMIiqtPlsSrYxRfs,12448
10
+ etlplus/run.py,sha256=X4kp5FQlIWVf1_d9oSrchKau7BFDCE1Zkscvu7WPaWw,12340
11
11
  etlplus/run_helpers.py,sha256=bj6MkaeFxjl3CeKG1HoXKx5DwAlXNERVW-GX-z1P_qQ,24373
12
12
  etlplus/transform.py,sha256=uAUVDDHYCgx7GpVez9IK3OAZM-CnCuMa9iox3vwGGJA,25296
13
13
  etlplus/types.py,sha256=SJiZ7wJiSnV4CEvF-9E5nSFLBo4DT9OqHQqj1GSHkv8,6042
@@ -31,21 +31,29 @@ etlplus/api/rate_limiting/__init__.py,sha256=ZySB1dZettEDnWvI1EHf_TZ9L08M_kKsNR-
31
31
  etlplus/api/rate_limiting/config.py,sha256=2b4wIynblN-1EyMqI4aXa71SljzSjXYh5N1Nngr3jOg,9406
32
32
  etlplus/api/rate_limiting/rate_limiter.py,sha256=Uxozqd_Ej5Lsj-M-mLT2WexChgWh7x35_YP10yqYPQA,7159
33
33
  etlplus/cli/__init__.py,sha256=J97-Rv931IL1_b4AXnB7Fbbd7HKnHBpx18NQfC_kE6c,299
34
- etlplus/cli/app.py,sha256=pc9VDUb3Qc8u5-XyDrHJkrSR9D3bq4e9zLbaD8KzyfY,32618
35
- etlplus/cli/handlers.py,sha256=aI_ZlnJCGGkVnVJJPhmPRCXc31MxtLaOeqqJoo3ci48,15816
36
- etlplus/cli/main.py,sha256=9hoitdc9FisrXzwZniTglPWwKsODFAW-A-2QQV4NkBs,12565
34
+ etlplus/cli/app.py,sha256=SYPO-NDwXgymJrACw39jZ_NJrSKAs0O8anuWR5o42WM,35893
35
+ etlplus/cli/handlers.py,sha256=nFMvqHQhJ8kJZPisDCiUHeOhjlqAO6hJvRjXiJTcU74,18951
36
+ etlplus/cli/main.py,sha256=ijYOy72SEsxrTEBan5yADW8CZyr0yddVF8HeMgFw6Zg,16576
37
37
  etlplus/config/__init__.py,sha256=VZWzOg7d2YR9NT6UwKTv44yf2FRUMjTHynkm1Dl5Qzo,1486
38
38
  etlplus/config/connector.py,sha256=0-TIwevHbKRHVmucvyGpPd-3tB1dKHB-dj0yJ6kq5eY,9809
39
39
  etlplus/config/jobs.py,sha256=hmzRCqt0OvCEZZR4ONKrd3lvSv0OmayjLc4yOBk3ug8,7399
40
- etlplus/config/pipeline.py,sha256=7w0WWe21vvPJvTlJoRTZh2j92I1_GOcdwan6xyKfQII,8973
40
+ etlplus/config/pipeline.py,sha256=Va4MQY6KEyKqHGMKPmh09ZcGpx95br-iNUjpkqtzVbw,9500
41
41
  etlplus/config/profile.py,sha256=Ss2zedQGjkaGSpvBLTD4SZaWViMJ7TJPLB8Q2_BTpPg,1898
42
42
  etlplus/config/types.py,sha256=a0epJ3z16HQ5bY3Ctf8s_cQPa3f0HHcwdOcjCP2xoG4,4954
43
43
  etlplus/config/utils.py,sha256=4SUHMkt5bKBhMhiJm-DrnmE2Q4TfOgdNCKz8PJDS27o,3443
44
+ etlplus/database/__init__.py,sha256=0gWnMlQiVHS6SVUxIT9zklQUHU36y-2RF_gN1cx7icg,1018
45
+ etlplus/database/ddl.py,sha256=lIar9KIOoBRslp_P0DnpoMDXzkjt64J5-iVV7CeSV_M,7747
46
+ etlplus/database/engine.py,sha256=54f-XtNKIuogJhsLV9cX_xPoBwcl_HNJTL5HqMCi8kw,3986
47
+ etlplus/database/orm.py,sha256=StjeguokM70oNKq7mNXLyc4_mYUZR-EKW3oGRlsd8QE,9962
48
+ etlplus/database/schema.py,sha256=BmRP2wwX2xex1phLm0tnHrP6A2AQgguA-hSLnK0xwwc,7003
49
+ etlplus/templates/__init__.py,sha256=tsniN7XJYs3NwYxJ6c2HD5upHP3CDkLx-bQCMt97UOM,106
50
+ etlplus/templates/ddl.sql.j2,sha256=s8fMWvcb4eaJVXkifuib1aQPljtZ8buuyB_uA-ZdU3Q,4734
51
+ etlplus/templates/view.sql.j2,sha256=Iy8DHfhq5yyvrUKDxqp_aHIEXY4Tm6j4wT7YDEFWAhk,2180
44
52
  etlplus/validation/__init__.py,sha256=Pe5Xg1_EA4uiNZGYu5WTF3j7odjmyxnAJ8rcioaplSQ,1254
45
53
  etlplus/validation/utils.py,sha256=Mtqg449VIke0ziy_wd2r6yrwJzQkA1iulZC87FzXMjo,10201
46
- etlplus-0.4.6.dist-info/licenses/LICENSE,sha256=MuNO63i6kWmgnV2pbP2SLqP54mk1BGmu7CmbtxMmT-U,1069
47
- etlplus-0.4.6.dist-info/METADATA,sha256=oq0-zYAVKNkZFCBPecObqjJ2LuYcbSX5kVjCErpiDO4,17278
48
- etlplus-0.4.6.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
49
- etlplus-0.4.6.dist-info/entry_points.txt,sha256=6w-2-jzuPa55spzK34h-UKh2JTEShh38adFRONNP9QE,45
50
- etlplus-0.4.6.dist-info/top_level.txt,sha256=aWWF-udn_sLGuHTM6W6MLh99ArS9ROkUWO8Mi8y1_2U,8
51
- etlplus-0.4.6.dist-info/RECORD,,
54
+ etlplus-0.7.0.dist-info/licenses/LICENSE,sha256=MuNO63i6kWmgnV2pbP2SLqP54mk1BGmu7CmbtxMmT-U,1069
55
+ etlplus-0.7.0.dist-info/METADATA,sha256=ulMPDyXMX6p-NcxMBSZfegGrv0LNwAu_686_TVrkJPM,19383
56
+ etlplus-0.7.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
57
+ etlplus-0.7.0.dist-info/entry_points.txt,sha256=6w-2-jzuPa55spzK34h-UKh2JTEShh38adFRONNP9QE,45
58
+ etlplus-0.7.0.dist-info/top_level.txt,sha256=aWWF-udn_sLGuHTM6W6MLh99ArS9ROkUWO8Mi8y1_2U,8
59
+ etlplus-0.7.0.dist-info/RECORD,,