New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support Field(default_factory) in validate_arguments #2176
Support Field(default_factory) in validate_arguments #2176
Conversation
Codecov Report
@@ Coverage Diff @@
## master #2176 +/- ##
==========================================
- Coverage 99.97% 99.90% -0.08%
==========================================
Files 23 25 +2
Lines 4500 5030 +530
Branches 909 1030 +121
==========================================
+ Hits 4499 5025 +526
Misses 1 1
- Partials 0 4 +4
Continue to review full report at Codecov.
|
This looks like a good start, I'm almost unnerved by how simple the change is. Are we sure there aren't any edge cases where behaviour has changed? Has has this had a significant adverse effect on performance? The other feature which would be cool here would be use of @validate_arguments
def foo(dt: Annotated[datetime, Field(default_factory=datetime.now)]):
print(dt) @JacobHayes might have some input here. Maybe that's a separate PR, but if it reduced the logic change I would personally prefer to only support |
Is it possible this would actually be fixed by #2147? |
#2147 doesn't fix it alone - the model gets instantiated, but the Seems like there could be a few paths, but this small tweak on top of #2147 fixes things for all existing tests + this new one (after moving default value -> Annotated): diff --git a/pydantic/decorator.py b/pydantic/decorator.py
index 98bc4e2..85dfdd9 100644
--- a/pydantic/decorator.py
+++ b/pydantic/decorator.py
@@ -81,6 +81,7 @@ class ValidatedFunction:
)
self.raw_function = function
+ self.raw_function_parameters = parameters
self.arg_mapping: Dict[int, str] = {}
self.positional_only_args = set()
self.v_args_name = 'args'
@@ -176,7 +177,9 @@ 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 (k in self.raw_function_parameters and v is not None)
+ }
kwargs = d.pop(self.v_kwargs_name, None)
if kwargs:
d.update(kwargs) I'm not sure how solid the One downside to the |
We've already removed @validate_arguments
def foo(dt: datetime = default_factory(datetime.now)):
print(dt) We would still need the Annotated type so we could use the other validation parts of @validate_arguments
def foo(dt: Annotated[datetime, Field(description="What time is it")] = default_factory(datetime.now)):
print(dt) We could define default_factory as: @dataclass
class DefaultFactory:
factory: Callable
def default_factory(factory: Callable[[], T]) -> T:
# We will check for default values of type DefaultFactory in validate_arguments and call it at the right time
df = DefaultFactory(factory)
# But lie here so mypy thinks we actually passed a default value of the correct type
return cast(T, df) |
@samuelcolvin I'm happy to discard this PR and have a go at making |
I've rebased on top of master (and so #2147) and this PR now becomes much simpler. Most cases are handled with #2147, e.g. The issue with mypy still stands as noted in #2176 (comment) There are two fixes:
Both work with this PR (and are in the test). @samuelcolvin which of the 2 approaches should I put in the docs? |
This looks great. @thomascobb, |
Great, thank you so much. 🎉 |
Change Summary
Add preliminary support for Field to validate_arguments
I expect there will be issues with my approach and edge cases, this is a starting point for a discussion
Related issue number
fix #1359
Checklist
changes/<pull request or issue id>-<github username>.md
file added describing change(see changes/README.md for details)