etlplus 0.4.6__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/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__
@@ -68,6 +70,32 @@ class _FormatAction(argparse.Action):
68
70
  # SECTION: INTERNAL FUNCTIONS =============================================== #
69
71
 
70
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
+
71
99
  def _add_config_option(
72
100
  parser: argparse.ArgumentParser,
73
101
  *,
@@ -129,32 +157,6 @@ def _add_format_options(
129
157
  )
130
158
 
131
159
 
132
- def _add_boolean_flag(
133
- parser: argparse.ArgumentParser,
134
- *,
135
- name: str,
136
- help_text: str,
137
- ) -> None:
138
- """Add a toggle that also supports the ``--no-`` prefix via 3.13.
139
-
140
- Parameters
141
- ----------
142
- parser : argparse.ArgumentParser
143
- Parser receiving the flag.
144
- name : str
145
- Primary flag name without leading dashes.
146
- help_text : str
147
- Help text rendered in ``--help`` output.
148
- """
149
-
150
- parser.add_argument(
151
- f'--{name}',
152
- action=argparse.BooleanOptionalAction,
153
- default=False,
154
- help=help_text,
155
- )
156
-
157
-
158
160
  def _cli_description() -> str:
159
161
  return '\n'.join(
160
162
  [
@@ -188,6 +190,93 @@ def _cli_epilog() -> str:
188
190
  )
189
191
 
190
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
+
191
280
  # SECTION: FUNCTIONS ======================================================== #
192
281
 
193
282
 
@@ -422,6 +511,9 @@ def main(
422
511
 
423
512
  Raises
424
513
  ------
514
+ click.exceptions.UsageError
515
+ Re-raises Typer/Click usage errors after printing help for unknown
516
+ commands.
425
517
  SystemExit
426
518
  Re-raises SystemExit exceptions to preserve exit codes.
427
519
 
@@ -442,6 +534,19 @@ def main(
442
534
  )
443
535
  return int(result or 0)
444
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
+
445
550
  except typer.Exit as exc:
446
551
  return int(exc.exit_code)
447
552
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: etlplus
3
- Version: 0.4.6
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
@@ -33,7 +33,7 @@ etlplus/api/rate_limiting/rate_limiter.py,sha256=Uxozqd_Ej5Lsj-M-mLT2WexChgWh7x3
33
33
  etlplus/cli/__init__.py,sha256=J97-Rv931IL1_b4AXnB7Fbbd7HKnHBpx18NQfC_kE6c,299
34
34
  etlplus/cli/app.py,sha256=pc9VDUb3Qc8u5-XyDrHJkrSR9D3bq4e9zLbaD8KzyfY,32618
35
35
  etlplus/cli/handlers.py,sha256=aI_ZlnJCGGkVnVJJPhmPRCXc31MxtLaOeqqJoo3ci48,15816
36
- etlplus/cli/main.py,sha256=9hoitdc9FisrXzwZniTglPWwKsODFAW-A-2QQV4NkBs,12565
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.6.dist-info/licenses/LICENSE,sha256=MuNO63i6kWmgnV2pbP2SLqP54mk1BGmu7CmbtxMmT-U,1069
47
- etlplus-0.4.6.dist-info/METADATA,sha256=oq0-zYAVKNkZFCBPecObqjJ2LuYcbSX5kVjCErpiDO4,17278
48
- etlplus-0.4.6.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
49
- etlplus-0.4.6.dist-info/entry_points.txt,sha256=6w-2-jzuPa55spzK34h-UKh2JTEShh38adFRONNP9QE,45
50
- etlplus-0.4.6.dist-info/top_level.txt,sha256=aWWF-udn_sLGuHTM6W6MLh99ArS9ROkUWO8Mi8y1_2U,8
51
- etlplus-0.4.6.dist-info/RECORD,,
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,,