mirror of
https://github.com/jbranchaud/til
synced 2026-07-03 08:08:24 +00:00
Add Validate Click Option With Callback as a Python TIL
This commit is contained in:
@@ -10,7 +10,7 @@ working across different projects via [VisualMode](https://www.visualmode.dev/).
|
|||||||
|
|
||||||
For a steady stream of TILs, [sign up for my newsletter](https://visualmode.kit.com/newsletter).
|
For a steady stream of TILs, [sign up for my newsletter](https://visualmode.kit.com/newsletter).
|
||||||
|
|
||||||
_1790 TILs and counting..._
|
_1791 TILs and counting..._
|
||||||
|
|
||||||
See some of the other learning resources I work on:
|
See some of the other learning resources I work on:
|
||||||
|
|
||||||
@@ -1085,6 +1085,7 @@ If you've learned something here, support my efforts writing daily TILs by
|
|||||||
- [Use pipx To Install End User Apps](python/use-pipx-to-install-end-user-apps.md)
|
- [Use pipx To Install End User Apps](python/use-pipx-to-install-end-user-apps.md)
|
||||||
- [Use `__post_init__` For `dataclass` Validations](python/use-post-init-for-dataclass-validations.md)
|
- [Use `__post_init__` For `dataclass` Validations](python/use-post-init-for-dataclass-validations.md)
|
||||||
- [Use Verbose Flag To Get More Diff](python/use-verbose-flag-to-get-more-diff.md)
|
- [Use Verbose Flag To Get More Diff](python/use-verbose-flag-to-get-more-diff.md)
|
||||||
|
- [Validate Click Option With Callback](python/validate-click-option-with-callback.md)
|
||||||
|
|
||||||
### Rails
|
### Rails
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,64 @@
|
|||||||
|
# Validate Click Option With Callback
|
||||||
|
|
||||||
|
I have a [click](https://click.palletsprojects.com/en/stable/) subcommand in my
|
||||||
|
[`py-vmt` project](https://github.com/jbranchaud/py-vmt) that includes an
|
||||||
|
`option` specified with the `--at` flag. This is what it originally looked like:
|
||||||
|
|
||||||
|
```python
|
||||||
|
# define `start` subcommand
|
||||||
|
@cli.command()
|
||||||
|
@click.argument("project-name")
|
||||||
|
@click.option("--at", help='Relative time in past to start the time, e.g. "2 hours ago", "33 minutes ago"')
|
||||||
|
@pass_cli
|
||||||
|
def start(cli_ctx: CliContext, project_name: str, at: str | None) -> None:
|
||||||
|
# ...
|
||||||
|
```
|
||||||
|
|
||||||
|
The value of `at` needs to be in the past. I need a way validate that it is or
|
||||||
|
otherwise bail early with a useful error message. The optional
|
||||||
|
[`callback`](https://click.palletsprojects.com/en/stable/advanced/#callbacks-for-validation)
|
||||||
|
to `@click.option` plus `click.BadParameter` are a good way to handle that.
|
||||||
|
|
||||||
|
First, I define a callback handler that does the validation. I even take it a
|
||||||
|
step further and have it return the transformed value (`datetime`) that the
|
||||||
|
subcommand logic will need.
|
||||||
|
|
||||||
|
```python
|
||||||
|
def validate_past_time(_ctx, _param, value: str | None) -> datetime:
|
||||||
|
now = datetime.now(timezone.utc)
|
||||||
|
|
||||||
|
if value == None:
|
||||||
|
return now
|
||||||
|
|
||||||
|
start_at = time_helpers.parse_to_datetime(value)
|
||||||
|
|
||||||
|
if start_time == None or start_at > now:
|
||||||
|
raise click.BadParameter("must be a relative time in the past")
|
||||||
|
|
||||||
|
return start_at
|
||||||
|
```
|
||||||
|
|
||||||
|
I ignore the first two arguments because I only need to work with `value`. Value
|
||||||
|
might be something like `"33 minutes ago"` and I attempt to transform that with
|
||||||
|
`dateparser` into a `datetime` instance. If it can't be parsed or it isn't in
|
||||||
|
the past, then I raise `click.BadParameter` which presents the user with useful
|
||||||
|
usage details.
|
||||||
|
|
||||||
|
This callback can then be incorporated into the subcommand like so:
|
||||||
|
|
||||||
|
```python
|
||||||
|
# define `start` subcommand
|
||||||
|
@cli.command()
|
||||||
|
@click.argument("project-name")
|
||||||
|
@click.option(
|
||||||
|
"--at",
|
||||||
|
help='Relative time in past to start the time, e.g. "2 hours ago", "33 minutes ago"',
|
||||||
|
callback=validate_past_time
|
||||||
|
)
|
||||||
|
@pass_cli
|
||||||
|
def start(cli_ctx: CliContext, project_name: str, at: datetime) -> None:
|
||||||
|
# ...
|
||||||
|
```
|
||||||
|
|
||||||
|
Now I can expect the incoming `at` option to be a `datetime` which helps
|
||||||
|
simplify several lines of logic in the `start` implementation.
|
||||||
Reference in New Issue
Block a user