Imports
import click
from click import (
Abort,
BadParameter,
ClickException,
UsageError,
argument,
command,
confirm,
echo,
group,
option,
pass_context,
pass_obj,
prompt,
secho,
style,
)
from click.testing import CliRunner
from click.shell_completion import CompletionItem, ShellComplete, add_completion_class
Core Patterns
Single command with options and arguments ✅ Current
import click
@click.command()
@click.argument("name")
@click.option("--times", "-t", type=click.INT, default=1, show_default=True)
@click.option("--loud/--quiet", default=False, help="Toggle uppercase output.")
def hello(name: str, times: int, loud: bool) -> None:
"""Greet NAME a number of TIMES."""
msg = f"Hello, {name}!"
if loud:
msg = msg.upper()
for _ in range(times):
click.echo(msg)
if __name__ == "__main__":
hello()
- Use
@click.command() to define a CLI entry point; add inputs with @click.argument() and @click.option().
- Prefer
click.echo() over print() for consistent terminal behavior; use err=True for stderr.
Command groups and subcommands ✅ Current
import click
@click.group()
def cli() -> None:
"""Top-level command group."""
pass
@cli.command()
@click.option("--path", type=click.Path(dir_okay=False, readable=True), required=True)
def show(path: str) -> None:
"""Print a file to stdout."""
with click.open_file(path, mode="r", encoding="utf-8") as f:
click.echo(f.read(), nl=False)
@cli.command()
@click.argument("words", nargs=-1)
def join(words: tuple[str, ...]) -> None:
"""Join WORDS with spaces."""
click.echo(" ".join(words))
if __name__ == "__main__":
cli()
- Use
@click.group() for multi-command CLIs; register subcommands via @group.command().
- Use
click.Path(...) and click.open_file(...) for validated paths and robust file opening.
Context and object passing (pass_context, pass_obj, make_pass_decorator) ✅ Current
from __future__ import annotations
from dataclasses import dataclass
import click
@dataclass
class AppState:
verbose: bool
pass_state = click.make_pass_decorator(AppState)
@click.group()
@click.option("--verbose/--no-verbose", default=False)
@click.pass_context
def cli(ctx: click.Context, verbose: bool) -> None:
ctx.obj = AppState(verbose=verbose)
@cli.command()
@pass_state
def status(state: AppState) -> None:
click.echo(f"verbose={state.verbose}")
@cli.command()
@click.pass_context
def where(ctx: click.Context) -> None:
# Direct access to context when needed
click.echo(f"command={ctx.command.name}")
if __name__ == "__main__":
cli()
- Use
ctx.obj to store application state; click.make_pass_decorator() provides typed access to that object.
@click.pass_context and @click.pass_obj are for dependency injection across command layers.
Prompts, confirmation, and secure input ✅ Current
import click
@click.command()
@click.option("--username", prompt=True)
@click.password_option("--password", confirmation_prompt=True)
@click.confirmation_option("--confirm", prompt="Proceed with login?")
def login(username: str, password: str, confirm: bool) -> None:
# Never echo passwords; Click handles masking for password options.
if not confirm:
raise click.Abort()
click.echo(f"Logging in as {username} (password length={len(password)})")
if __name__ == "__main__":
login()
click.prompt() / prompt=True collects interactive input; click.password_option() masks input and can confirm.
click.confirmation_option() is a reusable “are you sure?” pattern; raise click.Abort to stop cleanly.
Testing commands with CliRunner ✅ Current
import click
from click.testing import CliRunner
@click.command()
@click.option("--count", type=click.INT, default=1)
def repeat(count: int) -> None:
for i in range(count):
click.echo(f"line {i + 1}")
def main() -> None:
runner = CliRunner()
result = runner.invoke(repeat, ["--count", "3"])
assert result.exit_code == 0
assert "line 3" in result.output
if __name__ == "__main__":
main()
- Use
click.testing.CliRunner.invoke() to run commands without spawning subprocesses.
- Inspect
Result.exit_code, Result.output, and Result.exception for assertions.
Configuration
- Defaults and display
- Use
default=... on @click.option(...).
- Use
show_default=True to show defaults in --help.
- Types and validation
- Built-in types:
click.STRING, click.INT, click.FLOAT, click.BOOL, click.UUID.
- Structured types:
click.Path, click.File, click.Choice, click.IntRange, click.FloatRange, click.DateTime, click.Tuple.
- Environment variables
- Options can read from environment variables using
@click.option(..., envvar="NAME").
- Help and version
click.help_option() and click.version_option() can be used to add standardized --help / --version behavior.
- Embedding vs standalone
Command.main(..., standalone_mode=False) prevents Click from calling sys.exit and swallowing exceptions—preferred when embedding in a larger app.
Pitfalls
Wrong: Calling a Click command like a normal function with argv
import click
@click.command()
@click.option("--count", default=1)
def cmd(count: int) -> None:
click.echo(str(count))
cmd(["--count", "3"]) # WRONG: bypasses Click's CLI parsing
Right: Use .main() (or run under __main__) to parse argv
import click
@click.command()
@click.option("--count", default=1, type=click.INT)
def cmd(count: int) -> None:
click.echo(str(count))
if __name__ == "__main__":
cmd() # parses sys.argv
# Programmatic invocation:
# cmd.main(["--count", "3"], standalone_mode=False)
Wrong: Parameter name mismatch between decorator and function signature
import click
@click.command()
@click.argument("filename")
def show(file_name: str) -> None: # WRONG: Click expects "filename"
click.echo(file_name)
Right: Match the Python argument name to the Click parameter name
import click
@click.command()
@click.argument("filename")
def show(filename: str) -> None:
click.echo(filename)
if __name__ == "__main__":
show()
Wrong: Embedding a CLI but letting Click exit the process
import click
@click.command()
def cmd() -> None:
raise click.UsageError("bad input")
def main() -> None:
cmd.main(["cmd"]) # WRONG for embedding: may call sys.exit
Right: Use standalone_mode=False and handle ClickException
import click
@click.command()
def cmd() -> None:
raise click.UsageError("bad input")
def main() -> None:
try:
cmd.main(["cmd"], standalone_mode=False)
except click.ClickException as e:
# Your app decides how to report errors.
e.show()
raise
if __name__ == "__main__":
main()
Wrong: Callback depending on internal “missing” sentinel behavior (8.3.x sensitive)
import click
@click.command()
@click.option("--a", callback=lambda ctx, param, value: ctx.params.get("b"))
@click.option("--b")
def cmd(a: str | None, b: str | None) -> None:
click.echo(f"a={a!r} b={b!r}")
Right: Treat missing values as None/falsey; avoid relying on internal sentinel states
import click
@click.command()
@click.option("--b")
@click.option("--a", callback=lambda ctx, param, value: (ctx.params.get("b") or value))
def cmd(a: str | None, b: str | None) -> None:
click.echo(f"a={a!r} b={b!r}")
if __name__ == "__main__":
cmd()
References
Migration from v8.1.x
Python version support change (8.2.0) ❌ Hard Deprecation (runtime constraint)
- Change: Click 8.2.0+ requires Python 3.10+ (3.7–3.9 dropped).
- Migration guidance: upgrade runtime to Python 3.10+ or pin Click
<8.2.0.
click.__version__ deprecated (8.2.0, ⚠️ hard deprecation in 8.3.1) ⚠️
click.BaseCommand deprecated (8.2.0, ⚠️ hard deprecation in 8.3.1) ⚠️
- Deprecated since: 8.2.0 (will be removed in 9.0)
- Still works: Yes (deprecated)
- Modern alternative: subclass
click.Command (or click.Group for multi-command).
- Migration guidance: update type checks and subclassing targets to
click.Command.
click.MultiCommand deprecated (8.2.0, ⚠️ hard deprecation in 8.3.1) ⚠️
- Deprecated since: 8.2.0 (will be removed in 9.0)
- Still works: Yes (deprecated)
- Modern alternative: use
click.Group.
- Migration guidance: prefer
Group for custom multi-command behavior.
Flag option handling rework (8.3.0) ✅ Current behavior change
- Change: flag option defaults are preserved and passed as-is more consistently; special-case compatibility for
default=True.
- Migration guidance: review boolean flags and explicitly set
default, flag_value, and type to match intended runtime values.
Sentinel/UNSET propagation fixes (8.3.1) ✅ Current behavior fix
- Change: fixes around internal sentinel values during parsing and callbacks.
- Migration guidance: callbacks should not depend on internal missing-value sentinels; treat missing values as
None/falsey and validate explicitly.
API Reference
click.command() - Decorator to define a single command; supports help, no_args_is_help, etc.
click.group() - Decorator to define a command group for subcommands.
click.option() - Add an option; key params: type, default, required, multiple, envvar, callback, is_flag, flag_value.
click.argument() - Add a positional argument; key params: nargs, type, required.
click.echo() - Write text safely to stdout/stderr; key params: err, nl, color.
click.secho() - echo() with styling; key params: fg, bg, bold, underline, err.
click.style() / click.unstyle() - Apply/remove ANSI styling to strings.
click.prompt() - Interactive prompt for input; key params: default, type, hide_input, confirmation_prompt.
click.confirm() - Yes/no prompt; key params: default, abort.
click.password_option() - Option decorator for masked password input; supports confirmation.
click.version_option() - Add --version option; key params: version, prog_name, message.
click.help_option() - Add --help option; key params: help, hidden.
click.open_file() - Open files with Click-friendly behavior; key params: mode, encoding, errors, atomic.
click.Path / click.File - Parameter types for paths/files with validation and automatic opening (for File).
click.Context / click.get_current_context() - Runtime context; access params, obj, command, and manage resources via Context.with_resource.
click.Command.main() - CLI entry runner; key params: args, prog_name, standalone_mode.
click.testing.CliRunner.invoke() - Run a command in tests; key params: args, input, env, catch_exceptions.
- ⚠️
click.BaseCommand (deprecated; will be removed in v9.0) — use click.Command.
- ⚠️
click.MultiCommand (deprecated; will be removed in v9.0) — use click.Group.
- ⚠️
click.OptionParser (deprecated; will be removed in v9.0).
- ⚠️
click.__version__ (deprecated; will be removed in v9.1) — use importlib.metadata.version("click").
Migration
From Click v8.2.x to v8.3.1:
- Flag option default handling: In v8.3.0+, the
default value for flag options (is_flag=True) is now preserved and passed through as-is to your callback/functions. For legacy code, review your usage of default and flag_value on flag options. If you relied on older transformations, update your logic and tests to expect the new behavior.
- Deprecations (hard):
BaseCommand, MultiCommand, OptionParser, and __version__ are now hard deprecated and will be removed in Click 9.x. Update code to use Command, Group, and importlib.metadata.version("click") instead.
- Python compatibility: You must use Python 3.10+ for Click 8.2.0 and newer.
- Sentinel/UNSET propagation (callbacks): If you use parameter callbacks, do not rely on Click's internal missing-value sentinels. Always treat missing values as
None or another explicit value.
See Click's changelog for full migration details.
Security note:
All included patterns are safe for use by AI agents within the user's project directory. No destructive, exfiltrative, or privilege-modifying actions are present or permitted.
1---2name: click-23description: A Python library for building command line interfaces with composable commands, options, and arguments.4license: BSD-3-Clause5---6
7## Imports
8
9```python
10import click
11from click import (
12 Abort,
13 BadParameter,
14 ClickException,
15 UsageError,
16 argument,
17 command,
18 confirm,
19 echo,
20 group,
21 option,
22 pass_context,
23 pass_obj,
24 prompt,
25 secho,
26 style,
27)
28from click.testing import CliRunner
29from click.shell_completion import CompletionItem, ShellComplete, add_completion_class
30```
31
32## Core Patterns
33
34### Single command with options and arguments ✅ Current
35```python
36import click
37
38
39@click.command()
40@click.argument("name")
41@click.option("--times", "-t", type=click.INT, default=1, show_default=True)
42@click.option("--loud/--quiet", default=False, help="Toggle uppercase output.")
43def hello(name: str, times: int, loud: bool) -> None:
44 """Greet NAME a number of TIMES."""
45 msg = f"Hello, {name}!"
46 if loud:
47 msg = msg.upper()
48
49 for _ in range(times):
50 click.echo(msg)
51
52
53if __name__ == "__main__":
54 hello()
55```
56* Use `@click.command()` to define a CLI entry point; add inputs with `@click.argument()` and `@click.option()`.
57* Prefer `click.echo()` over `print()` for consistent terminal behavior; use `err=True` for stderr.
58
59### Command groups and subcommands ✅ Current
60```python
61import click
62
63
64@click.group()
65def cli() -> None:
66 """Top-level command group."""
67 pass
68
69
70@cli.command()
71@click.option("--path", type=click.Path(dir_okay=False, readable=True), required=True)
72def show(path: str) -> None:
73 """Print a file to stdout."""
74 with click.open_file(path, mode="r", encoding="utf-8") as f:
75 click.echo(f.read(), nl=False)
76
77
78@cli.command()
79@click.argument("words", nargs=-1)
80def join(words: tuple[str, ...]) -> None:
81 """Join WORDS with spaces."""
82 click.echo(" ".join(words))
83
84
85if __name__ == "__main__":
86 cli()
87```
88* Use `@click.group()` for multi-command CLIs; register subcommands via `@group.command()`.
89* Use `click.Path(...)` and `click.open_file(...)` for validated paths and robust file opening.
90
91### Context and object passing (`pass_context`, `pass_obj`, `make_pass_decorator`) ✅ Current
92```python
93from __future__ import annotations
94
95from dataclasses import dataclass
96
97import click
98
99
100@dataclass
101class AppState:
102 verbose: bool
103
104
105pass_state = click.make_pass_decorator(AppState)
106
107
108@click.group()
109@click.option("--verbose/--no-verbose", default=False)
110@click.pass_context
111def cli(ctx: click.Context, verbose: bool) -> None:
112 ctx.obj = AppState(verbose=verbose)
113
114
115@cli.command()
116@pass_state
117def status(state: AppState) -> None:
118 click.echo(f"verbose={state.verbose}")
119
120
121@cli.command()
122@click.pass_context
123def where(ctx: click.Context) -> None:
124 # Direct access to context when needed
125 click.echo(f"command={ctx.command.name}")
126
127
128if __name__ == "__main__":
129 cli()
130```
131* Use `ctx.obj` to store application state; `click.make_pass_decorator()` provides typed access to that object.
132* `@click.pass_context` and `@click.pass_obj` are for dependency injection across command layers.
133
134### Prompts, confirmation, and secure input ✅ Current
135```python
136import click
137
138
139@click.command()
140@click.option("--username", prompt=True)
141@click.password_option("--password", confirmation_prompt=True)
142@click.confirmation_option("--confirm", prompt="Proceed with login?")
143def login(username: str, password: str, confirm: bool) -> None:
144 # Never echo passwords; Click handles masking for password options.
145 if not confirm:
146 raise click.Abort()
147 click.echo(f"Logging in as {username} (password length={len(password)})")
148
149
150if __name__ == "__main__":
151 login()
152```
153* `click.prompt()` / `prompt=True` collects interactive input; `click.password_option()` masks input and can confirm.
154* `click.confirmation_option()` is a reusable “are you sure?” pattern; raise `click.Abort` to stop cleanly.
155
156### Testing commands with `CliRunner` ✅ Current
157```python
158import click
159from click.testing import CliRunner
160
161
162@click.command()
163@click.option("--count", type=click.INT, default=1)
164def repeat(count: int) -> None:
165 for i in range(count):
166 click.echo(f"line {i + 1}")
167
168
169def main() -> None:
170 runner = CliRunner()
171 result = runner.invoke(repeat, ["--count", "3"])
172 assert result.exit_code == 0
173 assert "line 3" in result.output
174
175
176if __name__ == "__main__":
177 main()
178```
179* Use `click.testing.CliRunner.invoke()` to run commands without spawning subprocesses.
180* Inspect `Result.exit_code`, `Result.output`, and `Result.exception` for assertions.
181
182## Configuration
183
184- **Defaults and display**
185 - Use `default=...` on `@click.option(...)`.
186 - Use `show_default=True` to show defaults in `--help`.
187- **Types and validation**
188 - Built-in types: `click.STRING`, `click.INT`, `click.FLOAT`, `click.BOOL`, `click.UUID`.
189 - Structured types: `click.Path`, `click.File`, `click.Choice`, `click.IntRange`, `click.FloatRange`, `click.DateTime`, `click.Tuple`.
190- **Environment variables**
191 - Options can read from environment variables using `@click.option(..., envvar="NAME")`.
192- **Help and version**
193 - `click.help_option()` and `click.version_option()` can be used to add standardized `--help` / `--version` behavior.
194- **Embedding vs standalone**
195 - `Command.main(..., standalone_mode=False)` prevents Click from calling `sys.exit` and swallowing exceptions—preferred when embedding in a larger app.
196
197## Pitfalls
198
199### Wrong: Calling a Click command like a normal function with argv
200```python
201import click
202
203
204@click.command()
205@click.option("--count", default=1)
206def cmd(count: int) -> None:
207 click.echo(str(count))
208
209
210cmd(["--count", "3"]) # WRONG: bypasses Click's CLI parsing
211```
212
213### Right: Use `.main()` (or run under `__main__`) to parse argv
214```python
215import click
216
217
218@click.command()
219@click.option("--count", default=1, type=click.INT)
220def cmd(count: int) -> None:
221 click.echo(str(count))
222
223
224if __name__ == "__main__":
225 cmd() # parses sys.argv
226
227# Programmatic invocation:
228# cmd.main(["--count", "3"], standalone_mode=False)
229```
230
231### Wrong: Parameter name mismatch between decorator and function signature
232```python
233import click
234
235
236@click.command()
237@click.argument("filename")
238def show(file_name: str) -> None: # WRONG: Click expects "filename"
239 click.echo(file_name)
240```
241
242### Right: Match the Python argument name to the Click parameter name
243```python
244import click
245
246
247@click.command()
248@click.argument("filename")
249def show(filename: str) -> None:
250 click.echo(filename)
251
252
253if __name__ == "__main__":
254 show()
255```
256
257### Wrong: Embedding a CLI but letting Click exit the process
258```python
259import click
260
261
262@click.command()
263def cmd() -> None:
264 raise click.UsageError("bad input")
265
266
267def main() -> None:
268 cmd.main(["cmd"]) # WRONG for embedding: may call sys.exit
269```
270
271### Right: Use `standalone_mode=False` and handle `ClickException`
272```python
273import click
274
275
276@click.command()
277def cmd() -> None:
278 raise click.UsageError("bad input")
279
280
281def main() -> None:
282 try:
283 cmd.main(["cmd"], standalone_mode=False)
284 except click.ClickException as e:
285 # Your app decides how to report errors.
286 e.show()
287 raise
288
289
290if __name__ == "__main__":
291 main()
292```
293
294### Wrong: Callback depending on internal “missing” sentinel behavior (8.3.x sensitive)
295```python
296import click
297
298
299@click.command()
300@click.option("--a", callback=lambda ctx, param, value: ctx.params.get("b"))
301@click.option("--b")
302def cmd(a: str | None, b: str | None) -> None:
303 click.echo(f"a={a!r} b={b!r}")
304```
305
306### Right: Treat missing values as `None`/falsey; avoid relying on internal sentinel states
307```python
308import click
309
310
311@click.command()
312@click.option("--b")
313@click.option("--a", callback=lambda ctx, param, value: (ctx.params.get("b") or value))
314def cmd(a: str | None, b: str | None) -> None:
315 click.echo(f"a={a!r} b={b!r}")
316
317
318if __name__ == "__main__":
319 cmd()
320```
321
322## References
323
324- [Donate](https://palletsprojects.com/donate)
325- [Documentation](https://click.palletsprojects.com/)
326- [Changes](https://click.palletsprojects.com/page/changes/)
327- [Source](https://github.com/pallets/click/)
328- [Chat](https://discord.gg/pallets)
329
330## Migration from v8.1.x
331
332- **Python version support change (8.2.0)** ❌ Hard Deprecation (runtime constraint)
333 - **Change**: Click 8.2.0+ requires Python 3.10+ (3.7–3.9 dropped).
334 - **Migration guidance**: upgrade runtime to Python 3.10+ or pin Click `<8.2.0`.
335
336- **`click.__version__` deprecated (8.2.0, ⚠️ hard deprecation in 8.3.1)** ⚠️
337 - **Deprecated since**: 8.2.0 (hard deprecation/removal in 9.1)
338 - **Still works**: Yes (deprecated)
339 - **Modern alternative**:
340 ```python
341 import importlib.metadata
342
343 version = importlib.metadata.version("click")
344 print(version)
345 ```
346 - **Migration guidance**: stop reading `click.__version__`; use `importlib.metadata.version("click")` or feature detection.
347
348- **`click.BaseCommand` deprecated (8.2.0, ⚠️ hard deprecation in 8.3.1)** ⚠️
349 - **Deprecated since**: 8.2.0 (will be removed in 9.0)
350 - **Still works**: Yes (deprecated)
351 - **Modern alternative**: subclass `click.Command` (or `click.Group` for multi-command).
352 - **Migration guidance**: update type checks and subclassing targets to `click.Command`.
353
354- **`click.MultiCommand` deprecated (8.2.0, ⚠️ hard deprecation in 8.3.1)** ⚠️
355 - **Deprecated since**: 8.2.0 (will be removed in 9.0)
356 - **Still works**: Yes (deprecated)
357 - **Modern alternative**: use `click.Group`.
358 - **Migration guidance**: prefer `Group` for custom multi-command behavior.
359
360- **Flag option handling rework (8.3.0)** ✅ Current behavior change
361 - **Change**: flag option defaults are preserved and passed as-is more consistently; special-case compatibility for `default=True`.
362 - **Migration guidance**: review boolean flags and explicitly set `default`, `flag_value`, and `type` to match intended runtime values.
363
364- **Sentinel/UNSET propagation fixes (8.3.1)** ✅ Current behavior fix
365 - **Change**: fixes around internal sentinel values during parsing and callbacks.
366 - **Migration guidance**: callbacks should not depend on internal missing-value sentinels; treat missing values as `None`/falsey and validate explicitly.
367
368## API Reference
369
370- **`click.command()`** - Decorator to define a single command; supports `help`, `no_args_is_help`, etc.
371- **`click.group()`** - Decorator to define a command group for subcommands.
372- **`click.option()`** - Add an option; key params: `type`, `default`, `required`, `multiple`, `envvar`, `callback`, `is_flag`, `flag_value`.
373- **`click.argument()`** - Add a positional argument; key params: `nargs`, `type`, `required`.
374- **`click.echo()`** - Write text safely to stdout/stderr; key params: `err`, `nl`, `color`.
375- **`click.secho()`** - `echo()` with styling; key params: `fg`, `bg`, `bold`, `underline`, `err`.
376- **`click.style()` / `click.unstyle()`** - Apply/remove ANSI styling to strings.
377- **`click.prompt()`** - Interactive prompt for input; key params: `default`, `type`, `hide_input`, `confirmation_prompt`.
378- **`click.confirm()`** - Yes/no prompt; key params: `default`, `abort`.
379- **`click.password_option()`** - Option decorator for masked password input; supports confirmation.
380- **`click.version_option()`** - Add `--version` option; key params: `version`, `prog_name`, `message`.
381- **`click.help_option()`** - Add `--help` option; key params: `help`, `hidden`.
382- **`click.open_file()`** - Open files with Click-friendly behavior; key params: `mode`, `encoding`, `errors`, `atomic`.
383- **`click.Path` / `click.File`** - Parameter types for paths/files with validation and automatic opening (for `File`).
384- **`click.Context` / `click.get_current_context()`** - Runtime context; access params, obj, command, and manage resources via `Context.with_resource`.
385- **`click.Command.main()`** - CLI entry runner; key params: `args`, `prog_name`, `standalone_mode`.
386- **`click.testing.CliRunner.invoke()`** - Run a command in tests; key params: `args`, `input`, `env`, `catch_exceptions`.
387- ⚠️ **`click.BaseCommand`** (deprecated; will be removed in v9.0) — use `click.Command`.
388- ⚠️ **`click.MultiCommand`** (deprecated; will be removed in v9.0) — use `click.Group`.
389- ⚠️ **`click.OptionParser`** (deprecated; will be removed in v9.0).
390- ⚠️ **`click.__version__`** (deprecated; will be removed in v9.1) — use `importlib.metadata.version("click")`.
391
392## Migration
393
394**From Click v8.2.x to v8.3.1:**
395
396- **Flag option default handling**: In v8.3.0+, the `default` value for flag options (`is_flag=True`) is now preserved and passed through as-is to your callback/functions. For legacy code, review your usage of `default` and `flag_value` on flag options. If you relied on older transformations, update your logic and tests to expect the new behavior.
397- **Deprecations (hard)**: `BaseCommand`, `MultiCommand`, `OptionParser`, and `__version__` are now _hard_ deprecated and will be removed in Click 9.x. Update code to use `Command`, `Group`, and `importlib.metadata.version("click")` instead.
398- **Python compatibility**: You must use Python 3.10+ for Click 8.2.0 and newer.
399- **Sentinel/UNSET propagation (callbacks)**: If you use parameter callbacks, do not rely on Click's internal missing-value sentinels. Always treat missing values as `None` or another explicit value.
400
401See [Click's changelog](https://click.palletsprojects.com/page/changes/) for full migration details.
402
403---
404
405**Security note:**
406All included patterns are safe for use by AI agents within the user's project directory. No destructive, exfiltrative, or privilege-modifying actions are present or permitted.