|
15 | 15 | FrozenSet,
|
16 | 16 | Generator,
|
17 | 17 | List,
|
| 18 | + NamedTuple, |
18 | 19 | Pattern,
|
19 | 20 | Set,
|
20 | 21 | Tuple,
|
|
34 | 35 | get_class,
|
35 | 36 | is_callable_type,
|
36 | 37 | is_literal_type,
|
| 38 | + is_named_tuple_type, |
| 39 | + is_typed_dict_type, |
37 | 40 | )
|
38 | 41 | from .utils import almost_equal_floats, lenient_issubclass, sequence_like
|
39 | 42 |
|
40 | 43 | if TYPE_CHECKING:
|
41 | 44 | from .fields import ModelField
|
42 |
| - from .main import BaseConfig |
| 45 | + from .main import BaseConfig, BaseModel |
43 | 46 | from .types import ConstrainedDecimal, ConstrainedFloat, ConstrainedInt
|
44 | 47 |
|
45 | 48 | ConstrainedNumber = Union[ConstrainedDecimal, ConstrainedFloat, ConstrainedInt]
|
@@ -523,6 +526,43 @@ def pattern_validator(v: Any) -> Pattern[str]:
|
523 | 526 | raise errors.PatternError()
|
524 | 527 |
|
525 | 528 |
|
| 529 | +NamedTupleT = TypeVar('NamedTupleT', bound=NamedTuple) |
| 530 | + |
| 531 | + |
| 532 | +def make_named_tuple_validator(type_: Type[NamedTupleT]) -> Callable[[Tuple[Any, ...]], NamedTupleT]: |
| 533 | + from .main import create_model |
| 534 | + |
| 535 | + # A named tuple can be created with `typing,NamedTuple` with types |
| 536 | + # but also with `collections.namedtuple` with just the fields |
| 537 | + # in which case we consider the type to be `Any` |
| 538 | + named_tuple_annotations: Dict[str, Type[Any]] = getattr(type_, '__annotations__', {k: Any for k in type_._fields}) |
| 539 | + field_definitions: Dict[str, Any] = { |
| 540 | + field_name: (field_type, ...) for field_name, field_type in named_tuple_annotations.items() |
| 541 | + } |
| 542 | + NamedTupleModel: Type['BaseModel'] = create_model('NamedTupleModel', **field_definitions) |
| 543 | + |
| 544 | + def named_tuple_validator(values: Tuple[Any, ...]) -> NamedTupleT: |
| 545 | + dict_values: Dict[str, Any] = dict(zip(named_tuple_annotations, values)) |
| 546 | + validated_dict_values: Dict[str, Any] = dict(NamedTupleModel(**dict_values)) |
| 547 | + return type_(**validated_dict_values) |
| 548 | + |
| 549 | + return named_tuple_validator |
| 550 | + |
| 551 | + |
| 552 | +def make_typed_dict_validator(type_: Type[Dict[str, Any]]) -> Callable[[Any], Dict[str, Any]]: |
| 553 | + from .main import create_model |
| 554 | + |
| 555 | + field_definitions: Dict[str, Any] = { |
| 556 | + field_name: (field_type, ...) for field_name, field_type in type_.__annotations__.items() |
| 557 | + } |
| 558 | + TypedDictModel: Type['BaseModel'] = create_model('TypedDictModel', **field_definitions) |
| 559 | + |
| 560 | + def typed_dict_validator(values: Dict[str, Any]) -> Dict[str, Any]: |
| 561 | + return dict(TypedDictModel(**values)) |
| 562 | + |
| 563 | + return typed_dict_validator |
| 564 | + |
| 565 | + |
526 | 566 | class IfConfig:
|
527 | 567 | def __init__(self, validator: AnyCallable, *config_attr_names: str) -> None:
|
528 | 568 | self.validator = validator
|
@@ -610,6 +650,13 @@ def find_validators( # noqa: C901 (ignore complexity)
|
610 | 650 | if type_ is IntEnum:
|
611 | 651 | yield int_enum_validator
|
612 | 652 | return
|
| 653 | + if is_named_tuple_type(type_): |
| 654 | + yield tuple_validator |
| 655 | + yield make_named_tuple_validator(type_) |
| 656 | + return |
| 657 | + if is_typed_dict_type(type_): |
| 658 | + yield make_typed_dict_validator(type_) |
| 659 | + return |
613 | 660 |
|
614 | 661 | class_ = get_class(type_)
|
615 | 662 | if class_ is not None:
|
|
0 commit comments