Skip to content

Commit

Permalink
Add is_auto utility (strawberry-graphql#1721)
Browse files Browse the repository at this point in the history
* Add is_auto utility

Add `is_auto` utility for checking if a type is `strawberry.auto`,
considering the possibility of it being a `StrawberryAnnotation` or
even being used inside `Annotated`.
  • Loading branch information
bellini666 authored and Mateusz Sobas committed Apr 10, 2022
1 parent 29fdef6 commit 603acaf
Show file tree
Hide file tree
Showing 5 changed files with 102 additions and 14 deletions.
5 changes: 5 additions & 0 deletions RELEASE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Release type: minor

Add `is_auto` utility for checking if a type is `strawberry.auto`,
considering the possibility of it being a `StrawberryAnnotation` or
even being used inside `Annotated`.
64 changes: 56 additions & 8 deletions strawberry/auto.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,66 @@
from typing import Any, ClassVar, Optional
from __future__ import annotations

from typing_extensions import Annotated, Final
from typing import Any, Optional, Union, cast

from typing_extensions import Annotated, Final, get_args, get_origin

class StrawberryAuto:
_instance: ClassVar[Optional["StrawberryAuto"]] = None
from strawberry.type import StrawberryType

def __new__(cls, *args, **kwargs):
if cls._instance is not None:
return cls._instance
from .annotation import StrawberryAnnotation


class StrawberryAutoMeta(type):
"""Metaclass for StrawberryAuto.
This is used to make sure StrawberryAuto is a singleton and also to
override the behavior of `isinstance` so that it consider the following
cases:
>> isinstance(StrawberryAuto(), StrawberryAuto)
True
>> isinstance(StrawberryAnnotation(StrawberryAuto()), StrawberryAuto)
True
>> isinstance(Annotated[StrawberryAuto(), object()), StrawberryAuto)
True
"""

def __init__(self, *args, **kwargs):
self._instance: Optional[StrawberryAuto] = None
super().__init__(*args, **kwargs)

def __call__(cls, *args, **kwargs):
if cls._instance is None:
cls._instance = super().__call__(*args, **kwargs)

cls._instance = super().__new__(cls, *args, **kwargs)
return cls._instance

def __instancecheck__(
self,
instance: Union[StrawberryAuto, StrawberryAnnotation, StrawberryType, type],
):
if isinstance(instance, StrawberryAnnotation):
resolved = instance.annotation
if isinstance(resolved, str):
namespace = instance.namespace
resolved = namespace and namespace.get(resolved)

if resolved is not None:
instance = cast(type, resolved)

if instance is auto:
return True

# Support uses of Annotated[auto, something()]
if get_origin(instance) is Annotated:
args = get_args(instance)
if args[0] is Any:
return any(isinstance(arg, StrawberryAuto) for arg in args[1:])

return False


class StrawberryAuto(metaclass=StrawberryAutoMeta):
def __str__(self):
return "auto"

Expand Down
10 changes: 7 additions & 3 deletions strawberry/experimental/pydantic/error_type.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from pydantic import BaseModel
from pydantic.fields import ModelField

import strawberry
from strawberry.auto import StrawberryAuto
from strawberry.experimental.pydantic.utils import (
get_private_fields,
get_strawberry_type_from_model,
Expand Down Expand Up @@ -68,7 +68,11 @@ def wrap(cls):

existing_fields = getattr(cls, "__annotations__", {})
fields_set = fields_set.union(
set(name for name, typ in existing_fields.items() if typ == strawberry.auto)
set(
name
for name, type_ in existing_fields.items()
if isinstance(type_, StrawberryAuto)
)
)

if all_fields:
Expand Down Expand Up @@ -105,7 +109,7 @@ def wrap(cls):
field,
)
for field in extra_fields + private_fields
if field.type != strawberry.auto
if not isinstance(field.type, StrawberryAuto)
)
)

Expand Down
8 changes: 6 additions & 2 deletions strawberry/experimental/pydantic/object_type.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@

from graphql import GraphQLResolveInfo

import strawberry
from strawberry.arguments import UNSET
from strawberry.auto import StrawberryAuto
from strawberry.experimental.pydantic.conversion import (
convert_pydantic_model_to_strawberry_class,
convert_strawberry_class_to_pydantic_model,
Expand Down Expand Up @@ -175,7 +175,11 @@ def wrap(cls: Any) -> Type[StrawberryTypeFromPydantic[PydanticModel]]:
# these are the fields that were marked with strawberry.auto and
# should copy their type from the pydantic model
auto_fields_set = original_fields_set.union(
set(name for name, typ in existing_fields.items() if typ == strawberry.auto)
set(
name
for name, type_ in existing_fields.items()
if isinstance(type_, StrawberryAuto)
)
)

if all_fields:
Expand Down
29 changes: 28 additions & 1 deletion tests/test_auto.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
from typing import Any
from typing import Any, cast

from typing_extensions import Annotated, get_args

from strawberry.annotation import StrawberryAnnotation
from strawberry.auto import StrawberryAuto, auto


Expand All @@ -15,3 +16,29 @@ def test_annotated():
some_obj = object()
new_annotated = Annotated[auto, some_obj]
assert get_args(new_annotated) == (Any, StrawberryAuto(), some_obj)


def test_str():
assert str(StrawberryAuto()) == "auto"


def test_repr():
assert repr(StrawberryAuto()) == "<auto>"


def test_isinstance():
assert isinstance(auto, StrawberryAuto)
assert not isinstance(object, StrawberryAuto)
assert not isinstance(cast(Any, object()), StrawberryAuto)


def test_isinstance_with_annotation():
annotation = StrawberryAnnotation(auto)
assert isinstance(annotation, StrawberryAuto)
str_annotation = StrawberryAnnotation("auto", namespace=globals())
assert isinstance(str_annotation, StrawberryAuto)


def test_isinstance_with_annotated():
assert isinstance(Annotated[auto, object()], StrawberryAuto)
assert not isinstance(Annotated[str, auto], StrawberryAuto)

0 comments on commit 603acaf

Please sign in to comment.