etlplus 0.7.0__py3-none-any.whl → 0.8.3__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/api/README.md +24 -26
- etlplus/cli/commands.py +870 -0
- etlplus/cli/constants.py +65 -0
- etlplus/cli/handlers.py +300 -417
- etlplus/cli/io.py +320 -0
- etlplus/cli/main.py +14 -417
- etlplus/cli/options.py +49 -0
- etlplus/cli/state.py +335 -0
- etlplus/cli/types.py +33 -0
- etlplus/database/__init__.py +2 -0
- etlplus/database/ddl.py +37 -29
- etlplus/database/engine.py +10 -5
- etlplus/database/orm.py +18 -11
- etlplus/database/schema.py +3 -2
- etlplus/database/types.py +33 -0
- etlplus/types.py +5 -0
- etlplus/utils.py +0 -31
- {etlplus-0.7.0.dist-info → etlplus-0.8.3.dist-info}/METADATA +5 -4
- {etlplus-0.7.0.dist-info → etlplus-0.8.3.dist-info}/RECORD +23 -17
- etlplus/cli/app.py +0 -1367
- {etlplus-0.7.0.dist-info → etlplus-0.8.3.dist-info}/WHEEL +0 -0
- {etlplus-0.7.0.dist-info → etlplus-0.8.3.dist-info}/entry_points.txt +0 -0
- {etlplus-0.7.0.dist-info → etlplus-0.8.3.dist-info}/licenses/LICENSE +0 -0
- {etlplus-0.7.0.dist-info → etlplus-0.8.3.dist-info}/top_level.txt +0 -0
etlplus/cli/commands.py
ADDED
|
@@ -0,0 +1,870 @@
|
|
|
1
|
+
"""
|
|
2
|
+
:mod:`etlplus.cli.commands` module.
|
|
3
|
+
|
|
4
|
+
Typer application and subcommands for the ``etlplus`` command-line interface
|
|
5
|
+
(CLI). Typer (Click) is used for CLI parsing, help text, and subcommand
|
|
6
|
+
dispatch. The Typer layer focuses on ergonomics (git-style subcommands,
|
|
7
|
+
optional inference of resource types, stdin/stdout piping, and quality-of-life
|
|
8
|
+
flags), while delegating business logic to the existing :func:`*_handler`
|
|
9
|
+
handlers.
|
|
10
|
+
|
|
11
|
+
Subcommands
|
|
12
|
+
-----------
|
|
13
|
+
- ``check``: inspect a pipeline configuration
|
|
14
|
+
- ``extract``: extract data from files, databases, or REST APIs
|
|
15
|
+
- ``load``: load data to files, databases, or REST APIs
|
|
16
|
+
- ``render``: render SQL DDL from table schema specs
|
|
17
|
+
- ``transform``: transform records
|
|
18
|
+
- ``validate``: validate data against rules
|
|
19
|
+
|
|
20
|
+
Notes
|
|
21
|
+
-----
|
|
22
|
+
- Use ``-`` to read from stdin or to write to stdout.
|
|
23
|
+
- Commands ``extract`` and ``transform`` support the command-line option
|
|
24
|
+
``--source-type`` to override inferred resource types.
|
|
25
|
+
- Commands ``transform`` and ``load`` support the command-line option
|
|
26
|
+
``--target-type`` to override inferred resource types.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
from __future__ import annotations
|
|
30
|
+
|
|
31
|
+
from typing import Annotated
|
|
32
|
+
from typing import Any
|
|
33
|
+
from typing import Literal
|
|
34
|
+
from typing import cast
|
|
35
|
+
|
|
36
|
+
import typer
|
|
37
|
+
|
|
38
|
+
from .. import __version__
|
|
39
|
+
from ..enums import FileFormat
|
|
40
|
+
from . import handlers
|
|
41
|
+
from .constants import CLI_DESCRIPTION
|
|
42
|
+
from .constants import CLI_EPILOG
|
|
43
|
+
from .constants import DATA_CONNECTORS
|
|
44
|
+
from .constants import FILE_FORMATS
|
|
45
|
+
from .io import parse_json_payload
|
|
46
|
+
from .options import typer_format_option_kwargs
|
|
47
|
+
from .state import CliState
|
|
48
|
+
from .state import ensure_state
|
|
49
|
+
from .state import infer_resource_type_or_exit
|
|
50
|
+
from .state import infer_resource_type_soft
|
|
51
|
+
from .state import log_inferred_resource
|
|
52
|
+
from .state import optional_choice
|
|
53
|
+
from .state import resolve_resource_type
|
|
54
|
+
from .state import validate_choice
|
|
55
|
+
|
|
56
|
+
# SECTION: EXPORTS ========================================================== #
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
__all__ = ['app']
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
# SECTION: TYPE ALIASES ==================================================== #
|
|
63
|
+
|
|
64
|
+
OperationsOption = Annotated[
|
|
65
|
+
str,
|
|
66
|
+
typer.Option(
|
|
67
|
+
'--operations',
|
|
68
|
+
help='Transformation operations as JSON string.',
|
|
69
|
+
),
|
|
70
|
+
]
|
|
71
|
+
|
|
72
|
+
PipelineConfigOption = Annotated[
|
|
73
|
+
str,
|
|
74
|
+
typer.Option(
|
|
75
|
+
...,
|
|
76
|
+
'--config',
|
|
77
|
+
metavar='PATH',
|
|
78
|
+
help='Path to pipeline YAML configuration file.',
|
|
79
|
+
),
|
|
80
|
+
]
|
|
81
|
+
|
|
82
|
+
RenderConfigOption = Annotated[
|
|
83
|
+
str | None,
|
|
84
|
+
typer.Option(
|
|
85
|
+
'--config',
|
|
86
|
+
metavar='PATH',
|
|
87
|
+
help='Pipeline YAML that includes table_schemas for rendering.',
|
|
88
|
+
show_default=False,
|
|
89
|
+
),
|
|
90
|
+
]
|
|
91
|
+
|
|
92
|
+
RenderOutputOption = Annotated[
|
|
93
|
+
str | None,
|
|
94
|
+
typer.Option(
|
|
95
|
+
'--output',
|
|
96
|
+
'-o',
|
|
97
|
+
metavar='PATH',
|
|
98
|
+
help='Write rendered SQL to PATH (default: stdout).',
|
|
99
|
+
),
|
|
100
|
+
]
|
|
101
|
+
|
|
102
|
+
RenderSpecOption = Annotated[
|
|
103
|
+
str | None,
|
|
104
|
+
typer.Option(
|
|
105
|
+
'--spec',
|
|
106
|
+
metavar='PATH',
|
|
107
|
+
help='Standalone table spec file (.yml/.yaml/.json).',
|
|
108
|
+
show_default=False,
|
|
109
|
+
),
|
|
110
|
+
]
|
|
111
|
+
|
|
112
|
+
RenderTableOption = Annotated[
|
|
113
|
+
str | None,
|
|
114
|
+
typer.Option(
|
|
115
|
+
'--table',
|
|
116
|
+
metavar='NAME',
|
|
117
|
+
help='Filter to a single table name from table_schemas.',
|
|
118
|
+
),
|
|
119
|
+
]
|
|
120
|
+
|
|
121
|
+
RenderTemplateOption = Annotated[
|
|
122
|
+
Literal['ddl', 'view'] | None,
|
|
123
|
+
typer.Option(
|
|
124
|
+
'--template',
|
|
125
|
+
'-t',
|
|
126
|
+
metavar='KEY',
|
|
127
|
+
help='Template key (ddl/view).',
|
|
128
|
+
show_default=True,
|
|
129
|
+
),
|
|
130
|
+
]
|
|
131
|
+
|
|
132
|
+
RenderTemplatePathOption = Annotated[
|
|
133
|
+
str | None,
|
|
134
|
+
typer.Option(
|
|
135
|
+
'--template-path',
|
|
136
|
+
metavar='PATH',
|
|
137
|
+
help=(
|
|
138
|
+
'Explicit path to a Jinja template file (overrides template key).'
|
|
139
|
+
),
|
|
140
|
+
),
|
|
141
|
+
]
|
|
142
|
+
|
|
143
|
+
RulesOption = Annotated[
|
|
144
|
+
str,
|
|
145
|
+
typer.Option(
|
|
146
|
+
'--rules',
|
|
147
|
+
help='Validation rules as JSON string.',
|
|
148
|
+
),
|
|
149
|
+
]
|
|
150
|
+
|
|
151
|
+
SourceFormatOption = Annotated[
|
|
152
|
+
FileFormat | None,
|
|
153
|
+
typer.Option(
|
|
154
|
+
'--source-format',
|
|
155
|
+
**typer_format_option_kwargs(context='source'),
|
|
156
|
+
),
|
|
157
|
+
]
|
|
158
|
+
|
|
159
|
+
SourceInputArg = Annotated[
|
|
160
|
+
str,
|
|
161
|
+
typer.Argument(
|
|
162
|
+
...,
|
|
163
|
+
metavar='SOURCE',
|
|
164
|
+
help=(
|
|
165
|
+
'Extract from SOURCE. Use --from/--source-type to override the '
|
|
166
|
+
'inferred connector when needed.'
|
|
167
|
+
),
|
|
168
|
+
),
|
|
169
|
+
]
|
|
170
|
+
|
|
171
|
+
SourceOverrideOption = Annotated[
|
|
172
|
+
str | None,
|
|
173
|
+
typer.Option(
|
|
174
|
+
'--source-type',
|
|
175
|
+
metavar='CONNECTOR',
|
|
176
|
+
show_default=False,
|
|
177
|
+
rich_help_panel='I/O overrides',
|
|
178
|
+
help='Override the inferred source type (file, database, api).',
|
|
179
|
+
),
|
|
180
|
+
]
|
|
181
|
+
|
|
182
|
+
StdinFormatOption = Annotated[
|
|
183
|
+
FileFormat | None,
|
|
184
|
+
typer.Option(
|
|
185
|
+
'--source-format',
|
|
186
|
+
**typer_format_option_kwargs(context='source'),
|
|
187
|
+
),
|
|
188
|
+
]
|
|
189
|
+
|
|
190
|
+
StreamingSourceArg = Annotated[
|
|
191
|
+
str,
|
|
192
|
+
typer.Argument(
|
|
193
|
+
...,
|
|
194
|
+
metavar='SOURCE',
|
|
195
|
+
help=(
|
|
196
|
+
'Data source to transform or validate (path, JSON payload, or '
|
|
197
|
+
'- for stdin).'
|
|
198
|
+
),
|
|
199
|
+
),
|
|
200
|
+
]
|
|
201
|
+
|
|
202
|
+
TargetFormatOption = Annotated[
|
|
203
|
+
FileFormat | None,
|
|
204
|
+
typer.Option(
|
|
205
|
+
'--target-format',
|
|
206
|
+
**typer_format_option_kwargs(context='target'),
|
|
207
|
+
),
|
|
208
|
+
]
|
|
209
|
+
|
|
210
|
+
TargetInputArg = Annotated[
|
|
211
|
+
str,
|
|
212
|
+
typer.Argument(
|
|
213
|
+
...,
|
|
214
|
+
metavar='TARGET',
|
|
215
|
+
help=(
|
|
216
|
+
'Load JSON data from stdin into TARGET. Use --to/--target-type '
|
|
217
|
+
'to override connector inference when needed. Source data must '
|
|
218
|
+
'be piped into stdin.'
|
|
219
|
+
),
|
|
220
|
+
),
|
|
221
|
+
]
|
|
222
|
+
|
|
223
|
+
TargetOverrideOption = Annotated[
|
|
224
|
+
str | None,
|
|
225
|
+
typer.Option(
|
|
226
|
+
'--target-type',
|
|
227
|
+
metavar='CONNECTOR',
|
|
228
|
+
show_default=False,
|
|
229
|
+
rich_help_panel='I/O overrides',
|
|
230
|
+
help='Override the inferred target type (file, database, api).',
|
|
231
|
+
),
|
|
232
|
+
]
|
|
233
|
+
|
|
234
|
+
TargetPathOption = Annotated[
|
|
235
|
+
str | None,
|
|
236
|
+
typer.Option(
|
|
237
|
+
'--target',
|
|
238
|
+
metavar='PATH',
|
|
239
|
+
help='Target file for transformed or validated output (- for stdout).',
|
|
240
|
+
),
|
|
241
|
+
]
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
# SECTION: INTERNAL FUNCTIONS =============================================== #
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def _parse_json_option(
|
|
248
|
+
value: str,
|
|
249
|
+
flag: str,
|
|
250
|
+
) -> Any:
|
|
251
|
+
"""
|
|
252
|
+
Parse JSON option values and surface a helpful CLI error.
|
|
253
|
+
|
|
254
|
+
Parameters
|
|
255
|
+
----------
|
|
256
|
+
value : str
|
|
257
|
+
The JSON string to parse.
|
|
258
|
+
flag : str
|
|
259
|
+
The CLI flag name for error messages.
|
|
260
|
+
|
|
261
|
+
Returns
|
|
262
|
+
-------
|
|
263
|
+
Any
|
|
264
|
+
The parsed JSON value.
|
|
265
|
+
|
|
266
|
+
Raises
|
|
267
|
+
------
|
|
268
|
+
typer.BadParameter
|
|
269
|
+
When the JSON is invalid.
|
|
270
|
+
"""
|
|
271
|
+
try:
|
|
272
|
+
return parse_json_payload(value)
|
|
273
|
+
except ValueError as e:
|
|
274
|
+
raise typer.BadParameter(
|
|
275
|
+
f'Invalid JSON for {flag}: {e}',
|
|
276
|
+
) from e
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
# SECTION: TYPER APP ======================================================== #
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
app = typer.Typer(
|
|
283
|
+
name='etlplus',
|
|
284
|
+
help=CLI_DESCRIPTION,
|
|
285
|
+
epilog=CLI_EPILOG,
|
|
286
|
+
add_completion=True,
|
|
287
|
+
no_args_is_help=False,
|
|
288
|
+
rich_markup_mode='markdown',
|
|
289
|
+
)
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
@app.callback(invoke_without_command=True)
|
|
293
|
+
def _root(
|
|
294
|
+
ctx: typer.Context,
|
|
295
|
+
version: bool = typer.Option(
|
|
296
|
+
False,
|
|
297
|
+
'--version',
|
|
298
|
+
'-V',
|
|
299
|
+
is_eager=True,
|
|
300
|
+
help='Show the version and exit.',
|
|
301
|
+
),
|
|
302
|
+
pretty: bool = typer.Option(
|
|
303
|
+
True,
|
|
304
|
+
'--pretty/--no-pretty',
|
|
305
|
+
help='Pretty-print JSON output (default: pretty).',
|
|
306
|
+
),
|
|
307
|
+
quiet: bool = typer.Option(
|
|
308
|
+
False,
|
|
309
|
+
'--quiet',
|
|
310
|
+
'-q',
|
|
311
|
+
help='Suppress warnings and non-essential output.',
|
|
312
|
+
),
|
|
313
|
+
verbose: bool = typer.Option(
|
|
314
|
+
False,
|
|
315
|
+
'--verbose',
|
|
316
|
+
'-v',
|
|
317
|
+
help='Emit extra diagnostics to stderr.',
|
|
318
|
+
),
|
|
319
|
+
) -> None:
|
|
320
|
+
"""
|
|
321
|
+
Seed the Typer context with runtime flags and handle root-only options.
|
|
322
|
+
|
|
323
|
+
Parameters
|
|
324
|
+
----------
|
|
325
|
+
ctx : typer.Context
|
|
326
|
+
The Typer command context.
|
|
327
|
+
version : bool, optional
|
|
328
|
+
Show the version and exit. Default is ``False``.
|
|
329
|
+
pretty : bool, optional
|
|
330
|
+
Whether to pretty-print JSON output. Default is ``True``.
|
|
331
|
+
quiet : bool, optional
|
|
332
|
+
Whether to suppress warnings and non-essential output. Default is
|
|
333
|
+
``False``.
|
|
334
|
+
verbose : bool, optional
|
|
335
|
+
Whether to emit extra diagnostics to stderr. Default is ``False``.
|
|
336
|
+
|
|
337
|
+
Raises
|
|
338
|
+
------
|
|
339
|
+
typer.Exit
|
|
340
|
+
When ``--version`` is provided or no subcommand is invoked.
|
|
341
|
+
"""
|
|
342
|
+
ctx.obj = CliState(pretty=pretty, quiet=quiet, verbose=verbose)
|
|
343
|
+
|
|
344
|
+
if version:
|
|
345
|
+
typer.echo(f'etlplus {__version__}')
|
|
346
|
+
raise typer.Exit(0)
|
|
347
|
+
|
|
348
|
+
if ctx.invoked_subcommand is None and not ctx.resilient_parsing:
|
|
349
|
+
typer.echo(ctx.command.get_help(ctx))
|
|
350
|
+
raise typer.Exit(0)
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
@app.command('check')
|
|
354
|
+
def check_cmd(
|
|
355
|
+
ctx: typer.Context,
|
|
356
|
+
config: PipelineConfigOption,
|
|
357
|
+
jobs: bool = typer.Option(
|
|
358
|
+
False,
|
|
359
|
+
'--jobs',
|
|
360
|
+
help='List available job names and exit',
|
|
361
|
+
),
|
|
362
|
+
pipelines: bool = typer.Option(
|
|
363
|
+
False,
|
|
364
|
+
'--pipelines',
|
|
365
|
+
help='List ETL pipelines',
|
|
366
|
+
),
|
|
367
|
+
sources: bool = typer.Option(
|
|
368
|
+
False,
|
|
369
|
+
'--sources',
|
|
370
|
+
help='List data sources',
|
|
371
|
+
),
|
|
372
|
+
summary: bool = typer.Option(
|
|
373
|
+
False,
|
|
374
|
+
'--summary',
|
|
375
|
+
help='Show pipeline summary (name, version, sources, targets, jobs)',
|
|
376
|
+
),
|
|
377
|
+
targets: bool = typer.Option(
|
|
378
|
+
False,
|
|
379
|
+
'--targets',
|
|
380
|
+
help='List data targets',
|
|
381
|
+
),
|
|
382
|
+
transforms: bool = typer.Option(
|
|
383
|
+
False,
|
|
384
|
+
'--transforms',
|
|
385
|
+
help='List data transforms',
|
|
386
|
+
),
|
|
387
|
+
) -> int:
|
|
388
|
+
"""
|
|
389
|
+
Inspect a pipeline configuration.
|
|
390
|
+
|
|
391
|
+
Parameters
|
|
392
|
+
----------
|
|
393
|
+
ctx : typer.Context
|
|
394
|
+
The Typer context.
|
|
395
|
+
config : PipelineConfigOption
|
|
396
|
+
Path to pipeline YAML configuration file.
|
|
397
|
+
jobs : bool, optional
|
|
398
|
+
List available job names and exit. Default is ``False``.
|
|
399
|
+
pipelines : bool, optional
|
|
400
|
+
List ETL pipelines. Default is ``False``.
|
|
401
|
+
sources : bool, optional
|
|
402
|
+
List data sources. Default is ``False``.
|
|
403
|
+
summary : bool, optional
|
|
404
|
+
Show pipeline summary (name, version, sources, targets, jobs). Default
|
|
405
|
+
is ``False``.
|
|
406
|
+
targets : bool, optional
|
|
407
|
+
List data targets. Default is ``False``.
|
|
408
|
+
transforms : bool, optional
|
|
409
|
+
List data transforms. Default is ``False``.
|
|
410
|
+
|
|
411
|
+
Returns
|
|
412
|
+
-------
|
|
413
|
+
int
|
|
414
|
+
Exit code.
|
|
415
|
+
"""
|
|
416
|
+
state = ensure_state(ctx)
|
|
417
|
+
return int(
|
|
418
|
+
handlers.check_handler(
|
|
419
|
+
config=config,
|
|
420
|
+
jobs=jobs,
|
|
421
|
+
pipelines=pipelines,
|
|
422
|
+
sources=sources,
|
|
423
|
+
summary=summary,
|
|
424
|
+
targets=targets,
|
|
425
|
+
transforms=transforms,
|
|
426
|
+
pretty=state.pretty,
|
|
427
|
+
),
|
|
428
|
+
)
|
|
429
|
+
|
|
430
|
+
|
|
431
|
+
@app.command('extract')
|
|
432
|
+
def extract_cmd(
|
|
433
|
+
ctx: typer.Context,
|
|
434
|
+
source: SourceInputArg,
|
|
435
|
+
source_format: SourceFormatOption | None = None,
|
|
436
|
+
source_type: SourceOverrideOption | None = None,
|
|
437
|
+
) -> int:
|
|
438
|
+
"""
|
|
439
|
+
Extract data from files, databases, or REST APIs.
|
|
440
|
+
|
|
441
|
+
Parameters
|
|
442
|
+
----------
|
|
443
|
+
ctx : typer.Context
|
|
444
|
+
The Typer context.
|
|
445
|
+
source : SourceInputArg
|
|
446
|
+
Extract from SOURCE. Use --from/--source-type to override the inferred
|
|
447
|
+
connector when needed.
|
|
448
|
+
source_format : SourceFormatOption | None, optional
|
|
449
|
+
Format of the source. Overrides filename-based inference when provided.
|
|
450
|
+
Default is ``None``.
|
|
451
|
+
source_type : SourceOverrideOption | None, optional
|
|
452
|
+
Override the inferred source type (file, database, api). Default is
|
|
453
|
+
``None``.
|
|
454
|
+
|
|
455
|
+
Returns
|
|
456
|
+
-------
|
|
457
|
+
int
|
|
458
|
+
Exit code.
|
|
459
|
+
"""
|
|
460
|
+
state = ensure_state(ctx)
|
|
461
|
+
|
|
462
|
+
source_type = optional_choice(
|
|
463
|
+
source_type,
|
|
464
|
+
DATA_CONNECTORS,
|
|
465
|
+
label='source_type',
|
|
466
|
+
)
|
|
467
|
+
source_format = cast(
|
|
468
|
+
SourceFormatOption,
|
|
469
|
+
optional_choice(
|
|
470
|
+
source_format,
|
|
471
|
+
FILE_FORMATS,
|
|
472
|
+
label='source_format',
|
|
473
|
+
),
|
|
474
|
+
)
|
|
475
|
+
|
|
476
|
+
resolved_source = source
|
|
477
|
+
resolved_source_type = source_type or infer_resource_type_or_exit(
|
|
478
|
+
resolved_source,
|
|
479
|
+
)
|
|
480
|
+
|
|
481
|
+
log_inferred_resource(
|
|
482
|
+
state,
|
|
483
|
+
role='source',
|
|
484
|
+
value=resolved_source,
|
|
485
|
+
resource_type=resolved_source_type,
|
|
486
|
+
)
|
|
487
|
+
|
|
488
|
+
return int(
|
|
489
|
+
handlers.extract_handler(
|
|
490
|
+
source_type=resolved_source_type,
|
|
491
|
+
source=resolved_source,
|
|
492
|
+
format_hint=source_format,
|
|
493
|
+
format_explicit=source_format is not None,
|
|
494
|
+
pretty=state.pretty,
|
|
495
|
+
),
|
|
496
|
+
)
|
|
497
|
+
|
|
498
|
+
|
|
499
|
+
@app.command('load')
|
|
500
|
+
def load_cmd(
|
|
501
|
+
ctx: typer.Context,
|
|
502
|
+
target: TargetInputArg,
|
|
503
|
+
source_format: StdinFormatOption = None,
|
|
504
|
+
target_format: TargetFormatOption = None,
|
|
505
|
+
target_type: TargetOverrideOption = None,
|
|
506
|
+
) -> int:
|
|
507
|
+
"""
|
|
508
|
+
Load data into a file, database, or REST API.
|
|
509
|
+
|
|
510
|
+
Parameters
|
|
511
|
+
----------
|
|
512
|
+
ctx : typer.Context
|
|
513
|
+
The Typer context.
|
|
514
|
+
target : TargetInputArg
|
|
515
|
+
Load JSON data from stdin into TARGET. Use --to/--target-type to
|
|
516
|
+
override connector inference when needed. Source data must be piped
|
|
517
|
+
into stdin.
|
|
518
|
+
source_format : StdinFormatOption, optional
|
|
519
|
+
Format of the source. Overrides filename-based inference when provided.
|
|
520
|
+
Default is ``None``.
|
|
521
|
+
target_format : TargetFormatOption, optional
|
|
522
|
+
Format of the target. Overrides filename-based inference when provided.
|
|
523
|
+
Default is ``None``.
|
|
524
|
+
target_type : TargetOverrideOption, optional
|
|
525
|
+
Override the inferred target type (file, database, api). Default is
|
|
526
|
+
``None``.
|
|
527
|
+
|
|
528
|
+
Returns
|
|
529
|
+
-------
|
|
530
|
+
int
|
|
531
|
+
Exit code.
|
|
532
|
+
"""
|
|
533
|
+
state = ensure_state(ctx)
|
|
534
|
+
|
|
535
|
+
source_format = cast(
|
|
536
|
+
StdinFormatOption,
|
|
537
|
+
optional_choice(
|
|
538
|
+
source_format,
|
|
539
|
+
FILE_FORMATS,
|
|
540
|
+
label='source_format',
|
|
541
|
+
),
|
|
542
|
+
)
|
|
543
|
+
target_type = optional_choice(
|
|
544
|
+
target_type,
|
|
545
|
+
DATA_CONNECTORS,
|
|
546
|
+
label='target_type',
|
|
547
|
+
)
|
|
548
|
+
target_format = cast(
|
|
549
|
+
TargetFormatOption,
|
|
550
|
+
optional_choice(
|
|
551
|
+
target_format,
|
|
552
|
+
FILE_FORMATS,
|
|
553
|
+
label='target_format',
|
|
554
|
+
),
|
|
555
|
+
)
|
|
556
|
+
|
|
557
|
+
resolved_target = target
|
|
558
|
+
resolved_target_type = target_type or infer_resource_type_or_exit(
|
|
559
|
+
resolved_target,
|
|
560
|
+
)
|
|
561
|
+
|
|
562
|
+
resolved_source_value = '-'
|
|
563
|
+
resolved_source_type = infer_resource_type_soft(resolved_source_value)
|
|
564
|
+
|
|
565
|
+
log_inferred_resource(
|
|
566
|
+
state,
|
|
567
|
+
role='source',
|
|
568
|
+
value=resolved_source_value,
|
|
569
|
+
resource_type=resolved_source_type,
|
|
570
|
+
)
|
|
571
|
+
log_inferred_resource(
|
|
572
|
+
state,
|
|
573
|
+
role='target',
|
|
574
|
+
value=resolved_target,
|
|
575
|
+
resource_type=resolved_target_type,
|
|
576
|
+
)
|
|
577
|
+
|
|
578
|
+
return int(
|
|
579
|
+
handlers.load_handler(
|
|
580
|
+
source=resolved_source_value,
|
|
581
|
+
target_type=resolved_target_type,
|
|
582
|
+
target=resolved_target,
|
|
583
|
+
source_format=source_format,
|
|
584
|
+
target_format=target_format,
|
|
585
|
+
format_explicit=target_format is not None,
|
|
586
|
+
output=None,
|
|
587
|
+
pretty=state.pretty,
|
|
588
|
+
),
|
|
589
|
+
)
|
|
590
|
+
|
|
591
|
+
|
|
592
|
+
@app.command('render')
|
|
593
|
+
def render_cmd(
|
|
594
|
+
ctx: typer.Context,
|
|
595
|
+
config: RenderConfigOption = None,
|
|
596
|
+
spec: RenderSpecOption = None,
|
|
597
|
+
table: RenderTableOption = None,
|
|
598
|
+
template: RenderTemplateOption = 'ddl',
|
|
599
|
+
template_path: RenderTemplatePathOption = None,
|
|
600
|
+
output: RenderOutputOption = None,
|
|
601
|
+
) -> int:
|
|
602
|
+
"""
|
|
603
|
+
Render SQL DDL from table schemas defined in YAML/JSON configs.
|
|
604
|
+
|
|
605
|
+
Parameters
|
|
606
|
+
----------
|
|
607
|
+
ctx : typer.Context
|
|
608
|
+
The Typer context.
|
|
609
|
+
config : RenderConfigOption
|
|
610
|
+
Pipeline YAML that includes table_schemas for rendering.
|
|
611
|
+
spec : RenderSpecOption, optional
|
|
612
|
+
Standalone table spec file (.yml/.yaml/.json).
|
|
613
|
+
table : RenderTableOption, optional
|
|
614
|
+
Filter to a single table name from table_schemas.
|
|
615
|
+
template : RenderTemplateOption
|
|
616
|
+
Template key (ddl/view) or path to a Jinja template file.
|
|
617
|
+
template_path : RenderTemplatePathOption, optional
|
|
618
|
+
Explicit path to a Jinja template file (overrides template key).
|
|
619
|
+
output : RenderOutputOption, optional
|
|
620
|
+
Write rendered SQL to PATH (default: stdout).
|
|
621
|
+
|
|
622
|
+
Returns
|
|
623
|
+
-------
|
|
624
|
+
int
|
|
625
|
+
Exit code.
|
|
626
|
+
"""
|
|
627
|
+
state = ensure_state(ctx)
|
|
628
|
+
return int(
|
|
629
|
+
handlers.render_handler(
|
|
630
|
+
config=config,
|
|
631
|
+
spec=spec,
|
|
632
|
+
table=table,
|
|
633
|
+
template=template,
|
|
634
|
+
template_path=template_path,
|
|
635
|
+
output=output,
|
|
636
|
+
pretty=state.pretty,
|
|
637
|
+
quiet=state.quiet,
|
|
638
|
+
),
|
|
639
|
+
)
|
|
640
|
+
|
|
641
|
+
|
|
642
|
+
@app.command('run')
|
|
643
|
+
def run_cmd(
|
|
644
|
+
ctx: typer.Context,
|
|
645
|
+
config: PipelineConfigOption,
|
|
646
|
+
job: str | None = typer.Option(
|
|
647
|
+
None,
|
|
648
|
+
'-j',
|
|
649
|
+
'--job',
|
|
650
|
+
help='Name of the job to run',
|
|
651
|
+
),
|
|
652
|
+
pipeline: str | None = typer.Option(
|
|
653
|
+
None,
|
|
654
|
+
'-p',
|
|
655
|
+
'--pipeline',
|
|
656
|
+
help='Name of the pipeline to run',
|
|
657
|
+
),
|
|
658
|
+
) -> int:
|
|
659
|
+
"""
|
|
660
|
+
Execute an ETL job or pipeline from a YAML configuration.
|
|
661
|
+
|
|
662
|
+
Parameters
|
|
663
|
+
----------
|
|
664
|
+
ctx : typer.Context
|
|
665
|
+
The Typer context.
|
|
666
|
+
config : PipelineConfigOption
|
|
667
|
+
Path to pipeline YAML configuration file.
|
|
668
|
+
job : str | None, optional
|
|
669
|
+
Name of the job to run. Default is ``None``.
|
|
670
|
+
pipeline : str | None, optional
|
|
671
|
+
Name of the pipeline to run. Default is ``None``.
|
|
672
|
+
|
|
673
|
+
Returns
|
|
674
|
+
-------
|
|
675
|
+
int
|
|
676
|
+
Exit code.
|
|
677
|
+
"""
|
|
678
|
+
state = ensure_state(ctx)
|
|
679
|
+
return int(
|
|
680
|
+
handlers.run_handler(
|
|
681
|
+
config=config,
|
|
682
|
+
job=job,
|
|
683
|
+
pipeline=pipeline,
|
|
684
|
+
pretty=state.pretty,
|
|
685
|
+
),
|
|
686
|
+
)
|
|
687
|
+
|
|
688
|
+
|
|
689
|
+
@app.command('transform')
|
|
690
|
+
def transform_cmd(
|
|
691
|
+
ctx: typer.Context,
|
|
692
|
+
operations: OperationsOption = '{}',
|
|
693
|
+
source: StreamingSourceArg = '-',
|
|
694
|
+
source_format: SourceFormatOption = None,
|
|
695
|
+
source_type: SourceOverrideOption = None,
|
|
696
|
+
target: TargetPathOption = None,
|
|
697
|
+
target_format: TargetFormatOption = None,
|
|
698
|
+
target_type: TargetOverrideOption = None,
|
|
699
|
+
) -> int:
|
|
700
|
+
"""
|
|
701
|
+
Transform records using JSON-described operations.
|
|
702
|
+
|
|
703
|
+
Parameters
|
|
704
|
+
----------
|
|
705
|
+
ctx : typer.Context
|
|
706
|
+
The Typer context.
|
|
707
|
+
operations : OperationsOption
|
|
708
|
+
Transformation operations as JSON string.
|
|
709
|
+
source : StreamingSourceArg
|
|
710
|
+
Data source to transform (path, JSON payload, or - for stdin).
|
|
711
|
+
source_format : SourceFormatOption, optional
|
|
712
|
+
Format of the source. Overrides filename-based inference when provided.
|
|
713
|
+
Default is ``None``.
|
|
714
|
+
source_type : SourceOverrideOption, optional
|
|
715
|
+
Override the inferred source type (file, database, api). Default is
|
|
716
|
+
``None``.
|
|
717
|
+
target : TargetPathOption, optional
|
|
718
|
+
Target file for transformed output (- for stdout). Default is ``None``.
|
|
719
|
+
target_format : TargetFormatOption, optional
|
|
720
|
+
Format of the target. Overrides filename-based inference when provided.
|
|
721
|
+
Default is ``None``.
|
|
722
|
+
target_type : TargetOverrideOption, optional
|
|
723
|
+
Override the inferred target type (file, database, api). Default is
|
|
724
|
+
``None``.
|
|
725
|
+
|
|
726
|
+
Returns
|
|
727
|
+
-------
|
|
728
|
+
int
|
|
729
|
+
Exit code.
|
|
730
|
+
"""
|
|
731
|
+
state = ensure_state(ctx)
|
|
732
|
+
|
|
733
|
+
source_format = cast(
|
|
734
|
+
SourceFormatOption,
|
|
735
|
+
optional_choice(
|
|
736
|
+
source_format,
|
|
737
|
+
FILE_FORMATS,
|
|
738
|
+
label='source_format',
|
|
739
|
+
),
|
|
740
|
+
)
|
|
741
|
+
source_type = optional_choice(
|
|
742
|
+
source_type,
|
|
743
|
+
DATA_CONNECTORS,
|
|
744
|
+
label='source_type',
|
|
745
|
+
)
|
|
746
|
+
target_format = cast(
|
|
747
|
+
TargetFormatOption,
|
|
748
|
+
optional_choice(
|
|
749
|
+
target_format,
|
|
750
|
+
FILE_FORMATS,
|
|
751
|
+
label='target_format',
|
|
752
|
+
),
|
|
753
|
+
)
|
|
754
|
+
target_type = optional_choice(
|
|
755
|
+
target_type,
|
|
756
|
+
DATA_CONNECTORS,
|
|
757
|
+
label='target_type',
|
|
758
|
+
)
|
|
759
|
+
|
|
760
|
+
resolved_source_type = source_type or infer_resource_type_soft(source)
|
|
761
|
+
resolved_source_value = source if source is not None else '-'
|
|
762
|
+
resolved_target_value = target if target is not None else '-'
|
|
763
|
+
|
|
764
|
+
if resolved_source_type is not None:
|
|
765
|
+
resolved_source_type = validate_choice(
|
|
766
|
+
resolved_source_type,
|
|
767
|
+
DATA_CONNECTORS,
|
|
768
|
+
label='source_type',
|
|
769
|
+
)
|
|
770
|
+
|
|
771
|
+
resolved_target_type = resolve_resource_type(
|
|
772
|
+
explicit_type=None,
|
|
773
|
+
override_type=target_type,
|
|
774
|
+
value=resolved_target_value,
|
|
775
|
+
label='target_type',
|
|
776
|
+
)
|
|
777
|
+
|
|
778
|
+
log_inferred_resource(
|
|
779
|
+
state,
|
|
780
|
+
role='source',
|
|
781
|
+
value=resolved_source_value,
|
|
782
|
+
resource_type=resolved_source_type,
|
|
783
|
+
)
|
|
784
|
+
log_inferred_resource(
|
|
785
|
+
state,
|
|
786
|
+
role='target',
|
|
787
|
+
value=resolved_target_value,
|
|
788
|
+
resource_type=resolved_target_type,
|
|
789
|
+
)
|
|
790
|
+
|
|
791
|
+
return int(
|
|
792
|
+
handlers.transform_handler(
|
|
793
|
+
source=resolved_source_value,
|
|
794
|
+
operations=_parse_json_option(operations, '--operations'),
|
|
795
|
+
target=resolved_target_value,
|
|
796
|
+
source_format=source_format,
|
|
797
|
+
target_format=target_format,
|
|
798
|
+
format_explicit=target_format is not None,
|
|
799
|
+
pretty=state.pretty,
|
|
800
|
+
),
|
|
801
|
+
)
|
|
802
|
+
|
|
803
|
+
|
|
804
|
+
@app.command('validate')
|
|
805
|
+
def validate_cmd(
|
|
806
|
+
ctx: typer.Context,
|
|
807
|
+
rules: RulesOption = '{}',
|
|
808
|
+
source: StreamingSourceArg = '-',
|
|
809
|
+
source_format: SourceFormatOption = None,
|
|
810
|
+
source_type: SourceOverrideOption = None,
|
|
811
|
+
target: TargetPathOption = None,
|
|
812
|
+
) -> int:
|
|
813
|
+
"""
|
|
814
|
+
Validate data against JSON-described rules.
|
|
815
|
+
|
|
816
|
+
Parameters
|
|
817
|
+
----------
|
|
818
|
+
ctx : typer.Context
|
|
819
|
+
The Typer context.
|
|
820
|
+
rules : RulesOption
|
|
821
|
+
Validation rules as JSON string.
|
|
822
|
+
source : StreamingSourceArg
|
|
823
|
+
Data source to validate (path, JSON payload, or - for stdin).
|
|
824
|
+
source_format : SourceFormatOption, optional
|
|
825
|
+
Format of the source. Overrides filename-based inference when provided.
|
|
826
|
+
Default is ``None``.
|
|
827
|
+
source_type : SourceOverrideOption, optional
|
|
828
|
+
Override the inferred source type (file, database, api). Default is
|
|
829
|
+
``None``.
|
|
830
|
+
target : TargetPathOption, optional
|
|
831
|
+
Target file for validated output (- for stdout). Default is ``None``.
|
|
832
|
+
|
|
833
|
+
Returns
|
|
834
|
+
-------
|
|
835
|
+
int
|
|
836
|
+
Exit code.
|
|
837
|
+
"""
|
|
838
|
+
source_format = cast(
|
|
839
|
+
SourceFormatOption,
|
|
840
|
+
optional_choice(
|
|
841
|
+
source_format,
|
|
842
|
+
FILE_FORMATS,
|
|
843
|
+
label='source_format',
|
|
844
|
+
),
|
|
845
|
+
)
|
|
846
|
+
source_type = optional_choice(
|
|
847
|
+
source_type,
|
|
848
|
+
DATA_CONNECTORS,
|
|
849
|
+
label='source_type',
|
|
850
|
+
)
|
|
851
|
+
state = ensure_state(ctx)
|
|
852
|
+
resolved_source_type = source_type or infer_resource_type_soft(source)
|
|
853
|
+
|
|
854
|
+
log_inferred_resource(
|
|
855
|
+
state,
|
|
856
|
+
role='source',
|
|
857
|
+
value=source,
|
|
858
|
+
resource_type=resolved_source_type,
|
|
859
|
+
)
|
|
860
|
+
|
|
861
|
+
return int(
|
|
862
|
+
handlers.validate_handler(
|
|
863
|
+
source=source,
|
|
864
|
+
rules=_parse_json_option(rules, '--rules'),
|
|
865
|
+
source_format=source_format,
|
|
866
|
+
target=target,
|
|
867
|
+
format_explicit=source_format is not None,
|
|
868
|
+
pretty=state.pretty,
|
|
869
|
+
),
|
|
870
|
+
)
|