mirror of
https://github.com/kennethreitz/pydantic.git
synced 2026-06-05 23:00:18 +00:00
Support Field(default_factory) in validate_arguments (#2176)
* Support Field(default_factory) in validate_arguments * Added docs for validate_arguments with Field
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
Allow `Field` with a `default_factory` to be used as an argument to a function
|
||||
decorated with `validate_arguments`
|
||||
@@ -0,0 +1,22 @@
|
||||
from datetime import datetime
|
||||
from pydantic import validate_arguments, Field, ValidationError
|
||||
from pydantic.typing import Annotated
|
||||
|
||||
|
||||
@validate_arguments
|
||||
def how_many(num: Annotated[int, Field(gt=10)]):
|
||||
return num
|
||||
|
||||
|
||||
try:
|
||||
how_many(1)
|
||||
except ValidationError as e:
|
||||
print(e)
|
||||
|
||||
|
||||
@validate_arguments
|
||||
def when(dt: datetime = Field(default_factory=datetime.now)):
|
||||
return dt
|
||||
|
||||
|
||||
print(type(when()))
|
||||
@@ -55,6 +55,18 @@ To demonstrate all the above parameter types:
|
||||
```
|
||||
_(This script is complete, it should run "as is")_
|
||||
|
||||
## Using Field to describe function arguments
|
||||
|
||||
[Field](schema.md#field-customisation) can also be used with `validate_arguments` to provide extra information about
|
||||
the field and validations. In general it should be used in a type hint with
|
||||
[Annotated](schema.md#typingannotated-fields), unless `default_factory` is specified, in which case it should be used
|
||||
as the default value of the field:
|
||||
|
||||
```py
|
||||
{!.tmp_examples/validation_decorator_field.py!}
|
||||
```
|
||||
_(This script is complete, it should run "as is")_
|
||||
|
||||
## Usage with mypy
|
||||
|
||||
The `validate_arguments` decorator should work "out of the box" with [mypy](http://mypy-lang.org/) since it's
|
||||
@@ -93,13 +105,13 @@ _(This script is complete, it should run "as is")_
|
||||
|
||||
## Custom Config
|
||||
|
||||
The model behind `validate_arguments` can be customised using a config setting which is equivalent to
|
||||
The model behind `validate_arguments` can be customised using a config setting which is equivalent to
|
||||
setting the `Config` sub-class in normal models.
|
||||
|
||||
!!! warning
|
||||
The `fields` and `alias_generator` properties of `Config` which allow aliases to be configured are not supported
|
||||
yet with `@validate_arguments`, using them will raise an error.
|
||||
|
||||
|
||||
Configuration is set using the `config` keyword argument to the decorator, it may be either a config class
|
||||
or a dict of properties which are converted to a class later.
|
||||
|
||||
@@ -154,7 +166,7 @@ in future.
|
||||
### Config and Validators
|
||||
|
||||
`fields` and `alias_generator` on custom [`Config`](model_config.md) are not supported, see [above](#custom-config).
|
||||
|
||||
|
||||
Neither are [validators](validators.md).
|
||||
|
||||
### Model fields and reserved arguments
|
||||
|
||||
@@ -184,7 +184,7 @@ class ValidatedFunction:
|
||||
return values
|
||||
|
||||
def execute(self, m: BaseModel) -> Any:
|
||||
d = {k: v for k, v in m._iter() if k in m.__fields_set__}
|
||||
d = {k: v for k, v in m._iter() if k in m.__fields_set__ or m.__fields__[k].default_factory}
|
||||
var_kwargs = d.pop(self.v_kwargs_name, {})
|
||||
|
||||
if self.v_args_name in d:
|
||||
|
||||
+21
-1
@@ -3,12 +3,14 @@ import inspect
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import List
|
||||
from unittest.mock import ANY
|
||||
|
||||
import pytest
|
||||
|
||||
from pydantic import BaseModel, ValidationError, validate_arguments
|
||||
from pydantic import BaseModel, Field, ValidationError, validate_arguments
|
||||
from pydantic.decorator import ValidatedFunction
|
||||
from pydantic.errors import ConfigError
|
||||
from pydantic.typing import Annotated
|
||||
|
||||
skip_pre_38 = pytest.mark.skipif(sys.version_info < (3, 8), reason='testing >= 3.8 behaviour only')
|
||||
|
||||
@@ -142,6 +144,24 @@ def test_var_args_kwargs(validated):
|
||||
assert foo(1, 2, kwargs=4, e=5) == "a=1, b=2, args=(), d=3, kwargs={'kwargs': 4, 'e': 5}"
|
||||
|
||||
|
||||
def test_field_can_provide_factory() -> None:
|
||||
@validate_arguments
|
||||
def foo(a: int, b: int = Field(default_factory=lambda: 99), *args: int) -> int:
|
||||
"""mypy is happy with this"""
|
||||
return a + b + sum(args)
|
||||
|
||||
assert foo(3) == 102
|
||||
assert foo(1, 2, 3) == 6
|
||||
|
||||
|
||||
@pytest.mark.skipif(not Annotated, reason='typing_extensions not installed')
|
||||
def test_annotated_field_can_provide_factory() -> None:
|
||||
@validate_arguments
|
||||
def foo2(a: int, b: Annotated[int, Field(default_factory=lambda: 99)] = ANY, *args: int) -> int:
|
||||
"""mypy reports Incompatible default for argument "b" if we don't supply ANY as default"""
|
||||
return a + b + sum(args)
|
||||
|
||||
|
||||
@skip_pre_38
|
||||
def test_positional_only(create_module):
|
||||
module = create_module(
|
||||
|
||||
Reference in New Issue
Block a user