Preserve Pydantic Validators (Annotated, @field_validator, @model_validator) + Enable Boolean Field Support
#13
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Summary
This PR fixes critical limitations in
pydantic-typerthat prevented proper handling of Pydantic validators and boolean fields. This fix enables full support for:BeforeValidator,AfterValidator,PlainValidator,WrapValidator@field_validator,@model_validatorHttpUrl,EmailStr,IPv4Address,SecretStr, etc.--flag Falseinstead of requiring--no-flagsyntaxProblems
Problem 1: Validators Were Lost
When using custom validators on Pydantic model fields, the validators were not preserved in the CLI parameter processing. This caused type validation to fail for types that needed custom parsing logic.
Example That Failed Before
Before this fix:
$ python script.py --settings.min_duration 5 # Error: Input should be a valid timedelta, "day" identifier in duration not correctly formattedAfter this fix:
Problem 2: Boolean Fields Couldn't Accept Explicit Values
Typer automatically converts boolean fields with defaults into flags (
--flag/--no-flag), but many CLIs need to accept explicit boolean values like--flag Falsefor clarity and consistency.Example That Failed Before
Before this fix:
$ python script.py --settings.use_token_latencies False # Error: Got unexpected extra argument (False)After this fix:
Problem 3: Annotated Models with Typer Annotations Failed
When Pydantic models had fields with explicit
typer.Option()ortyper.Argument()annotations, the code would crash with "multiple Typer annotations" errors.Before this fix:
$ python script.py --help # Error: Cannot specify multiple `Annotated` Typer arguments for '_pydantic_user_id'After this fix:
$ python script.py --help # ✓ Works correctly!Root Causes
There were multiple issues preventing proper functionality:
Annotated validators were lost: In
_flatten_pydantic_model(), usingfield.annotationonly returned the base type, losing validators attached viaAnnotated.Class-level validators were bypassed: The code used
TypeAdapterfor early type validation, which bypassed@field_validatorand@model_validatordecorators that only run during full model construction.Boolean fields became flags: Typer treats
boolparameters with defaults as flags, preventing explicit value passing like--flag False.Duplicate ParameterInfo in metadata: When fields had explicit Typer annotations, they were duplicated in the metadata, causing "multiple annotations" errors.
Wrong type passed to
_flatten_pydantic_model: Passingparameter.annotation(which could beAnnotated[Model, ArgumentInfo]) instead of the base model type caused type hint errors.Solutions
Solution 1: Preserve Annotated Validators
Use
get_type_hints()withinclude_extras=Trueto retrieve the fullAnnotatedtype including all validatorsFilter out
FieldInfoandParameterInfofrom the metadata to avoid duplication (since we add our own)Update metadata unpacking to handle variable-length metadata (validators + typer_param + qualifier)
Solution 2: Enable Class-Level Validators
Add
_is_flattened_model_param()helper to identify parameters from flattened Pydantic modelsSkip
TypeAdaptervalidation for flattened model parameters, allowing the full model construction to apply@field_validatorand@model_validatordecoratorsSolution 3: Enable Boolean Fields with Explicit Values
Detect boolean fields in flattened Pydantic models using
_is_flattened_model_param()Replace
boolwithstrtype annotation to prevent Typer's automatic flag conversionLet Pydantic handle parsing - Pydantic naturally converts string values like
"False","True","0","1"to booleanSolution 4: Fix Annotated Type Handling
Pass
base_annotationinstead ofparameter.annotationto_flatten_pydantic_model()to avoid passingAnnotated[Model, TyperAnnotation]Filter
ParameterInfofrom metadata alongsideFieldInfoto prevent duplicate Typer annotationsChanges
1. Import
get_type_hints2. Updated
_flatten_pydantic_model()3. Fixed
enable_pydantic()to pass base annotation4. Added
_is_flattened_model_param()Helper5. Updated
enable_pydantic_type_validation()for Boolean Fields6. Updated
enable_pydantic()wrapperTesting
Comprehensive test suite with 47 tests organized into test classes:
Test File 1:
test_010_annotated_validators.py(11 tests)Tests for validators attached via
Annotated:TestBeforeValidator(4 tests)TestAfterValidator(3 tests)TestCombinedValidators(4 tests)Test File 2:
test_011_field_validator.py(13 tests)Tests for
@field_validatordecorators:TestFieldValidatorBefore(4 tests)mode="before"TestFieldValidatorAfter(3 tests)mode="after"TestFieldValidatorWithPydanticTypes(6 tests)mode="before")mode="after")mode="before")mode="after")Test File 3:
test_012_model_validator.py(12 tests)Tests for
@model_validatordecorators:TestModelValidatorBefore(1 test)mode="before"TestModelValidatorAfter(2 tests)TestModelValidatorCombined(4 tests)TestModelValidatorWithPydanticTypes(5 tests)Test File 4:
test_006_boolean_fields.py(11 tests) NEW!Tests for boolean field handling in Pydantic models:
Falseas explicit valueTrueas explicit valuefalsesupporttruesupport1parsed as True0parsed as FalseAll 47 tests pass! ✓
Benefits
Preserves Pydantic validators in Annotated types:
BeforeValidator,AfterValidator,PlainValidator,WrapValidatorEnables
@field_validatordecorators: Bothmode="before"andmode="after"work correctlyEnables
@model_validatordecorators: Cross-field validation and data normalizationWorks with advanced Pydantic types:
HttpUrl,EmailStr,IPv4Address,SecretStr, etc.Boolean fields accept explicit values: Use
--flag Falseinstead of--no-flagfor clearer, more consistent CLISupports various boolean formats:
True,False,true,false,1,0all work via Pydantic's parsingFixes Annotated model handling: Models with
typer.Option()ortyper.Argument()annotations now work correctlyEnables flexible CLI input parsing: Users can define custom parsers for more user-friendly CLI experiences
Backwards compatible: Falls back to existing behavior if no validators are present
Follows Pydantic best practices: Uses the same mechanisms Pydantic uses internally
Validator Support Overview
✅ This fix supports ALL Pydantic validator types:
1. Annotated Validators (Reusable)
2. field_validator Decorators (Model-specific)
3. model_validator Decorators (Cross-field)
How it works: The fix preserves
Annotatedvalidators throughget_type_hints()and enables@field_validator/@model_validatordecorators by skipping prematureTypeAdaptervalidation for flattened model fields. SeeVALIDATOR_COMPATIBILITY.mdfor detailed guide on when to use each approach.Use Cases Enabled
This fix enables many powerful use cases:
1. Flexible timedelta parsing
2. URL normalization with domain validation
3. Email normalization
4. Cross-field validation with model_validator
5. Boolean fields with explicit values NEW!
Migration Guide
Existing code continues to work without changes. This fix only adds new functionality.
To take advantage of flexible parsing:
For boolean fields, no changes needed - they now automatically support explicit values:
Python Compatibility
✅ Python 3.8+ - Uses
Annotated.__class_getitem__()for backward compatibilityReferences
get_type_hints(): https://docs.python.org/3/library/typing.html#typing.get_type_hints