From f6c74a55d5b272add88d4cbe2efa46c4abc1760f Mon Sep 17 00:00:00 2001 From: Rory Byrne Date: Mon, 8 Aug 2022 20:33:30 +0100 Subject: [PATCH] feat: pass config to NamedTuple fields (#4225) * feat: pass config to create_model_from_namedtuple * feat: tests for arbitrary_types_allowed in NamedTuple fields * misc: add changelog file for the PR --- changes/4219-synek.md | 1 + pydantic/validators.py | 7 +++++-- tests/test_annotated_types.py | 23 +++++++++++++++++++++++ 3 files changed, 29 insertions(+), 2 deletions(-) create mode 100644 changes/4219-synek.md diff --git a/changes/4219-synek.md b/changes/4219-synek.md new file mode 100644 index 0000000000..3dfa1e9898 --- /dev/null +++ b/changes/4219-synek.md @@ -0,0 +1 @@ +Use parent model's `Config` when validating nested `NamedTuple` fields. diff --git a/pydantic/validators.py b/pydantic/validators.py index 3d6e5217ff..c6b92f8db2 100644 --- a/pydantic/validators.py +++ b/pydantic/validators.py @@ -567,11 +567,14 @@ def pattern_validator(v: Any) -> Pattern[str]: NamedTupleT = TypeVar('NamedTupleT', bound=NamedTuple) -def make_namedtuple_validator(namedtuple_cls: Type[NamedTupleT]) -> Callable[[Tuple[Any, ...]], NamedTupleT]: +def make_namedtuple_validator( + namedtuple_cls: Type[NamedTupleT], config: Type['BaseConfig'] +) -> Callable[[Tuple[Any, ...]], NamedTupleT]: from .annotated_types import create_model_from_namedtuple NamedTupleModel = create_model_from_namedtuple( namedtuple_cls, + __config__=config, __module__=namedtuple_cls.__module__, ) namedtuple_cls.__pydantic_model__ = NamedTupleModel # type: ignore[attr-defined] @@ -704,7 +707,7 @@ def find_validators( # noqa: C901 (ignore complexity) return if is_namedtuple(type_): yield tuple_validator - yield make_namedtuple_validator(type_) + yield make_namedtuple_validator(type_, config) return if is_typeddict(type_): yield make_typeddict_validator(type_, config) diff --git a/tests/test_annotated_types.py b/tests/test_annotated_types.py index 43c13d08b5..e8b6cb9a06 100644 --- a/tests/test_annotated_types.py +++ b/tests/test_annotated_types.py @@ -151,6 +151,29 @@ class Model(BaseModel): Model.parse_obj({'t': [-1]}) +def test_namedtuple_arbitrary_type(): + class CustomClass: + pass + + class Tup(NamedTuple): + c: CustomClass + + class Model(BaseModel): + x: Tup + + class Config: + arbitrary_types_allowed = True + + data = {'x': Tup(c=CustomClass())} + model = Model.parse_obj(data) + assert isinstance(model.x.c, CustomClass) + + with pytest.raises(RuntimeError): + + class ModelNoArbitraryTypes(BaseModel): + x: Tup + + def test_typeddict(): class TD(TypedDict): a: int