pydantic
Pydantic Utility Functions for Declarative Typed Argument Parsing.
The pydantic
module contains utility functions used for interacting with the
internals of pydantic
, such as constructing field validators, updating
field validator dictionaries and constructing new model classes with
dynamically generated validators and environment variable parsers.
as_validator(field, caster)
Shortcut to wrap a caster and construct a validator for a given field.
The provided caster function must cast from a string to the type required
by the field. Once wrapped, the constructed validator will pass through any
non-string values, or any values that cause the caster function to raise an
exception to let the built-in pydantic
field validation handle them. The
validator will also cast empty strings to None
.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
field |
pydantic.fields.ModelField |
Field to construct validator for. |
required |
caster |
Callable[[str], Any] |
String to field type caster function. |
required |
Returns:
Type | Description |
---|---|
PydanticValidator |
Constructed field validator function. |
Source code in pydantic_argparse/utils/pydantic.py
def as_validator(
field: pydantic.fields.ModelField,
caster: Callable[[str], Any],
) -> PydanticValidator:
"""Shortcut to wrap a caster and construct a validator for a given field.
The provided caster function must cast from a string to the type required
by the field. Once wrapped, the constructed validator will pass through any
non-string values, or any values that cause the caster function to raise an
exception to let the built-in `pydantic` field validation handle them. The
validator will also cast empty strings to `None`.
Args:
field (pydantic.fields.ModelField): Field to construct validator for.
caster (Callable[[str], Any]): String to field type caster function.
Returns:
PydanticValidator: Constructed field validator function.
"""
# Dynamically construct a `pydantic` validator function for the supplied
# field. The constructed validator must be `pre=True` so that the validator
# is called before the built-in `pydantic` field validation occurs and is
# provided with the raw input data. The constructed validator must also be
# `allow_reuse=True` so the `__validator` function name can be reused
# multiple times when being decorated as a `pydantic` validator. Note that
# despite the `__validator` function *name* being reused, each instance of
# the validator function is uniquely constructed for the supplied field.
@pydantic.validator(field.name, pre=True, allow_reuse=True)
def __validator(cls: Type[Any], value: T) -> Union[T, None, Any]:
if not isinstance(value, str):
return value
if not value:
return None
try:
return caster(value)
except Exception:
return value
# Rename the validator uniquely for this field to avoid any collisions. The
# leading `__` and prefix of `pydantic_argparse` should guard against any
# potential collisions with user defined validators.
__validator.__name__ = f"__pydantic_argparse_{field.name}"
# Return the constructed validator
return __validator
update_validators(validators, validator)
Updates a validators dictionary with a possible new field validator.
Note that this function mutates the validators dictionary in-place, and does not return the dictionary.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
validators |
Dict[str, PydanticValidator] |
Validators to update. |
required |
validator |
Optional[PydanticValidator] |
Possible field validator. |
required |
Source code in pydantic_argparse/utils/pydantic.py
def update_validators(
validators: Dict[str, PydanticValidator],
validator: Optional[PydanticValidator],
) -> None:
"""Updates a validators dictionary with a possible new field validator.
Note that this function mutates the validators dictionary *in-place*, and
does not return the dictionary.
Args:
validators (Dict[str, PydanticValidator]): Validators to update.
validator (Optional[PydanticValidator]): Possible field validator.
"""
# Check for Validator
if validator:
# Add Validator
validators[validator.__name__] = validator
model_with_validators(model, validators)
Generates a new pydantic
model class with the supplied validators.
If the supplied base model is a subclass of pydantic.BaseSettings
, then
the newly generated model will also have a new parse_env_var
classmethod
monkeypatched onto it that suppresses any exceptions raised when initially
parsing the environment variables. This allows the raw values to still be
passed through to the pydantic
field validators if initial parsing fails.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
model |
Type[PydanticModelT] |
Model type to use as base class. |
required |
validators |
Dict[str, PydanticValidator] |
Field validators to add. |
required |
Returns:
Type | Description |
---|---|
Type[PydanticModelT] |
New |
Source code in pydantic_argparse/utils/pydantic.py
def model_with_validators(
model: Type[PydanticModelT],
validators: Dict[str, PydanticValidator],
) -> Type[PydanticModelT]:
"""Generates a new `pydantic` model class with the supplied validators.
If the supplied base model is a subclass of `pydantic.BaseSettings`, then
the newly generated model will also have a new `parse_env_var` classmethod
monkeypatched onto it that suppresses any exceptions raised when initially
parsing the environment variables. This allows the raw values to still be
passed through to the `pydantic` field validators if initial parsing fails.
Args:
model (Type[PydanticModelT]): Model type to use as base class.
validators (Dict[str, PydanticValidator]): Field validators to add.
Returns:
Type[PydanticModelT]: New `pydantic` model type with field validators.
"""
# Construct New Model with Validators
model = pydantic.create_model(
model.__name__,
__base__=model,
__validators__=validators,
)
# Check if the model is a `BaseSettings`
if issubclass(model, pydantic.BaseSettings):
# Hold a reference to the current `parse_env_var` classmethod
parse_env_var = model.__config__.parse_env_var
# Construct a new `parse_env_var` function which suppresses exceptions
# raised by the current `parse_env_var` classmethod. This allows the
# raw values to be passed through to the `pydantic` field validator
# methods if they cannot be parsed initially.
def __parse_env_var(field_name: str, raw_val: str) -> Any:
with contextlib.suppress(Exception):
return parse_env_var(field_name, raw_val)
return raw_val
# Monkeypatch `parse_env_var`
model.__config__.parse_env_var = __parse_env_var # type: ignore[method-assign]
# Return Constructed Model
return model