-
-
Notifications
You must be signed in to change notification settings - Fork 8
Expand file tree
/
Copy pathcommands.md
More file actions
608 lines (452 loc) · 23.9 KB
/
commands.md
File metadata and controls
608 lines (452 loc) · 23.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
# {octicon}`command-palette` Commands & groups
## Drop-in replacement
The whole namespace of `click_extra` is a superset of both `click` and `cloup` namespaces. Click Extra's main decorators, functions and classes extends and enhance Click and Cloup ones. Those left untouched by Click Extra are directly proxied to Cloup or Click.
This means if you want to [upgrade an existing CLI to Click Extra](tutorial.md), you can often replace imports of the `click` namespace by `click_extra` and it will work as expected.
## Click and Cloup inheritance
At the module level, `click_extra` imports all elements from `click.*`, then all elements from the `cloup.*` namespace.
Which means all elements not redefined by Click Extra fallback to Cloup. And if Cloup itself does not redefine them, they fallback to Click.
For example:
- `click_extra.echo` is a direct alias to `click.echo` because neither Click Extra or Cloup re-implements an `echo` helper.
- [`@cloup.option_group` is a specific feature of Cloup](https://cloup.readthedocs.io/en/stable/pages/option-groups.html) that is only implemented by it. It is not modified by Click Extra, and Click does not implement it. Still, `@click_extra.option_group` is a direct alias to Cloup's one.
- `@click_extra.timer` is a new decorator only implemented by Click Extra. So it is not a proxy of anything.
- As for `@click_extra.version_option`, it is a re-implementation of `@click.version_option`, and so overrides it. If you want to use its original version, import it directly from `click` namespace.
Here is some of the main decorators of Click Extra and how they wraps and extends Cloup and Click ones:
| Decorators from `click_extra` | Wrapped decorator | Base class |
| :---------------------------- | :-------------------- | :------------------------------- |
| `@command` | `@cloup.command` | `click_extra.ExtraCommand` |
| `@group` | `@cloup.group` | `click_extra.ExtraGroup` |
| `@lazy_group` | `@click_extra.group` | `click_extra.LazyGroup` |
| `@option` | `@cloup.option` | `click_extra.Option` |
| `@argument` | `@cloup.argument` | `click_extra.Argument` |
| `@version_option` | `@click_extra.option` | `click_extra.ExtraVersionOption` |
| `@color_option` | `@click_extra.option` | `click_extra.ColorOption` |
| `@config_option` | `@click_extra.option` | `click_extra.ConfigOption` |
| `@no_config_option` | `@click_extra.option` | `click_extra.NoConfigOption` |
| `@show_params_option` | `@click_extra.option` | `click_extra.ShowParamsOption` |
| `@table_format_option` | `@click_extra.option` | `click_extra.TableFormatOption` |
| `@telemetry_option` | `@click_extra.option` | `click_extra.TelemetryOption` |
| `@timer_option` | `@click_extra.option` | `click_extra.TimerOption` |
| `@verbose_option` | `@click_extra.option` | `click_extra.VerboseOption` |
| `@verbosity_option` | `@click_extra.option` | `click_extra.VerbosityOption` |
| `@option_group` | `@cloup.option_group` | `cloup.OptionGroup` |
| `@pass_context` | `@click.pass_context` | - |
| `@help_option` | `@click.help_option` | - |
| … | … | … |
Same for the main classes and functions, where some are re-implemented by Click Extra, and others are direct aliases to Cloup or Click ones:
| Classes from `click_extra` | Alias to | Parent class |
| :------------------------- | :--------------------------- | :------------------------ |
| `ExtraCommand` | - | `cloup.Command` |
| `ExtraGroup` | - | `cloup.Group` |
| `LazyGroup` | - | `click_extra.ExtraGroup` |
| `Option` | - | `cloup.Option` |
| `Argument` | - | `cloup.Argument` |
| `ExtraContext` | - | `cloup.Context` |
| `HelpFormatter` | `cloup.HelpFormatter` | |
| `HelpExtraFormatter` | - | `cloup.HelpFormatter` |
| `HelpTheme` | `cloup.HelpThene` | |
| `HelpExtraTheme` | - | `cloup.HelpThene` |
| `ExtraCliRunner` | - | `click.testing.CliRunner` |
| `ExtraVersionOption` | - | |
| `Style` | `cloup.Style` | |
| `echo` | `click.echo` | |
| `ParameterSource` | `click.core.ParameterSource` | |
| `UNSET` | `click._utils.UNSET` | |
| `Choice` | `click.Choice` | |
| `EnumChoice` | - | `click.Choice` |
| … | … | … |
```{hint}
You can inspect the implementation details in:
- [`click_extra.__init__`](https://github.com/kdeldycke/click-extra/blob/main/click_extra/__init__.py)
- [`cloup.__init__`](https://github.com/janluke/cloup/blob/master/cloup/__init__.py)
- [`click.__init__`](https://github.com/pallets/click/blob/main/src/click/__init__.py)
```
## Default options
The `@command` and `@group` decorators are [pre-configured with a set of default options](commands.md#click_extra.commands.default_extra_params).
### Remove default options
You can remove all default options by resetting the `params` argument to `None`:
```{click:source}
:emphasize-lines: 3
from click_extra import command
@command(params=None)
def bare_cli():
pass
```
Which results in:
```{click:run}
from textwrap import dedent
result = invoke(bare_cli, args=["--help"])
assert result.output == dedent(
"""\
\x1b[94m\x1b[1m\x1b[4mUsage:\x1b[0m \x1b[97mbare-cli\x1b[0m \x1b[36m\x1b[2m[OPTIONS]\x1b[0m
\x1b[94m\x1b[1m\x1b[4mOptions:\x1b[0m
\x1b[36m-h\x1b[0m, \x1b[36m--help\x1b[0m Show this message and exit.
"""
)
```
As you can see, all options are stripped out, but the colouring and formatting of the help message is preserved.
### Change default options
To override the default options, you can provide the `params=` argument to the command. But note how we use classes instead of option decorators:
```{click:source}
:emphasize-lines: 4-7
from click_extra import command, ConfigOption, VerbosityOption
@command(
params=[
ConfigOption(default="ex.yml"),
VerbosityOption(default="DEBUG"),
]
)
def cli():
pass
```
And now you get:
```{click:run}
:emphasize-lines: 5-9
from textwrap import dedent
result = invoke(cli, args=["--help"])
assert result.stdout.startswith(dedent(
"""\
\x1b[94m\x1b[1m\x1b[4mUsage:\x1b[0m \x1b[97mcli\x1b[0m \x1b[36m\x1b[2m[OPTIONS]\x1b[0m
\x1b[94m\x1b[1m\x1b[4mOptions:\x1b[0m
\x1b[36m--config\x1b[0m \x1b[36m\x1b[2mCONFIG_PATH\x1b[0m"""
))
```
This let you replace the preset options by your own set, tweak their order and fine-tune their defaults.
````{admonition} Duplicate options
:class: caution
If you try to add option decorators to a command which already have them by default, you will end up with duplicate entries ([as seen in issue #232](https://github.com/kdeldycke/click-extra/issues/232)):
```{click:source}
:emphasize-lines: 4
from click_extra import command, version_option
@command
@version_option(version="0.1")
def cli():
pass
```
See how the `--version` option gets duplicated at the end:
```{click:run}
:emphasize-lines: 26,27
from textwrap import dedent
result = invoke(cli, args=["--help"])
assert (
" \x1b[36m--version\x1b[0m Show the version and exit.\n"
" \x1b[36m--version\x1b[0m Show the version and exit.\n"
" \x1b[36m-h\x1b[0m, \x1b[36m--help\x1b[0m Show this message and exit.\n"
) in result.output
```
This is by design: decorators are cumulative, to allow you to add your own options to the preset of `@command` and `@group`.
But notice the `UserWarning` log messages: `The parameter --version is used more than once. Remove its duplicate as parameters should be unique.`. As it is not a good practice to have duplicate options and you must avoid it. There's also a non-zero chance for this situation to result in complete failure in a future Click release.
Finally, if the second `--version` option is placed right before the `--help` option, it is because [Click is adding its own generated `--help` option at the end of the list](https://kdeldycke.github.io/click-extra/commands.html#click_extra.commands.default_extra_params).
````
### Option order
Notice how the options above are ordered in the help message.
The default behavior of `@command` is to order options in the way they are provided to the `params=` argument of the decorator. Then adds to that list the additional option decorators positioned after the `@command` decorator.
After that, there is a final [sorting step applied to options](https://kdeldycke.github.io/click-extra/commands.html#click_extra.commands.ExtraCommand). This is done by the `extra_option_at_end` option, which is `True` by default.
### Option's defaults
Because Click Extra inherits from Click, you can [override the defaults the same way Click allows you to](https://click.palletsprojects.com/en/stable/commands/#context-defaults). Here is a reminder on how to do it.
For example, the [`--verbosity` option defaults to the `WARNING` level](logging.md#click_extra.logging.DEFAULT_LEVEL_NAME). Now we'd like to change this default to `INFO`.
If you manage your own `--verbosity` option, you can [pass the `default` argument to its decorator like we did above](#change-default-options):
```{click:source}
:emphasize-lines: 2,5
import click
from click_extra import verbosity_option
@click.command
@verbosity_option(default="INFO")
def cli():
pass
```
This also works in its class form:
```{click:source}
:emphasize-lines: 2,4
import click
from click_extra import VerbosityOption
@click.command(params=[VerbosityOption(default="INFO")])
def cli():
pass
```
With a `@click_extra.command` instead of `@click.command`, it is the same, you also have the alternative to pass a `default_map` via the `context_settings`:
```{click:source}
:emphasize-lines: 1,3
import click_extra
@click_extra.command(context_settings={"default_map": {"verbosity": "INFO"}})
def cli():
pass
```
Which results in `[default: INFO]` being featured in the help message:
```{click:run}
:emphasize-lines: 22
result = invoke(cli, args=["--help"])
assert (
" \x1b[2m[\x1b[0m\x1b[2mdefault: \x1b[0m\x1b[32m\x1b[2m\x1b[3mINFO\x1b[0m\x1b[2m]\x1b[0m\n"
) in result.stdout
```
```{tip}
The advantage of the `context_settings` method we demonstrated above, is that it let you change the default of the `--verbosity` option provided by Click Extra, [without having to touch the `params` argument](#change-default-options).
```
### Version fields
Click's `@version_option(prog_name=...)` lets you customize the name displayed by `--version`. But with Click Extra's default options, the `ExtraVersionOption` is created for you — so there's no decorator call to pass `prog_name` to.
The `version_fields` parameter on `@command` and `@group` solves this. It forwards values to the `ExtraVersionOption` in the default params list, without replacing it. It accepts any field from `ExtraVersionOption.template_fields`:
```{click:source}
:emphasize-lines: 3
from click_extra import command
@command(name="my-tool", version_fields={"prog_name": "My Tool"})
def my_tool():
"""My Tool CLI."""
```
The `name` controls the usage line, while `prog_name` controls the `--version` output:
```{click:run}
result = invoke(my_tool, args=["--help"])
assert result.exit_code == 0
assert "\x1b[97mmy-tool\x1b[0m" in result.stdout
```
```{click:run}
result = invoke(my_tool, args=["--version"])
assert result.exit_code == 0
assert "\x1b[97mMy Tool\x1b[0m" in result.output
```
```{hint}
When `prog_name` is not set, `--version` falls back to the command `name`, which is Click's standard behavior.
```
Multiple fields can be overridden at once, including the version message template:
```{click:source}
from click_extra import command
@command(
version_fields={
"prog_name": "Acme CLI",
"version": "42.0",
"git_branch": "release/42",
},
)
def acme():
pass
```
```{click:run}
result = invoke(acme, args=["--version"])
assert result.exit_code == 0
assert "Acme CLI" in result.output
assert "42.0" in result.output
```
## Lazily loading subcommands
Click Extra provides a `LazyGroup` class and `@lazy_group` decorator to create command groups that only load their subcommands when they are invoked.
This implementation is based on the one provided in Click's documentation, so refer to the [*Lazily loading subcommands*](https://click.palletsprojects.com/en/stable/complex/#defining-the-lazy-group) section for more details.
## Third-party commands composition
Click Extra is capable of composing with existing Click CLI in various situation.
### Wrap other commands
Click allows you to build up a hierarchy of command and subcommands. Click Extra inherits this behavior, which means we are free to assemble multiple third-party subcommands into a top-level one.
For this example, let's imagine you are working for an operation team that is relying daily on a couple of CLIs. Like [`dbt`](https://github.com/dbt-labs/dbt-core) to manage your data workflows, and [`aws-sam-cli`](https://github.com/aws/aws-sam-cli) to deploy them in the cloud.
For some practical reasons, you'd like to wrap all these commands into a big one. This is how to do it.
````{note}
Here is how I initialized this example on my machine:
```{code-block} shell-session
$ git clone https://github.com/kdeldycke/click-extra
(...)
$ cd click-extra
(...)
$ python -m pip install uv
(...)
$ uv venv
(...)
$ source .venv/bin/activate
(...)
$ uv sync --all-extras
(...)
$ uv pip install dbt-core
(...)
$ uv pip install aws-sam-cli
(...)
```
That way I had the latest Click Extra, `dbt` and `aws-sam-cli` installed in the same virtual environment:
```{code-block} shell-session
$ uv run -- dbt --version
Core:
- installed: 1.6.1
- latest: 1.6.2 - Update available!
Your version of dbt-core is out of date!
You can find instructions for upgrading here:
https://docs.getdbt.com/docs/installation
Plugins:
```
```{code-block} shell-session
$ uv run -- sam --version
SAM CLI, version 1.97.0
```
````
Once you identified the entry points of each commands, you can easily wrap them into a top-level Click Extra CLI, here in a local script I called `wrap.py`:
```{code-block} python
:caption: `wrap.py`
:emphasize-lines: 3-4,12-13
import click_extra
from samcli.cli.main import cli as sam_cli
from dbt.cli.main import cli as dbt_cli
@click_extra.group
def main():
pass
main.add_command(cmd=sam_cli, name="aws_sam")
main.add_command(cmd=dbt_cli, name="dbt")
if __name__ == "__main__":
main()
```
And this simple script gets rendered into:
```{code-block} shell-session
:emphasize-lines: 27-29
$ uv run -- python ./wrap.py
Usage: wrap.py [OPTIONS] COMMAND [ARGS]...
Options:
--time / --no-time Measure and print elapsed execution time. [default: no-
time]
--color, --ansi / --no-color, --no-ansi
Strip out all colors and all ANSI codes from output.
[default: color]
--config CONFIG_PATH Location of the configuration file. Supports glob
pattern of local path and remote URL. [default:
~/Library/Application
Support/wrap.py/*.{toml,yaml,yml,json,ini,xml}]
--no-config Ignore all configuration files and only use command line
parameters and environment variables.
--show-params Show all CLI parameters, their provenance, defaults and
value, then exit.
--table-format [asciidoc|csv|csv-excel|csv-excel-tab|csv-unix|double-grid|double-outline|fancy-grid|fancy-outline|github|grid|heavy-grid|heavy-outline|html|jira|latex|latex-booktabs|latex-longtable|latex-raw|mediawiki|mixed-grid|mixed-outline|moinmoin|orgtbl|outline|pipe|plain|presto|pretty|psql|rounded-grid|rounded-outline|rst|simple|simple-grid|simple-outline|textile|tsv|unsafehtml|vertical|youtrack]
Rendering style of tables. [default: rounded-outline]
--verbosity LEVEL Either CRITICAL, ERROR, WARNING, INFO, DEBUG. [default:
INFO]
-v, --verbose Increase the default WARNING verbosity by one level for
each additional repetition of the option. [default: 0]
--version Show the version and exit.
-h, --help Show this message and exit.
Commands:
aws_sam AWS Serverless Application Model (SAM) CLI
dbt An ELT tool for managing your SQL transformations and data models.
```
Here you can see that the top-level CLI gets [all the default options and behavior (including coloring)](tutorial.md#all-bells-and-whistles) of `@group`. But it also made available the standalone `aws_sam` and `dbt` CLI as standard subcommands.
And they are perfectly functional as-is.
You can compare the output of the `aws_sam` subcommand with its original one:
`````{tab-set}
````{tab-item} aws_sam subcommand in wrap.py
```{code-block} shell-session
:emphasize-lines: 1-2,59
$ uv run -- python ./wrap.py aws_sam --help
Usage: wrap.py aws_sam [OPTIONS] COMMAND [ARGS]...
AWS Serverless Application Model (SAM) CLI
The AWS Serverless Application Model Command Line Interface (AWS SAM CLI) is
a command line tool that you can use with AWS SAM templates and supported
third-party integrations to build and run your serverless applications.
Learn more: https://docs.aws.amazon.com/serverless-application-model/
Commands:
Learn:
docs NEW! Launch the AWS SAM CLI documentation in a browser.
Create an App:
init Initialize an AWS SAM application.
Develop your App:
build Build your AWS serverless function code.
local Run your AWS serverless function locally.
validate Validate an AWS SAM template.
sync NEW! Sync an AWS SAM project to AWS.
remote NEW! Invoke or send an event to cloud resources in your AWS
Cloudformation stack.
Deploy your App:
package Package an AWS SAM application.
deploy Deploy an AWS SAM application.
Monitor your App:
logs Fetch AWS Cloudwatch logs for AWS Lambda Functions or
Cloudwatch Log groups.
traces Fetch AWS X-Ray traces.
And More:
list NEW! Fetch the state of your AWS serverless application.
delete Delete an AWS SAM application and the artifacts created
by sam deploy.
pipeline Manage the continuous delivery of your AWS serverless
application.
publish Publish a packaged AWS SAM template to AWS Serverless
Application Repository for easy sharing.
Options:
--beta-features / --no-beta-features
Enable/Disable beta features.
--debug Turn on debug logging to print debug message
generated by AWS SAM CLI and display
timestamps.
--version Show the version and exit.
--info Show system and dependencies information.
-h, --help Show this message and exit.
Examples:
Get Started: $wrap.py aws_sam init
```
````
````{tab-item} Vanilla sam CLI
```{code-block} shell-session
:emphasize-lines: 1-2,59
$ uv run -- sam --help
Usage: sam [OPTIONS] COMMAND [ARGS]...
AWS Serverless Application Model (SAM) CLI
The AWS Serverless Application Model Command Line Interface (AWS SAM CLI) is
a command line tool that you can use with AWS SAM templates and supported
third-party integrations to build and run your serverless applications.
Learn more: https://docs.aws.amazon.com/serverless-application-model/
Commands:
Learn:
docs NEW! Launch the AWS SAM CLI documentation in a browser.
Create an App:
init Initialize an AWS SAM application.
Develop your App:
build Build your AWS serverless function code.
local Run your AWS serverless function locally.
validate Validate an AWS SAM template.
sync NEW! Sync an AWS SAM project to AWS.
remote NEW! Invoke or send an event to cloud resources in your AWS
Cloudformation stack.
Deploy your App:
package Package an AWS SAM application.
deploy Deploy an AWS SAM application.
Monitor your App:
logs Fetch AWS Cloudwatch logs for AWS Lambda Functions or
Cloudwatch Log groups.
traces Fetch AWS X-Ray traces.
And More:
list NEW! Fetch the state of your AWS serverless application.
delete Delete an AWS SAM application and the artifacts created
by sam deploy.
pipeline Manage the continuous delivery of your AWS serverless
application.
publish Publish a packaged AWS SAM template to AWS Serverless
Application Repository for easy sharing.
Options:
--beta-features / --no-beta-features
Enable/Disable beta features.
--debug Turn on debug logging to print debug message
generated by AWS SAM CLI and display
timestamps.
--version Show the version and exit.
--info Show system and dependencies information.
-h, --help Show this message and exit.
Examples:
Get Started: $sam init
```
````
`````
Here is the highlighted differences to make them even more obvious:
```{code-block} diff
:emphasize-lines: 2-5,13-14
@@ -1,5 +1,5 @@
-$ uv run -- python ./wrap.py aws_sam --help
-Usage: wrap.py aws_sam [OPTIONS] COMMAND [ARGS]...
+$ uv run -- sam --help
+Usage: sam [OPTIONS] COMMAND [ARGS]...
AWS Serverless Application Model (SAM) CLI
@@ -56,4 +56,4 @@
Examples:
- Get Started: $wrap.py aws_sam init
+ Get Started: $sam init
```
Now that all commands are under the same umbrella, there is no limit to your imagination!
```{caution}
This might looks janky, but this franken-CLI might be a great way to solve practical problems in your situation.
You can augment them with your custom glue code. Or maybe mashing them up will simplify the re-distribution of these CLIs on your production machines. Or control their common dependencies. Or freeze their versions. Or hard-code some parameters. Or apply monkey-patches. Or chain these commands to create new kind of automation...
There is a miriad of possibilities. If you have some other examples in the same vein, please share them in an issue or even directly via a PR. I'd love to complement this documentation with creative use-cases.
```
## `click_extra.commands` API
```{autoclasstree} click_extra.commands
:strict:
```
```{automodule} click_extra.commands
:members:
:undoc-members:
:show-inheritance:
```