From cea6c75e1c292b9b98508ff413366b65a913fc24 Mon Sep 17 00:00:00 2001 From: jbranchaud Date: Sun, 24 May 2026 17:35:11 -0500 Subject: [PATCH] Add Check Precondition Before Click Arg Parsing as a Python TIL --- README.md | 3 +- ...k-precondition-before-click-arg-parsing.md | 53 +++++++++++++++++++ 2 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 python/check-precondition-before-click-arg-parsing.md diff --git a/README.md b/README.md index 1579f6f..629731b 100644 --- a/README.md +++ b/README.md @@ -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). -_1796 TILs and counting..._ +_1797 TILs and counting..._ See some of the other learning resources I work on: @@ -1061,6 +1061,7 @@ If you've learned something here, support my efforts writing daily TILs by - [Avoid Modification With Frozen Dataclass](python/avoid-modification-with-frozen-dataclass.md) - [Break Debugger On First Line Of Program](python/break-debugger-on-first-line-of-program.md) - [Check If Package Is Installed With Pip](python/check-if-package-is-installed-with-pip.md) +- [Check Precondition Before Click Arg Parsing](python/check-precondition-before-click-arg-parsing.md) - [Control Passing Of Time In Tests](python/control-passing-of-time-in-tests.md) - [Create A Dummy DataFrame In Pandas](python/create-a-dummy-dataframe-in-pandas.md) - [Create A Range Of Descending Values](python/create-a-range-of-descending-values.md) diff --git a/python/check-precondition-before-click-arg-parsing.md b/python/check-precondition-before-click-arg-parsing.md new file mode 100644 index 0000000..544c423 --- /dev/null +++ b/python/check-precondition-before-click-arg-parsing.md @@ -0,0 +1,53 @@ +# Check Precondition Before Click Arg Parsing + +When setting up various [Click](https://click.palletsprojects.com/en/stable/) +subcommands with options, I ran into an issue with the order of some validation +checks. I was putting the same precondition validation logic at the beginning of +several subcommands. I was also putting callback validations on specific options +to those subcommands. Ideally the option validations could rely on those +precondition validations. However, the option callbacks run before anything in +the body of the subcommands. + +The solution was to move those preconditions out of the subcommand body +(simplifying the subcommand) and into a `click.Command` subclass. + +To demonstrate that, I'll first show the `click.Command` subclass: + +```python +class RequireActiveSessionCommand(click.Command): + def parse_args(self, ctx, args): + if ctx.obj.active_session is None: + msg = "No active session being tracked. Start a session first." + raise click.UsageError(msg) + + return super().parse_args(ctx, args) +``` + +The only thing this subclass overrides is `parse_args` where it gets ahead of +the standard arg parsing logic to first check the precondition. In this case, I +check that there is an active session. If there isn't, then I can raise a +`click.UsageError`. Otherwise, it delegates back to the super-class +implementation of `parse_args`. + +This subclass then gets used for the commands that need to enforce this +precondition. Two prime examples of that are the `stop` and `cancel` subcommands. + +```python +@cli.command(cls=RequireActiveSessionCommand) +@click.option("--at", help='Hours previous to end the timer, e.g. "2 hours ago"', callback=validate_stop_at) +@pass_cli +def stop(cli_ctx: CliContext, at: datetime) -> None: + # ... implementation omitted + +@cli.command(cls=RequireActiveSessionCommand) +@pass_cli +def cancel(cli_ctx: CliContext): + # ... implementation omitted +``` + +Other subcommands, like `start` and `status` that don't need to enforce this +precondition use the `@cli.command()` decorator without passing in a custom +subclass. + +This example is pulled directly from [this commit](https://github.com/jbranchaud/py-vmt/commit/505109b7a4013e05f085cded666c6b1ac7c3c250) +of my [`py-vmt` time tracker tool](https://github.com/jbranchaud/py-vmt).