etlplus 0.5.2__py3-none-any.whl → 0.9.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/main.py CHANGED
@@ -9,193 +9,32 @@ This module exposes :func:`main` for the console script as well as
9
9
 
10
10
  from __future__ import annotations
11
11
 
12
- import argparse
13
12
  import contextlib
14
13
  import sys
15
- from collections.abc import Sequence
16
- from typing import Literal
14
+ import warnings
17
15
 
18
16
  import click
19
17
  import typer
20
18
 
21
- from .. import __version__
22
- from ..enums import DataConnectorType
23
- from ..enums import FileFormat
24
- from ..utils import json_type
25
- from .app import PROJECT_URL
26
- from .app import app
27
- from .handlers import cmd_extract
28
- from .handlers import cmd_list
29
- from .handlers import cmd_load
30
- from .handlers import cmd_pipeline
31
- from .handlers import cmd_render
32
- from .handlers import cmd_run
33
- from .handlers import cmd_transform
34
- from .handlers import cmd_validate
19
+ from .commands import app
35
20
 
36
21
  # SECTION: EXPORTS ========================================================== #
37
22
 
38
23
 
39
24
  __all__ = [
40
25
  # Functions
41
- 'create_parser',
42
26
  'main',
43
27
  ]
44
28
 
45
29
 
46
- # SECTION: TYPE ALIASES ===================================================== #
47
-
48
-
49
- type FormatContext = Literal['source', 'target']
50
-
51
-
52
- # SECTION: INTERNAL CLASSES ================================================= #
53
-
54
-
55
- class _FormatAction(argparse.Action):
56
- """
57
- Argparse action that records when ``--source-format`` or
58
- ``--target-format`` is provided."""
59
-
60
- def __call__(
61
- self,
62
- parser: argparse.ArgumentParser,
63
- namespace: argparse.Namespace,
64
- values: str | Sequence[object] | None,
65
- option_string: str | None = None,
66
- ) -> None: # pragma: no cover
67
- setattr(namespace, self.dest, values)
68
- namespace._format_explicit = True
69
-
70
-
71
30
  # SECTION: INTERNAL FUNCTIONS =============================================== #
72
31
 
73
32
 
74
- def _add_boolean_flag(
75
- parser: argparse.ArgumentParser,
76
- *,
77
- name: str,
78
- help_text: str,
79
- ) -> None:
80
- """Add a toggle that also supports the ``--no-`` prefix via 3.13.
81
-
82
- Parameters
83
- ----------
84
- parser : argparse.ArgumentParser
85
- Parser receiving the flag.
86
- name : str
87
- Primary flag name without leading dashes.
88
- help_text : str
89
- Help text rendered in ``--help`` output.
90
- """
91
-
92
- parser.add_argument(
93
- f'--{name}',
94
- action=argparse.BooleanOptionalAction,
95
- default=False,
96
- help=help_text,
97
- )
98
-
99
-
100
- def _add_config_option(
101
- parser: argparse.ArgumentParser,
102
- *,
103
- required: bool = True,
104
- ) -> None:
105
- """Attach the shared ``--config`` option used by legacy commands.
106
-
107
- Parameters
108
- ----------
109
- parser : argparse.ArgumentParser
110
- Parser receiving the option.
111
- required : bool, optional
112
- Whether the flag must be provided. Defaults to ``True``.
113
- """
114
-
115
- parser.add_argument(
116
- '--config',
117
- required=required,
118
- help='Path to pipeline YAML configuration file',
119
- )
120
-
121
-
122
- def _add_format_options(
123
- parser: argparse.ArgumentParser,
124
- *,
125
- context: FormatContext,
126
- ) -> None:
127
- """
128
- Attach shared ``--source-format`` or ``--target-format`` options to
129
- extract/load parsers.
130
-
131
- Parameters
132
- ----------
133
- parser : argparse.ArgumentParser
134
- Parser to augment.
135
- context : FormatContext
136
- Context for the format option: either ``'source'`` or ``'target'``
137
- """
138
- parser.set_defaults(_format_explicit=False)
139
- parser.add_argument(
140
- '--source-format',
141
- choices=list(FileFormat.choices()),
142
- default='json',
143
- action=_FormatAction,
144
- help=(
145
- f'Format of the {context}. Overrides filename-based inference '
146
- 'when provided.'
147
- ),
148
- )
149
- parser.add_argument(
150
- '--target-format',
151
- choices=list(FileFormat.choices()),
152
- default='json',
153
- action=_FormatAction,
154
- help=(
155
- f'Format of the {context}. Overrides filename-based inference '
156
- 'when provided.'
157
- ),
158
- )
159
-
160
-
161
- def _cli_description() -> str:
162
- return '\n'.join(
163
- [
164
- 'ETLPlus - A Swiss Army knife for simple ETL operations.',
165
- '',
166
- ' Provide a subcommand and options. Examples:',
167
- '',
168
- ' etlplus extract file in.csv > out.json',
169
- ' etlplus validate in.json --rules \'{"required": ["id"]}\'',
170
- (
171
- ' etlplus transform --from file in.csv --operations '
172
- '\'{"select": ["id"]}\' --to file -o out.json'
173
- ),
174
- ' etlplus extract in.csv | etlplus load --to file out.json',
175
- '',
176
- ' Override format inference when extensions are misleading:',
177
- '',
178
- ' etlplus extract data.txt --source-format csv',
179
- ' etlplus load payload.bin --target-format json',
180
- ],
181
- )
182
-
183
-
184
- def _cli_epilog() -> str:
185
- return '\n'.join(
186
- [
187
- 'Tip:',
188
- ' --source-format and --target-format override format '
189
- 'inference based on filename extensions when needed.',
190
- ],
191
- )
192
-
193
-
194
33
  def _emit_context_help(
195
34
  ctx: click.Context | None,
196
35
  ) -> bool:
197
36
  """
198
- Mirror Click help output for the provided context onto stderr.
37
+ Mirror Click help output for the provided context onto STDERR.
199
38
 
200
39
  Parameters
201
40
  ----------
@@ -253,6 +92,7 @@ def _is_illegal_option_error(
253
92
  exc,
254
93
  (
255
94
  click.exceptions.BadOptionUsage,
95
+ click.exceptions.BadParameter,
256
96
  click.exceptions.NoSuchOption,
257
97
  ),
258
98
  )
@@ -281,265 +121,23 @@ def _is_unknown_command_error(
281
121
  # SECTION: FUNCTIONS ======================================================== #
282
122
 
283
123
 
284
- def create_parser() -> argparse.ArgumentParser:
124
+ def create_parser() -> object:
285
125
  """
286
- Return the legacy :mod:`argparse` parser wired to current handlers.
126
+ Deprecated legacy entrypoint.
287
127
 
288
- Returns
289
- -------
290
- argparse.ArgumentParser
291
- Parser compatible with historical ``etlplus`` entry points.
128
+ The argparse-based parser has been removed. Use the Typer-powered
129
+ ``etlplus`` CLI instead (``etlplus.cli.commands.app``).
292
130
  """
293
-
294
- parser = argparse.ArgumentParser(
295
- prog='etlplus',
296
- description=_cli_description(),
297
- epilog=_cli_epilog(),
298
- formatter_class=argparse.RawDescriptionHelpFormatter,
299
- )
300
-
301
- parser.add_argument(
302
- '-V',
303
- '--version',
304
- action='version',
305
- version=f'%(prog)s {__version__}',
131
+ warnings.warn(
132
+ 'create_parser is deprecated and no longer returns an argparse '
133
+ 'parser. Use the Typer CLI entrypoint instead.',
134
+ DeprecationWarning,
135
+ stacklevel=2,
306
136
  )
307
-
308
- subparsers = parser.add_subparsers(
309
- dest='command',
310
- help='Available commands',
137
+ raise RuntimeError(
138
+ 'The legacy argparse parser has been removed. Invoke the Typer-based '
139
+ 'CLI via `etlplus` or import `etlplus.cli.commands.app`.',
311
140
  )
312
- subparsers.required = True
313
-
314
- extract_parser = subparsers.add_parser(
315
- 'extract',
316
- help='Extract data from sources (files, databases, REST APIs)',
317
- formatter_class=argparse.ArgumentDefaultsHelpFormatter,
318
- )
319
- extract_parser.add_argument(
320
- 'source_type',
321
- choices=list(DataConnectorType.choices()),
322
- help='Type of source to extract from',
323
- )
324
- extract_parser.add_argument(
325
- 'source',
326
- help=(
327
- 'Source location (file path, database connection string, '
328
- 'or API URL)'
329
- ),
330
- )
331
- _add_format_options(extract_parser, context='source')
332
- extract_parser.set_defaults(func=cmd_extract)
333
-
334
- validate_parser = subparsers.add_parser(
335
- 'validate',
336
- help='Validate data from sources',
337
- formatter_class=argparse.ArgumentDefaultsHelpFormatter,
338
- )
339
- validate_parser.add_argument(
340
- 'source',
341
- help='Data source to validate (file path or JSON string)',
342
- )
343
- validate_parser.add_argument(
344
- '--rules',
345
- type=json_type,
346
- default={},
347
- help='Validation rules as JSON string',
348
- )
349
- validate_parser.set_defaults(func=cmd_validate)
350
-
351
- transform_parser = subparsers.add_parser(
352
- 'transform',
353
- help='Transform data',
354
- formatter_class=argparse.ArgumentDefaultsHelpFormatter,
355
- )
356
- transform_parser.add_argument(
357
- 'source',
358
- help='Data source to transform (file path or JSON string)',
359
- )
360
- transform_parser.add_argument(
361
- '--operations',
362
- type=json_type,
363
- default={},
364
- help='Transformation operations as JSON string',
365
- )
366
- transform_parser.add_argument(
367
- '--from',
368
- dest='from_',
369
- choices=list(DataConnectorType.choices()),
370
- help='Override the inferred source type (file, database, api).',
371
- )
372
- transform_parser.add_argument(
373
- '--to',
374
- dest='to',
375
- choices=list(DataConnectorType.choices()),
376
- help='Override the inferred target type (file, database, api).',
377
- )
378
- transform_parser.add_argument(
379
- '--source-format',
380
- choices=list(FileFormat.choices()),
381
- dest='source_format',
382
- help=(
383
- 'Input payload format when SOURCE is - or a literal payload. '
384
- 'File sources infer format from the extension.'
385
- ),
386
- )
387
- transform_parser.add_argument(
388
- '--target-format',
389
- dest='target_format',
390
- choices=list(FileFormat.choices()),
391
- help=(
392
- 'Output payload format '
393
- 'when writing to stdout or non-file targets. '
394
- 'File targets infer format from the extension.'
395
- ),
396
- )
397
- transform_parser.set_defaults(func=cmd_transform)
398
-
399
- load_parser = subparsers.add_parser(
400
- 'load',
401
- help='Load data to targets (files, databases, REST APIs)',
402
- formatter_class=argparse.ArgumentDefaultsHelpFormatter,
403
- )
404
- load_parser.add_argument(
405
- 'source',
406
- help='Data source to load (file path or JSON string)',
407
- )
408
- load_parser.add_argument(
409
- 'target_type',
410
- choices=list(DataConnectorType.choices()),
411
- help='Type of target to load to',
412
- )
413
- load_parser.add_argument(
414
- 'target',
415
- help=(
416
- 'Target location (file path, database connection string, '
417
- 'or API URL)'
418
- ),
419
- )
420
- _add_format_options(load_parser, context='target')
421
- load_parser.set_defaults(func=cmd_load)
422
-
423
- pipe_parser = subparsers.add_parser(
424
- 'pipeline',
425
- help=(
426
- 'DEPRECATED: use "list" (for summary/jobs) or "run" (to execute); '
427
- 'see '
428
- f'{PROJECT_URL}/blob/main/docs/pipeline-guide.md'
429
- ),
430
- formatter_class=argparse.ArgumentDefaultsHelpFormatter,
431
- )
432
- _add_config_option(pipe_parser)
433
- pipe_parser.add_argument(
434
- '--list',
435
- action='store_true',
436
- help='List available job names and exit',
437
- )
438
- pipe_parser.add_argument(
439
- '--run',
440
- metavar='JOB',
441
- help='Run a specific job by name',
442
- )
443
- pipe_parser.set_defaults(func=cmd_pipeline)
444
-
445
- render_parser = subparsers.add_parser(
446
- 'render',
447
- help='Render SQL DDL from table schema specs',
448
- formatter_class=argparse.ArgumentDefaultsHelpFormatter,
449
- )
450
- render_parser.add_argument(
451
- '--config',
452
- help='Pipeline YAML containing table_schemas',
453
- )
454
- render_parser.add_argument(
455
- '-o',
456
- '--output',
457
- help='Write SQL to this path (stdout when omitted)',
458
- )
459
- render_parser.add_argument(
460
- '--spec',
461
- help='Standalone table spec file (.yml/.yaml/.json)',
462
- )
463
- render_parser.add_argument(
464
- '--table',
465
- help='Render only the table matching this name',
466
- )
467
- render_parser.add_argument(
468
- '--template',
469
- default='ddl',
470
- help='Template key (ddl/view) or path to a Jinja template file',
471
- )
472
- render_parser.add_argument(
473
- '--template-path',
474
- dest='template_path',
475
- help=(
476
- 'Explicit path to a Jinja template file (overrides template key).'
477
- ),
478
- )
479
- render_parser.set_defaults(func=cmd_render)
480
-
481
- list_parser = subparsers.add_parser(
482
- 'list',
483
- help='List ETL pipeline metadata',
484
- formatter_class=argparse.ArgumentDefaultsHelpFormatter,
485
- )
486
- _add_config_option(list_parser)
487
- _add_boolean_flag(
488
- list_parser,
489
- name='jobs',
490
- help_text='List ETL jobs',
491
- )
492
- _add_boolean_flag(
493
- list_parser,
494
- name='pipelines',
495
- help_text='List ETL pipelines',
496
- )
497
- _add_boolean_flag(
498
- list_parser,
499
- name='sources',
500
- help_text='List data sources',
501
- )
502
- _add_boolean_flag(
503
- list_parser,
504
- name='summary',
505
- help_text=(
506
- 'Show pipeline summary (name, version, sources, targets, jobs)'
507
- ),
508
- )
509
- _add_boolean_flag(
510
- list_parser,
511
- name='targets',
512
- help_text='List data targets',
513
- )
514
- _add_boolean_flag(
515
- list_parser,
516
- name='transforms',
517
- help_text='List data transforms',
518
- )
519
- list_parser.set_defaults(func=cmd_list)
520
-
521
- run_parser = subparsers.add_parser(
522
- 'run',
523
- help=(
524
- 'Run an ETL pipeline '
525
- f'(see {PROJECT_URL}/blob/main/docs/run-module.md)'
526
- ),
527
- formatter_class=argparse.ArgumentDefaultsHelpFormatter,
528
- )
529
- _add_config_option(run_parser)
530
- run_parser.add_argument(
531
- '-j',
532
- '--job',
533
- help='Name of the job to run',
534
- )
535
- run_parser.add_argument(
536
- '-p',
537
- '--pipeline',
538
- help='Name of the pipeline to run',
539
- )
540
- run_parser.set_defaults(func=cmd_run)
541
-
542
- return parser
543
141
 
544
142
 
545
143
  def main(
etlplus/cli/options.py ADDED
@@ -0,0 +1,49 @@
1
+ """
2
+ :mod:`etlplus.cli.options` module.
3
+
4
+ Shared Typer helper utilities for command-line interface (CLI) option
5
+ configuration.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from .types import DataConnectorContext
11
+
12
+ # SECTION: EXPORTS ========================================================== #
13
+
14
+
15
+ __all__ = [
16
+ # Functions
17
+ 'typer_format_option_kwargs',
18
+ ]
19
+
20
+
21
+ def typer_format_option_kwargs(
22
+ *,
23
+ context: DataConnectorContext,
24
+ rich_help_panel: str = 'Format overrides',
25
+ ) -> dict[str, object]:
26
+ """
27
+ Return common Typer option kwargs for format overrides.
28
+
29
+ Parameters
30
+ ----------
31
+ context : DataConnectorContext
32
+ Either ``'source'`` or ``'target'`` to tailor help text.
33
+ rich_help_panel : str, optional
34
+ The rich help panel name. Default is ``'Format overrides'``.
35
+
36
+ Returns
37
+ -------
38
+ dict[str, object]
39
+ The Typer option keyword arguments.
40
+ """
41
+ return {
42
+ 'metavar': 'FORMAT',
43
+ 'show_default': False,
44
+ 'rich_help_panel': rich_help_panel,
45
+ 'help': (
46
+ f'Payload format when the {context} is STDIN/inline or a '
47
+ 'non-file connector. File connectors infer from extensions.'
48
+ ),
49
+ }