etlplus 0.4.1__py3-none-any.whl → 0.4.7__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/app.py +669 -430
- etlplus/cli/handlers.py +175 -196
- etlplus/cli/main.py +236 -74
- {etlplus-0.4.1.dist-info → etlplus-0.4.7.dist-info}/METADATA +44 -36
- {etlplus-0.4.1.dist-info → etlplus-0.4.7.dist-info}/RECORD +9 -9
- {etlplus-0.4.1.dist-info → etlplus-0.4.7.dist-info}/WHEEL +0 -0
- {etlplus-0.4.1.dist-info → etlplus-0.4.7.dist-info}/entry_points.txt +0 -0
- {etlplus-0.4.1.dist-info → etlplus-0.4.7.dist-info}/licenses/LICENSE +0 -0
- {etlplus-0.4.1.dist-info → etlplus-0.4.7.dist-info}/top_level.txt +0 -0
etlplus/cli/main.py
CHANGED
|
@@ -10,10 +10,12 @@ This module exposes :func:`main` for the console script as well as
|
|
|
10
10
|
from __future__ import annotations
|
|
11
11
|
|
|
12
12
|
import argparse
|
|
13
|
+
import contextlib
|
|
13
14
|
import sys
|
|
14
15
|
from collections.abc import Sequence
|
|
15
16
|
from typing import Literal
|
|
16
17
|
|
|
18
|
+
import click
|
|
17
19
|
import typer
|
|
18
20
|
|
|
19
21
|
from .. import __version__
|
|
@@ -22,7 +24,6 @@ from ..enums import FileFormat
|
|
|
22
24
|
from ..utils import json_type
|
|
23
25
|
from .app import PROJECT_URL
|
|
24
26
|
from .app import app
|
|
25
|
-
from .handlers import FORMAT_ENV_KEY
|
|
26
27
|
from .handlers import cmd_extract
|
|
27
28
|
from .handlers import cmd_list
|
|
28
29
|
from .handlers import cmd_load
|
|
@@ -51,7 +52,9 @@ type FormatContext = Literal['source', 'target']
|
|
|
51
52
|
|
|
52
53
|
|
|
53
54
|
class _FormatAction(argparse.Action):
|
|
54
|
-
"""
|
|
55
|
+
"""
|
|
56
|
+
Argparse action that records when ``--source-format`` or
|
|
57
|
+
``--target-format`` is provided."""
|
|
55
58
|
|
|
56
59
|
def __call__(
|
|
57
60
|
self,
|
|
@@ -67,31 +70,89 @@ class _FormatAction(argparse.Action):
|
|
|
67
70
|
# SECTION: INTERNAL FUNCTIONS =============================================== #
|
|
68
71
|
|
|
69
72
|
|
|
73
|
+
def _add_boolean_flag(
|
|
74
|
+
parser: argparse.ArgumentParser,
|
|
75
|
+
*,
|
|
76
|
+
name: str,
|
|
77
|
+
help_text: str,
|
|
78
|
+
) -> None:
|
|
79
|
+
"""Add a toggle that also supports the ``--no-`` prefix via 3.13.
|
|
80
|
+
|
|
81
|
+
Parameters
|
|
82
|
+
----------
|
|
83
|
+
parser : argparse.ArgumentParser
|
|
84
|
+
Parser receiving the flag.
|
|
85
|
+
name : str
|
|
86
|
+
Primary flag name without leading dashes.
|
|
87
|
+
help_text : str
|
|
88
|
+
Help text rendered in ``--help`` output.
|
|
89
|
+
"""
|
|
90
|
+
|
|
91
|
+
parser.add_argument(
|
|
92
|
+
f'--{name}',
|
|
93
|
+
action=argparse.BooleanOptionalAction,
|
|
94
|
+
default=False,
|
|
95
|
+
help=help_text,
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _add_config_option(
|
|
100
|
+
parser: argparse.ArgumentParser,
|
|
101
|
+
*,
|
|
102
|
+
required: bool = True,
|
|
103
|
+
) -> None:
|
|
104
|
+
"""Attach the shared ``--config`` option used by legacy commands.
|
|
105
|
+
|
|
106
|
+
Parameters
|
|
107
|
+
----------
|
|
108
|
+
parser : argparse.ArgumentParser
|
|
109
|
+
Parser receiving the option.
|
|
110
|
+
required : bool, optional
|
|
111
|
+
Whether the flag must be provided. Defaults to ``True``.
|
|
112
|
+
"""
|
|
113
|
+
|
|
114
|
+
parser.add_argument(
|
|
115
|
+
'--config',
|
|
116
|
+
required=required,
|
|
117
|
+
help='Path to pipeline YAML configuration file',
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
|
|
70
121
|
def _add_format_options(
|
|
71
122
|
parser: argparse.ArgumentParser,
|
|
72
123
|
*,
|
|
73
124
|
context: FormatContext,
|
|
74
125
|
) -> None:
|
|
75
|
-
"""
|
|
126
|
+
"""
|
|
127
|
+
Attach shared ``--source-format`` or ``--target-format`` options to
|
|
128
|
+
extract/load parsers.
|
|
76
129
|
|
|
130
|
+
Parameters
|
|
131
|
+
----------
|
|
132
|
+
parser : argparse.ArgumentParser
|
|
133
|
+
Parser to augment.
|
|
134
|
+
context : FormatContext
|
|
135
|
+
Context for the format option: either ``'source'`` or ``'target'``
|
|
136
|
+
"""
|
|
77
137
|
parser.set_defaults(_format_explicit=False)
|
|
78
138
|
parser.add_argument(
|
|
79
|
-
'--
|
|
80
|
-
|
|
139
|
+
'--source-format',
|
|
140
|
+
choices=list(FileFormat.choices()),
|
|
141
|
+
default='json',
|
|
142
|
+
action=_FormatAction,
|
|
81
143
|
help=(
|
|
82
|
-
'
|
|
83
|
-
|
|
144
|
+
f'Format of the {context}. Overrides filename-based inference '
|
|
145
|
+
'when provided.'
|
|
84
146
|
),
|
|
85
147
|
)
|
|
86
148
|
parser.add_argument(
|
|
87
|
-
'--format',
|
|
149
|
+
'--target-format',
|
|
88
150
|
choices=list(FileFormat.choices()),
|
|
89
151
|
default='json',
|
|
90
152
|
action=_FormatAction,
|
|
91
153
|
help=(
|
|
92
|
-
f'Format of the {context}
|
|
93
|
-
'
|
|
94
|
-
'filename extension.'
|
|
154
|
+
f'Format of the {context}. Overrides filename-based inference '
|
|
155
|
+
'when provided.'
|
|
95
156
|
),
|
|
96
157
|
)
|
|
97
158
|
|
|
@@ -103,44 +164,119 @@ def _cli_description() -> str:
|
|
|
103
164
|
'',
|
|
104
165
|
' Provide a subcommand and options. Examples:',
|
|
105
166
|
'',
|
|
106
|
-
' etlplus extract file in.csv
|
|
167
|
+
' etlplus extract file in.csv > out.json',
|
|
107
168
|
' etlplus validate in.json --rules \'{"required": ["id"]}\'',
|
|
108
169
|
(
|
|
109
|
-
' etlplus transform in.
|
|
110
|
-
'\'{"select": ["id"]}\''
|
|
170
|
+
' etlplus transform --from file in.csv --operations '
|
|
171
|
+
'\'{"select": ["id"]}\' --to file -o out.json'
|
|
111
172
|
),
|
|
112
|
-
' etlplus
|
|
173
|
+
' etlplus extract in.csv | etlplus load --to file out.json',
|
|
113
174
|
'',
|
|
114
|
-
'
|
|
175
|
+
' Override format inference when extensions are misleading:',
|
|
115
176
|
'',
|
|
116
|
-
' etlplus extract
|
|
117
|
-
|
|
118
|
-
' etlplus load in.json file out.csv --format csv '
|
|
119
|
-
'--strict-format'
|
|
120
|
-
),
|
|
177
|
+
' etlplus extract data.txt --source-format csv',
|
|
178
|
+
' etlplus load payload.bin --target-format json',
|
|
121
179
|
],
|
|
122
180
|
)
|
|
123
181
|
|
|
124
182
|
|
|
125
|
-
def _cli_epilog(
|
|
183
|
+
def _cli_epilog() -> str:
|
|
126
184
|
return '\n'.join(
|
|
127
185
|
[
|
|
128
|
-
'
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
'--format is provided for files.'
|
|
132
|
-
),
|
|
133
|
-
' Values:',
|
|
134
|
-
' - error|fail|strict: treat as error',
|
|
135
|
-
' - warn (default): print a warning',
|
|
136
|
-
' - ignore|silent: no message',
|
|
137
|
-
'',
|
|
138
|
-
'Note:',
|
|
139
|
-
' --strict-format overrides the environment behavior.',
|
|
186
|
+
'Tip:',
|
|
187
|
+
' --source-format and --target-format override format '
|
|
188
|
+
'inference based on filename extensions when needed.',
|
|
140
189
|
],
|
|
141
190
|
)
|
|
142
191
|
|
|
143
192
|
|
|
193
|
+
def _emit_context_help(
|
|
194
|
+
ctx: click.Context | None,
|
|
195
|
+
) -> bool:
|
|
196
|
+
"""
|
|
197
|
+
Mirror Click help output for the provided context onto stderr.
|
|
198
|
+
|
|
199
|
+
Parameters
|
|
200
|
+
----------
|
|
201
|
+
ctx : click.Context | None
|
|
202
|
+
The Click context to emit help for.
|
|
203
|
+
|
|
204
|
+
Returns
|
|
205
|
+
-------
|
|
206
|
+
bool
|
|
207
|
+
``True`` when help was emitted, ``False`` when ``ctx`` was ``None``.
|
|
208
|
+
"""
|
|
209
|
+
if ctx is None:
|
|
210
|
+
return False
|
|
211
|
+
|
|
212
|
+
with contextlib.redirect_stdout(sys.stderr):
|
|
213
|
+
ctx.get_help()
|
|
214
|
+
return True
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def _emit_root_help(
|
|
218
|
+
command: click.Command,
|
|
219
|
+
) -> None:
|
|
220
|
+
"""
|
|
221
|
+
Print the root ``etlplus`` help text to stderr.
|
|
222
|
+
|
|
223
|
+
Parameters
|
|
224
|
+
----------
|
|
225
|
+
command : click.Command
|
|
226
|
+
The root Typer/Click command.
|
|
227
|
+
"""
|
|
228
|
+
ctx = command.make_context('etlplus', [], resilient_parsing=True)
|
|
229
|
+
try:
|
|
230
|
+
_emit_context_help(ctx)
|
|
231
|
+
finally:
|
|
232
|
+
ctx.close()
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
def _is_illegal_option_error(
|
|
236
|
+
exc: click.exceptions.UsageError,
|
|
237
|
+
) -> bool:
|
|
238
|
+
"""
|
|
239
|
+
Return ``True`` when usage errors stem from invalid options.
|
|
240
|
+
|
|
241
|
+
Parameters
|
|
242
|
+
----------
|
|
243
|
+
exc : click.exceptions.UsageError
|
|
244
|
+
The usage error to inspect.
|
|
245
|
+
|
|
246
|
+
Returns
|
|
247
|
+
-------
|
|
248
|
+
bool
|
|
249
|
+
``True`` when the error indicates illegal options.
|
|
250
|
+
"""
|
|
251
|
+
return isinstance(
|
|
252
|
+
exc,
|
|
253
|
+
(
|
|
254
|
+
click.exceptions.BadOptionUsage,
|
|
255
|
+
click.exceptions.NoSuchOption,
|
|
256
|
+
),
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
def _is_unknown_command_error(
|
|
261
|
+
exc: click.exceptions.UsageError,
|
|
262
|
+
) -> bool:
|
|
263
|
+
"""
|
|
264
|
+
Return ``True`` when a :class:`UsageError` indicates bad subcommand.
|
|
265
|
+
|
|
266
|
+
Parameters
|
|
267
|
+
----------
|
|
268
|
+
exc : click.exceptions.UsageError
|
|
269
|
+
The usage error to inspect.
|
|
270
|
+
|
|
271
|
+
Returns
|
|
272
|
+
-------
|
|
273
|
+
bool
|
|
274
|
+
``True`` when the error indicates an unknown command.
|
|
275
|
+
"""
|
|
276
|
+
message = getattr(exc, 'message', None) or str(exc)
|
|
277
|
+
return message.startswith('No such command ')
|
|
278
|
+
|
|
279
|
+
|
|
144
280
|
# SECTION: FUNCTIONS ======================================================== #
|
|
145
281
|
|
|
146
282
|
|
|
@@ -157,7 +293,7 @@ def create_parser() -> argparse.ArgumentParser:
|
|
|
157
293
|
parser = argparse.ArgumentParser(
|
|
158
294
|
prog='etlplus',
|
|
159
295
|
description=_cli_description(),
|
|
160
|
-
epilog=_cli_epilog(
|
|
296
|
+
epilog=_cli_epilog(),
|
|
161
297
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
162
298
|
)
|
|
163
299
|
|
|
@@ -172,6 +308,7 @@ def create_parser() -> argparse.ArgumentParser:
|
|
|
172
308
|
dest='command',
|
|
173
309
|
help='Available commands',
|
|
174
310
|
)
|
|
311
|
+
subparsers.required = True
|
|
175
312
|
|
|
176
313
|
extract_parser = subparsers.add_parser(
|
|
177
314
|
'extract',
|
|
@@ -190,11 +327,6 @@ def create_parser() -> argparse.ArgumentParser:
|
|
|
190
327
|
'or API URL)'
|
|
191
328
|
),
|
|
192
329
|
)
|
|
193
|
-
extract_parser.add_argument(
|
|
194
|
-
'-o',
|
|
195
|
-
'--output',
|
|
196
|
-
help='Output file to save extracted data (JSON format)',
|
|
197
|
-
)
|
|
198
330
|
_add_format_options(extract_parser, context='source')
|
|
199
331
|
extract_parser.set_defaults(func=cmd_extract)
|
|
200
332
|
|
|
@@ -231,9 +363,35 @@ def create_parser() -> argparse.ArgumentParser:
|
|
|
231
363
|
help='Transformation operations as JSON string',
|
|
232
364
|
)
|
|
233
365
|
transform_parser.add_argument(
|
|
234
|
-
'
|
|
235
|
-
'
|
|
236
|
-
|
|
366
|
+
'--from',
|
|
367
|
+
dest='from_',
|
|
368
|
+
choices=list(DataConnectorType.choices()),
|
|
369
|
+
help='Override the inferred source type (file, database, api).',
|
|
370
|
+
)
|
|
371
|
+
transform_parser.add_argument(
|
|
372
|
+
'--to',
|
|
373
|
+
dest='to',
|
|
374
|
+
choices=list(DataConnectorType.choices()),
|
|
375
|
+
help='Override the inferred target type (file, database, api).',
|
|
376
|
+
)
|
|
377
|
+
transform_parser.add_argument(
|
|
378
|
+
'--source-format',
|
|
379
|
+
choices=list(FileFormat.choices()),
|
|
380
|
+
dest='source_format',
|
|
381
|
+
help=(
|
|
382
|
+
'Input payload format when SOURCE is - or a literal payload. '
|
|
383
|
+
'File sources infer format from the extension.'
|
|
384
|
+
),
|
|
385
|
+
)
|
|
386
|
+
transform_parser.add_argument(
|
|
387
|
+
'--target-format',
|
|
388
|
+
dest='target_format',
|
|
389
|
+
choices=list(FileFormat.choices()),
|
|
390
|
+
help=(
|
|
391
|
+
'Output payload format '
|
|
392
|
+
'when writing to stdout or non-file targets. '
|
|
393
|
+
'File targets infer format from the extension.'
|
|
394
|
+
),
|
|
237
395
|
)
|
|
238
396
|
transform_parser.set_defaults(func=cmd_transform)
|
|
239
397
|
|
|
@@ -269,11 +427,7 @@ def create_parser() -> argparse.ArgumentParser:
|
|
|
269
427
|
),
|
|
270
428
|
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
|
271
429
|
)
|
|
272
|
-
pipe_parser
|
|
273
|
-
'--config',
|
|
274
|
-
required=True,
|
|
275
|
-
help='Path to pipeline YAML configuration file',
|
|
276
|
-
)
|
|
430
|
+
_add_config_option(pipe_parser)
|
|
277
431
|
pipe_parser.add_argument(
|
|
278
432
|
'--list',
|
|
279
433
|
action='store_true',
|
|
@@ -291,30 +445,26 @@ def create_parser() -> argparse.ArgumentParser:
|
|
|
291
445
|
help='List ETL pipeline metadata',
|
|
292
446
|
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
|
293
447
|
)
|
|
294
|
-
list_parser
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
448
|
+
_add_config_option(list_parser)
|
|
449
|
+
_add_boolean_flag(
|
|
450
|
+
list_parser,
|
|
451
|
+
name='pipelines',
|
|
452
|
+
help_text='List ETL pipelines',
|
|
298
453
|
)
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
)
|
|
304
|
-
list_parser.add_argument(
|
|
305
|
-
'--sources',
|
|
306
|
-
action='store_true',
|
|
307
|
-
help='List data sources',
|
|
454
|
+
_add_boolean_flag(
|
|
455
|
+
list_parser,
|
|
456
|
+
name='sources',
|
|
457
|
+
help_text='List data sources',
|
|
308
458
|
)
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
459
|
+
_add_boolean_flag(
|
|
460
|
+
list_parser,
|
|
461
|
+
name='targets',
|
|
462
|
+
help_text='List data targets',
|
|
313
463
|
)
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
464
|
+
_add_boolean_flag(
|
|
465
|
+
list_parser,
|
|
466
|
+
name='transforms',
|
|
467
|
+
help_text='List data transforms',
|
|
318
468
|
)
|
|
319
469
|
list_parser.set_defaults(func=cmd_list)
|
|
320
470
|
|
|
@@ -326,11 +476,7 @@ def create_parser() -> argparse.ArgumentParser:
|
|
|
326
476
|
),
|
|
327
477
|
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
|
328
478
|
)
|
|
329
|
-
run_parser
|
|
330
|
-
'--config',
|
|
331
|
-
required=True,
|
|
332
|
-
help='Path to pipeline YAML configuration file',
|
|
333
|
-
)
|
|
479
|
+
_add_config_option(run_parser)
|
|
334
480
|
run_parser.add_argument(
|
|
335
481
|
'-j',
|
|
336
482
|
'--job',
|
|
@@ -365,6 +511,9 @@ def main(
|
|
|
365
511
|
|
|
366
512
|
Raises
|
|
367
513
|
------
|
|
514
|
+
click.exceptions.UsageError
|
|
515
|
+
Re-raises Typer/Click usage errors after printing help for unknown
|
|
516
|
+
commands.
|
|
368
517
|
SystemExit
|
|
369
518
|
Re-raises SystemExit exceptions to preserve exit codes.
|
|
370
519
|
|
|
@@ -385,6 +534,19 @@ def main(
|
|
|
385
534
|
)
|
|
386
535
|
return int(result or 0)
|
|
387
536
|
|
|
537
|
+
except click.exceptions.UsageError as exc:
|
|
538
|
+
if _is_unknown_command_error(exc):
|
|
539
|
+
typer.echo(f'Error: {exc}', err=True)
|
|
540
|
+
_emit_root_help(command)
|
|
541
|
+
return int(getattr(exc, 'exit_code', 2))
|
|
542
|
+
if _is_illegal_option_error(exc):
|
|
543
|
+
typer.echo(f'Error: {exc}', err=True)
|
|
544
|
+
if not _emit_context_help(exc.ctx):
|
|
545
|
+
_emit_root_help(command)
|
|
546
|
+
return int(getattr(exc, 'exit_code', 2))
|
|
547
|
+
|
|
548
|
+
raise
|
|
549
|
+
|
|
388
550
|
except typer.Exit as exc:
|
|
389
551
|
return int(exc.exit_code)
|
|
390
552
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: etlplus
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.7
|
|
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
|
|
@@ -67,7 +67,7 @@ package and command-line interface for data extraction, validation, transformati
|
|
|
67
67
|
- [Load Data](#load-data)
|
|
68
68
|
- [Python API](#python-api)
|
|
69
69
|
- [Complete ETL Pipeline Example](#complete-etl-pipeline-example)
|
|
70
|
-
- [
|
|
70
|
+
- [Format Overrides](#format-overrides)
|
|
71
71
|
- [Transformation Operations](#transformation-operations)
|
|
72
72
|
- [Filter Operations](#filter-operations)
|
|
73
73
|
- [Aggregation Functions](#aggregation-functions)
|
|
@@ -79,6 +79,8 @@ package and command-line interface for data extraction, validation, transformati
|
|
|
79
79
|
- [Test Layers](#test-layers)
|
|
80
80
|
- [Code Coverage](#code-coverage)
|
|
81
81
|
- [Linting](#linting)
|
|
82
|
+
- [Updating Demo Snippets](#updating-demo-snippets)
|
|
83
|
+
- [Releasing to PyPI](#releasing-to-pypi)
|
|
82
84
|
- [Links](#links)
|
|
83
85
|
- [License](#license)
|
|
84
86
|
- [Contributing](#contributing)
|
|
@@ -169,9 +171,9 @@ etlplus --version
|
|
|
169
171
|
|
|
170
172
|
#### Extract Data
|
|
171
173
|
|
|
172
|
-
Note: For file sources, the format is inferred from the filename extension
|
|
173
|
-
|
|
174
|
-
|
|
174
|
+
Note: For file sources, the format is normally inferred from the filename extension. Use
|
|
175
|
+
`--source-format` to override inference when a file lacks an extension or when you want to force a
|
|
176
|
+
specific parser.
|
|
175
177
|
|
|
176
178
|
Extract from JSON file:
|
|
177
179
|
```bash
|
|
@@ -212,6 +214,20 @@ etlplus validate examples/data/sample.json --rules '{"email": {"type": "string",
|
|
|
212
214
|
|
|
213
215
|
#### Transform Data
|
|
214
216
|
|
|
217
|
+
When piping data through `etlplus transform`, use `--source-format` whenever the SOURCE argument is
|
|
218
|
+
`-` or a literal payload, mirroring the `etlplus extract` semantics. Use `--target-format` to
|
|
219
|
+
control the emitted format for stdout or other non-file outputs, just like `etlplus load`. File
|
|
220
|
+
paths continue to infer formats from their extensions. Use `--from` to override the inferred source
|
|
221
|
+
connector type and `--to` to override the inferred target connector type, matching the `etlplus
|
|
222
|
+
extract`/`etlplus load` behavior.
|
|
223
|
+
|
|
224
|
+
Transform file inputs while overriding connector types:
|
|
225
|
+
```bash
|
|
226
|
+
etlplus transform --from file examples/data/sample.json \
|
|
227
|
+
--operations '{"select": ["name", "email"]}' \
|
|
228
|
+
--to file -o temp/selected_output.json
|
|
229
|
+
```
|
|
230
|
+
|
|
215
231
|
Filter and select fields:
|
|
216
232
|
```bash
|
|
217
233
|
etlplus transform '[{"name": "John", "age": 30}, {"name": "Jane", "age": 25}]' \
|
|
@@ -235,19 +251,24 @@ etlplus transform examples/data/sample.json --operations '{"map": {"name": "new_
|
|
|
235
251
|
|
|
236
252
|
#### Load Data
|
|
237
253
|
|
|
254
|
+
`etlplus load` consumes JSON from stdin; provide only the target argument plus optional flags.
|
|
255
|
+
|
|
238
256
|
Load to JSON file:
|
|
239
257
|
```bash
|
|
240
|
-
etlplus
|
|
258
|
+
etlplus extract file examples/data/sample.json \
|
|
259
|
+
| etlplus load --to file temp/sample_output.json
|
|
241
260
|
```
|
|
242
261
|
|
|
243
262
|
Load to CSV file:
|
|
244
263
|
```bash
|
|
245
|
-
etlplus
|
|
264
|
+
etlplus extract file examples/data/sample.csv \
|
|
265
|
+
| etlplus load --to file temp/sample_output.csv
|
|
246
266
|
```
|
|
247
267
|
|
|
248
268
|
Load to REST API:
|
|
249
269
|
```bash
|
|
250
|
-
|
|
270
|
+
cat examples/data/sample.json \
|
|
271
|
+
| etlplus load --to api https://api.example.com/endpoint
|
|
251
272
|
```
|
|
252
273
|
|
|
253
274
|
### Python API
|
|
@@ -301,41 +322,28 @@ etlplus validate temp/sample_transformed.json \
|
|
|
301
322
|
--rules '{"name": {"type": "string", "required": true}, "email": {"type": "string", "required": true}}'
|
|
302
323
|
|
|
303
324
|
# 4. Load to CSV
|
|
304
|
-
|
|
325
|
+
cat temp/sample_transformed.json \
|
|
326
|
+
| etlplus load --to temp/sample_output.csv
|
|
305
327
|
```
|
|
306
328
|
|
|
307
|
-
###
|
|
308
|
-
|
|
309
|
-
ETLPlus honors a small number of environment toggles to refine CLI behavior:
|
|
329
|
+
### Format Overrides
|
|
310
330
|
|
|
311
|
-
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
- `error|fail|strict`: treat as error (non-zero exit)
|
|
315
|
-
- `warn` (default): print a warning to stderr
|
|
316
|
-
- `ignore|silent`: no message
|
|
317
|
-
- Precedence: the CLI flag `--strict-format` overrides the environment.
|
|
331
|
+
`--source-format` and `--target-format` override whichever format would normally be inferred from a
|
|
332
|
+
file extension. This is useful when an input lacks an extension (for example, `records.txt` that
|
|
333
|
+
actually contains CSV) or when you intentionally want to treat a file as another format.
|
|
318
334
|
|
|
319
335
|
Examples (zsh):
|
|
320
336
|
|
|
321
337
|
```zsh
|
|
322
|
-
#
|
|
323
|
-
etlplus extract file data.
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
# Equivalent strict behavior via flag (overrides environment)
|
|
333
|
-
etlplus extract file data.csv --format csv --strict-format
|
|
334
|
-
etlplus load data.json file out.csv --format csv --strict-format
|
|
335
|
-
|
|
336
|
-
# Recommended: rely on extension, no --format needed for files
|
|
337
|
-
etlplus extract file data.csv
|
|
338
|
-
etlplus load data.json file out.csv
|
|
338
|
+
# Force CSV parsing for an extension-less file
|
|
339
|
+
etlplus extract --from file data.txt --source-format csv
|
|
340
|
+
|
|
341
|
+
# Write CSV to a file without the .csv suffix
|
|
342
|
+
etlplus load --to file output.bin --target-format csv < data.json
|
|
343
|
+
|
|
344
|
+
# Leave the flags off when extensions already match the desired format
|
|
345
|
+
etlplus extract --from file data.csv
|
|
346
|
+
etlplus load --to file data.json < data.json
|
|
339
347
|
```
|
|
340
348
|
|
|
341
349
|
## Transformation Operations
|
|
@@ -31,9 +31,9 @@ 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=
|
|
35
|
-
etlplus/cli/handlers.py,sha256=
|
|
36
|
-
etlplus/cli/main.py,sha256=
|
|
34
|
+
etlplus/cli/app.py,sha256=pc9VDUb3Qc8u5-XyDrHJkrSR9D3bq4e9zLbaD8KzyfY,32618
|
|
35
|
+
etlplus/cli/handlers.py,sha256=aI_ZlnJCGGkVnVJJPhmPRCXc31MxtLaOeqqJoo3ci48,15816
|
|
36
|
+
etlplus/cli/main.py,sha256=LaE3q4jbQ8KTTvs4D_khpOdlTKrdaPmxB28WML6hnLg,15059
|
|
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
|
|
@@ -43,9 +43,9 @@ etlplus/config/types.py,sha256=a0epJ3z16HQ5bY3Ctf8s_cQPa3f0HHcwdOcjCP2xoG4,4954
|
|
|
43
43
|
etlplus/config/utils.py,sha256=4SUHMkt5bKBhMhiJm-DrnmE2Q4TfOgdNCKz8PJDS27o,3443
|
|
44
44
|
etlplus/validation/__init__.py,sha256=Pe5Xg1_EA4uiNZGYu5WTF3j7odjmyxnAJ8rcioaplSQ,1254
|
|
45
45
|
etlplus/validation/utils.py,sha256=Mtqg449VIke0ziy_wd2r6yrwJzQkA1iulZC87FzXMjo,10201
|
|
46
|
-
etlplus-0.4.
|
|
47
|
-
etlplus-0.4.
|
|
48
|
-
etlplus-0.4.
|
|
49
|
-
etlplus-0.4.
|
|
50
|
-
etlplus-0.4.
|
|
51
|
-
etlplus-0.4.
|
|
46
|
+
etlplus-0.4.7.dist-info/licenses/LICENSE,sha256=MuNO63i6kWmgnV2pbP2SLqP54mk1BGmu7CmbtxMmT-U,1069
|
|
47
|
+
etlplus-0.4.7.dist-info/METADATA,sha256=zD88Gnu6IAFvbIR_L--yZE5EN2VpEK-Q3B1CZypmF9I,17278
|
|
48
|
+
etlplus-0.4.7.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
49
|
+
etlplus-0.4.7.dist-info/entry_points.txt,sha256=6w-2-jzuPa55spzK34h-UKh2JTEShh38adFRONNP9QE,45
|
|
50
|
+
etlplus-0.4.7.dist-info/top_level.txt,sha256=aWWF-udn_sLGuHTM6W6MLh99ArS9ROkUWO8Mi8y1_2U,8
|
|
51
|
+
etlplus-0.4.7.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|