etlplus 0.8.2__py3-none-any.whl → 0.8.4__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/io.py CHANGED
@@ -6,7 +6,6 @@ Shared I/O helpers for CLI handlers (stdin/stdout, payload hydration).
6
6
 
7
7
  from __future__ import annotations
8
8
 
9
- import argparse
10
9
  import csv
11
10
  import io as _io
12
11
  import json
@@ -19,7 +18,6 @@ from typing import cast
19
18
  from ..enums import FileFormat
20
19
  from ..file import File
21
20
  from ..types import JSONData
22
- from ..utils import json_type
23
21
  from ..utils import print_json
24
22
 
25
23
  # SECTION: EXPORTS ========================================================== #
@@ -29,11 +27,10 @@ __all__ = [
29
27
  # Functions
30
28
  'emit_json',
31
29
  'emit_or_write',
32
- 'explicit_cli_format',
33
30
  'infer_payload_format',
34
31
  'materialize_file_payload',
32
+ 'parse_json_payload',
35
33
  'parse_text_payload',
36
- 'presentation_flags',
37
34
  'read_csv_rows',
38
35
  'read_stdin_text',
39
36
  'resolve_cli_payload',
@@ -96,34 +93,6 @@ def emit_or_write(
96
93
  emit_json(data, pretty=pretty)
97
94
 
98
95
 
99
- def explicit_cli_format(
100
- args: argparse.Namespace,
101
- ) -> str | None:
102
- """
103
- Return explicit format hint when provided on CLI.
104
-
105
- Parameters
106
- ----------
107
- args : argparse.Namespace
108
- The argparse namespace containing CLI arguments.
109
-
110
- Returns
111
- -------
112
- str | None
113
- The explicit format hint if provided, otherwise None.
114
- """
115
- if not getattr(args, '_format_explicit', False):
116
- return None
117
- for attr in ('format', 'target_format', 'source_format'):
118
- value = getattr(args, attr, None)
119
- if value is None:
120
- continue
121
- normalized = str(value).strip().lower()
122
- if normalized:
123
- return normalized
124
- return None
125
-
126
-
127
96
  def infer_payload_format(
128
97
  text: str,
129
98
  ) -> str:
@@ -200,6 +169,33 @@ def materialize_file_payload(
200
169
  return File(path, fmt).read()
201
170
 
202
171
 
172
+ def parse_json_payload(text: str) -> JSONData:
173
+ """
174
+ Parse JSON text and surface a concise error when it fails.
175
+
176
+ Parameters
177
+ ----------
178
+ text : str
179
+ The JSON text to parse.
180
+
181
+ Returns
182
+ -------
183
+ JSONData
184
+ The parsed JSON data.
185
+
186
+ Raises
187
+ ------
188
+ ValueError
189
+ When the JSON text is invalid.
190
+ """
191
+ try:
192
+ return cast(JSONData, json.loads(text))
193
+ except json.JSONDecodeError as e:
194
+ raise ValueError(
195
+ f'Invalid JSON payload: {e.msg} (pos {e.pos})',
196
+ ) from e
197
+
198
+
203
199
  def parse_text_payload(
204
200
  text: str,
205
201
  fmt: str | None,
@@ -221,32 +217,13 @@ def parse_text_payload(
221
217
  """
222
218
  effective = (fmt or '').strip().lower() or infer_payload_format(text)
223
219
  if effective == 'json':
224
- return cast(JSONData, json_type(text))
220
+ return parse_json_payload(text)
225
221
  if effective == 'csv':
226
222
  reader = csv.DictReader(_io.StringIO(text))
227
223
  return [dict(row) for row in reader]
228
224
  return text
229
225
 
230
226
 
231
- def presentation_flags(
232
- args: argparse.Namespace,
233
- ) -> tuple[bool, bool]:
234
- """
235
- Return (pretty, quiet) toggles with safe defaults.
236
-
237
- Parameters
238
- ----------
239
- args : argparse.Namespace
240
- The argparse namespace containing CLI arguments.
241
-
242
- Returns
243
- -------
244
- tuple[bool, bool]
245
- A tuple containing the pretty and quiet flags.
246
- """
247
- return getattr(args, 'pretty', True), getattr(args, 'quiet', False)
248
-
249
-
250
227
  def read_csv_rows(
251
228
  path: Path,
252
229
  ) -> list[dict[str, str]]:
etlplus/cli/main.py CHANGED
@@ -9,31 +9,20 @@ 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
14
+ import warnings
15
15
 
16
16
  import click
17
17
  import typer
18
18
 
19
- from .. import __version__
20
- from ..utils import json_type
21
- from . import handlers
22
19
  from .commands import app
23
- from .constants import CLI_DESCRIPTION
24
- from .constants import CLI_EPILOG
25
- from .constants import DATA_CONNECTORS
26
- from .constants import FILE_FORMATS
27
- from .constants import PROJECT_URL
28
- from .options import add_argparse_format_options
29
- from .types import DataConnectorContext
30
20
 
31
21
  # SECTION: EXPORTS ========================================================== #
32
22
 
33
23
 
34
24
  __all__ = [
35
25
  # Functions
36
- 'create_parser',
37
26
  'main',
38
27
  ]
39
28
 
@@ -41,74 +30,6 @@ __all__ = [
41
30
  # SECTION: INTERNAL FUNCTIONS =============================================== #
42
31
 
43
32
 
44
- def _add_boolean_flag(
45
- parser: argparse.ArgumentParser,
46
- *,
47
- name: str,
48
- help_text: str,
49
- ) -> None:
50
- """Add a toggle that also supports the ``--no-`` prefix via 3.13.
51
-
52
- Parameters
53
- ----------
54
- parser : argparse.ArgumentParser
55
- Parser receiving the flag.
56
- name : str
57
- Primary flag name without leading dashes.
58
- help_text : str
59
- Help text rendered in ``--help`` output.
60
- """
61
-
62
- parser.add_argument(
63
- f'--{name}',
64
- action=argparse.BooleanOptionalAction,
65
- default=False,
66
- help=help_text,
67
- )
68
-
69
-
70
- def _add_config_option(
71
- parser: argparse.ArgumentParser,
72
- *,
73
- required: bool = True,
74
- ) -> None:
75
- """Attach the shared ``--config`` option used by legacy commands.
76
-
77
- Parameters
78
- ----------
79
- parser : argparse.ArgumentParser
80
- Parser receiving the option.
81
- required : bool, optional
82
- Whether the flag must be provided. Defaults to ``True``.
83
- """
84
-
85
- parser.add_argument(
86
- '--config',
87
- required=required,
88
- help='Path to pipeline YAML configuration file',
89
- )
90
-
91
-
92
- def _add_format_options(
93
- parser: argparse.ArgumentParser,
94
- *,
95
- context: DataConnectorContext,
96
- ) -> None:
97
- """
98
- Attach shared ``--source-format`` or ``--target-format`` options to
99
- extract/load parsers.
100
-
101
- Parameters
102
- ----------
103
- parser : argparse.ArgumentParser
104
- Parser to augment.
105
- context : DataConnectorContext
106
- Context for the format option: either ``'source'`` or ``'target'``
107
- """
108
- parser.set_defaults(_format_explicit=False)
109
- add_argparse_format_options(parser, context=context)
110
-
111
-
112
33
  def _emit_context_help(
113
34
  ctx: click.Context | None,
114
35
  ) -> bool:
@@ -199,262 +120,23 @@ def _is_unknown_command_error(
199
120
  # SECTION: FUNCTIONS ======================================================== #
200
121
 
201
122
 
202
- def create_parser() -> argparse.ArgumentParser:
123
+ def create_parser() -> object:
203
124
  """
204
- Return the legacy :mod:`argparse` parser wired to current handlers.
125
+ Deprecated legacy entrypoint.
205
126
 
206
- Returns
207
- -------
208
- argparse.ArgumentParser
209
- Parser compatible with historical ``etlplus`` entry points.
127
+ The argparse-based parser has been removed. Use the Typer-powered
128
+ ``etlplus`` CLI instead (``etlplus.cli.commands.app``).
210
129
  """
211
-
212
- parser = argparse.ArgumentParser(
213
- prog='etlplus',
214
- description=CLI_DESCRIPTION,
215
- epilog=CLI_EPILOG,
216
- formatter_class=argparse.RawDescriptionHelpFormatter,
217
- )
218
-
219
- parser.add_argument(
220
- '-V',
221
- '--version',
222
- action='version',
223
- version=f'%(prog)s {__version__}',
224
- )
225
-
226
- parser.add_argument(
227
- '--pretty',
228
- action=argparse.BooleanOptionalAction,
229
- default=True,
230
- help='Pretty-print JSON output (default: pretty).',
130
+ warnings.warn(
131
+ 'create_parser is deprecated and no longer returns an argparse '
132
+ 'parser. Use the Typer CLI entrypoint instead.',
133
+ DeprecationWarning,
134
+ stacklevel=2,
231
135
  )
232
- parser.add_argument(
233
- '--quiet',
234
- action=argparse.BooleanOptionalAction,
235
- default=False,
236
- help='Suppress warnings and non-essential output.',
237
- )
238
- parser.add_argument(
239
- '--verbose',
240
- action=argparse.BooleanOptionalAction,
241
- default=False,
242
- help='Emit extra diagnostics to stderr.',
243
- )
244
-
245
- subparsers = parser.add_subparsers(
246
- dest='command',
247
- help='Available commands',
248
- )
249
- subparsers.required = True
250
-
251
- extract_parser = subparsers.add_parser(
252
- 'extract',
253
- help='Extract data from sources (files, databases, REST APIs)',
254
- formatter_class=argparse.ArgumentDefaultsHelpFormatter,
255
- )
256
- extract_parser.add_argument(
257
- 'source_type',
258
- choices=sorted(DATA_CONNECTORS),
259
- help='Type of source to extract from',
260
- )
261
- extract_parser.add_argument(
262
- 'source',
263
- help=(
264
- 'Source location (file path, database connection string, '
265
- 'or API URL)'
266
- ),
267
- )
268
- _add_format_options(extract_parser, context='source')
269
- extract_parser.set_defaults(func=handlers.extract_handler)
270
-
271
- validate_parser = subparsers.add_parser(
272
- 'validate',
273
- help='Validate data from sources',
274
- formatter_class=argparse.ArgumentDefaultsHelpFormatter,
136
+ raise RuntimeError(
137
+ 'The legacy argparse parser has been removed. Invoke the Typer-based '
138
+ 'CLI via `etlplus` or import `etlplus.cli.commands.app`.',
275
139
  )
276
- validate_parser.add_argument(
277
- 'source',
278
- help='Data source to validate (file path or JSON string)',
279
- )
280
- validate_parser.add_argument(
281
- '--rules',
282
- type=json_type,
283
- default={},
284
- help='Validation rules as JSON string',
285
- )
286
- validate_parser.set_defaults(func=handlers.validate_handler)
287
-
288
- transform_parser = subparsers.add_parser(
289
- 'transform',
290
- help='Transform data',
291
- formatter_class=argparse.ArgumentDefaultsHelpFormatter,
292
- )
293
- transform_parser.add_argument(
294
- 'source',
295
- help='Data source to transform (file path or JSON string)',
296
- )
297
- transform_parser.add_argument(
298
- '--operations',
299
- type=json_type,
300
- default={},
301
- help='Transformation operations as JSON string',
302
- )
303
- transform_parser.add_argument(
304
- '--from',
305
- dest='from_',
306
- choices=sorted(DATA_CONNECTORS),
307
- help='Override the inferred source type (file, database, api).',
308
- )
309
- transform_parser.add_argument(
310
- '--to',
311
- dest='to',
312
- choices=sorted(DATA_CONNECTORS),
313
- help='Override the inferred target type (file, database, api).',
314
- )
315
- transform_parser.add_argument(
316
- '--source-format',
317
- choices=sorted(FILE_FORMATS),
318
- dest='source_format',
319
- help=(
320
- 'Input payload format when SOURCE is - or a literal payload. '
321
- 'File sources infer format from the extension.'
322
- ),
323
- )
324
- transform_parser.add_argument(
325
- '--target-format',
326
- dest='target_format',
327
- choices=sorted(FILE_FORMATS),
328
- help=(
329
- 'Output payload format '
330
- 'when writing to stdout or non-file targets. '
331
- 'File targets infer format from the extension.'
332
- ),
333
- )
334
- transform_parser.set_defaults(func=handlers.transform_handler)
335
-
336
- load_parser = subparsers.add_parser(
337
- 'load',
338
- help='Load data to targets (files, databases, REST APIs)',
339
- formatter_class=argparse.ArgumentDefaultsHelpFormatter,
340
- )
341
- load_parser.add_argument(
342
- 'source',
343
- help='Data source to load (file path or JSON string)',
344
- )
345
- load_parser.add_argument(
346
- 'target_type',
347
- choices=sorted(DATA_CONNECTORS),
348
- help='Type of target to load to',
349
- )
350
- load_parser.add_argument(
351
- 'target',
352
- help=(
353
- 'Target location (file path, database connection string, '
354
- 'or API URL)'
355
- ),
356
- )
357
- _add_format_options(load_parser, context='target')
358
- load_parser.set_defaults(func=handlers.load_handler)
359
-
360
- render_parser = subparsers.add_parser(
361
- 'render',
362
- help='Render SQL DDL from table schema specs',
363
- formatter_class=argparse.ArgumentDefaultsHelpFormatter,
364
- )
365
- render_parser.add_argument(
366
- '--config',
367
- help='Pipeline YAML containing table_schemas',
368
- )
369
- render_parser.add_argument(
370
- '-o',
371
- '--output',
372
- help='Write SQL to this path (stdout when omitted)',
373
- )
374
- render_parser.add_argument(
375
- '--spec',
376
- help='Standalone table spec file (.yml/.yaml/.json)',
377
- )
378
- render_parser.add_argument(
379
- '--table',
380
- help='Render only the table matching this name',
381
- )
382
- render_parser.add_argument(
383
- '--template',
384
- default='ddl',
385
- help='Template key (ddl/view) or path to a Jinja template file',
386
- )
387
- render_parser.add_argument(
388
- '--template-path',
389
- dest='template_path',
390
- help=(
391
- 'Explicit path to a Jinja template file (overrides template key).'
392
- ),
393
- )
394
- render_parser.set_defaults(func=handlers.render_handler)
395
-
396
- check_parser = subparsers.add_parser(
397
- 'check',
398
- help='Inspect ETL pipeline metadata',
399
- formatter_class=argparse.ArgumentDefaultsHelpFormatter,
400
- )
401
- _add_config_option(check_parser)
402
- _add_boolean_flag(
403
- check_parser,
404
- name='jobs',
405
- help_text='List ETL jobs',
406
- )
407
- _add_boolean_flag(
408
- check_parser,
409
- name='pipelines',
410
- help_text='List ETL pipelines',
411
- )
412
- _add_boolean_flag(
413
- check_parser,
414
- name='sources',
415
- help_text='List data sources',
416
- )
417
- _add_boolean_flag(
418
- check_parser,
419
- name='summary',
420
- help_text=(
421
- 'Show pipeline summary (name, version, sources, targets, jobs)'
422
- ),
423
- )
424
- _add_boolean_flag(
425
- check_parser,
426
- name='targets',
427
- help_text='List data targets',
428
- )
429
- _add_boolean_flag(
430
- check_parser,
431
- name='transforms',
432
- help_text='List data transforms',
433
- )
434
- check_parser.set_defaults(func=handlers.check_handler)
435
-
436
- run_parser = subparsers.add_parser(
437
- 'run',
438
- help=(
439
- 'Run an ETL pipeline '
440
- f'(see {PROJECT_URL}/blob/main/docs/run-module.md)'
441
- ),
442
- formatter_class=argparse.ArgumentDefaultsHelpFormatter,
443
- )
444
- _add_config_option(run_parser)
445
- run_parser.add_argument(
446
- '-j',
447
- '--job',
448
- help='Name of the job to run',
449
- )
450
- run_parser.add_argument(
451
- '-p',
452
- '--pipeline',
453
- help='Name of the pipeline to run',
454
- )
455
- run_parser.set_defaults(func=handlers.run_handler)
456
-
457
- return parser
458
140
 
459
141
 
460
142
  def main(
etlplus/cli/options.py CHANGED
@@ -1,89 +1,23 @@
1
1
  """
2
2
  :mod:`etlplus.cli.options` module.
3
3
 
4
- Shared command-line interface (CLI) option helpers for both Typer and argparse
5
- entry points.
4
+ Shared Typer helper utilities for command-line interface (CLI) option
5
+ configuration.
6
6
  """
7
7
 
8
8
  from __future__ import annotations
9
9
 
10
- import argparse
11
- from collections.abc import Sequence
12
-
13
- from .constants import DEFAULT_FILE_FORMAT
14
- from .constants import FILE_FORMATS
15
10
  from .types import DataConnectorContext
16
11
 
17
12
  # SECTION: EXPORTS ========================================================== #
18
13
 
19
14
 
20
15
  __all__ = [
21
- # Classes
22
- 'FormatAction',
23
16
  # Functions
24
- 'add_argparse_format_options',
25
17
  'typer_format_option_kwargs',
26
18
  ]
27
19
 
28
20
 
29
- # SECTION: CLASSES ========================================================== #
30
-
31
-
32
- class FormatAction(argparse.Action):
33
- """Record when a format override flag is provided."""
34
-
35
- def __call__(
36
- self,
37
- parser: argparse.ArgumentParser,
38
- namespace: argparse.Namespace,
39
- values: str | Sequence[object] | None,
40
- option_string: str | None = None,
41
- ) -> None: # pragma: no cover - argparse wiring
42
- setattr(namespace, self.dest, values)
43
- namespace._format_explicit = True
44
-
45
-
46
- # SECTION: FUNCTIONS ======================================================== #
47
-
48
-
49
- def add_argparse_format_options(
50
- parser: argparse.ArgumentParser,
51
- *,
52
- context: DataConnectorContext,
53
- ) -> None:
54
- """
55
- Attach ``--source-format`` and ``--target-format`` arguments.
56
-
57
- Parameters
58
- ----------
59
- parser : argparse.ArgumentParser
60
- Parser receiving the options.
61
- context : DataConnectorContext
62
- Either ``'source'`` or ``'target'`` to tailor help text.
63
- """
64
- parser.set_defaults(_format_explicit=False)
65
- parser.add_argument(
66
- '--source-format',
67
- choices=sorted(FILE_FORMATS),
68
- default=DEFAULT_FILE_FORMAT,
69
- action=FormatAction,
70
- help=(
71
- f'Format of the {context}. Overrides filename-based inference '
72
- 'when provided.'
73
- ),
74
- )
75
- parser.add_argument(
76
- '--target-format',
77
- choices=sorted(FILE_FORMATS),
78
- default=DEFAULT_FILE_FORMAT,
79
- action=FormatAction,
80
- help=(
81
- f'Format of the {context}. Overrides filename-based inference '
82
- 'when provided.'
83
- ),
84
- )
85
-
86
-
87
21
  def typer_format_option_kwargs(
88
22
  *,
89
23
  context: DataConnectorContext,