From e820f9a519b585ee14903a23225bc7e040c0530b Mon Sep 17 00:00:00 2001 From: "Tjeerd.Verschragen" Date: Thu, 29 Jun 2023 10:45:43 +0200 Subject: [PATCH] Add pydantic conversion compatibility with specialized list class - move `_is_list` check before the `_is_generic` check in `StrawberryAnnotation.resolve`. - change `StrawberryAnnotation._is_list` to check if the `annotation` extends from list and can be considered a list. --- strawberry/annotation.py | 12 +++++-- .../experimental/pydantic/test_conversion.py | 36 ++++++++++++++++++- 2 files changed, 44 insertions(+), 4 deletions(-) diff --git a/strawberry/annotation.py b/strawberry/annotation.py index b65f7541aa..b151b3ebbd 100644 --- a/strawberry/annotation.py +++ b/strawberry/annotation.py @@ -100,6 +100,8 @@ def resolve(self) -> Union[StrawberryType, type]: evaled_type = self._strip_async_type(evaled_type) if self._is_lazy_type(evaled_type): return evaled_type + if self._is_list(evaled_type): + return self.create_list(evaled_type) if self._is_generic(evaled_type): if any(is_type_var(type_) for type_ in evaled_type.__args__): @@ -114,8 +116,6 @@ def resolve(self) -> Union[StrawberryType, type]: # a StrawberryType if self._is_enum(evaled_type): return self.create_enum(evaled_type) - if self._is_list(evaled_type): - return self.create_list(evaled_type) elif self._is_optional(evaled_type): return self.create_optional(evaled_type) elif self._is_union(evaled_type): @@ -250,8 +250,14 @@ def _is_list(cls, annotation: Any) -> bool: """Returns True if annotation is a List""" annotation_origin = getattr(annotation, "__origin__", None) + annotation_mro = getattr(annotation, "__mro__", []) + is_list = any(x is list for x in annotation_mro) - return (annotation_origin in (list, tuple)) or annotation_origin is abc.Sequence + return ( + (annotation_origin in (list, tuple)) + or annotation_origin is abc.Sequence + or is_list + ) @classmethod def _is_strawberry_type(cls, evaled_type: Any) -> bool: diff --git a/tests/experimental/pydantic/test_conversion.py b/tests/experimental/pydantic/test_conversion.py index 300520d4ef..f8414ad162 100644 --- a/tests/experimental/pydantic/test_conversion.py +++ b/tests/experimental/pydantic/test_conversion.py @@ -3,7 +3,7 @@ import re import sys from enum import Enum -from typing import Any, Dict, List, NewType, Optional, Union, cast +from typing import Any, Dict, List, NewType, Optional, TypeVar, Union, cast import pytest from pydantic import BaseConfig, BaseModel, Field, ValidationError @@ -1194,3 +1194,37 @@ class Test: assert test.optional_list == [1, 2, 3] assert test.optional_str is None + + +SI = TypeVar("SI", covariant=True) # pragma: no mutate + + +class SpecialList(List[SI]): + pass + + +def test_can_convert_pydantic_type_to_strawberry_with_constrained_list(): + class WorkModel(BaseModel): + name: str + + class workList(SpecialList[SI]): + min_items = 1 + + class UserModel(BaseModel): + work: workList[WorkModel] + + @strawberry.experimental.pydantic.type(WorkModel) + class Work: + name: strawberry.auto + + @strawberry.experimental.pydantic.type(UserModel) + class User: + work: strawberry.auto + + origin_user = UserModel( + work=[WorkModel(name="developer"), WorkModel(name="tester")] + ) + + user = User.from_pydantic(origin_user) + + assert user == User(work=[Work(name="developer"), Work(name="tester")])