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
Dynamic default value #1210
Dynamic default value #1210
Conversation
Codecov Report
@@ Coverage Diff @@
## master #1210 +/- ##
=======================================
Coverage 99.89% 99.89%
=======================================
Files 21 21
Lines 3689 3706 +17
Branches 726 730 +4
=======================================
+ Hits 3685 3702 +17
Misses 2 2
Partials 2 2 Continue to review full report at Codecov.
|
Discussed in #866 So I would like to see this: from datetime import datetime
from pydantic import BaseModel
class Record(BaseModel):
name: str
description: str
ts: datetime = datetime.now
record = Record(name='Foo', description='Bar and Baz also.')
print(record.ts)
#> 2020-02-06 22:11:43.454720 However if type of field is callable, it must remain untouched: from random import randint
from typing import Callable
from pydantic import BaseModel
class IntGenerator(BaseModel):
fabric: Callable[..., int] = randint
generator = IntGenerator()
print(generator.fabric(0, 1))
#> 1 @samuelcolvin, any thoughts? |
@MrMrRobat Thanks ! I thought about the implementation with |
ba4603a
to
5f7fc5d
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mostly looks goods, needs tests fixing and docs.
pydantic/fields.py
Outdated
@@ -76,8 +77,12 @@ class FieldInfo(Representation): | |||
'extra', | |||
) | |||
|
|||
def __init__(self, default: Any, **kwargs: Any) -> None: | |||
def __init__(self, default: Any = Undefined, *, default_factory: Any = None, **kwargs: Any) -> None: | |||
if default is not Undefined and default_factory is not None: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
perhaps better to move this check into Field
m then you can use pop()
as is done elsewhere.
pydantic/fields.py
Outdated
*, | ||
default_factory: Any = None, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
default_factory: Any = None, | |
default_factory: Optional[Callable[[], Any]] = None, |
or something
I'm keen on the approach of from datetime import datetime
from pydantic import BaseModel
class Record(BaseModel):
ts: datetime = datetime.now However I think it could cause subtle changes in behaviour in edge cases so let's wait for v2. Getting |
6b24d27
to
065cf78
Compare
# With a callable: we still should be able to set callables as defaults | ||
class FunctionModel(BaseModel): | ||
a: int = 1 | ||
uid: Callable[[], UUID] = Field(uuid4) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Had to use = Field(uuid)
instead of = uuid4
as functions are ignored when creating fields and the default is hence not properly set
I think this behaviour should be fixed but in another PR (maybe for v2 with the other syntax for dynamic values)
pydantic/fields.py
Outdated
*, | ||
default_factory: Optional['Callable[[], Any]'] = None, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Had to use 'Callable...'
because it would raise
TypeError: 'ABCMeta' object is not subscriptable
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How is that possible? It already used in this module without an issues. That could be the case for collections.Callable, but it's a different thing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah right ! I didnt' see the Callable
came from .typing
. Thanks !
96fa43c
to
5b08aef
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's a chance we might need to change the signature or behaviour of default_factory
in future.
Therefore might be useful to comment in the documentation that this feature is provisional and might change in future minor releases? See #1179 for an example.
Otherwise looking good.
changes/1210-prettywood.md
Outdated
@@ -0,0 +1 @@ | |||
Add `default_factory` `Field` argument to create a dynamic default value by passing a zero-argument callable. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add `default_factory` `Field` argument to create a dynamic default value by passing a zero-argument callable. | |
Add `default_factory` argument to `Field` to create a dynamic default value by passing a zero-argument callable. |
pydantic/fields.py
Outdated
@@ -3,6 +3,7 @@ | |||
from typing import ( | |||
TYPE_CHECKING, | |||
Any, | |||
Callable, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if the below is TypingCallable
, what is this? Maybe good to give this an alias.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I decided to add a NoArgAnyCallable
directly in the local typing
module. Makes things clearer imo
pydantic/fields.py
Outdated
@@ -307,8 +321,9 @@ def prepare(self) -> None: | |||
Note: this method is **not** idempotent (because _type_analysis is not idempotent), | |||
e.g. calling it it multiple times may modify the field and configure it incorrectly. | |||
""" | |||
if self.default is not None and self.type_ is None: | |||
self.type_ = type(self.default) | |||
default_value = self.default_factory() if self.default_factory is not None else self.default |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
perhaps useful to have a get_default()
function on this class which is used here and in main.py
and perhaps deals with deepcopy?
@samuelcolvin Thanks for the review ! Changes done |
5b08aef
to
3802985
Compare
3802985
to
fce7c10
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
otherwise looks good, sorry I missed this before.
Co-Authored-By: Samuel Colvin <samcolvin@gmail.com>
@samuelcolvin When do you think a release will be ready with this PR? Looking forward to it! |
@jmagnusson see #1341 for details on release date. |
Change Summary
Following #155 and #866, here is a proposal of implementation to simplify dynamic default values
Right now, to have a dynamic default value, we need to use
validators
likeNow, we can use
default_factory
(same name as dataclasses.to be consistent)Related issue number
#155 and #866
Checklist
changes/<pull request or issue id>-<github username>.md
file added describing change(see changes/README.md for details)