mirror of
https://github.com/kennethreitz/pydantic.git
synced 2026-06-05 23:00:18 +00:00
support ForwardRef on dataclasses (#399)
* support ForwardRef on dataclasses, fix #397 * explicit docs about when postponed annotations don't work
This commit is contained in:
@@ -7,6 +7,7 @@ v0.20.0 (unreleased)
|
||||
....................
|
||||
* fix tests for python 3.8, #396 by @samuelcolvin
|
||||
* Adds fields to the ``dir`` method for autocompletion in interactive sessions, #398 by @dgasmith
|
||||
* support ``ForwardRef`` (and therefore ``from __future__ import annotations``) with dataclasses, #397 by @samuelcolvin
|
||||
|
||||
v0.20.0a1 (2019-02-13)
|
||||
......................
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
from __future__ import annotations
|
||||
from pydantic import BaseModel
|
||||
|
||||
def this_is_broken():
|
||||
from typing import List # <-- List is defined inside the function so is not in the module's global scope
|
||||
class Model(BaseModel):
|
||||
a: List[int]
|
||||
print(Model(a=(1, 2)))
|
||||
@@ -0,0 +1,8 @@
|
||||
from __future__ import annotations
|
||||
from typing import List # <-- List is defined in the module's global scope
|
||||
from pydantic import BaseModel
|
||||
|
||||
def this_works():
|
||||
class Model(BaseModel):
|
||||
a: List[int]
|
||||
print(Model(a=(1, 2)))
|
||||
@@ -709,6 +709,22 @@ to properly set types before the model can be used.
|
||||
|
||||
(This script is complete, it should run "as is")
|
||||
|
||||
.. warning::
|
||||
|
||||
To resolve strings (type names) into annotations (types) *pydantic* needs a dict to lookup,
|
||||
for this is uses ``module.__dict__`` just as ``get_type_hints`` does. That means *pydantic* does not play well
|
||||
with types not defined in the global scope of a module.
|
||||
|
||||
For example, this works fine:
|
||||
|
||||
.. literalinclude:: examples/postponed_works.py
|
||||
|
||||
While this will break:
|
||||
|
||||
.. literalinclude:: examples/postponed_broken.py
|
||||
|
||||
Resolving this is beyond the call for *pydantic*: either remove the future import or declare the types globally.
|
||||
|
||||
.. _benchmarks_tag:
|
||||
|
||||
Benchmarks
|
||||
|
||||
@@ -74,7 +74,7 @@ def _process_class(
|
||||
fields: Dict[str, Any] = {name: (field.type, field.default) for name, field in cls.__dataclass_fields__.items()}
|
||||
cls.__post_init_original__ = post_init_original
|
||||
|
||||
cls.__pydantic_model__ = create_model(cls.__name__, __config__=config, __base__=None, **fields)
|
||||
cls.__pydantic_model__ = create_model(cls.__name__, __config__=config, __module__=_cls.__module__, **fields)
|
||||
|
||||
cls.__initialised__ = False
|
||||
cls.__validate__ = classmethod(_validate_dataclass)
|
||||
|
||||
+9
-4
@@ -504,7 +504,12 @@ class BaseModel(metaclass=MetaModel):
|
||||
|
||||
|
||||
def create_model(
|
||||
model_name: str, *, __config__: Type[BaseConfig] = None, __base__: Type[BaseModel] = None, **field_definitions: Any
|
||||
model_name: str,
|
||||
*,
|
||||
__config__: Type[BaseConfig] = None,
|
||||
__base__: Type[BaseModel] = None,
|
||||
__module__: Optional[str] = None,
|
||||
**field_definitions: Any,
|
||||
) -> BaseModel:
|
||||
"""
|
||||
Dynamically create a model.
|
||||
@@ -542,7 +547,7 @@ def create_model(
|
||||
annotations[f_name] = f_annotation
|
||||
fields[f_name] = f_value
|
||||
|
||||
namespace: 'DictStrAny' = {'__annotations__': annotations}
|
||||
namespace: 'DictStrAny' = {'__annotations__': annotations, '__module__': __module__}
|
||||
namespace.update(fields)
|
||||
if __config__:
|
||||
namespace['Config'] = inherit_config(__config__, BaseConfig)
|
||||
@@ -565,8 +570,8 @@ def validate_model( # noqa: C901 (ignore complexity)
|
||||
for name, field in model.__fields__.items():
|
||||
if type(field.type_) == ForwardRef:
|
||||
raise ConfigError(
|
||||
f"field {field.name} not yet prepared and type is still a ForwardRef, "
|
||||
f"you'll need to call {model.__class__.__name__}.update_forward_refs()"
|
||||
f'field "{field.name}" not yet prepared so type is still a ForwardRef, '
|
||||
f'you might need to call {model.__class__.__name__}.update_forward_refs().'
|
||||
)
|
||||
|
||||
value = input_data.get(field.alias, _missing)
|
||||
|
||||
+18
-1
@@ -122,4 +122,21 @@ class Foo(BaseModel):
|
||||
)
|
||||
with pytest.raises(ConfigError) as exc_info:
|
||||
module.Foo(b=123)
|
||||
assert str(exc_info.value).startswith('field b not yet prepared and type is still a ForwardRef')
|
||||
assert str(exc_info.value).startswith('field "b" not yet prepared so type is still a ForwardRef')
|
||||
|
||||
|
||||
@skip_not_37
|
||||
def test_forward_ref_dataclass(create_module):
|
||||
module = create_module(
|
||||
"""
|
||||
from __future__ import annotations
|
||||
from pydantic import UrlStr
|
||||
from pydantic.dataclasses import dataclass
|
||||
|
||||
@dataclass
|
||||
class Dataclass:
|
||||
url: UrlStr
|
||||
"""
|
||||
)
|
||||
m = module.Dataclass('http://example.com ')
|
||||
assert m.url == 'http://example.com'
|
||||
|
||||
Reference in New Issue
Block a user