Skip to content

Commit

Permalink
feat: support schema for NamedTuple
Browse files Browse the repository at this point in the history
  • Loading branch information
PrettyWood committed Jan 22, 2021
1 parent 08868ab commit 567bf4d
Show file tree
Hide file tree
Showing 4 changed files with 59 additions and 2 deletions.
2 changes: 1 addition & 1 deletion pydantic/annotated_types.py
Expand Up @@ -56,4 +56,4 @@ def create_model_from_namedtuple(namedtuple_cls: Type['NamedTuple'], **kwargs: A
field_definitions: Dict[str, Any] = {
field_name: (field_type, Required) for field_name, field_type in namedtuple_annotations.items()
}
return create_model(f'{namedtuple_cls.__name__}Model', **kwargs, **field_definitions)
return create_model(namedtuple_cls.__name__, **kwargs, **field_definitions)
11 changes: 11 additions & 0 deletions pydantic/schema.py
Expand Up @@ -65,6 +65,7 @@
get_origin,
is_callable_type,
is_literal_type,
is_namedtuple,
literal_values,
)
from .utils import ROOT_KEY, get_model, lenient_issubclass, sequence_like
Expand Down Expand Up @@ -795,6 +796,16 @@ def field_singleton_schema( # noqa: C901 (ignore complexity)
f_schema, schema_overrides = get_field_info_schema(field)
f_schema.update(get_schema_ref(enum_name, ref_prefix, ref_template, schema_overrides))
definitions[enum_name] = enum_process_schema(field_type)
elif is_namedtuple(field_type):
sub_schema, *_ = model_process_schema(
field_type.__pydantic_model__,
by_alias=by_alias,
model_name_map=model_name_map,
ref_prefix=ref_prefix,
ref_template=ref_template,
known_models=known_models,
)
f_schema.update({'type': 'array', 'items': list(sub_schema['properties'].values())})
elif not hasattr(field_type, '__pydantic_model__'):
add_field_type_to_schema(field_type, f_schema)

Expand Down
1 change: 1 addition & 0 deletions pydantic/validators.py
Expand Up @@ -547,6 +547,7 @@ def make_namedtuple_validator(namedtuple_cls: Type[NamedTupleT]) -> Callable[[Tu
from .annotated_types import create_model_from_namedtuple

NamedTupleModel = create_model_from_namedtuple(namedtuple_cls)
namedtuple_cls.__pydantic_model__ = NamedTupleModel # type: ignore[attr-defined]

def namedtuple_validator(values: Tuple[Any, ...]) -> NamedTupleT:
dict_values: Dict[str, Any] = dict(zip(NamedTupleModel.__annotations__, values))
Expand Down
47 changes: 46 additions & 1 deletion tests/test_annotated_types.py
Expand Up @@ -5,7 +5,7 @@
"""
import sys
from collections import namedtuple
from typing import List, NamedTuple
from typing import List, NamedTuple, Tuple

if sys.version_info < (3, 9):
try:
Expand Down Expand Up @@ -59,6 +59,51 @@ class Model(BaseModel):
]


def test_namedtuple_schema():
class Position1(NamedTuple):
x: int
y: int

Position2 = namedtuple('Position2', 'x y')

class Model(BaseModel):
pos1: Position1
pos2: Position2
pos3: Tuple[int, int]

assert Model.schema() == {
'title': 'Model',
'type': 'object',
'properties': {
'pos1': {
'title': 'Pos1',
'type': 'array',
'items': [
{'title': 'X', 'type': 'integer'},
{'title': 'Y', 'type': 'integer'},
],
},
'pos2': {
'title': 'Pos2',
'type': 'array',
'items': [
{'title': 'X'},
{'title': 'Y'},
],
},
'pos3': {
'title': 'Pos3',
'type': 'array',
'items': [
{'type': 'integer'},
{'type': 'integer'},
],
},
},
'required': ['pos1', 'pos2', 'pos3'],
}


@pytest.mark.skipif(not TypedDict, reason='typing_extensions not installed')
def test_typeddict():
class TD(TypedDict):
Expand Down

0 comments on commit 567bf4d

Please sign in to comment.