etlplus 0.5.5__py3-none-any.whl → 0.6.1__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.
- etlplus/cli/handlers.py +2 -2
- etlplus/database/__init__.py +23 -0
- etlplus/{ddl.py → database/ddl.py} +160 -46
- {etlplus-0.5.5.dist-info → etlplus-0.6.1.dist-info}/METADATA +1 -1
- {etlplus-0.5.5.dist-info → etlplus-0.6.1.dist-info}/RECORD +9 -8
- {etlplus-0.5.5.dist-info → etlplus-0.6.1.dist-info}/WHEEL +0 -0
- {etlplus-0.5.5.dist-info → etlplus-0.6.1.dist-info}/entry_points.txt +0 -0
- {etlplus-0.5.5.dist-info → etlplus-0.6.1.dist-info}/licenses/LICENSE +0 -0
- {etlplus-0.5.5.dist-info → etlplus-0.6.1.dist-info}/top_level.txt +0 -0
etlplus/cli/handlers.py
CHANGED
|
@@ -18,8 +18,8 @@ from typing import cast
|
|
|
18
18
|
|
|
19
19
|
from ..config import PipelineConfig
|
|
20
20
|
from ..config import load_pipeline_config
|
|
21
|
-
from ..
|
|
22
|
-
from ..
|
|
21
|
+
from ..database import load_table_spec
|
|
22
|
+
from ..database import render_tables
|
|
23
23
|
from ..enums import FileFormat
|
|
24
24
|
from ..extract import extract
|
|
25
25
|
from ..file import File
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""
|
|
2
|
+
:mod:`etlplus.database` package.
|
|
3
|
+
|
|
4
|
+
This package defines database-related utilities for ETLPlus, including:
|
|
5
|
+
- DDL rendering and schema management.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from .ddl import load_table_spec
|
|
11
|
+
from .ddl import render_table_sql
|
|
12
|
+
from .ddl import render_tables
|
|
13
|
+
from .ddl import render_tables_to_string
|
|
14
|
+
|
|
15
|
+
# SECTION: EXPORTS ========================================================== #
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
__all__ = [
|
|
19
|
+
'load_table_spec',
|
|
20
|
+
'render_table_sql',
|
|
21
|
+
'render_tables',
|
|
22
|
+
'render_tables_to_string',
|
|
23
|
+
]
|
|
@@ -11,18 +11,20 @@ can emit DDLs without shelling out to that script.
|
|
|
11
11
|
from __future__ import annotations
|
|
12
12
|
|
|
13
13
|
import importlib.resources
|
|
14
|
-
import json
|
|
15
14
|
import os
|
|
16
15
|
from collections.abc import Iterable
|
|
17
16
|
from collections.abc import Mapping
|
|
18
17
|
from pathlib import Path
|
|
19
18
|
from typing import Any
|
|
19
|
+
from typing import Final
|
|
20
20
|
|
|
21
21
|
from jinja2 import DictLoader
|
|
22
22
|
from jinja2 import Environment
|
|
23
23
|
from jinja2 import FileSystemLoader
|
|
24
24
|
from jinja2 import StrictUndefined
|
|
25
25
|
|
|
26
|
+
from ..file import File
|
|
27
|
+
|
|
26
28
|
# SECTION: EXPORTS ========================================================== #
|
|
27
29
|
|
|
28
30
|
|
|
@@ -31,13 +33,26 @@ __all__ = [
|
|
|
31
33
|
'load_table_spec',
|
|
32
34
|
'render_table_sql',
|
|
33
35
|
'render_tables',
|
|
36
|
+
'render_tables_to_string',
|
|
34
37
|
]
|
|
35
38
|
|
|
36
39
|
|
|
40
|
+
# SECTION: INTERNAL CONSTANTS =============================================== #
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
_SUPPORTED_SPEC_SUFFIXES: Final[frozenset[str]] = frozenset(
|
|
44
|
+
{
|
|
45
|
+
'.json',
|
|
46
|
+
'.yml',
|
|
47
|
+
'.yaml',
|
|
48
|
+
},
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
|
|
37
52
|
# SECTION: CONSTANTS ======================================================== #
|
|
38
53
|
|
|
39
54
|
|
|
40
|
-
TEMPLATES = {
|
|
55
|
+
TEMPLATES: Final[dict[str, str]] = {
|
|
41
56
|
'ddl': 'ddl.sql.j2',
|
|
42
57
|
'view': 'view.sql.j2',
|
|
43
58
|
}
|
|
@@ -46,12 +61,68 @@ TEMPLATES = {
|
|
|
46
61
|
# SECTION: INTERNAL FUNCTIONS =============================================== #
|
|
47
62
|
|
|
48
63
|
|
|
49
|
-
def
|
|
64
|
+
def _load_template_text(
|
|
65
|
+
filename: str,
|
|
66
|
+
) -> str:
|
|
67
|
+
"""Return the bundled template text.
|
|
68
|
+
|
|
69
|
+
Parameters
|
|
70
|
+
----------
|
|
71
|
+
filename : str
|
|
72
|
+
Template filename located inside the package data folder.
|
|
73
|
+
|
|
74
|
+
Returns
|
|
75
|
+
-------
|
|
76
|
+
str
|
|
77
|
+
Raw template contents.
|
|
78
|
+
|
|
79
|
+
Raises
|
|
80
|
+
------
|
|
81
|
+
FileNotFoundError
|
|
82
|
+
If the template file cannot be located in package data.
|
|
83
|
+
"""
|
|
84
|
+
|
|
85
|
+
try:
|
|
86
|
+
return (
|
|
87
|
+
importlib.resources.files(
|
|
88
|
+
'etlplus.templates',
|
|
89
|
+
)
|
|
90
|
+
.joinpath(filename)
|
|
91
|
+
.read_text(encoding='utf-8')
|
|
92
|
+
)
|
|
93
|
+
except FileNotFoundError as exc: # pragma: no cover - deployment guard
|
|
94
|
+
raise FileNotFoundError(
|
|
95
|
+
f'Could not load template {filename} '
|
|
96
|
+
f'from etlplus.templates package data.',
|
|
97
|
+
) from exc
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def _resolve_template(
|
|
50
101
|
*,
|
|
51
102
|
template_key: str | None,
|
|
52
103
|
template_path: str | None,
|
|
53
|
-
) -> Environment:
|
|
54
|
-
"""Return
|
|
104
|
+
) -> tuple[Environment, str]:
|
|
105
|
+
"""Return environment and template name for rendering.
|
|
106
|
+
|
|
107
|
+
Parameters
|
|
108
|
+
----------
|
|
109
|
+
template_key : str | None
|
|
110
|
+
Named template key bundled with the package.
|
|
111
|
+
template_path : str | None
|
|
112
|
+
Explicit template file override.
|
|
113
|
+
|
|
114
|
+
Returns
|
|
115
|
+
-------
|
|
116
|
+
tuple[Environment, str]
|
|
117
|
+
Pair of configured Jinja environment and the template identifier.
|
|
118
|
+
|
|
119
|
+
Raises
|
|
120
|
+
------
|
|
121
|
+
FileNotFoundError
|
|
122
|
+
If the provided template path does not exist.
|
|
123
|
+
ValueError
|
|
124
|
+
If the template key is unknown.
|
|
125
|
+
"""
|
|
55
126
|
file_override = template_path or os.environ.get('TEMPLATE_NAME')
|
|
56
127
|
if file_override:
|
|
57
128
|
path = Path(file_override)
|
|
@@ -64,8 +135,7 @@ def _build_env(
|
|
|
64
135
|
trim_blocks=True,
|
|
65
136
|
lstrip_blocks=True,
|
|
66
137
|
)
|
|
67
|
-
env
|
|
68
|
-
return env
|
|
138
|
+
return env, path.name
|
|
69
139
|
|
|
70
140
|
key = (template_key or 'ddl').strip()
|
|
71
141
|
if key not in TEMPLATES:
|
|
@@ -84,51 +154,59 @@ def _build_env(
|
|
|
84
154
|
trim_blocks=True,
|
|
85
155
|
lstrip_blocks=True,
|
|
86
156
|
)
|
|
87
|
-
env
|
|
88
|
-
return env
|
|
157
|
+
return env, key
|
|
89
158
|
|
|
90
159
|
|
|
91
|
-
|
|
92
|
-
"""Return the raw template text bundled with the package."""
|
|
160
|
+
# SECTION: FUNCTIONS ======================================================== #
|
|
93
161
|
|
|
94
|
-
try:
|
|
95
|
-
return (
|
|
96
|
-
importlib.resources.files(
|
|
97
|
-
'etlplus.templates',
|
|
98
|
-
)
|
|
99
|
-
.joinpath(filename)
|
|
100
|
-
.read_text(encoding='utf-8')
|
|
101
|
-
)
|
|
102
|
-
except FileNotFoundError as exc: # pragma: no cover - deployment guard
|
|
103
|
-
raise FileNotFoundError(
|
|
104
|
-
f'Could not load template {filename} '
|
|
105
|
-
f'from etlplus.templates package data.',
|
|
106
|
-
) from exc
|
|
107
162
|
|
|
163
|
+
def load_table_spec(
|
|
164
|
+
path: Path | str,
|
|
165
|
+
) -> dict[str, Any]:
|
|
166
|
+
"""
|
|
167
|
+
Load a table specification from disk.
|
|
108
168
|
|
|
109
|
-
|
|
169
|
+
Parameters
|
|
170
|
+
----------
|
|
171
|
+
path : Path | str
|
|
172
|
+
Path to the JSON or YAML specification file.
|
|
110
173
|
|
|
174
|
+
Returns
|
|
175
|
+
-------
|
|
176
|
+
dict[str, Any]
|
|
177
|
+
Parsed table specification mapping.
|
|
111
178
|
|
|
112
|
-
|
|
113
|
-
|
|
179
|
+
Raises
|
|
180
|
+
------
|
|
181
|
+
ImportError
|
|
182
|
+
If the file cannot be read due to missing dependencies.
|
|
183
|
+
RuntimeError
|
|
184
|
+
If the YAML dependency is missing for YAML specs.
|
|
185
|
+
TypeError
|
|
186
|
+
If the loaded spec is not a mapping.
|
|
187
|
+
ValueError
|
|
188
|
+
If the file suffix is not supported.
|
|
189
|
+
"""
|
|
114
190
|
|
|
115
191
|
spec_path = Path(path)
|
|
116
|
-
text = spec_path.read_text(encoding='utf-8')
|
|
117
192
|
suffix = spec_path.suffix.lower()
|
|
118
193
|
|
|
119
|
-
if suffix
|
|
120
|
-
|
|
194
|
+
if suffix not in _SUPPORTED_SPEC_SUFFIXES:
|
|
195
|
+
raise ValueError('Spec must be .json, .yml, or .yaml')
|
|
121
196
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
197
|
+
try:
|
|
198
|
+
spec = File.read_file(spec_path)
|
|
199
|
+
except ImportError as e:
|
|
200
|
+
if suffix in {'.yml', '.yaml'}:
|
|
126
201
|
raise RuntimeError(
|
|
127
202
|
'Missing dependency: pyyaml is required for YAML specs.',
|
|
128
|
-
) from
|
|
129
|
-
|
|
203
|
+
) from e
|
|
204
|
+
raise
|
|
130
205
|
|
|
131
|
-
|
|
206
|
+
if not isinstance(spec, Mapping):
|
|
207
|
+
raise TypeError('Table spec must be a mapping')
|
|
208
|
+
|
|
209
|
+
return dict(spec)
|
|
132
210
|
|
|
133
211
|
|
|
134
212
|
def render_table_sql(
|
|
@@ -153,16 +231,11 @@ def render_table_sql(
|
|
|
153
231
|
-------
|
|
154
232
|
str
|
|
155
233
|
Rendered SQL string.
|
|
156
|
-
|
|
157
|
-
Raises
|
|
158
|
-
------
|
|
159
|
-
TypeError
|
|
160
|
-
If the loaded template name is not a string.
|
|
161
234
|
"""
|
|
162
|
-
env =
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
235
|
+
env, template_name = _resolve_template(
|
|
236
|
+
template_key=template,
|
|
237
|
+
template_path=template_path,
|
|
238
|
+
)
|
|
166
239
|
tmpl = env.get_template(template_name)
|
|
167
240
|
return tmpl.render(spec=spec).rstrip() + '\n'
|
|
168
241
|
|
|
@@ -195,3 +268,44 @@ def render_tables(
|
|
|
195
268
|
render_table_sql(spec, template=template, template_path=template_path)
|
|
196
269
|
for spec in specs
|
|
197
270
|
]
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
def render_tables_to_string(
|
|
274
|
+
spec_paths: Iterable[Path | str],
|
|
275
|
+
*,
|
|
276
|
+
template: str | None = 'ddl',
|
|
277
|
+
template_path: Path | str | None = None,
|
|
278
|
+
) -> str:
|
|
279
|
+
"""
|
|
280
|
+
Render one or more specs and concatenate the SQL payloads.
|
|
281
|
+
|
|
282
|
+
Parameters
|
|
283
|
+
----------
|
|
284
|
+
spec_paths : Iterable[Path | str]
|
|
285
|
+
Paths to table specification files.
|
|
286
|
+
template : str | None, optional
|
|
287
|
+
Template key bundled with ETLPlus. Defaults to ``'ddl'``.
|
|
288
|
+
template_path : Path | str | None, optional
|
|
289
|
+
Custom Jinja template to override the bundled templates.
|
|
290
|
+
|
|
291
|
+
Returns
|
|
292
|
+
-------
|
|
293
|
+
str
|
|
294
|
+
Concatenated SQL payload suitable for writing to disk or stdout.
|
|
295
|
+
"""
|
|
296
|
+
|
|
297
|
+
resolved_template_path = (
|
|
298
|
+
str(template_path) if template_path is not None else None
|
|
299
|
+
)
|
|
300
|
+
rendered_sql: list[str] = []
|
|
301
|
+
for spec_path in spec_paths:
|
|
302
|
+
spec = load_table_spec(spec_path)
|
|
303
|
+
rendered_sql.append(
|
|
304
|
+
render_table_sql(
|
|
305
|
+
spec,
|
|
306
|
+
template=template,
|
|
307
|
+
template_path=resolved_template_path,
|
|
308
|
+
),
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
return ''.join(rendered_sql)
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
etlplus/__init__.py,sha256=M2gScnyir6WOMAh_EuoQIiAzdcTls0_5hbd_Q6of8I0,1021
|
|
2
2
|
etlplus/__main__.py,sha256=btoROneNiigyfBU7BSzPKZ1R9gzBMpxcpsbPwmuHwTM,479
|
|
3
3
|
etlplus/__version__.py,sha256=1E0GMK_yUWCMQFKxXjTvyMwofi0qT2k4CDNiHWiymWE,327
|
|
4
|
-
etlplus/ddl.py,sha256=uYkiMTx1uDlUypnXCYy0K5ARnHRMHFVzzg8PizBQRLg,5306
|
|
5
4
|
etlplus/enums.py,sha256=V_j18Ud2BCXpFsBk2pZGrvCVrvAMJ7uja1z9fppFGso,10175
|
|
6
5
|
etlplus/extract.py,sha256=f44JdHhNTACxgn44USx05paKTwq7LQY-V4wANCW9hVM,6173
|
|
7
6
|
etlplus/file.py,sha256=RxIAsGDN4f_vNA2B5-ct88JNd_ISAyYbooIRE5DstS8,17972
|
|
@@ -33,7 +32,7 @@ etlplus/api/rate_limiting/config.py,sha256=2b4wIynblN-1EyMqI4aXa71SljzSjXYh5N1Nn
|
|
|
33
32
|
etlplus/api/rate_limiting/rate_limiter.py,sha256=Uxozqd_Ej5Lsj-M-mLT2WexChgWh7x35_YP10yqYPQA,7159
|
|
34
33
|
etlplus/cli/__init__.py,sha256=J97-Rv931IL1_b4AXnB7Fbbd7HKnHBpx18NQfC_kE6c,299
|
|
35
34
|
etlplus/cli/app.py,sha256=SYPO-NDwXgymJrACw39jZ_NJrSKAs0O8anuWR5o42WM,35893
|
|
36
|
-
etlplus/cli/handlers.py,sha256=
|
|
35
|
+
etlplus/cli/handlers.py,sha256=nFMvqHQhJ8kJZPisDCiUHeOhjlqAO6hJvRjXiJTcU74,18951
|
|
37
36
|
etlplus/cli/main.py,sha256=ijYOy72SEsxrTEBan5yADW8CZyr0yddVF8HeMgFw6Zg,16576
|
|
38
37
|
etlplus/config/__init__.py,sha256=VZWzOg7d2YR9NT6UwKTv44yf2FRUMjTHynkm1Dl5Qzo,1486
|
|
39
38
|
etlplus/config/connector.py,sha256=0-TIwevHbKRHVmucvyGpPd-3tB1dKHB-dj0yJ6kq5eY,9809
|
|
@@ -42,14 +41,16 @@ etlplus/config/pipeline.py,sha256=Va4MQY6KEyKqHGMKPmh09ZcGpx95br-iNUjpkqtzVbw,95
|
|
|
42
41
|
etlplus/config/profile.py,sha256=Ss2zedQGjkaGSpvBLTD4SZaWViMJ7TJPLB8Q2_BTpPg,1898
|
|
43
42
|
etlplus/config/types.py,sha256=a0epJ3z16HQ5bY3Ctf8s_cQPa3f0HHcwdOcjCP2xoG4,4954
|
|
44
43
|
etlplus/config/utils.py,sha256=4SUHMkt5bKBhMhiJm-DrnmE2Q4TfOgdNCKz8PJDS27o,3443
|
|
44
|
+
etlplus/database/__init__.py,sha256=MNaqpiPNTMbwbHxdh865GXS3q4H4dkAn2YLl3GFQU8E,525
|
|
45
|
+
etlplus/database/ddl.py,sha256=Hvg1PwwaIMU3y8emMWml4CzvQGvvg6KZfsHo3-EWJjg,7738
|
|
45
46
|
etlplus/templates/__init__.py,sha256=tsniN7XJYs3NwYxJ6c2HD5upHP3CDkLx-bQCMt97UOM,106
|
|
46
47
|
etlplus/templates/ddl.sql.j2,sha256=s8fMWvcb4eaJVXkifuib1aQPljtZ8buuyB_uA-ZdU3Q,4734
|
|
47
48
|
etlplus/templates/view.sql.j2,sha256=Iy8DHfhq5yyvrUKDxqp_aHIEXY4Tm6j4wT7YDEFWAhk,2180
|
|
48
49
|
etlplus/validation/__init__.py,sha256=Pe5Xg1_EA4uiNZGYu5WTF3j7odjmyxnAJ8rcioaplSQ,1254
|
|
49
50
|
etlplus/validation/utils.py,sha256=Mtqg449VIke0ziy_wd2r6yrwJzQkA1iulZC87FzXMjo,10201
|
|
50
|
-
etlplus-0.
|
|
51
|
-
etlplus-0.
|
|
52
|
-
etlplus-0.
|
|
53
|
-
etlplus-0.
|
|
54
|
-
etlplus-0.
|
|
55
|
-
etlplus-0.
|
|
51
|
+
etlplus-0.6.1.dist-info/licenses/LICENSE,sha256=MuNO63i6kWmgnV2pbP2SLqP54mk1BGmu7CmbtxMmT-U,1069
|
|
52
|
+
etlplus-0.6.1.dist-info/METADATA,sha256=NX1ADs4_MZ3J9QLGj7bPRyXc43HJJxfOzfcmIg3VYHc,19288
|
|
53
|
+
etlplus-0.6.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
54
|
+
etlplus-0.6.1.dist-info/entry_points.txt,sha256=6w-2-jzuPa55spzK34h-UKh2JTEShh38adFRONNP9QE,45
|
|
55
|
+
etlplus-0.6.1.dist-info/top_level.txt,sha256=aWWF-udn_sLGuHTM6W6MLh99ArS9ROkUWO8Mi8y1_2U,8
|
|
56
|
+
etlplus-0.6.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|