Skip to content

Conversation

@sirosen
Copy link
Contributor

@sirosen sirosen commented Sep 25, 2025

In order to ensure that user-defined type_cast_value hooks are never
called with a value of UNSET, it is necessary to intercept UNSET
values and, in the particular cases in which UNSET needs special
handling, preempt any call to type_cast_value().

process_value() is updated to check before calling type_cast_value
and to skip the call entirely if an UNSET value is detected. Passing
a value of None would not have the same effect for click.Option,
which previously had logic which is specific to the value is UNSET
case, and which cannot be reliably detected if a None value is
passed in.

This means that type_cast_value() is no longer guaranteed to be called
during argument processing, but when it is called, it's guaranteed not to
see an UNSET value. New tests explore this only from the perspective of
checking if UNSET is ever passed, not in terms of pinning down when/if
the hook gets called.

resolves #3069


I think this change is worth considering as a fix for #3069, but I'm not
certain that it's good because it seems to trade one kind of incompatibility,
whether or not UNSET gets passed, for a different one: whether or not
type_cast_value() gets called at all.

If type_cast_value() is supposed to never see an UNSET value, and
process_value is "holding an UNSET value, then you might think that all
we need is

type_cast_value(..., value if value is not UNSET else None)

but Option.type_cast_value throws a wrench in that, since it needs to
see the UNSET sentinel to properly determine its behavior.

I chose to take the same approach for handling vis-a-vis multiple and
nargs=-1 in Parameter, on the grounds that it seemed "better aligned" with
what has to happen in Option.

In order to ensure that user-defined `type_cast_value` hooks are never
called with a value of `UNSET`, it is necessary to intercept `UNSET`
values and, in the particular cases in which `UNSET` needs special
handling, preempt any call to `type_cast_value()`.

`process_value()` is updated to check before calling `type_cast_value`
and to skip the call entirely if an `UNSET` value is detected. Passing
a value of `None` would not have the same effect for `click.Option`,
which previously had logic which is specific to the `value is UNSET`
case, and which cannot be reliably detected if a `None` value is
passed in.

This means that `type_cast_value()` is no longer guaranteed to be called
during argument processing, but when it is called, it's guaranteed not to
see an `UNSET` value. New tests explore this only from the perspective of
checking if `UNSET` is ever passed, not in terms of pinning down when/if
the hook gets called.

resolves pallets#3069
@kdeldycke kdeldycke added this to the 8.3.1 milestone Oct 8, 2025
Copy link
Collaborator

@kdeldycke kdeldycke left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK I understand the trade off and I am ok with them given that this PR:

  • pragmaticcaly solve #3069
  • adds dozen of new unit tests over uncovered edge-cases

It is not worse than the current state, as:

  • in-place multiple checks and normalization for tuples already happens a lot (i.e. self.multiple or self.nargs == -1)
  • other in-place checks on UNSET have been added in upcoming 8.3.1 for compatibility reasons

Overall it is a net gain as it will keep the community happy and provide grounds for upcoming refactors in 9.x.

:attr:`type`, :attr:`multiple`, and :attr:`nargs`.
"""
if value in (None, UNSET):
if value is None:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like this as it returns type_cast_value to its original code before my #3030 PR.

@kdeldycke kdeldycke added bug f:parameters feature: input parameter types labels Oct 8, 2025
@kdeldycke kdeldycke changed the title Ensure that 'type_cast_value' never gets 'UNSET' Ensure that type_cast_value never gets UNSET Oct 8, 2025
@kdeldycke
Copy link
Collaborator

Oh and also: I checked this branch against some private code and I could not find any issues.

@kdeldycke
Copy link
Collaborator

Looking in my uncommitted tentative code of refactoring some part of the option parsing, I also identified the type_cast_value of Option to be unnecessary and misplaced. So yes, I am even more convinced that this is the direction to go.

@kdeldycke
Copy link
Collaborator

This PR is ready to be merged upstream.

@Rowlando13 Rowlando13 merged commit 8d4051e into pallets:stable Oct 9, 2025
10 checks passed
@sirosen sirosen deleted the fix-type-cast-sees-unset branch October 9, 2025 04:40
@dotlambda
Copy link

It would be nice if you released version 8.3.1 with this fix.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Oct 30, 2025
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

bug f:parameters feature: input parameter types

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants