mirror of
https://github.com/kennethreitz/pydantic.git
synced 2026-06-05 23:00:18 +00:00
Include all annotated fields in order (#715)
* Include all annotated fields in order * Update docs and changes * fix field ordering * update change info * fix coverage
This commit is contained in:
@@ -0,0 +1 @@
|
||||
make all annotated fields occur in the order declared
|
||||
+6
-5
@@ -204,6 +204,12 @@ A few things to note on validators:
|
||||
- If validation fails on another field (or that field is missing) it will not be included in ``values``, hence
|
||||
``if 'password1' in values and ...`` in this example.
|
||||
|
||||
.. warning::
|
||||
|
||||
Be aware that mixing annotated and non-annotated fields may alter the order of your fields in metadata and errors,
|
||||
and for validation: annotated fields will always come before non-annotated fields.
|
||||
(Within each group fields remain in the order they were defined.)
|
||||
|
||||
|
||||
.. note::
|
||||
|
||||
@@ -917,11 +923,6 @@ Required Fields and mypy
|
||||
|
||||
The ellipsis notation ``...`` will not work with mypy, you need to use annotation only fields as in the example above.
|
||||
|
||||
.. warning::
|
||||
|
||||
Be aware that using annotation only fields will alter the order of your fields in metadata and errors:
|
||||
annotation only fields will always come first, but still in the order they were defined.
|
||||
|
||||
To get round this you can use the ``Required`` (via ``from pydantic import Required``) field as an alias for
|
||||
ellipses or annotation only.
|
||||
|
||||
|
||||
+10
-6
@@ -175,29 +175,33 @@ class MetaModel(ABCMeta):
|
||||
f.prepare()
|
||||
|
||||
set_extra(config, name)
|
||||
annotations = resolve_annotations(namespace.get('__annotations__', {}), namespace.get('__module__', None))
|
||||
|
||||
class_vars = set()
|
||||
if (namespace.get('__module__'), namespace.get('__qualname__')) != ('pydantic.main', 'BaseModel'):
|
||||
annotations = resolve_annotations(namespace.get('__annotations__', {}), namespace.get('__module__', None))
|
||||
untouched_types = UNTOUCHED_TYPES + config.keep_untouched
|
||||
# annotation only fields need to come first in fields
|
||||
for ann_name, ann_type in annotations.items():
|
||||
if is_classvar(ann_type):
|
||||
class_vars.add(ann_name)
|
||||
elif is_valid_field(ann_name) and ann_name not in namespace:
|
||||
elif is_valid_field(ann_name):
|
||||
validate_field_name(bases, ann_name)
|
||||
value = namespace.get(ann_name, ...)
|
||||
if isinstance(value, untouched_types) and ann_type != PyObject:
|
||||
continue
|
||||
fields[ann_name] = Field.infer(
|
||||
name=ann_name,
|
||||
value=...,
|
||||
value=value,
|
||||
annotation=ann_type,
|
||||
class_validators=vg.get_validators(ann_name),
|
||||
config=config,
|
||||
)
|
||||
|
||||
untouched_types = UNTOUCHED_TYPES + config.keep_untouched
|
||||
for var_name, value in namespace.items():
|
||||
if (
|
||||
is_valid_field(var_name)
|
||||
and (annotations.get(var_name) == PyObject or not isinstance(value, untouched_types))
|
||||
var_name not in annotations
|
||||
and is_valid_field(var_name)
|
||||
and not isinstance(value, untouched_types)
|
||||
and var_name not in class_vars
|
||||
):
|
||||
validate_field_name(bases, var_name)
|
||||
|
||||
@@ -930,3 +930,14 @@ def test_init_inspection():
|
||||
super().__init__(**data)
|
||||
|
||||
Foobar(x=1)
|
||||
|
||||
|
||||
def test_ignored_type():
|
||||
def foobar():
|
||||
pass
|
||||
|
||||
class Model(BaseModel):
|
||||
a: int = foobar
|
||||
b: int
|
||||
|
||||
assert Model.__fields__.keys() == {'b'}
|
||||
|
||||
+1
-2
@@ -312,8 +312,7 @@ def test_field_order():
|
||||
a: str
|
||||
d: dict = {}
|
||||
|
||||
# fields are ordered as defined except annotation-only fields come last
|
||||
assert list(Model.__fields__.keys()) == ['c', 'a', 'b', 'd']
|
||||
assert list(Model.__fields__.keys()) == ['c', 'b', 'a', 'd']
|
||||
|
||||
|
||||
def test_required():
|
||||
|
||||
+6
-6
@@ -6,7 +6,7 @@ from datetime import date, datetime, time, timedelta
|
||||
from decimal import Decimal
|
||||
from enum import Enum, IntEnum
|
||||
from pathlib import Path
|
||||
from typing import Dict, Iterator, List, NewType, Pattern, Sequence, Set, Tuple
|
||||
from typing import Dict, Iterator, List, NewType, Optional, Pattern, Sequence, Set, Tuple
|
||||
from uuid import UUID
|
||||
|
||||
import pytest
|
||||
@@ -207,12 +207,12 @@ def test_constrained_str_too_long():
|
||||
|
||||
|
||||
class DsnModel(BaseModel):
|
||||
db_name = 'foobar'
|
||||
db_user = 'postgres'
|
||||
db_name: Optional[str] = 'foobar'
|
||||
db_user: Optional[str] = 'postgres'
|
||||
db_password: str = None
|
||||
db_host = 'localhost'
|
||||
db_port = '5432'
|
||||
db_driver = 'postgres'
|
||||
db_host: Optional[str] = 'localhost'
|
||||
db_port: Optional[str] = '5432'
|
||||
db_driver: str = 'postgres'
|
||||
db_query: dict = None
|
||||
dsn: DSN = None
|
||||
|
||||
|
||||
Reference in New Issue
Block a user