mirror of
https://github.com/aljazceru/Tutorial-Codebase-Knowledge.git
synced 2025-12-19 07:24:20 +01:00
init push
This commit is contained in:
197
docs/Click/01_command___group.md
Normal file
197
docs/Click/01_command___group.md
Normal file
@@ -0,0 +1,197 @@
|
||||
# Chapter 1: Commands and Groups: The Building Blocks
|
||||
|
||||
Welcome to your first step in learning Click! Imagine you want to create your own command-line tool, maybe something like `git` or `docker`. How do you tell your program what to do when someone types `git commit` or `docker build`? That's where **Commands** and **Groups** come in. They are the fundamental building blocks for any Click application.
|
||||
|
||||
Think about a simple tool. Maybe you want a program that can greet someone. You'd type `greet Alice` in your terminal, and it would print "Hello Alice!". In Click, this single action, "greet", would be represented by a `Command`.
|
||||
|
||||
Now, what if your tool needed to do *more* than one thing? Maybe besides greeting, it could also say goodbye. You might want to type `mytool greet Alice` or `mytool goodbye Bob`. The main `mytool` part acts like a container or a menu, holding the different actions (`greet`, `goodbye`). This container is what Click calls a `Group`.
|
||||
|
||||
So:
|
||||
|
||||
* `Command`: Represents a single action your tool can perform.
|
||||
* `Group`: Represents a collection of related actions (Commands or other Groups).
|
||||
|
||||
Let's dive in and see how to create them!
|
||||
|
||||
## Your First Command
|
||||
|
||||
Creating a command in Click is surprisingly simple. You basically write a normal Python function and then "decorate" it to tell Click it's a command-line command.
|
||||
|
||||
Let's make a command that just prints "Hello World!".
|
||||
|
||||
```python
|
||||
# hello_app.py
|
||||
import click
|
||||
|
||||
@click.command()
|
||||
def hello():
|
||||
"""A simple command that says Hello World"""
|
||||
print("Hello World!")
|
||||
|
||||
if __name__ == '__main__':
|
||||
hello()
|
||||
```
|
||||
|
||||
Let's break this down:
|
||||
|
||||
1. `import click`: We need to import the Click library first.
|
||||
2. `@click.command()`: This is the magic part! It's called a decorator. It transforms the Python function `hello()` right below it into a Click `Command` object. We'll learn more about [Decorators](02_decorators.md) in the next chapter, but for now, just know this line turns `hello` into something Click understands as a command.
|
||||
3. `def hello(): ...`: This is a standard Python function. The code inside this function is what will run when you execute the command from your terminal.
|
||||
4. `"""A simple command that says Hello World"""`: This is a docstring. Click cleverly uses the function's docstring as the help text for the command!
|
||||
5. `if __name__ == '__main__': hello()`: This standard Python construct checks if the script is being run directly. If it is, it calls our `hello` command function (which is now actually a Click `Command` object).
|
||||
|
||||
**Try running it!** Save the code above as `hello_app.py`. Open your terminal in the same directory and run:
|
||||
|
||||
```bash
|
||||
$ python hello_app.py
|
||||
Hello World!
|
||||
```
|
||||
|
||||
It works! You just created your first command-line command with Click.
|
||||
|
||||
**Bonus: Automatic Help!**
|
||||
|
||||
Click automatically generates help screens for you. Try running your command with `--help`:
|
||||
|
||||
```bash
|
||||
$ python hello_app.py --help
|
||||
Usage: hello_app.py [OPTIONS]
|
||||
|
||||
A simple command that says Hello World
|
||||
|
||||
Options:
|
||||
--help Show this message and exit.
|
||||
```
|
||||
|
||||
See? Click used the docstring we wrote (`A simple command that says Hello World`) and added a standard `--help` option for free!
|
||||
|
||||
## Grouping Commands
|
||||
|
||||
Okay, one command is nice, but real tools often have multiple commands. Like `git` has `commit`, `pull`, `push`, etc. Let's say we want our tool to have two commands: `hello` and `goodbye`.
|
||||
|
||||
We need a way to group these commands together. That's what `click.group()` is for. A `Group` acts as the main entry point and can have other commands attached to it.
|
||||
|
||||
```python
|
||||
# multi_app.py
|
||||
import click
|
||||
|
||||
# 1. Create the main group
|
||||
@click.group()
|
||||
def cli():
|
||||
"""A simple tool with multiple commands."""
|
||||
pass # The group function itself doesn't need to do anything
|
||||
|
||||
# 2. Define the 'hello' command
|
||||
@click.command()
|
||||
def hello():
|
||||
"""Says Hello World"""
|
||||
print("Hello World!")
|
||||
|
||||
# 3. Define the 'goodbye' command
|
||||
@click.command()
|
||||
def goodbye():
|
||||
"""Says Goodbye World"""
|
||||
print("Goodbye World!")
|
||||
|
||||
# 4. Attach the commands to the group
|
||||
cli.add_command(hello)
|
||||
cli.add_command(goodbye)
|
||||
|
||||
if __name__ == '__main__':
|
||||
cli() # Run the main group
|
||||
```
|
||||
|
||||
What's changed?
|
||||
|
||||
1. We created a function `cli` and decorated it with `@click.group()`. This makes `cli` our main entry point, a container for other commands. Notice the function body is just `pass` – often, the group function itself doesn't need logic; its job is to hold other commands.
|
||||
2. We defined `hello` and `goodbye` just like before, using `@click.command()`.
|
||||
3. Crucially, we *attached* our commands to the group: `cli.add_command(hello)` and `cli.add_command(goodbye)`. This tells Click that `hello` and `goodbye` are subcommands of `cli`.
|
||||
4. Finally, in the `if __name__ == '__main__':` block, we run `cli()`, our main group.
|
||||
|
||||
**Let's run this!** Save it as `multi_app.py`.
|
||||
|
||||
First, check the main help screen:
|
||||
|
||||
```bash
|
||||
$ python multi_app.py --help
|
||||
Usage: multi_app.py [OPTIONS] COMMAND [ARGS]...
|
||||
|
||||
A simple tool with multiple commands.
|
||||
|
||||
Options:
|
||||
--help Show this message and exit.
|
||||
|
||||
Commands:
|
||||
goodbye Says Goodbye World
|
||||
hello Says Hello World
|
||||
```
|
||||
|
||||
Look! Click now lists `goodbye` and `hello` under "Commands". It automatically figured out their names from the function names (`goodbye`, `hello`) and their help text from their docstrings.
|
||||
|
||||
Now, run the specific commands:
|
||||
|
||||
```bash
|
||||
$ python multi_app.py hello
|
||||
Hello World!
|
||||
|
||||
$ python multi_app.py goodbye
|
||||
Goodbye World!
|
||||
```
|
||||
|
||||
You've successfully created a multi-command CLI tool!
|
||||
|
||||
*(Self-promotion: There's an even shorter way to attach commands using decorators directly on the group, which we'll see in [Decorators](02_decorators.md)!)*
|
||||
|
||||
## How It Works Under the Hood
|
||||
|
||||
What's really happening when you use `@click.command()` or `@click.group()`?
|
||||
|
||||
1. **Decoration:** The decorator (`@click.command` or `@click.group`) takes your Python function (`hello`, `goodbye`, `cli`). It wraps this function inside a Click object – either a `Command` instance or a `Group` instance (which is actually a special type of `Command`). These objects store your original function as the `callback` to be executed later. They also store metadata like the command name (derived from the function name) and the help text (from the docstring). You can find the code for these decorators in `decorators.py` and the `Command`/`Group` classes in `core.py`.
|
||||
|
||||
2. **Execution:** When you run `python multi_app.py hello`, Python executes the `cli()` call at the bottom. Since `cli` is a `Group` object created by Click, it knows how to parse the command-line arguments (`hello` in this case).
|
||||
|
||||
3. **Parsing & Dispatch:** The `cli` group looks at the first argument (`hello`). It checks its list of registered subcommands (which we added using `cli.add_command`). It finds a match with the `hello` command object.
|
||||
|
||||
4. **Callback:** The `cli` group then invokes the `hello` command object. The `hello` command object, in turn, calls the original Python function (`hello()`) that it stored earlier as its `callback`.
|
||||
|
||||
Here's a simplified view of what happens when you run `python multi_app.py hello`:
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant User
|
||||
participant Terminal
|
||||
participant PythonScript (multi_app.py)
|
||||
participant ClickRuntime
|
||||
participant cli_Group as cli (Group Object)
|
||||
participant hello_Command as hello (Command Object)
|
||||
|
||||
User->>Terminal: python multi_app.py hello
|
||||
Terminal->>PythonScript: Executes script with args ["hello"]
|
||||
PythonScript->>ClickRuntime: Calls cli() entry point
|
||||
ClickRuntime->>cli_Group: Asks to handle args ["hello"]
|
||||
cli_Group->>cli_Group: Parses args, identifies "hello" as subcommand
|
||||
cli_Group->>hello_Command: Invokes the 'hello' command
|
||||
hello_Command->>hello_Command: Executes its callback (the original hello() function)
|
||||
hello_Command-->>PythonScript: Prints "Hello World!"
|
||||
PythonScript-->>Terminal: Shows output
|
||||
Terminal-->>User: Displays "Hello World!"
|
||||
```
|
||||
|
||||
This process of parsing arguments and calling the right function based on the command structure is the core job of Click, making it easy for *you* to just focus on writing the functions for each command.
|
||||
|
||||
## Conclusion
|
||||
|
||||
You've learned about the two most fundamental concepts in Click:
|
||||
|
||||
* `Command`: Represents a single action, created by decorating a function with `@click.command()`.
|
||||
* `Group`: Acts as a container for multiple commands (or other groups), created with `@click.group()`. Groups allow you to structure your CLI application logically.
|
||||
|
||||
We saw how Click uses decorators to transform simple Python functions into powerful command-line interface components, automatically handling things like help text generation and command dispatching.
|
||||
|
||||
Commands and Groups form the basic structure, but how do we pass information *into* our commands (like `git commit -m "My message"`)? And what other cool things can decorators do? We'll explore that starting with a deeper look at decorators in the next chapter!
|
||||
|
||||
Next up: [Chapter 2: Decorators](02_decorators.md)
|
||||
|
||||
---
|
||||
|
||||
Generated by [AI Codebase Knowledge Builder](https://github.com/The-Pocket/Tutorial-Codebase-Knowledge)
|
||||
278
docs/Click/02_decorators.md
Normal file
278
docs/Click/02_decorators.md
Normal file
@@ -0,0 +1,278 @@
|
||||
# Chapter 2: Decorators: Magic Wands for Your Functions
|
||||
|
||||
In [Chapter 1: Commands and Groups](01_command___group.md), we learned how to create basic command-line actions (`Command`) and group them together (`Group`). You might have noticed those strange `@click.command()` and `@click.group()` lines above our functions. What are they, and why do we use them?
|
||||
|
||||
Those are **Decorators**, and they are the heart of how you build Click applications! Think of them as special annotations or modifiers you place *on top* of your Python functions to give them command-line superpowers.
|
||||
|
||||
## Why Decorators? Making Life Easier
|
||||
|
||||
Imagine you didn't have decorators. To create a simple command like `hello` from Chapter 1, you might have to write something like this (this is *not* real Click code, just an illustration):
|
||||
|
||||
```python
|
||||
# NOT how Click works, but imagine...
|
||||
import click
|
||||
|
||||
def hello_logic():
|
||||
"""My command's help text"""
|
||||
print("Hello World!")
|
||||
|
||||
# Manually create a Command object
|
||||
hello_command = click.Command(
|
||||
name='hello', # Give it a name
|
||||
callback=hello_logic, # Tell it which function to run
|
||||
help=hello_logic.__doc__ # Copy the help text
|
||||
)
|
||||
|
||||
if __name__ == '__main__':
|
||||
# Manually parse arguments and run
|
||||
# (This part would be complex!)
|
||||
pass
|
||||
```
|
||||
|
||||
That looks like a lot more work! You have to:
|
||||
|
||||
1. Write the function (`hello_logic`).
|
||||
2. Manually create a `Command` object.
|
||||
3. Explicitly tell the `Command` object its name, which function to run (`callback`), and its help text.
|
||||
|
||||
Now, let's remember the Click way from Chapter 1:
|
||||
|
||||
```python
|
||||
# The actual Click way
|
||||
import click
|
||||
|
||||
@click.command() # <-- The Decorator!
|
||||
def hello():
|
||||
"""A simple command that says Hello World"""
|
||||
print("Hello World!")
|
||||
|
||||
if __name__ == '__main__':
|
||||
hello()
|
||||
```
|
||||
|
||||
Much cleaner, right? The `@click.command()` decorator handles creating the `Command` object, figuring out the name (`hello`), and grabbing the help text from the docstring (`"""..."""`) all automatically!
|
||||
|
||||
Decorators let you *declare* what you want ("this function is a command") right next to the function's code, making your CLI definition much more readable and concise.
|
||||
|
||||
## What is a Decorator in Python? (A Quick Peek)
|
||||
|
||||
Before diving deeper into Click's decorators, let's understand what a decorator *is* in Python itself.
|
||||
|
||||
In Python, a decorator is essentially a function that takes another function as input and returns a *modified* version of that function. It's like wrapping a gift: you still have the original gift inside, but the wrapping adds something extra.
|
||||
|
||||
The `@` symbol is just syntactic sugar – a shortcut – for applying a decorator.
|
||||
|
||||
Here's a super simple example (not using Click):
|
||||
|
||||
```python
|
||||
# A simple Python decorator
|
||||
def simple_decorator(func):
|
||||
def wrapper():
|
||||
print("Something is happening before the function is called.")
|
||||
func() # Call the original function
|
||||
print("Something is happening after the function is called.")
|
||||
return wrapper # Return the modified function
|
||||
|
||||
@simple_decorator # Apply the decorator
|
||||
def say_whee():
|
||||
print("Whee!")
|
||||
|
||||
# Now, when we call say_whee...
|
||||
say_whee()
|
||||
```
|
||||
|
||||
Running this would print:
|
||||
|
||||
```
|
||||
Something is happening before the function is called.
|
||||
Whee!
|
||||
Something is happening after the function is called.
|
||||
```
|
||||
|
||||
See? `simple_decorator` took our `say_whee` function and wrapped it with extra print statements. The `@simple_decorator` line is equivalent to writing `say_whee = simple_decorator(say_whee)` after defining `say_whee`.
|
||||
|
||||
Click's decorators (`@click.command`, `@click.group`, etc.) do something similar, but instead of just printing, they wrap your function inside Click's `Command` or `Group` objects and configure them.
|
||||
|
||||
## Click's Main Decorators
|
||||
|
||||
Click provides several decorators. The most common ones you'll use are:
|
||||
|
||||
* `@click.command()`: Turns a function into a single CLI command.
|
||||
* `@click.group()`: Turns a function into a container for other commands.
|
||||
* `@click.option()`: Adds an *option* (like `--name` or `-v`) to your command. Options are typically optional parameters.
|
||||
* `@click.argument()`: Adds an *argument* (like a required filename) to your command. Arguments are typically required and positional.
|
||||
|
||||
We already saw `@click.command` and `@click.group` in Chapter 1. Let's focus on how decorators streamline adding commands to groups and introduce options.
|
||||
|
||||
## Decorators in Action: Simplifying Groups and Adding Options
|
||||
|
||||
Remember the `multi_app.py` example from Chapter 1? We had to define the group `cli` and the commands `hello` and `goodbye` separately, then manually attach them using `cli.add_command()`.
|
||||
|
||||
```python
|
||||
# multi_app_v1.py (from Chapter 1)
|
||||
import click
|
||||
|
||||
@click.group()
|
||||
def cli():
|
||||
"""A simple tool with multiple commands."""
|
||||
pass
|
||||
|
||||
@click.command()
|
||||
def hello():
|
||||
"""Says Hello World"""
|
||||
print("Hello World!")
|
||||
|
||||
@click.command()
|
||||
def goodbye():
|
||||
"""Says Goodbye World"""
|
||||
print("Goodbye World!")
|
||||
|
||||
# Manual attachment
|
||||
cli.add_command(hello)
|
||||
cli.add_command(goodbye)
|
||||
|
||||
if __name__ == '__main__':
|
||||
cli()
|
||||
```
|
||||
|
||||
Decorators provide a more elegant way! If you have a `@click.group()`, you can use *its* `.command()` method as a decorator to automatically attach the command.
|
||||
|
||||
Let's rewrite `multi_app.py` using this decorator pattern and also add a simple name option to the `hello` command using `@click.option`:
|
||||
|
||||
```python
|
||||
# multi_app_v2.py (using decorators more effectively)
|
||||
import click
|
||||
|
||||
# 1. Create the main group
|
||||
@click.group()
|
||||
def cli():
|
||||
"""A simple tool with multiple commands."""
|
||||
pass # Group function still doesn't need to do much
|
||||
|
||||
# 2. Define 'hello' and attach it to 'cli' using a decorator
|
||||
@cli.command() # <-- Decorator from the 'cli' group object!
|
||||
@click.option('--name', default='World', help='Who to greet.')
|
||||
def hello(name): # The 'name' parameter matches the option
|
||||
"""Says Hello"""
|
||||
print(f"Hello {name}!")
|
||||
|
||||
# 3. Define 'goodbye' and attach it to 'cli' using a decorator
|
||||
@cli.command() # <-- Decorator from the 'cli' group object!
|
||||
def goodbye():
|
||||
"""Says Goodbye"""
|
||||
print("Goodbye World!")
|
||||
|
||||
# No need for cli.add_command() anymore!
|
||||
|
||||
if __name__ == '__main__':
|
||||
cli()
|
||||
```
|
||||
|
||||
What changed?
|
||||
|
||||
1. Instead of `@click.command()`, we used `@cli.command()` above `hello` and `goodbye`. This tells Click, "This function is a command, *and* it belongs to the `cli` group." No more manual `cli.add_command()` needed!
|
||||
2. We added `@click.option('--name', default='World', help='Who to greet.')` right below `@cli.command()` for the `hello` function. This adds a command-line option named `--name`.
|
||||
3. The `hello` function now accepts an argument `name`. Click automatically passes the value provided via the `--name` option to this function parameter. If the user doesn't provide `--name`, it uses the `default='World'`.
|
||||
|
||||
**Let's run this new version:**
|
||||
|
||||
Check the help for the main command:
|
||||
|
||||
```bash
|
||||
$ python multi_app_v2.py --help
|
||||
Usage: multi_app_v2.py [OPTIONS] COMMAND [ARGS]...
|
||||
|
||||
A simple tool with multiple commands.
|
||||
|
||||
Options:
|
||||
--help Show this message and exit.
|
||||
|
||||
Commands:
|
||||
goodbye Says Goodbye
|
||||
hello Says Hello
|
||||
```
|
||||
|
||||
Now check the help for the `hello` subcommand:
|
||||
|
||||
```bash
|
||||
$ python multi_app_v2.py hello --help
|
||||
Usage: multi_app_v2.py hello [OPTIONS]
|
||||
|
||||
Says Hello
|
||||
|
||||
Options:
|
||||
--name TEXT Who to greet. [default: World]
|
||||
--help Show this message and exit.
|
||||
```
|
||||
|
||||
See? The `--name` option is listed, along with its help text and default value!
|
||||
|
||||
Finally, run `hello` with and without the option:
|
||||
|
||||
```bash
|
||||
$ python multi_app_v2.py hello
|
||||
Hello World!
|
||||
|
||||
$ python multi_app_v2.py hello --name Alice
|
||||
Hello Alice!
|
||||
```
|
||||
|
||||
It works! Decorators made adding the command to the group cleaner, and adding the option was as simple as adding another decorator line and a function parameter. We'll learn much more about configuring options and arguments in the next chapter, [Parameter (Option / Argument)](03_parameter__option___argument_.md).
|
||||
|
||||
## How Click Decorators Work (Under the Hood)
|
||||
|
||||
So what's the "magic" behind these `@` symbols in Click?
|
||||
|
||||
1. **Decorator Functions:** When you write `@click.command()` or `@click.option()`, you're calling functions defined in Click (specifically in `decorators.py`). These functions are designed to *return another function* (the actual decorator).
|
||||
2. **Wrapping the User Function:** Python takes the function you defined (e.g., `hello`) and passes it to the decorator function returned in step 1.
|
||||
3. **Attaching Information:**
|
||||
* `@click.option` / `@click.argument`: These decorators typically don't create the final `Command` object immediately. Instead, they attach the parameter information (like the option name `--name`, type, default value) to your function object itself, often using a special temporary attribute (like `__click_params__`). They then return the *original function*, but now with this extra metadata attached.
|
||||
* `@click.command` / `@click.group`: This decorator usually runs *last* (decorators are applied bottom-up). It looks for any parameter information attached by previous `@option` or `@argument` decorators (like `__click_params__`). It then creates the actual `Command` or `Group` object (defined in `core.py`), configures it with the command name, help text (from the docstring), the attached parameters, and stores your original function as the `callback` to be executed. It returns this newly created `Command` or `Group` object, effectively replacing your original function definition with the Click object.
|
||||
4. **Group Attachment:** When you use `@cli.command()`, the `@cli.command()` decorator not only creates the `Command` object but also automatically calls `cli.add_command()` to register the new command with the `cli` group object.
|
||||
|
||||
Here's a simplified sequence diagram showing what happens when you define the `hello` command in `multi_app_v2.py`:
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant PythonInterpreter
|
||||
participant click_option as @click.option('--name')
|
||||
participant hello_func as hello(name)
|
||||
participant cli_command as @cli.command()
|
||||
participant cli_Group as cli (Group Object)
|
||||
participant hello_Command as hello (New Command Object)
|
||||
|
||||
Note over PythonInterpreter, hello_func: Python processes decorators bottom-up
|
||||
PythonInterpreter->>click_option: Processes @click.option('--name', ...) decorator
|
||||
click_option->>hello_func: Attaches Option info (like in __click_params__)
|
||||
click_option-->>PythonInterpreter: Returns original hello_func (with attached info)
|
||||
|
||||
PythonInterpreter->>cli_command: Processes @cli.command() decorator
|
||||
cli_command->>hello_func: Reads function name, docstring, attached params (__click_params__)
|
||||
cli_command->>hello_Command: Creates new Command object for 'hello'
|
||||
cli_command->>cli_Group: Calls cli.add_command(hello_Command)
|
||||
cli_command-->>PythonInterpreter: Returns the new hello_Command object
|
||||
|
||||
Note over PythonInterpreter: 'hello' in the code now refers to the Command object
|
||||
```
|
||||
|
||||
The key takeaway is that decorators allow Click to gather all the necessary information (function logic, command name, help text, options, arguments) right where you define the function, and build the corresponding Click objects behind the scenes. You can find the implementation details in `click/decorators.py` and `click/core.py`. The `_param_memo` helper function in `decorators.py` is often used internally by `@option` and `@argument` to attach parameter info to the function before `@command` processes it.
|
||||
|
||||
## Conclusion
|
||||
|
||||
Decorators are fundamental to Click's design philosophy. They provide a clean, readable, and *declarative* way to turn your Python functions into powerful command-line interface components.
|
||||
|
||||
You've learned:
|
||||
|
||||
* Decorators are Python features (`@`) that modify functions.
|
||||
* Click uses decorators like `@click.command`, `@click.group`, `@click.option`, and `@click.argument` extensively.
|
||||
* Decorators handle the creation and configuration of `Command`, `Group`, `Option`, and `Argument` objects for you.
|
||||
* Using decorators like `@group.command()` automatically attaches commands to groups.
|
||||
* They make defining your CLI structure intuitive and keep related code together.
|
||||
|
||||
We've only scratched the surface of `@click.option` and `@click.argument`. How do you make options required? How do you handle different data types (numbers, files)? How do you define arguments that take multiple values? We'll explore all of this in the next chapter!
|
||||
|
||||
Next up: [Chapter 3: Parameter (Option / Argument)](03_parameter__option___argument_.md)
|
||||
|
||||
---
|
||||
|
||||
Generated by [AI Codebase Knowledge Builder](https://github.com/The-Pocket/Tutorial-Codebase-Knowledge)
|
||||
249
docs/Click/03_parameter__option___argument_.md
Normal file
249
docs/Click/03_parameter__option___argument_.md
Normal file
@@ -0,0 +1,249 @@
|
||||
# Chapter 3: Parameter (Option / Argument) - Giving Your Commands Input
|
||||
|
||||
In the last chapter, [Decorators](02_decorators.md), we saw how decorators like `@click.command()` and `@click.option()` act like magic wands, transforming our Python functions into CLI commands and adding features like command-line options.
|
||||
|
||||
But how do our commands actually *receive* information from the user? If we have a command `greet`, how do we tell it *who* to greet, like `greet --name Alice`? Or if we have a `copy` command, how do we specify the source and destination files, like `copy report.txt backup.txt`?
|
||||
|
||||
This is where **Parameters** come in. Parameters define the inputs your commands can accept, just like arguments define the inputs for a regular Python function. Click handles parsing these inputs from the command line, validating them, and making them available to your command function.
|
||||
|
||||
There are two main types of parameters in Click:
|
||||
|
||||
1. **Options:** These are usually preceded by flags like `--verbose` or `-f`. They are often optional and can either take a value (like `--name Alice`) or act as simple on/off switches (like `--verbose`). You define them using the `@click.option()` decorator.
|
||||
2. **Arguments:** These are typically positional values that come *after* any options. They often represent required inputs, like a filename (`report.txt`). You define them using the `@click.argument()` decorator.
|
||||
|
||||
Let's see how to use them!
|
||||
|
||||
## Options: The Named Inputs (`@click.option`)
|
||||
|
||||
Think of options like keyword arguments in Python functions. In `def greet(name="World"):`, `name` is a keyword argument with a default value. Options serve a similar purpose for your CLI.
|
||||
|
||||
Let's modify our `hello` command from the previous chapter to accept a `--name` option.
|
||||
|
||||
```python
|
||||
# greet_app.py
|
||||
import click
|
||||
|
||||
@click.group()
|
||||
def cli():
|
||||
"""A simple tool with a greeting command."""
|
||||
pass
|
||||
|
||||
@cli.command()
|
||||
@click.option('--name', default='World', help='Who to greet.')
|
||||
def hello(name): # <-- The 'name' parameter matches the option
|
||||
"""Greets the person specified by the --name option."""
|
||||
print(f"Hello {name}!")
|
||||
|
||||
if __name__ == '__main__':
|
||||
cli()
|
||||
```
|
||||
|
||||
Let's break down the new parts:
|
||||
|
||||
1. `@click.option('--name', default='World', help='Who to greet.')`: This decorator defines an option.
|
||||
* `'--name'`: This is the primary name of the option on the command line.
|
||||
* `default='World'`: If the user doesn't provide the `--name` option, the value `World` will be used.
|
||||
* `help='Who to greet.'`: This text will appear in the help message for the `hello` command.
|
||||
2. `def hello(name):`: Notice how the `hello` function now accepts an argument named `name`. Click cleverly matches the option name (`name`) to the function parameter name and passes the value automatically!
|
||||
|
||||
**Try running it!**
|
||||
|
||||
First, check the help message for the `hello` command:
|
||||
|
||||
```bash
|
||||
$ python greet_app.py hello --help
|
||||
Usage: greet_app.py hello [OPTIONS]
|
||||
|
||||
Greets the person specified by the --name option.
|
||||
|
||||
Options:
|
||||
--name TEXT Who to greet. [default: World]
|
||||
--help Show this message and exit.
|
||||
```
|
||||
|
||||
See? Click added our `--name` option to the help screen, including the help text and default value we provided. The `TEXT` part indicates the type of value expected (we'll cover types in [ParamType](04_paramtype.md)).
|
||||
|
||||
Now, run it with and without the option:
|
||||
|
||||
```bash
|
||||
$ python greet_app.py hello
|
||||
Hello World!
|
||||
|
||||
$ python greet_app.py hello --name Alice
|
||||
Hello Alice!
|
||||
```
|
||||
|
||||
It works perfectly! Click parsed the `--name Alice` option and passed `"Alice"` to our `hello` function's `name` parameter. When we didn't provide the option, it used the default value `"World"`.
|
||||
|
||||
### Option Flavors: Short Names and Flags
|
||||
|
||||
Options can have variations:
|
||||
|
||||
* **Short Names:** You can provide shorter aliases, like `-n` for `--name`.
|
||||
* **Flags:** Options that don't take a value but act as switches (e.g., `--verbose`).
|
||||
|
||||
Let's add a short name `-n` to our `--name` option and a `--shout` flag to make the greeting uppercase.
|
||||
|
||||
```python
|
||||
# greet_app_v2.py
|
||||
import click
|
||||
|
||||
@click.group()
|
||||
def cli():
|
||||
"""A simple tool with a greeting command."""
|
||||
pass
|
||||
|
||||
@cli.command()
|
||||
@click.option('--name', '-n', default='World', help='Who to greet.') # Added '-n'
|
||||
@click.option('--shout', is_flag=True, help='Greet loudly.') # Added '--shout' flag
|
||||
def hello(name, shout): # <-- Function now accepts 'shout' too
|
||||
"""Greets the person, optionally shouting."""
|
||||
greeting = f"Hello {name}!"
|
||||
if shout:
|
||||
greeting = greeting.upper()
|
||||
print(greeting)
|
||||
|
||||
if __name__ == '__main__':
|
||||
cli()
|
||||
```
|
||||
|
||||
Changes:
|
||||
|
||||
1. `@click.option('--name', '-n', ...)`: We added `'-n'` as the second argument to the decorator. Now, both `--name` and `-n` work.
|
||||
2. `@click.option('--shout', is_flag=True, ...)`: This defines a flag. `is_flag=True` tells Click this option doesn't take a value; its presence makes the corresponding parameter `True`, otherwise it's `False`.
|
||||
3. `def hello(name, shout):`: The function signature is updated to accept the `shout` parameter.
|
||||
|
||||
**Run it again!**
|
||||
|
||||
```bash
|
||||
$ python greet_app_v2.py hello -n Bob
|
||||
Hello Bob!
|
||||
|
||||
$ python greet_app_v2.py hello --name Carol --shout
|
||||
HELLO CAROL!
|
||||
|
||||
$ python greet_app_v2.py hello --shout
|
||||
HELLO WORLD!
|
||||
```
|
||||
|
||||
Flags and short names make your CLI more flexible and conventional!
|
||||
|
||||
## Arguments: The Positional Inputs (`@click.argument`)
|
||||
|
||||
Arguments are like positional arguments in Python functions. In `def copy(src, dst):`, `src` and `dst` are required positional arguments. Click arguments usually represent mandatory inputs that follow the command and any options.
|
||||
|
||||
Let's create a simple command that takes two arguments, `SRC` and `DST`, representing source and destination files (though we'll just print them for now).
|
||||
|
||||
```python
|
||||
# copy_app.py
|
||||
import click
|
||||
|
||||
@click.command()
|
||||
@click.argument('src') # Defines the first argument
|
||||
@click.argument('dst') # Defines the second argument
|
||||
def copy(src, dst): # Function parameters match argument names
|
||||
"""Copies SRC file to DST."""
|
||||
print(f"Pretending to copy '{src}' to '{dst}'")
|
||||
|
||||
if __name__ == '__main__':
|
||||
copy()
|
||||
```
|
||||
|
||||
What's happening here?
|
||||
|
||||
1. `@click.argument('src')`: Defines a positional argument named `src`. By default, arguments are required. The name `'src'` is used both internally and often capitalized (`SRC`) in help messages by convention.
|
||||
2. `@click.argument('dst')`: Defines the second required positional argument.
|
||||
3. `def copy(src, dst):`: The function parameters `src` and `dst` receive the values provided on the command line in the order they appear.
|
||||
|
||||
**Let's try it!**
|
||||
|
||||
First, see what happens if we forget the arguments:
|
||||
|
||||
```bash
|
||||
$ python copy_app.py
|
||||
Usage: copy_app.py [OPTIONS] SRC DST
|
||||
Try 'copy_app.py --help' for help.
|
||||
|
||||
Error: Missing argument 'SRC'.
|
||||
```
|
||||
|
||||
Click automatically detects the missing argument and gives a helpful error message!
|
||||
|
||||
Now, provide the arguments:
|
||||
|
||||
```bash
|
||||
$ python copy_app.py report.txt backup/report.txt
|
||||
Pretending to copy 'report.txt' to 'backup/report.txt'
|
||||
```
|
||||
|
||||
Click correctly captured the positional arguments and passed them to our `copy` function.
|
||||
|
||||
Arguments are essential for inputs that are fundamental to the command's operation, like the files to operate on. Options are better suited for modifying the command's behavior.
|
||||
|
||||
*(Note: Arguments can also be made optional or accept variable numbers of inputs, often involving the `required` and `nargs` settings, which tie into concepts we'll explore more in [ParamType](04_paramtype.md).)*
|
||||
|
||||
## How Parameters Work Together
|
||||
|
||||
When you run a command like `python greet_app_v2.py hello --shout -n Alice`, Click performs a sequence of steps:
|
||||
|
||||
1. **Parsing:** Click looks at the command-line arguments (`sys.argv`) provided by the operating system: `['greet_app_v2.py', 'hello', '--shout', '-n', 'Alice']`.
|
||||
2. **Command Identification:** It identifies `hello` as the command to execute.
|
||||
3. **Parameter Matching:** It scans the remaining arguments (`['--shout', '-n', 'Alice']`).
|
||||
* It sees `--shout`. It looks up the parameters defined for the `hello` command (using the `@click.option` and `@click.argument` decorators). It finds the `shout` option definition (which has `is_flag=True`). It marks the value for `shout` as `True`.
|
||||
* It sees `-n`. It finds the `name` option definition (which includes `-n` as an alias and expects a value).
|
||||
* It sees `Alice`. Since the previous token (`-n`) expected a value, Click associates `"Alice"` with the `-n` (and thus `--name`) option. It marks the value for `name` as `"Alice"`.
|
||||
4. **Validation & Conversion:** Click checks if all required parameters are present (they are). It also performs type conversion (though in this case, the default is string, which matches "Alice"). We'll see more complex conversions in the next chapter.
|
||||
5. **Function Call:** Finally, Click calls the command's underlying Python function (`hello`) with the collected values as keyword arguments: `hello(name='Alice', shout=True)`.
|
||||
|
||||
Here's a simplified view of the process:
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant User
|
||||
participant Terminal
|
||||
participant PythonScript as python greet_app_v2.py
|
||||
participant ClickRuntime
|
||||
participant hello_func as hello(name, shout)
|
||||
|
||||
User->>Terminal: python greet_app_v2.py hello --shout -n Alice
|
||||
Terminal->>PythonScript: Executes script with args ["hello", "--shout", "-n", "Alice"]
|
||||
PythonScript->>ClickRuntime: Calls cli() entry point
|
||||
ClickRuntime->>ClickRuntime: Parses args, finds 'hello' command
|
||||
ClickRuntime->>ClickRuntime: Identifies '--shout' as flag for 'shout' parameter (value=True)
|
||||
ClickRuntime->>ClickRuntime: Identifies '-n' as option for 'name' parameter
|
||||
ClickRuntime->>ClickRuntime: Consumes 'Alice' as value for '-n'/'name' parameter (value="Alice")
|
||||
ClickRuntime->>ClickRuntime: Validates parameters, performs type conversion
|
||||
ClickRuntime->>hello_func: Calls callback: hello(name="Alice", shout=True)
|
||||
hello_func-->>PythonScript: Prints "HELLO ALICE!"
|
||||
PythonScript-->>Terminal: Shows output
|
||||
Terminal-->>User: Displays "HELLO ALICE!"
|
||||
```
|
||||
|
||||
## Under the Hood: Decorators and Parameter Objects
|
||||
|
||||
How do `@click.option` and `@click.argument` actually work with `@click.command`?
|
||||
|
||||
1. **Parameter Definition (`decorators.py`, `core.py`):** When you use `@click.option(...)` or `@click.argument(...)`, these functions (defined in `click/decorators.py`) create instances of the `Option` or `Argument` classes (defined in `click/core.py`). These objects store all the configuration you provided (like `--name`, `-n`, `default='World'`, `is_flag=True`, etc.).
|
||||
2. **Attaching to Function (`decorators.py`):** Crucially, these decorators don't immediately add the parameters to a command. Instead, they attach the created `Option` or `Argument` object to the function they are decorating. Click uses a helper mechanism (like the internal `_param_memo` function which adds to a `__click_params__` list) to store these parameter objects *on* the function object temporarily.
|
||||
3. **Command Creation (`decorators.py`, `core.py`):** The `@click.command()` decorator (or `@group.command()`) runs *after* all the `@option` and `@argument` decorators for that function. It looks for the attached parameter objects (the `__click_params__` list). It gathers these objects and passes them to the constructor of the `Command` (or `Group`) object it creates. The `Command` object stores these parameters in its `params` attribute.
|
||||
4. **Parsing (`parser.py`, `core.py`):** When the command is invoked, the `Command` object uses its `params` list to configure an internal parser (historically based on Python's `optparse`, see `click/parser.py`). This parser processes the command-line string (`sys.argv`) according to the rules defined by the `Option` and `Argument` objects in the `params` list.
|
||||
5. **Callback Invocation (`core.py`):** After parsing and validation, Click takes the resulting values and calls the original Python function (stored as the `Command.callback`), passing the values as arguments.
|
||||
|
||||
So, the decorators work together: `@option`/`@argument` define the parameters and temporarily attach them to the function, while `@command` collects these definitions and builds the final `Command` object, ready for parsing.
|
||||
|
||||
## Conclusion
|
||||
|
||||
You've learned how to make your Click commands interactive by defining inputs using **Parameters**:
|
||||
|
||||
* **Options (`@click.option`):** Named inputs, often optional, specified with flags (`--name`, `-n`). Great for controlling behavior (like `--verbose`, `--shout`) or providing specific pieces of data (`--output file.txt`).
|
||||
* **Arguments (`@click.argument`):** Positional inputs, often required, that follow options (`input.csv`). Ideal for core data the command operates on (like source/destination files).
|
||||
|
||||
You saw how Click uses decorators to define these parameters and automatically handles parsing the command line, providing default values, generating help messages, and passing the final values to your Python function.
|
||||
|
||||
But what if you want an option to accept only numbers? Or a choice from a predefined list? Or maybe an argument that represents a file path that must exist? Click handles this through **Parameter Types**. Let's explore those next!
|
||||
|
||||
Next up: [Chapter 4: ParamType](04_paramtype.md)
|
||||
|
||||
---
|
||||
|
||||
Generated by [AI Codebase Knowledge Builder](https://github.com/The-Pocket/Tutorial-Codebase-Knowledge)
|
||||
257
docs/Click/04_paramtype.md
Normal file
257
docs/Click/04_paramtype.md
Normal file
@@ -0,0 +1,257 @@
|
||||
# Chapter 4: ParamType - Checking and Converting Inputs
|
||||
|
||||
In [Chapter 3: Parameter (Option / Argument)](03_parameter__option___argument_.md), we learned how to define inputs for our commands using `@click.option` and `@click.argument`. Our `greet` command could take a `--name` option, and our `copy` command took `SRC` and `DST` arguments.
|
||||
|
||||
But what if we need more control? What if our command needs a *number* as input, like `--count 3`? Or what if an option should only accept specific words, like `--level easy` or `--level hard`? Right now, Click treats most inputs as simple text strings.
|
||||
|
||||
This is where **ParamType** comes in! Think of `ParamType`s as the **gatekeepers** and **translators** for your command-line inputs. They:
|
||||
|
||||
1. **Validate:** Check if the user's input looks correct (e.g., "Is this actually a number?").
|
||||
2. **Convert:** Change the input text (which is always initially a string) into the Python type you need (e.g., the string `"3"` becomes the integer `3`).
|
||||
|
||||
`ParamType`s make your commands more robust by catching errors early and giving your Python code the data types it expects.
|
||||
|
||||
## Why Do We Need ParamTypes?
|
||||
|
||||
Imagine you're writing a command to repeat a message multiple times:
|
||||
|
||||
```bash
|
||||
repeat --times 5 "Hello!"
|
||||
```
|
||||
|
||||
Inside your Python function, you want the `times` variable to be an integer so you can use it in a loop. If the user types `repeat --times five "Hello!"`, your code might crash if it tries to use the string `"five"` like a number.
|
||||
|
||||
`ParamType` solves this. By telling Click that the `--times` option expects an integer, Click will automatically:
|
||||
|
||||
* Check if the input (`"5"`) can be turned into an integer.
|
||||
* If yes, convert it to the integer `5` and pass it to your function.
|
||||
* If no (like `"five"`), stop immediately and show the user a helpful error message *before* your function even runs!
|
||||
|
||||
## Using Built-in ParamTypes
|
||||
|
||||
Click provides several ready-to-use `ParamType`s. You specify which one to use with the `type` argument in `@click.option` or `@click.argument`.
|
||||
|
||||
Let's modify an example to use `click.INT`.
|
||||
|
||||
```python
|
||||
# count_app.py
|
||||
import click
|
||||
|
||||
@click.command()
|
||||
@click.option('--count', default=1, type=click.INT, help='Number of times to print.')
|
||||
@click.argument('message')
|
||||
def repeat(count, message):
|
||||
"""Prints MESSAGE the specified number of times."""
|
||||
# 'count' is now guaranteed to be an integer!
|
||||
for _ in range(count):
|
||||
click.echo(message)
|
||||
|
||||
if __name__ == '__main__':
|
||||
repeat()
|
||||
```
|
||||
|
||||
Breakdown:
|
||||
|
||||
1. `import click`: As always.
|
||||
2. `@click.option('--count', ..., type=click.INT, ...)`: This is the key change! We added `type=click.INT`. This tells Click that the value provided for `--count` must be convertible to an integer. `click.INT` is one of Click's built-in `ParamType` instances.
|
||||
3. `def repeat(count, message):`: The `count` parameter in our function will receive the *converted* integer value.
|
||||
|
||||
**Let's run it!**
|
||||
|
||||
```bash
|
||||
$ python count_app.py --count 3 "Woohoo!"
|
||||
Woohoo!
|
||||
Woohoo!
|
||||
Woohoo!
|
||||
```
|
||||
|
||||
It works! Click converted the input string `"3"` into the Python integer `3` before calling our `repeat` function.
|
||||
|
||||
Now, see what happens with invalid input:
|
||||
|
||||
```bash
|
||||
$ python count_app.py --count five "Oh no"
|
||||
Usage: count_app.py [OPTIONS] MESSAGE
|
||||
Try 'count_app.py --help' for help.
|
||||
|
||||
Error: Invalid value for '--count': 'five' is not a valid integer.
|
||||
```
|
||||
|
||||
Perfect! Click caught the error because `"five"` couldn't be converted by `click.INT`. It printed a helpful message and prevented our `repeat` function from running with bad data.
|
||||
|
||||
## Common Built-in Types
|
||||
|
||||
Click offers several useful built-in types:
|
||||
|
||||
* `click.STRING`: The default type. Converts the input to a string (usually doesn't change much unless the input was bytes).
|
||||
* `click.INT`: Converts to an integer. Fails if the input isn't a valid whole number.
|
||||
* `click.FLOAT`: Converts to a floating-point number. Fails if the input isn't a valid number (e.g., `3.14`, `-0.5`).
|
||||
* `click.BOOL`: Converts to a boolean (`True`/`False`). It's clever and understands inputs like `'1'`, `'true'`, `'t'`, `'yes'`, `'y'`, `'on'` as `True`, and `'0'`, `'false'`, `'f'`, `'no'`, `'n'`, `'off'` as `False`. Usually used for options that aren't flags.
|
||||
* `click.Choice`: Checks if the value is one of a predefined list of choices.
|
||||
|
||||
```python
|
||||
# choice_example.py
|
||||
import click
|
||||
|
||||
@click.command()
|
||||
@click.option('--difficulty', type=click.Choice(['easy', 'medium', 'hard'], case_sensitive=False), default='easy')
|
||||
def setup(difficulty):
|
||||
click.echo(f"Setting up game with difficulty: {difficulty}")
|
||||
|
||||
if __name__ == '__main__':
|
||||
setup()
|
||||
```
|
||||
|
||||
Running `python choice_example.py --difficulty MeDiUm` works (because `case_sensitive=False`), but `python choice_example.py --difficulty expert` would fail.
|
||||
|
||||
* `click.Path`: Represents a filesystem path. It can check if the path exists, if it's a file or directory, and if it has certain permissions (read/write/execute). It returns the path as a string (or `pathlib.Path` if configured).
|
||||
|
||||
```python
|
||||
# path_example.py
|
||||
import click
|
||||
|
||||
@click.command()
|
||||
@click.argument('output_dir', type=click.Path(exists=True, file_okay=False, dir_okay=True, writable=True))
|
||||
def process(output_dir):
|
||||
click.echo(f"Processing data into directory: {output_dir}")
|
||||
# We know output_dir exists, is a directory, and is writable!
|
||||
|
||||
if __name__ == '__main__':
|
||||
process()
|
||||
```
|
||||
|
||||
* `click.File`: Similar to `Path`, but it *automatically opens* the file and passes the open file object to your function. It also handles closing the file automatically. You can specify the mode (`'r'`, `'w'`, `'rb'`, `'wb'`).
|
||||
|
||||
```python
|
||||
# file_example.py
|
||||
import click
|
||||
|
||||
@click.command()
|
||||
@click.argument('input_file', type=click.File('r')) # Open for reading text
|
||||
def cat(input_file):
|
||||
# input_file is an open file handle!
|
||||
click.echo(input_file.read())
|
||||
# Click will close the file automatically after this function returns
|
||||
|
||||
if __name__ == '__main__':
|
||||
cat()
|
||||
```
|
||||
|
||||
These built-in types cover most common use cases for validating and converting command-line inputs.
|
||||
|
||||
## How ParamTypes Work Under the Hood
|
||||
|
||||
What happens when you specify `type=click.INT`?
|
||||
|
||||
1. **Parsing:** As described in [Chapter 3](03_parameter__option___argument_.md), Click's parser identifies the command-line arguments and matches them to your defined `Option`s and `Argument`s. It finds the raw string value provided by the user (e.g., `"3"` for `--count`).
|
||||
2. **Type Retrieval:** The parser looks at the `Parameter` object (the `Option` or `Argument`) and finds the `type` you assigned to it (e.g., the `click.INT` instance).
|
||||
3. **Conversion Attempt:** The parser calls the `convert()` method of the `ParamType` instance, passing the raw string value (`"3"`), the parameter object itself, and the current [Context](05_context.md).
|
||||
4. **Validation & Conversion Logic (Inside `ParamType.convert`)**:
|
||||
* The `click.INT.convert()` method tries to call Python's built-in `int("3")`.
|
||||
* If this succeeds, it returns the result (the integer `3`).
|
||||
* If it fails (e.g., `int("five")` would raise a `ValueError`), the `convert()` method catches this error.
|
||||
5. **Success or Failure**:
|
||||
* **Success:** The parser receives the converted value (`3`) and stores it. Later, it passes this value to your command function.
|
||||
* **Failure:** The `convert()` method calls its `fail()` helper method. The `fail()` method raises a `click.BadParameter` exception with a helpful error message (e.g., "'five' is not a valid integer."). Click catches this exception, stops further processing, and displays the error message to the user along with usage instructions.
|
||||
|
||||
Here's a simplified view of the successful conversion process:
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant User
|
||||
participant CLI
|
||||
participant ClickParser as Click Parser
|
||||
participant IntType as click.INT
|
||||
participant CommandFunc as Command Function
|
||||
|
||||
User->>CLI: python count_app.py --count 3 ...
|
||||
CLI->>ClickParser: Parse args, find '--count' option with value '3'
|
||||
ClickParser->>IntType: Call convert(value='3', param=..., ctx=...)
|
||||
IntType->>IntType: Attempt int('3') -> Success! returns 3
|
||||
IntType-->>ClickParser: Return converted value: 3
|
||||
ClickParser->>CommandFunc: Call repeat(count=3, ...)
|
||||
CommandFunc-->>CLI: Executes logic (prints message 3 times)
|
||||
```
|
||||
|
||||
And here's the failure process:
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant User
|
||||
participant CLI
|
||||
participant ClickParser as Click Parser
|
||||
participant IntType as click.INT
|
||||
participant ClickException as Click Exception Handling
|
||||
|
||||
User->>CLI: python count_app.py --count five ...
|
||||
CLI->>ClickParser: Parse args, find '--count' option with value 'five'
|
||||
ClickParser->>IntType: Call convert(value='five', param=..., ctx=...)
|
||||
IntType->>IntType: Attempt int('five') -> Fails! (ValueError)
|
||||
IntType->>ClickException: Catch error, call fail("'five' is not...") -> raises BadParameter
|
||||
ClickException-->>ClickParser: BadParameter exception raised
|
||||
ClickParser-->>CLI: Catch exception, stop processing
|
||||
CLI-->>User: Display "Error: Invalid value for '--count': 'five' is not a valid integer."
|
||||
```
|
||||
|
||||
The core logic for built-in types resides in `click/types.py`. Each type (like `IntParamType`, `Choice`, `Path`) inherits from the base `ParamType` class and implements its own `convert` method containing the specific validation and conversion rules.
|
||||
|
||||
```python
|
||||
# Simplified structure from click/types.py
|
||||
|
||||
class ParamType:
|
||||
name: str # Human-readable name like "integer" or "filename"
|
||||
|
||||
def convert(self, value, param, ctx):
|
||||
# Must be implemented by subclasses
|
||||
# Should return the converted value or call self.fail()
|
||||
raise NotImplementedError
|
||||
|
||||
def fail(self, message, param, ctx):
|
||||
# Raises a BadParameter exception
|
||||
raise BadParameter(message, ctx=ctx, param=param)
|
||||
|
||||
class IntParamType(ParamType):
|
||||
name = "integer"
|
||||
|
||||
def convert(self, value, param, ctx):
|
||||
try:
|
||||
# The core conversion logic!
|
||||
return int(value)
|
||||
except ValueError:
|
||||
# If conversion fails, raise the standard error
|
||||
self.fail(f"{value!r} is not a valid integer.", param, ctx)
|
||||
|
||||
# click.INT is just an instance of this class
|
||||
INT = IntParamType()
|
||||
```
|
||||
|
||||
## Custom Types
|
||||
|
||||
What if none of the built-in types do exactly what you need? Click allows you to create your own custom `ParamType`s! You can do this by subclassing `click.ParamType` and implementing the `name` attribute and the `convert` method. This is an advanced topic, but it provides great flexibility.
|
||||
|
||||
## Shell Completion Hints
|
||||
|
||||
An added benefit of using specific `ParamType`s is that they can provide hints for shell completion (when the user presses Tab). For example:
|
||||
* `click.Choice(['easy', 'medium', 'hard'])` can suggest `easy`, `medium`, or `hard`.
|
||||
* `click.Path` can suggest file and directory names from the current location.
|
||||
|
||||
This makes your CLI even more user-friendly.
|
||||
|
||||
## Conclusion
|
||||
|
||||
`ParamType`s are a fundamental part of Click, acting as the bridge between raw command-line text input and the well-typed data your Python functions need. They handle the crucial tasks of:
|
||||
|
||||
* **Validating** user input against expected formats or rules.
|
||||
* **Converting** input strings to appropriate Python types (integers, booleans, files, etc.).
|
||||
* **Generating** user-friendly error messages for invalid input.
|
||||
* Providing hints for **shell completion**.
|
||||
|
||||
By using built-in types like `click.INT`, `click.Choice`, `click.Path`, and `click.File`, you make your commands more robust, reliable, and easier to use.
|
||||
|
||||
So far, we've seen how commands are structured, how parameters get their values, and how those values are validated and converted. But how does Click manage the state *during* the execution of a command? How does it know which command is running or what the parent commands were? That's the job of the `Context`. Let's explore that next!
|
||||
|
||||
Next up: [Chapter 5: Context](05_context.md)
|
||||
|
||||
---
|
||||
|
||||
Generated by [AI Codebase Knowledge Builder](https://github.com/The-Pocket/Tutorial-Codebase-Knowledge)
|
||||
271
docs/Click/05_context.md
Normal file
271
docs/Click/05_context.md
Normal file
@@ -0,0 +1,271 @@
|
||||
# Chapter 5: Context - The Command's Nervous System
|
||||
|
||||
In the last chapter, [ParamType](04_paramtype.md), we saw how Click helps validate and convert user input into the right Python types, making our commands more robust. We used types like `click.INT` and `click.Path` to ensure data correctness.
|
||||
|
||||
But what happens *while* a command is running? How does Click keep track of which command is being executed, what parameters were passed, or even shared information between different commands in a nested structure (like `git remote add ...`)?
|
||||
|
||||
This is where the **Context** object, often referred to as `ctx`, comes into play. Think of the Context as the central nervous system for a single command invocation. It carries all the vital information about the current state of execution.
|
||||
|
||||
## Why Do We Need a Context?
|
||||
|
||||
Imagine you have a command that needs to behave differently based on a global configuration, maybe a `--verbose` flag set on the main application group. Or perhaps one command needs to call another command within the same application. How do they communicate?
|
||||
|
||||
The Context object solves these problems by providing a central place to:
|
||||
|
||||
* Access parameters passed to the *current* command.
|
||||
* Access parameters or settings from *parent* commands.
|
||||
* Share application-level objects (like configuration settings or database connections) between commands.
|
||||
* Manage resources that need cleanup (like automatically closing files opened with `click.File`).
|
||||
* Invoke other commands programmatically.
|
||||
|
||||
Let's explore how to access and use this powerful object.
|
||||
|
||||
## Getting the Context: `@pass_context`
|
||||
|
||||
Click doesn't automatically pass the Context object to your command function. You need to explicitly ask for it using a special decorator: `@click.pass_context`.
|
||||
|
||||
When you add `@click.pass_context` *above* your function definition (but typically *below* the `@click.command` or `@click.option` decorators), Click will automatically **inject** the `Context` object as the **very first argument** to your function.
|
||||
|
||||
Let's see a simple example:
|
||||
|
||||
```python
|
||||
# context_basics.py
|
||||
import click
|
||||
|
||||
@click.group()
|
||||
@click.pass_context # Request the context for the group function
|
||||
def cli(ctx):
|
||||
"""A simple CLI with context."""
|
||||
# We can store arbitrary data on the context's 'obj' attribute
|
||||
ctx.obj = {'verbose': False} # Initialize a shared dictionary
|
||||
|
||||
@cli.command()
|
||||
@click.option('--verbose', is_flag=True, help='Enable verbose mode.')
|
||||
@click.pass_context # Request the context for the command function
|
||||
def info(ctx, verbose):
|
||||
"""Prints info, possibly verbosely."""
|
||||
# Access the command name from the context
|
||||
click.echo(f"Executing command: {ctx.command.name}")
|
||||
|
||||
# Access parameters passed to *this* command
|
||||
click.echo(f"Verbose flag (local): {verbose}")
|
||||
|
||||
# We can modify the shared object from the parent context
|
||||
if verbose:
|
||||
ctx.obj['verbose'] = True
|
||||
|
||||
# Access the shared object from the parent context
|
||||
click.echo(f"Verbose setting (shared): {ctx.obj['verbose']}")
|
||||
|
||||
if __name__ == '__main__':
|
||||
cli()
|
||||
```
|
||||
|
||||
Let's break it down:
|
||||
|
||||
1. `@click.pass_context`: We apply this decorator to both the `cli` group function and the `info` command function.
|
||||
2. `def cli(ctx): ...`: Because of `@pass_context`, the `cli` function now receives the `Context` object as its first argument, which we've named `ctx`.
|
||||
3. `ctx.obj = {'verbose': False}`: The `ctx.obj` attribute is a special place designed for you to store and share *your own* application data. Here, the main `cli` group initializes it as a dictionary. This object will be automatically inherited by child command contexts.
|
||||
4. `def info(ctx, verbose): ...`: The `info` command function also receives the `Context` (`ctx`) as its first argument, followed by its own parameters (`verbose`).
|
||||
5. `ctx.command.name`: We access the `Command` object associated with the current context via `ctx.command` and get its name.
|
||||
6. `ctx.obj['verbose'] = True`: We can *modify* the shared `ctx.obj` from within the subcommand.
|
||||
7. `click.echo(f"Verbose setting (shared): {ctx.obj['verbose']}")`: We access the potentially modified shared state.
|
||||
|
||||
**Run it!**
|
||||
|
||||
```bash
|
||||
$ python context_basics.py info
|
||||
Executing command: info
|
||||
Verbose flag (local): False
|
||||
Verbose setting (shared): False
|
||||
|
||||
$ python context_basics.py info --verbose
|
||||
Executing command: info
|
||||
Verbose flag (local): True
|
||||
Verbose setting (shared): True
|
||||
```
|
||||
|
||||
You can see how `@pass_context` gives us access to the runtime environment (`ctx.command.name`) and allows us to use `ctx.obj` to share state between the parent group (`cli`) and the subcommand (`info`).
|
||||
|
||||
## Key Context Attributes
|
||||
|
||||
The `Context` object has several useful attributes:
|
||||
|
||||
* `ctx.command`: The [Command](01_command___group.md) object that this context belongs to. You can get its name (`ctx.command.name`), parameters, etc.
|
||||
* `ctx.parent`: The context of the invoking command. If this is the top-level command, `ctx.parent` will be `None`. This forms a linked list or chain back to the root context.
|
||||
* `ctx.params`: A dictionary mapping parameter names to the *final* values passed to the command, after parsing, type conversion, and defaults have been applied.
|
||||
```python
|
||||
# access_params.py
|
||||
import click
|
||||
|
||||
@click.command()
|
||||
@click.option('--name', default='Guest')
|
||||
@click.pass_context
|
||||
def hello(ctx, name):
|
||||
click.echo(f"Hello, {name}!")
|
||||
# Access the parameter value directly via ctx.params
|
||||
click.echo(f"(Value from ctx.params: {ctx.params['name']})")
|
||||
|
||||
if __name__ == '__main__':
|
||||
hello()
|
||||
```
|
||||
Running `python access_params.py --name Alice` would show `Hello, Alice!` and `(Value from ctx.params: Alice)`.
|
||||
* `ctx.obj`: As seen before, this is an arbitrary object that gets passed down the context chain. It's commonly used for shared configuration, database connections, or other application-level state. You can also use `@click.pass_obj` as a shortcut if you *only* need `ctx.obj`.
|
||||
* `ctx.info_name`: The name that was used on the command line to invoke this command or group (e.g., `info` in `python context_basics.py info`).
|
||||
* `ctx.invoked_subcommand`: For groups, this holds the name of the subcommand that was invoked (or `None` if no subcommand was called).
|
||||
|
||||
## Calling Other Commands
|
||||
|
||||
Sometimes, you want one command to trigger another. The Context provides methods for this:
|
||||
|
||||
* `ctx.invoke(other_command, **params)`: Calls another Click command (`other_command`), passing the current context's parent (`ctx.parent`) as the new command's parent. It uses the provided `params` for the call.
|
||||
* `ctx.forward(other_command)`: Similar to `invoke`, but it automatically passes all parameters from the *current* context (`ctx.params`) to the `other_command`. This is useful for creating alias commands.
|
||||
|
||||
```python
|
||||
# invoke_example.py
|
||||
import click
|
||||
|
||||
@click.group()
|
||||
def cli():
|
||||
pass
|
||||
|
||||
@cli.command()
|
||||
@click.argument('text')
|
||||
def print_it(text):
|
||||
"""Prints the given text."""
|
||||
click.echo(f"Printing: {text}")
|
||||
|
||||
@cli.command()
|
||||
@click.argument('message')
|
||||
@click.pass_context # Need context to call invoke
|
||||
def shout(ctx, message):
|
||||
"""Shouts the message by calling print_it."""
|
||||
click.echo("About to invoke print_it...")
|
||||
# Call the 'print_it' command, passing the uppercased message
|
||||
ctx.invoke(print_it, text=message.upper())
|
||||
click.echo("Finished invoking print_it.")
|
||||
|
||||
if __name__ == '__main__':
|
||||
cli()
|
||||
```
|
||||
|
||||
Running `python invoke_example.py shout "hello world"` will output:
|
||||
|
||||
```
|
||||
About to invoke print_it...
|
||||
Printing: HELLO WORLD
|
||||
Finished invoking print_it.
|
||||
```
|
||||
|
||||
The `shout` command successfully called the `print_it` command programmatically using `ctx.invoke()`.
|
||||
|
||||
## Resource Management (`ctx.call_on_close`)
|
||||
|
||||
Click uses the context internally to manage resources. For instance, when you use `type=click.File('w')`, Click opens the file and registers a cleanup function using `ctx.call_on_close(file.close)`. This ensures the file is closed when the context is finished, even if errors occur.
|
||||
|
||||
You can use this mechanism yourself if you need custom resource cleanup tied to the command's lifecycle.
|
||||
|
||||
```python
|
||||
# resource_management.py
|
||||
import click
|
||||
|
||||
class MockResource:
|
||||
def __init__(self, name):
|
||||
self.name = name
|
||||
click.echo(f"Resource '{self.name}' opened.")
|
||||
def close(self):
|
||||
click.echo(f"Resource '{self.name}' closed.")
|
||||
|
||||
@click.command()
|
||||
@click.pass_context
|
||||
def process(ctx):
|
||||
"""Opens and closes a mock resource."""
|
||||
res = MockResource("DataFile")
|
||||
# Register the close method to be called when the context ends
|
||||
ctx.call_on_close(res.close)
|
||||
click.echo("Processing with resource...")
|
||||
# Function ends, context tears down, call_on_close triggers
|
||||
|
||||
if __name__ == '__main__':
|
||||
process()
|
||||
```
|
||||
|
||||
Running this script will show:
|
||||
|
||||
```
|
||||
Resource 'DataFile' opened.
|
||||
Processing with resource...
|
||||
Resource 'DataFile' closed.
|
||||
```
|
||||
|
||||
The resource was automatically closed because we registered its `close` method with `ctx.call_on_close`.
|
||||
|
||||
## How Context Works Under the Hood
|
||||
|
||||
1. **Initial Context:** When you run your Click application (e.g., by calling `cli()`), Click creates the first `Context` object associated with the top-level command or group (`cli` in our examples).
|
||||
2. **Parsing and Subcommand:** Click parses the command-line arguments. If a subcommand is identified (like `info` in `python context_basics.py info`), Click finds the corresponding `Command` object.
|
||||
3. **Child Context Creation:** Before executing the subcommand's callback function, Click creates a *new* `Context` object for the subcommand. Crucially, it sets the `parent` attribute of this new context to the context of the invoking command (the `cli` context in our example).
|
||||
4. **Object Inheritance:** The `ctx.obj` attribute is automatically passed down from the parent context to the child context *by reference* (unless the child explicitly sets its own `ctx.obj`).
|
||||
5. **`@pass_context` Decorator:** This decorator (defined in `decorators.py`) wraps your callback function. When the wrapped function is called, the decorator uses `click.globals.get_current_context()` (which accesses a thread-local stack of contexts) to fetch the *currently active* context and inserts it as the first argument before calling your original function.
|
||||
6. **`ctx.invoke`:** When you call `ctx.invoke(other_cmd, ...)`, Click finds the `other_cmd` object, creates a *new* context for it (setting its parent to `ctx.parent`), populates its `params` from the arguments you provided, and then executes `other_cmd`'s callback within that new context.
|
||||
7. **Cleanup:** Once a command function finishes (or raises an exception that Click handles), its corresponding context is "torn down". This is when any functions registered with `ctx.call_on_close` are executed.
|
||||
|
||||
Here's a simplified diagram showing context creation and `ctx.obj` flow for `python context_basics.py info --verbose`:
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant User
|
||||
participant CLI as python context_basics.py
|
||||
participant ClickRuntime
|
||||
participant cli_ctx as cli Context
|
||||
participant info_ctx as info Context
|
||||
participant cli_func as cli(ctx)
|
||||
participant info_func as info(ctx, verbose)
|
||||
|
||||
User->>CLI: info --verbose
|
||||
CLI->>ClickRuntime: Calls cli() entry point
|
||||
ClickRuntime->>cli_ctx: Creates root context for 'cli' group
|
||||
Note over ClickRuntime, cli_func: ClickRuntime calls cli's callback (due to @click.group)
|
||||
ClickRuntime->>cli_func: cli(ctx=cli_ctx)
|
||||
cli_func->>cli_ctx: Sets ctx.obj = {'verbose': False}
|
||||
cli_func-->>ClickRuntime: Returns
|
||||
ClickRuntime->>ClickRuntime: Parses args, finds 'info' subcommand, '--verbose' option
|
||||
ClickRuntime->>info_ctx: Creates child context for 'info' command
|
||||
info_ctx->>cli_ctx: Sets info_ctx.parent = cli_ctx
|
||||
info_ctx->>info_ctx: Inherits ctx.obj from parent (value = {'verbose': False})
|
||||
Note over ClickRuntime, info_func: ClickRuntime prepares to call info's callback
|
||||
ClickRuntime->>ClickRuntime: Uses @pass_context to get info_ctx
|
||||
ClickRuntime->>info_func: info(ctx=info_ctx, verbose=True)
|
||||
info_func->>info_ctx: Accesses ctx.command.name
|
||||
info_func->>info_ctx: Accesses ctx.params['verbose'] (or local 'verbose')
|
||||
info_func->>info_ctx: Modifies ctx.obj['verbose'] = True
|
||||
info_func->>info_ctx: Accesses ctx.obj['verbose'] (now True)
|
||||
info_func-->>ClickRuntime: Returns
|
||||
ClickRuntime->>info_ctx: Tears down info_ctx (runs call_on_close)
|
||||
ClickRuntime->>cli_ctx: Tears down cli_ctx (runs call_on_close)
|
||||
ClickRuntime-->>CLI: Exits
|
||||
```
|
||||
|
||||
The core `Context` class is defined in `click/core.py`. The decorators `pass_context` and `pass_obj` are in `click/decorators.py`, and the mechanism for tracking the current context is in `click/globals.py`.
|
||||
|
||||
## Conclusion
|
||||
|
||||
The `Context` (`ctx`) is a cornerstone concept in Click, acting as the runtime carrier of information for a command invocation.
|
||||
|
||||
You've learned:
|
||||
|
||||
* The Context holds data like the current command, parameters, parent context, and shared application objects (`ctx.obj`).
|
||||
* The `@click.pass_context` decorator injects the current Context into your command function.
|
||||
* `ctx.obj` is essential for sharing state between nested commands.
|
||||
* `ctx.invoke()` and `ctx.forward()` allow commands to call each other programmatically.
|
||||
* Click uses the context for resource management (`ctx.call_on_close`), ensuring cleanup.
|
||||
|
||||
Understanding the Context is key to building more complex Click applications where commands need to interact with each other or with shared application state. It provides the structure and communication channels necessary for sophisticated CLI tools.
|
||||
|
||||
So far, we've focused on the logic and structure of commands. But how can we make the interaction in the terminal itself more engaging? How do we prompt users for input, show progress bars, or display colored output? Let's explore Click's terminal UI capabilities next!
|
||||
|
||||
Next up: [Chapter 6: Term UI (Terminal User Interface)](06_term_ui__terminal_user_interface_.md)
|
||||
|
||||
---
|
||||
|
||||
Generated by [AI Codebase Knowledge Builder](https://github.com/The-Pocket/Tutorial-Codebase-Knowledge)
|
||||
290
docs/Click/06_term_ui__terminal_user_interface_.md
Normal file
290
docs/Click/06_term_ui__terminal_user_interface_.md
Normal file
@@ -0,0 +1,290 @@
|
||||
# Chapter 6: Term UI (Terminal User Interface)
|
||||
|
||||
Welcome back! In [Chapter 5: Context](05_context.md), we learned how Click uses the `Context` object (`ctx`) to manage the state of a command while it's running, allowing us to share information and call other commands.
|
||||
|
||||
So far, our commands have mostly just printed simple text. But what if we want to make our command-line tools more interactive and user-friendly? How can we:
|
||||
|
||||
* Ask the user for input (like their name or a filename)?
|
||||
* Ask simple yes/no questions?
|
||||
* Show a progress bar for long-running tasks?
|
||||
* Make our output more visually appealing with colors or styles (like making errors red)?
|
||||
|
||||
This is where Click's **Terminal User Interface (Term UI)** functions come in handy. They are Click's toolkit for talking *back and forth* with the user through the terminal.
|
||||
|
||||
## Making Our Tools Talk: The Need for Term UI
|
||||
|
||||
Imagine you're building a tool that processes a large data file. A purely silent tool isn't very helpful. A better tool might:
|
||||
|
||||
1. Ask the user which file to process.
|
||||
2. Ask for confirmation before starting a potentially long operation.
|
||||
3. Show a progress bar while processing the data.
|
||||
4. Print a nice, colored "Success!" message at the end, or a red "Error!" message if something went wrong.
|
||||
|
||||
Doing all this reliably across different operating systems (like Linux, macOS, and Windows) can be tricky. For example, getting colored text to work correctly on Windows requires special handling.
|
||||
|
||||
Click's Term UI functions wrap up these common interactive tasks into easy-to-use functions that work consistently everywhere. Let's explore some of the most useful ones!
|
||||
|
||||
## Printing with `click.echo()`
|
||||
|
||||
We've seen `print()` in Python, but Click provides its own version: `click.echo()`. Why use it?
|
||||
|
||||
* **Smarter:** It works better with different kinds of data (like Unicode text and raw bytes).
|
||||
* **Cross-Platform:** It handles subtle differences between operating systems for you.
|
||||
* **Color Aware:** It automatically strips out color codes if the output isn't going to a terminal (like if you redirect output to a file), preventing garbled text.
|
||||
* **Integrated:** It works seamlessly with Click's other features, like redirecting output or testing.
|
||||
|
||||
Using it is just like `print()`:
|
||||
|
||||
```python
|
||||
# echo_example.py
|
||||
import click
|
||||
|
||||
@click.command()
|
||||
def cli():
|
||||
"""Demonstrates click.echo"""
|
||||
click.echo("Hello from Click!")
|
||||
# You can print errors to stderr easily
|
||||
click.echo("Oops, something went wrong!", err=True)
|
||||
|
||||
if __name__ == '__main__':
|
||||
cli()
|
||||
```
|
||||
|
||||
Running this:
|
||||
|
||||
```bash
|
||||
$ python echo_example.py
|
||||
Hello from Click!
|
||||
Oops, something went wrong! # (This line goes to stderr)
|
||||
```
|
||||
|
||||
Simple! For most printing in Click apps, `click.echo()` is preferred over `print()`.
|
||||
|
||||
## Adding Style: `click.style()` and `click.secho()`
|
||||
|
||||
Want to make your output stand out? Click makes it easy to add colors and styles (like bold or underline) to your text.
|
||||
|
||||
* `click.style(text, fg='color', bg='color', bold=True, ...)`: Takes your text and wraps it with special codes that terminals understand to change its appearance. It returns the modified string.
|
||||
* `click.secho(text, fg='color', ...)`: A shortcut that combines `style` and `echo`. It styles the text *and* prints it in one go.
|
||||
|
||||
Let's make our success and error messages more obvious:
|
||||
|
||||
```python
|
||||
# style_example.py
|
||||
import click
|
||||
|
||||
@click.command()
|
||||
def cli():
|
||||
"""Demonstrates styled output"""
|
||||
# Style the text first, then echo it
|
||||
success_message = click.style("Operation successful!", fg='green', bold=True)
|
||||
click.echo(success_message)
|
||||
|
||||
# Or use secho for style + echo in one step
|
||||
click.secho("Critical error!", fg='red', underline=True, err=True)
|
||||
|
||||
if __name__ == '__main__':
|
||||
cli()
|
||||
```
|
||||
|
||||
Running this (your terminal must support color):
|
||||
|
||||
```bash
|
||||
$ python style_example.py
|
||||
# Output will look something like:
|
||||
# Operation successful! (in bold green)
|
||||
# Critical error! (in underlined red, sent to stderr)
|
||||
```
|
||||
|
||||
Click supports various colors (`'red'`, `'green'`, `'blue'`, etc.) and styles (`bold`, `underline`, `blink`, `reverse`). This makes your CLI output much more informative at a glance!
|
||||
|
||||
## Getting User Input: `click.prompt()`
|
||||
|
||||
Sometimes you need to ask the user for information. `click.prompt()` is designed for this. It shows a message and waits for the user to type something and press Enter.
|
||||
|
||||
```python
|
||||
# prompt_example.py
|
||||
import click
|
||||
|
||||
@click.command()
|
||||
def cli():
|
||||
"""Asks for user input"""
|
||||
name = click.prompt("Please enter your name")
|
||||
click.echo(f"Hello, {name}!")
|
||||
|
||||
# You can specify a default value
|
||||
location = click.prompt("Enter location", default="Earth")
|
||||
click.echo(f"Location: {location}")
|
||||
|
||||
# You can also require a specific type (like an integer)
|
||||
age = click.prompt("Enter your age", type=int)
|
||||
click.echo(f"You are {age} years old.")
|
||||
|
||||
if __name__ == '__main__':
|
||||
cli()
|
||||
```
|
||||
|
||||
Running this interactively:
|
||||
|
||||
```bash
|
||||
$ python prompt_example.py
|
||||
Please enter your name: Alice
|
||||
Hello, Alice!
|
||||
Enter location [Earth]: # Just press Enter here
|
||||
Location: Earth
|
||||
Enter your age: 30
|
||||
You are 30 years old.
|
||||
```
|
||||
|
||||
If you enter something that can't be converted to the `type` (like "abc" for age), `click.prompt` will automatically show an error and ask again! It can also hide input for passwords (`hide_input=True`).
|
||||
|
||||
## Asking Yes/No: `click.confirm()`
|
||||
|
||||
A common need is asking for confirmation before doing something potentially destructive or time-consuming. `click.confirm()` handles this nicely.
|
||||
|
||||
```python
|
||||
# confirm_example.py
|
||||
import click
|
||||
import time
|
||||
|
||||
@click.command()
|
||||
@click.option('--yes', is_flag=True, help='Assume Yes to confirmation.')
|
||||
def cli(yes):
|
||||
"""Asks for confirmation."""
|
||||
click.echo("This might take a while or change things.")
|
||||
|
||||
# If --yes flag is given, `yes` is True, otherwise ask.
|
||||
# abort=True means if user says No, stop the program.
|
||||
if not yes:
|
||||
click.confirm("Do you want to continue?", abort=True)
|
||||
|
||||
click.echo("Starting operation...")
|
||||
time.sleep(2) # Simulate work
|
||||
click.echo("Done!")
|
||||
|
||||
if __name__ == '__main__':
|
||||
cli()
|
||||
```
|
||||
|
||||
Running interactively:
|
||||
|
||||
```bash
|
||||
$ python confirm_example.py
|
||||
This might take a while or change things.
|
||||
Do you want to continue? [y/N]: y # User types 'y'
|
||||
Starting operation...
|
||||
Done!
|
||||
```
|
||||
|
||||
If the user types 'n' (or just presses Enter, since the default is No - indicated by `[y/N]`), the program will stop immediately because of `abort=True`. If you run `python confirm_example.py --yes`, it skips the question entirely.
|
||||
|
||||
## Showing Progress: `click.progressbar()`
|
||||
|
||||
For tasks that take a while, it's good practice to show the user that something is happening. `click.progressbar()` creates a visual progress bar. You typically use it with a Python `with` statement around a loop.
|
||||
|
||||
Let's simulate processing a list of items:
|
||||
|
||||
```python
|
||||
# progress_example.py
|
||||
import click
|
||||
import time
|
||||
|
||||
items_to_process = range(100) # Simulate 100 items
|
||||
|
||||
@click.command()
|
||||
def cli():
|
||||
"""Shows a progress bar."""
|
||||
# 'items_to_process' is the iterable
|
||||
# 'label' is the text shown before the bar
|
||||
with click.progressbar(items_to_process, label="Processing items") as bar:
|
||||
for item in bar:
|
||||
# Simulate work for each item
|
||||
time.sleep(0.05)
|
||||
# The 'bar' automatically updates with each iteration
|
||||
|
||||
click.echo("Finished processing!")
|
||||
|
||||
if __name__ == '__main__':
|
||||
cli()
|
||||
```
|
||||
|
||||
When you run this, you'll see a progress bar update in your terminal:
|
||||
|
||||
```bash
|
||||
$ python progress_example.py
|
||||
Processing items [####################################] 100% 00:00:05
|
||||
Finished processing!
|
||||
# (The bar animates in place while running)
|
||||
```
|
||||
|
||||
The progress bar automatically figures out the percentage and estimated time remaining (ETA). It makes long tasks much less mysterious for the user. You can also use it without an iterable by manually calling the `bar.update(increment)` method inside the `with` block.
|
||||
|
||||
## How Term UI Works Under the Hood
|
||||
|
||||
These functions seem simple, but they handle quite a bit behind the scenes:
|
||||
|
||||
1. **Abstraction:** They provide a high-level API for common terminal tasks, hiding the low-level details.
|
||||
2. **Input Handling:** Functions like `prompt` and `confirm` use Python's built-in `input()` or `getpass.getpass()` (for hidden input). They add loops for retries, default value handling, and type conversion/validation (using [ParamType](04_paramtype.md) concepts internally).
|
||||
3. **Output Handling (`echo`, `secho`):**
|
||||
* They check if the output stream (`stdout` or `stderr`) is connected to a terminal (`isatty`).
|
||||
* If not a terminal, or if color is disabled, `style` codes are automatically removed (`strip_ansi`).
|
||||
* On Windows, if `colorama` is installed, Click wraps the output streams to translate ANSI color codes into Windows API calls, making colors work automatically.
|
||||
4. **Progress Bar (`progressbar`):**
|
||||
* It calculates the percentage complete based on the iterable's length (or the provided `length`).
|
||||
* It estimates the remaining time (ETA) by timing recent iterations.
|
||||
* It formats the bar (`#` and `-` characters) and info text.
|
||||
* Crucially, it uses special terminal control characters (like `\r` - carriage return) to move the cursor back to the beginning of the line before printing the updated bar. This makes the bar *appear* to update in place rather than printing many lines. It also hides/shows the cursor during updates (`\033[?25l`, `\033[?25h`) on non-Windows systems for a smoother look.
|
||||
5. **Cross-Platform Compatibility:** A major goal is to make these interactions work consistently across different operating systems and terminal types, handling quirks like Windows console limitations (`_winconsole.py`, `_compat.py`).
|
||||
|
||||
Let's visualize what might happen when you call `click.secho("Error!", fg='red', err=True)`:
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant UserCode as Your Code
|
||||
participant ClickSecho as click.secho()
|
||||
participant ClickStyle as click.style()
|
||||
participant ClickEcho as click.echo()
|
||||
participant CompatLayer as Click Compatibility Layer
|
||||
participant Terminal
|
||||
|
||||
UserCode->>ClickSecho: secho("Error!", fg='red', err=True)
|
||||
ClickSecho->>ClickStyle: style("Error!", fg='red', ...)
|
||||
ClickStyle-->>ClickSecho: Returns "\033[31mError!\033[0m" (styled text)
|
||||
ClickSecho->>ClickEcho: echo("\033[31mError!\033[0m", err=True)
|
||||
ClickEcho->>CompatLayer: Check if output (stderr) is a TTY
|
||||
CompatLayer-->>ClickEcho: Yes, it's a TTY
|
||||
ClickEcho->>CompatLayer: Check if color is enabled
|
||||
CompatLayer-->>ClickEcho: Yes, color is enabled
|
||||
Note over ClickEcho, Terminal: On Windows, may wrap stream with Colorama here
|
||||
ClickEcho->>CompatLayer: Write styled text to stderr
|
||||
CompatLayer->>Terminal: Writes "\033[31mError!\033[0m\n"
|
||||
Terminal-->>Terminal: Displays "Error!" in red
|
||||
```
|
||||
|
||||
The key is that Click adds layers of checks and formatting (`style`, color stripping, platform adaptation) around the basic act of printing (`echo`) or getting input (`prompt`).
|
||||
|
||||
You can find the implementation details in:
|
||||
* `click/termui.py`: Defines the main functions like `prompt`, `confirm`, `style`, `secho`, `progressbar`, `echo_via_pager`.
|
||||
* `click/_termui_impl.py`: Contains the implementations for more complex features like `ProgressBar`, `Editor`, `pager`, and `getchar`.
|
||||
* `click/utils.py`: Contains `echo` and helpers like `open_stream`.
|
||||
* `click/_compat.py` & `click/_winconsole.py`: Handle differences between Python versions and operating systems, especially for terminal I/O and color support on Windows.
|
||||
|
||||
## Conclusion
|
||||
|
||||
Click's **Term UI** functions are essential for creating command-line applications that are interactive, informative, and pleasant to use. You've learned how to:
|
||||
|
||||
* Print output reliably with `click.echo`.
|
||||
* Add visual flair with colors and styles using `click.style` and `click.secho`.
|
||||
* Ask the user for input with `click.prompt`.
|
||||
* Get yes/no confirmation using `click.confirm`.
|
||||
* Show progress for long tasks with `click.progressbar`.
|
||||
|
||||
These tools handle many cross-platform complexities, letting you focus on building the core logic of your interactive CLI.
|
||||
|
||||
But what happens when things go wrong? How does Click handle errors, like invalid user input or missing files? That's where Click's exception handling comes in. Let's dive into that next!
|
||||
|
||||
Next up: [Chapter 7: Click Exceptions](07_click_exceptions.md)
|
||||
|
||||
---
|
||||
|
||||
Generated by [AI Codebase Knowledge Builder](https://github.com/The-Pocket/Tutorial-Codebase-Knowledge)
|
||||
251
docs/Click/07_click_exceptions.md
Normal file
251
docs/Click/07_click_exceptions.md
Normal file
@@ -0,0 +1,251 @@
|
||||
# Chapter 7: Click Exceptions - Handling Errors Gracefully
|
||||
|
||||
In the last chapter, [Chapter 6: Term UI (Terminal User Interface)](06_term_ui__terminal_user_interface_.md), we explored how to make our command-line tools interactive and visually appealing using functions like `click.prompt`, `click.confirm`, and `click.secho`. We learned how to communicate effectively *with* the user.
|
||||
|
||||
But what happens when the user doesn't communicate effectively with *us*? What if they type the wrong command, forget a required argument, or enter text when a number was expected? Our programs need a way to handle these errors without just crashing.
|
||||
|
||||
This is where **Click Exceptions** come in. They are Click's way of signaling that something went wrong, usually because of a problem with the user's input or how they tried to run the command.
|
||||
|
||||
## Why Special Exceptions? The Problem with Crashes
|
||||
|
||||
Imagine you have a command that needs a number, like `--count 5`. You used `type=click.INT` like we learned in [Chapter 4: ParamType](04_paramtype.md). What happens if the user types `--count five`?
|
||||
|
||||
If Click didn't handle this specially, the `int("five")` conversion inside Click would fail, raising a standard Python `ValueError`. This might cause your program to stop with a long, confusing Python traceback message that isn't very helpful for the end-user. They might not understand what went wrong or how to fix it.
|
||||
|
||||
Click wants to provide a better experience. When something like this happens, Click catches the internal error and raises one of its own **custom exception types**. These special exceptions tell Click exactly what kind of problem occurred (e.g., bad input, missing argument).
|
||||
|
||||
## Meet the Click Exceptions
|
||||
|
||||
Click has a family of exception classes designed specifically for handling command-line errors. The most important ones inherit from the base class `click.ClickException`. Here are some common ones you'll encounter (or use):
|
||||
|
||||
* `ClickException`: The base for all Click-handled errors.
|
||||
* `UsageError`: A general error indicating the command was used incorrectly (e.g., wrong number of arguments). It usually prints the command's usage instructions.
|
||||
* `BadParameter`: Raised when the value provided for an option or argument is invalid (e.g., "five" for an integer type, or a value not in a `click.Choice`).
|
||||
* `MissingParameter`: Raised when a required option or argument is not provided.
|
||||
* `NoSuchOption`: Raised when the user tries to use an option that doesn't exist (e.g., `--verrbose` instead of `--verbose`).
|
||||
* `FileError`: Raised by `click.File` or `click.Path` if a file can't be opened or accessed correctly.
|
||||
* `Abort`: A special exception you can raise to stop execution immediately (like after a failed `click.confirm`).
|
||||
|
||||
**The Magic:** The really neat part is that Click's main command processing logic is designed to *catch* these specific exceptions. When it catches one, it doesn't just crash. Instead, it:
|
||||
|
||||
1. **Formats a helpful error message:** Often using information from the exception itself (like which parameter was bad).
|
||||
2. **Prints the message** (usually prefixed with "Error:") to the standard error stream (`stderr`).
|
||||
3. **Often shows relevant help text** (like the command's usage synopsis).
|
||||
4. **Exits the application cleanly** with a non-zero exit code (signaling to the system that an error occurred).
|
||||
|
||||
This gives the user clear feedback about what they did wrong and how to potentially fix it, without seeing scary Python tracebacks.
|
||||
|
||||
## Seeing Exceptions in Action (Automatically)
|
||||
|
||||
You've already seen Click exceptions working! Remember our `count_app.py` from [Chapter 4: ParamType](04_paramtype.md)?
|
||||
|
||||
```python
|
||||
# count_app.py (from Chapter 4)
|
||||
import click
|
||||
|
||||
@click.command()
|
||||
@click.option('--count', default=1, type=click.INT, help='Number of times to print.')
|
||||
@click.argument('message')
|
||||
def repeat(count, message):
|
||||
"""Prints MESSAGE the specified number of times."""
|
||||
for _ in range(count):
|
||||
click.echo(message)
|
||||
|
||||
if __name__ == '__main__':
|
||||
repeat()
|
||||
```
|
||||
|
||||
If you run this with invalid input for `--count`:
|
||||
|
||||
```bash
|
||||
$ python count_app.py --count five "Oh no"
|
||||
Usage: count_app.py [OPTIONS] MESSAGE
|
||||
Try 'count_app.py --help' for help.
|
||||
|
||||
Error: Invalid value for '--count': 'five' is not a valid integer.
|
||||
```
|
||||
|
||||
That clear "Error: Invalid value for '--count': 'five' is not a valid integer." message? That's Click catching a `BadParameter` exception (raised internally by `click.INT.convert`) and showing it nicely!
|
||||
|
||||
What if you forget the required `MESSAGE` argument?
|
||||
|
||||
```bash
|
||||
$ python count_app.py --count 3
|
||||
Usage: count_app.py [OPTIONS] MESSAGE
|
||||
Try 'count_app.py --help' for help.
|
||||
|
||||
Error: Missing argument 'MESSAGE'.
|
||||
```
|
||||
|
||||
Again, a clear error message! This time, Click caught a `MissingParameter` exception.
|
||||
|
||||
## Raising Exceptions Yourself: Custom Validation
|
||||
|
||||
Click raises exceptions automatically for many common errors. But sometimes, you have validation logic that's specific to your application. For example, maybe an `--age` option must be positive.
|
||||
|
||||
The standard way to report these custom validation errors is to **raise a `click.BadParameter` exception** yourself, usually from within a callback function.
|
||||
|
||||
Let's add a callback to our `count_app.py` to ensure `count` is positive.
|
||||
|
||||
```python
|
||||
# count_app_validate.py
|
||||
import click
|
||||
|
||||
# 1. Define a validation callback function
|
||||
def validate_count(ctx, param, value):
|
||||
"""Callback to ensure count is positive."""
|
||||
if value <= 0:
|
||||
# 2. Raise BadParameter if validation fails
|
||||
raise click.BadParameter("Count must be a positive number.")
|
||||
# 3. Return the value if it's valid
|
||||
return value
|
||||
|
||||
@click.command()
|
||||
# 4. Attach the callback to the --count option
|
||||
@click.option('--count', default=1, type=click.INT, help='Number of times to print.',
|
||||
callback=validate_count) # <-- Added callback
|
||||
@click.argument('message')
|
||||
def repeat(count, message):
|
||||
"""Prints MESSAGE the specified number of times (must be positive)."""
|
||||
for _ in range(count):
|
||||
click.echo(message)
|
||||
|
||||
if __name__ == '__main__':
|
||||
repeat()
|
||||
```
|
||||
|
||||
Let's break down the changes:
|
||||
|
||||
1. `def validate_count(ctx, param, value):`: We defined a function that takes the [Context](05_context.md), the [Parameter](03_parameter__option___argument_.md) object, and the *already type-converted* value.
|
||||
2. `raise click.BadParameter(...)`: If the `value` (which we know is an `int` thanks to `type=click.INT`) is not positive, we raise `click.BadParameter` with our custom error message.
|
||||
3. `return value`: If the value is valid, the callback **must** return it.
|
||||
4. `callback=validate_count`: We told the `--count` option to use our `validate_count` function after type conversion.
|
||||
|
||||
**Run it with invalid input:**
|
||||
|
||||
```bash
|
||||
$ python count_app_validate.py --count 0 "Zero?"
|
||||
Usage: count_app_validate.py [OPTIONS] MESSAGE
|
||||
Try 'count_app_validate.py --help' for help.
|
||||
|
||||
Error: Invalid value for '--count': Count must be a positive number.
|
||||
|
||||
$ python count_app_validate.py --count -5 "Negative?"
|
||||
Usage: count_app_validate.py [OPTIONS] MESSAGE
|
||||
Try 'count_app_validate.py --help' for help.
|
||||
|
||||
Error: Invalid value for '--count': Count must be a positive number.
|
||||
```
|
||||
|
||||
It works! Our custom validation logic triggered, we raised `click.BadParameter`, and Click caught it, displaying our specific error message cleanly. This is the standard way to integrate your own validation rules into Click's error handling.
|
||||
|
||||
## How Click Handles Exceptions (Under the Hood)
|
||||
|
||||
What exactly happens when a Click exception is raised, either by Click itself or by your code?
|
||||
|
||||
1. **Raise:** An operation fails (like type conversion, parsing finding a missing argument, or your custom callback). A specific `ClickException` subclass (e.g., `BadParameter`, `MissingParameter`) is instantiated and raised.
|
||||
2. **Catch:** Click's main application runner (usually triggered when you call your top-level `cli()` function) has a `try...except ClickException` block around the command execution logic.
|
||||
3. **Show:** When a `ClickException` is caught, the runner calls the exception object's `show()` method.
|
||||
4. **Format & Print:** The `show()` method (defined in `exceptions.py` for each exception type) formats the error message.
|
||||
* `UsageError` (and its subclasses like `BadParameter`, `MissingParameter`, `NoSuchOption`) typically includes the command's usage string (`ctx.get_usage()`) and a hint to try the `--help` option.
|
||||
* `BadParameter` adds context like "Invalid value for 'PARAMETER_NAME':".
|
||||
* `MissingParameter` formats "Missing argument/option 'PARAMETER_NAME'.".
|
||||
* The formatted message is printed to `stderr` using `click.echo()`, respecting color settings from the context.
|
||||
5. **Exit:** After showing the message, Click calls `sys.exit()` with the exception's `exit_code` (usually `1` for general errors, `2` for usage errors). This terminates the program and signals the error status to the calling shell or script.
|
||||
|
||||
Here’s a simplified sequence diagram for the `BadParameter` case when a user provides invalid input that fails type conversion:
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant User
|
||||
participant CLI as YourApp.py
|
||||
participant ClickRuntime
|
||||
participant ParamType as ParamType (e.g., click.INT)
|
||||
participant ClickExceptionHandling
|
||||
|
||||
User->>CLI: python YourApp.py --count five
|
||||
CLI->>ClickRuntime: Starts command execution
|
||||
ClickRuntime->>ParamType: Calls convert(value='five', ...) for '--count'
|
||||
ParamType->>ParamType: Tries int('five'), raises ValueError
|
||||
ParamType->>ClickExceptionHandling: Catches ValueError, calls self.fail(...)
|
||||
ClickExceptionHandling->>ClickExceptionHandling: Raises BadParameter("...'five' is not...")
|
||||
ClickExceptionHandling-->>ClickRuntime: BadParameter propagates up
|
||||
ClickRuntime->>ClickExceptionHandling: Catches BadParameter exception
|
||||
ClickExceptionHandling->>ClickExceptionHandling: Calls exception.show()
|
||||
ClickExceptionHandling->>CLI: Prints formatted "Error: Invalid value..." to stderr
|
||||
ClickExceptionHandling->>CLI: Calls sys.exit(exception.exit_code)
|
||||
CLI-->>User: Shows error message and exits
|
||||
```
|
||||
|
||||
The core exception classes are defined in `click/exceptions.py`. You can see how `ClickException` defines the basic `show` method and `exit_code`, and how subclasses like `UsageError` and `BadParameter` override `format_message` to provide more specific output based on the context (`ctx`) and parameter (`param`) they might hold.
|
||||
|
||||
```python
|
||||
# Simplified structure from click/exceptions.py
|
||||
|
||||
class ClickException(Exception):
|
||||
exit_code = 1
|
||||
|
||||
def __init__(self, message: str) -> None:
|
||||
# ... (stores message, gets color settings) ...
|
||||
self.message = message
|
||||
|
||||
def format_message(self) -> str:
|
||||
return self.message
|
||||
|
||||
def show(self, file=None) -> None:
|
||||
# ... (gets stderr if file is None) ...
|
||||
echo(f"Error: {self.format_message()}", file=file, color=self.show_color)
|
||||
|
||||
class UsageError(ClickException):
|
||||
exit_code = 2
|
||||
|
||||
def __init__(self, message: str, ctx=None) -> None:
|
||||
super().__init__(message)
|
||||
self.ctx = ctx
|
||||
# ...
|
||||
|
||||
def show(self, file=None) -> None:
|
||||
# ... (gets stderr, color) ...
|
||||
hint = ""
|
||||
if self.ctx is not None and self.ctx.command.get_help_option(self.ctx):
|
||||
hint = f"Try '{self.ctx.command_path} {self.ctx.help_option_names[0]}' for help.\n"
|
||||
if self.ctx is not None:
|
||||
echo(f"{self.ctx.get_usage()}\n{hint}", file=file, color=color)
|
||||
# Call the base class's logic to print "Error: ..."
|
||||
echo(f"Error: {self.format_message()}", file=file, color=color)
|
||||
|
||||
class BadParameter(UsageError):
|
||||
def __init__(self, message: str, ctx=None, param=None, param_hint=None) -> None:
|
||||
super().__init__(message, ctx)
|
||||
self.param = param
|
||||
self.param_hint = param_hint
|
||||
|
||||
def format_message(self) -> str:
|
||||
# ... (logic to get parameter name/hint) ...
|
||||
param_hint = self.param.get_error_hint(self.ctx) if self.param else self.param_hint
|
||||
# ...
|
||||
return f"Invalid value for {param_hint}: {self.message}"
|
||||
|
||||
# Other exceptions like MissingParameter, NoSuchOption follow similar patterns
|
||||
```
|
||||
|
||||
By using this structured exception system, Click ensures that user errors are reported consistently and helpfully across any Click application.
|
||||
|
||||
## Conclusion
|
||||
|
||||
Click Exceptions are the standard mechanism for reporting errors related to command usage and user input within Click applications.
|
||||
|
||||
You've learned:
|
||||
|
||||
* Click uses custom exceptions like `UsageError`, `BadParameter`, and `MissingParameter` to signal specific problems.
|
||||
* Click catches these exceptions automatically to display user-friendly error messages, usage hints, and exit cleanly.
|
||||
* You can (and should) raise exceptions like `click.BadParameter` in your own validation callbacks to report custom errors in a standard way.
|
||||
* This system prevents confusing Python tracebacks and provides helpful feedback to the user.
|
||||
|
||||
Understanding and using Click's exception hierarchy is key to building robust and user-friendly command-line interfaces that handle problems gracefully.
|
||||
|
||||
This concludes our journey through the core concepts of Click! We've covered everything from basic [Commands and Groups](01_command___group.md), [Decorators](02_decorators.md), [Parameters](03_parameter__option___argument_.md), and [Types](04_paramtype.md), to managing runtime state with the [Context](05_context.md), creating interactive [Terminal UIs](06_term_ui__terminal_user_interface_.md), and handling errors with [Click Exceptions](07_click_exceptions.md). Armed with this knowledge, you're well-equipped to start building your own powerful and elegant command-line tools with Click!
|
||||
|
||||
---
|
||||
|
||||
Generated by [AI Codebase Knowledge Builder](https://github.com/The-Pocket/Tutorial-Codebase-Knowledge)
|
||||
44
docs/Click/index.md
Normal file
44
docs/Click/index.md
Normal file
@@ -0,0 +1,44 @@
|
||||
# Tutorial: Click
|
||||
|
||||
Click is a Python library that makes creating **command-line interfaces (CLIs)** *easy and fun*.
|
||||
It uses simple Python **decorators** (`@click.command`, `@click.option`, etc.) to turn your functions into CLI commands with options and arguments.
|
||||
Click handles parsing user input, generating help messages, validating data types, and managing the flow between commands, letting you focus on your application's logic.
|
||||
It also provides tools for *terminal interactions* like prompting users and showing progress bars.
|
||||
|
||||
|
||||
**Source Repository:** [https://github.com/pallets/click/tree/main/src/click](https://github.com/pallets/click/tree/main/src/click)
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A0["Context"]
|
||||
A1["Command / Group"]
|
||||
A2["Parameter (Option / Argument)"]
|
||||
A3["ParamType"]
|
||||
A4["Decorators"]
|
||||
A5["Term UI (Terminal User Interface)"]
|
||||
A6["Click Exceptions"]
|
||||
A4 -- "Creates/Configures" --> A1
|
||||
A4 -- "Creates/Configures" --> A2
|
||||
A0 -- "Manages execution of" --> A1
|
||||
A0 -- "Holds parsed values for" --> A2
|
||||
A2 -- "Uses for validation/conversion" --> A3
|
||||
A3 -- "Raises on conversion error" --> A6
|
||||
A1 -- "Uses for user interaction" --> A5
|
||||
A0 -- "Handles/Raises" --> A6
|
||||
A4 -- "Injects via @pass_context" --> A0
|
||||
```
|
||||
|
||||
## Chapters
|
||||
|
||||
1. [Command / Group](01_command___group.md)
|
||||
2. [Decorators](02_decorators.md)
|
||||
3. [Parameter (Option / Argument)](03_parameter__option___argument_.md)
|
||||
4. [ParamType](04_paramtype.md)
|
||||
5. [Context](05_context.md)
|
||||
6. [Term UI (Terminal User Interface)](06_term_ui__terminal_user_interface_.md)
|
||||
7. [Click Exceptions](07_click_exceptions.md)
|
||||
|
||||
|
||||
---
|
||||
|
||||
Generated by [AI Codebase Knowledge Builder](https://github.com/The-Pocket/Tutorial-Codebase-Knowledge)
|
||||
Reference in New Issue
Block a user