From ad7862f03254f41b0519977b00fb3db3d331a0bc Mon Sep 17 00:00:00 2001 From: Eric Jolibois Date: Sat, 13 Feb 2021 12:26:43 +0100 Subject: [PATCH] fix: keep order of fields with `BaseModel.construct()` (#2282) * fix: keep order of fields with `BaseModel.construct()` closes #2281 * chore: keep extra fields in construct thanks @layday! --- changes/2281-PrettyWood.md | 1 + pydantic/main.py | 9 +++++++-- tests/test_construction.py | 22 ++++++++++++++++++++++ 3 files changed, 30 insertions(+), 2 deletions(-) create mode 100644 changes/2281-PrettyWood.md diff --git a/changes/2281-PrettyWood.md b/changes/2281-PrettyWood.md new file mode 100644 index 0000000..b554eb5 --- /dev/null +++ b/changes/2281-PrettyWood.md @@ -0,0 +1 @@ +fix: keep order of fields with `BaseModel.construct()` diff --git a/pydantic/main.py b/pydantic/main.py index a53786a..a3c1c0b 100644 --- a/pydantic/main.py +++ b/pydantic/main.py @@ -611,10 +611,15 @@ class BaseModel(Representation, metaclass=ModelMetaclass): """ Creates a new model setting __dict__ and __fields_set__ from trusted or pre-validated data. Default values are respected, but no other validation is performed. + Behaves as if `Config.extra = 'allow'` was set since it adds all passed values """ m = cls.__new__(cls) - # default field values - fields_values = {name: field.get_default() for name, field in cls.__fields__.items() if not field.required} + fields_values: Dict[str, Any] = {} + for name, field in cls.__fields__.items(): + if name in values: + fields_values[name] = values[name] + elif not field.required: + fields_values[name] = field.get_default() fields_values.update(values) object_setattr(m, '__dict__', fields_values) if _fields_set is None: diff --git a/tests/test_construction.py b/tests/test_construction.py index 0065715..1e473c3 100644 --- a/tests/test_construction.py +++ b/tests/test_construction.py @@ -35,6 +35,28 @@ def test_construct_fields_set(): assert m.dict() == {'a': 3, 'b': -1} +def test_construct_allow_extra(): + """construct() should allow extra fields""" + + class Foo(BaseModel): + x: int + + assert Foo.construct(x=1, y=2).dict() == {'x': 1, 'y': 2} + + +def test_construct_keep_order(): + class Foo(BaseModel): + a: int + b: int = 42 + c: float + + instance = Foo(a=1, b=321, c=3.14) + instance_construct = Foo.construct(**instance.dict()) + assert instance == instance_construct + assert instance.dict() == instance_construct.dict() + assert instance.json() == instance_construct.json() + + def test_large_any_str(): class Model(BaseModel): a: bytes