Skip to content

✨ Use Pydantic for parameter validation - #1831

Open
svlandeg wants to merge 144 commits into
fastapi:masterfrom
svlandeg:feat/pydantic
Open

✨ Use Pydantic for parameter validation#1831
svlandeg wants to merge 144 commits into
fastapi:masterfrom
svlandeg:feat/pydantic

Conversation

@svlandeg

@svlandeg svlandeg commented Jun 10, 2026

Copy link
Copy Markdown
Member

Meta: marked as a "feature", but definitely also "breaking" !

Description

Make Pydantic a required dependency and rely on it for parameter validation. Picked >=2.5.3 which is the current pin for github-actions.

Click's ParamType hierarchy is now replaced by a three-layer design:

  • TypeDescriptor objects store static facts about a parameter's type (in the new module coercion.py)
  • Pydantic TypeAdapter objects are defined in the new module adapters.py
  • RuntimeParam subclasses own coercion (also defined in coercion.py)

Further type-specific functionality is bundled in the new module param_types.py.

How to review this

  • Probably read all of the below first 👇
  • Note that _click/types.py and typer/_types.py are deleted
  • First look at typer/param_types.py, then typer/adapters.py, then typer/coercion.py
  • Look at the changes in typer/core.py
  • Check all other changes in typer/*.py
  • Check all changes in typer/_click/*.py
  • Check all changes in the test suite & documentation

Extended/improved functionality

  • More date time formats are now supported by default, instead of just the three [%Y-%m-%d|%Y-%m-%dT%H:%M:%S|%Y-%m-%d %H:%M:%S]. Unix timestamps as seconds or milliseconds since the Unix epoch are now also supported.
  • Started moving towards a TyperParameter, not quite finished yet with some ugly imports from within _click but we'll deal with those in follow-up work

Tests with same behaviour on master

  • Added test_bool_convert_valid to ensure that the "bool" conversion has remained the same for all kinds of inputs.
  • Rewrote several tests to use Typer public API instead of internals
  • test_path_resolves, cf point 1) in the Fable review 👇

Breaking changes

(more or less in order of breakingness from most to least)

  • Removed support for click_type and open bounds through min_open and max_open. This greatly simplifies the code base while still providing an alternative by setting parser instead.
  • A list of choices is now shown as <list[Eggs|Bacon|Cheese]> (before there was no visual distinction between a single choice, or a list of them).
  • When not providing a default for DateTime, we just show "<datetime>". Before, it would show the (only) 3 options, but those are not exhaustive anymore. When the user provides actual formats, those are shown (as before).
  • Validation error messages have changed from Click's to Pydantic's phrasing. This mostly requires updating test suites and otherwise shouldn't impact users too badly.
  • Removed repr functionality of param.type. This wasn't really used except for the tests that were recently added in ➖ Vendor Click and streamline Typer's functionality and code base #1774.
  • A parameter typed as Any does NOT raise RuntimeError: Type not yet supported anymore, but instead just falls through and gets a generic TypeAdapter(annotation)

Note that as before, nothing in the module typer/_click (a remnant of the recent vendoring) should be used directly by users and all of it is subject to change in the near future. Any changes in those modules are not considered to be "breaking".

Bug fixes (also breaking bwd compat)

  • def main(age: int = typer.Option(15.3)) will now throw a validation error by Pydantic instead of int(15.3) converting it to 15. I consider this a bug fix instead of a regression, and have added a test test_int_rejects_float_default for it.
  • Tuples with types that aren't allowed to be None (e.g. tuple[str, int, bool]), will fail validation as soon as one of those elements is None. Our documented example of setting that tuple's default to (None, None, None) is not valid anymore, it should be set to None instead. cf. change to ‎docs_src/multiple_values/options_with_multiple_values/tutorial001_an_py310.py.
  • Types are now inferred from the default if the parameter is untyped. Fixes Default value of `False` cast to `'False'` and is thus True #942, cf. new test test_default_infers_param_type.
  • With Rich disabled, types were always shown for options, but for arguments only when they were "informative" like a tuple or datetime. This PR makes everything internally consistent, and similar to how it looks with Rich enabled, by always showing the types also for arguments.

Decision points

  • _parse_cli_bool was created to ensure we still parse "" as False and to strip whitespace from something like " True ". This is just to mimic old Click behaviour. If we don't do this preprocessing (cleaner code base), the empty string and the non-stripped strings won't pass Pydantic validation and it would be slightly breaking.
  • How to display the datetime type if no formats are provided by the user (cf ☝️ "Breaking changes")
  • Should we allow an int or float as datetime input, to present Unix timestamps as seconds or milliseconds since the Unix epoch? Cf also Fable's concern in point 4 of its review below 👇
  • Should we try to do less with Pydantic to avoid runtime increase? (cf point 7 in Fable's review)

AI Disclaimer

Cursor was used as a micro-managed junior. Every edit was reviewed & understood by me.

TODO

  • Mention Pydantic dependency in the docs
  • Update the tutorial, focus on (changed) error messages
  • Fix coverage
  • Merge in the fix from 🏷️ Make Parameter.name typed as str instead of str | None #1878
  • I added tests for the change.
  • Coverage stays at 100%.
  • Metavar printing that came up in this PR has already been fixed in tests & documentation in 💥 Update metavar printing #1863.
  • Self review with Fable, after which: "This v2 is a substantial improvement — most of what we discussed landed, and several of the fixes are better than what I suggested." 😎

Follow-up work

  • Continue cleaning up code in typer/_click
  • Expand code/docs/tests to support many more data types (should be straightforward?!)
  • Test/document Pydantic models for re-usable CLI Options

@svlandeg svlandeg added the feature New feature, enhancement or request label Jun 10, 2026
("No", False),
],
)
def test_bool_convert_valid(cli_value: str, expected: bool) -> None:

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

This test currently mimics master behaviour 100%.

@svlandeg svlandeg self-assigned this Jun 10, 2026
@github-actions

github-actions Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

📝 Docs preview

Last commit be8bf40 at: https://6eff138d.typertiangolo.pages.dev

Modified Pages

@svlandeg
svlandeg marked this pull request as ready for review August 4, 2026 18:12
Comment thread typer/display.py
@@ -0,0 +1,22 @@
from typing import Any

@svlandeg svlandeg Aug 4, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

This module ended up being relatively small, we could also fold it in somewhere else.

@svlandeg svlandeg removed their assignment Aug 4, 2026
@github-actions github-actions Bot added the conflicts Automatically generated when a PR has a merge conflict label Aug 28, 2026
@github-actions

Copy link
Copy Markdown
Contributor

This pull request has a merge conflict that needs to be resolved.

@svlandeg svlandeg self-assigned this Aug 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

conflicts Automatically generated when a PR has a merge conflict feature New feature, enhancement or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants