Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor the backend typing system #906

Merged
merged 106 commits into from
Jul 20, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
106 commits
Select commit Hold shift + click to select a range
2607699
Move type.py to object_type.py to make room for the file for the new …
BryceBeagle Apr 15, 2021
7a594bd
Rough implementation of StrawberryAnnotation/StrawberryType
BryceBeagle Apr 29, 2021
d794eac
More work, including a lot on the schema converter
BryceBeagle Apr 30, 2021
215cfff
Tests get past collection
BryceBeagle Apr 30, 2021
ea7874d
"Fix" type annotation problems because of dataclasses
BryceBeagle Apr 30, 2021
af759ac
Fix issues with info object on fields
BryceBeagle Apr 30, 2021
b2a6d15
Fix exception with unknown object type
BryceBeagle Apr 30, 2021
d27212b
Deal with some type+annotation complexities
BryceBeagle Apr 30, 2021
4dbecd5
Get argument_converter working
BryceBeagle Apr 30, 2021
9aa7078
Don't directly set argument type
BryceBeagle Apr 30, 2021
3f80246
Fix enum conversion
BryceBeagle Apr 30, 2021
4d58667
Pass around EnumDefinitions instead of monkeypatched EnumMetas
BryceBeagle Apr 30, 2021
38f40d3
Harden _is_enum implementation for non-type objects
BryceBeagle Apr 30, 2021
b4946ae
Fix StrawberryArgument instantiation
BryceBeagle Apr 30, 2021
57b9253
Fix directives
BryceBeagle Apr 30, 2021
470ffdf
Fix enum tests
BryceBeagle Apr 30, 2021
9a330d8
Fix list tests
BryceBeagle Apr 30, 2021
dfd3845
Fix union tests
BryceBeagle Apr 30, 2021
b0c8692
Fix (most of) argument tests
BryceBeagle Apr 30, 2021
c56a314
Fix union tests
BryceBeagle Apr 30, 2021
c4ac61d
Fix lambda resolvers
BryceBeagle Apr 30, 2021
a6e32aa
Merge branch 'main' into feature/strawberry_type_system
BryceBeagle Apr 30, 2021
a89be3d
Fix enum tests
BryceBeagle Apr 30, 2021
24b10ac
Fix list type tests
BryceBeagle Apr 30, 2021
702224f
Fix optional type tests
BryceBeagle Apr 30, 2021
2e2bf2c
Fix string type tests
BryceBeagle Apr 30, 2021
3015c2e
Better handling of (still broken) LazyType
BryceBeagle Apr 30, 2021
2d7b103
Fix forward reference test
BryceBeagle Apr 30, 2021
c7a6cfd
Fix pydantic tests
BryceBeagle Apr 30, 2021
dc5a482
Fix more pydantic tests
BryceBeagle Apr 30, 2021
35326e1
Fix some pydantic conversion tests
BryceBeagle Apr 30, 2021
391c93a
Fix pydantic error type tests
BryceBeagle Apr 30, 2021
93148df
Clean up generics tests
BryceBeagle May 16, 2021
c149b63
Merge branch 'main' into feature/strawberry_type_system
BryceBeagle May 20, 2021
bebaced
WIP generic support
patrick91 May 23, 2021
697e058
Wip generic support for unions
patrick91 May 23, 2021
86e04c4
Base copy with
patrick91 May 23, 2021
ec668c7
Restructure
patrick91 May 23, 2021
a9b0854
Something works
patrick91 May 23, 2021
4eed706
Scalars
patrick91 May 23, 2021
85e8b81
typevar_map -> type_var_map
BryceBeagle May 30, 2021
33cb05c
Give EnumDefinition a copy_with
BryceBeagle May 30, 2021
f8874a9
Minor cleanup of pydantic->strawberry conversion
BryceBeagle May 30, 2021
61b26aa
Fix pydantic conversion
BryceBeagle May 30, 2021
2ba94d1
Easier to understand generic-checking logic
BryceBeagle May 30, 2021
753abfa
Revert to old error-checking functionality
BryceBeagle May 30, 2021
cc4065a
Improve copy_with
BryceBeagle May 30, 2021
48733b2
Clean up copy_with logic
BryceBeagle May 31, 2021
40e56a9
Remove debug code
BryceBeagle May 31, 2021
b4d1657
Copy resolvers when copying fields
BryceBeagle May 31, 2021
9b6503e
typevar_map -> type_var_map
BryceBeagle May 31, 2021
80c9d7f
Fix ability to use strawberry.ID and other type aliases
BryceBeagle May 31, 2021
bd1c1b7
Fix type constructor
BryceBeagle Jun 3, 2021
397aab0
Fix some generic tests
BryceBeagle Jun 3, 2021
c6fff53
Fix list copies
BryceBeagle Jun 3, 2021
701841c
Only make type copies if generic
BryceBeagle Jun 3, 2021
012d185
Rename test function
BryceBeagle Jun 13, 2021
bf594da
Get generics working with Unions
BryceBeagle Jun 18, 2021
d6ddc8f
Improve comparisons between StrawberryType, StrawberryAnnotation, and…
BryceBeagle Jun 20, 2021
7bb65ba
Handle interface types explicity
BryceBeagle Jun 20, 2021
e851f7a
Removed debug test
BryceBeagle Jun 20, 2021
48d27c2
schema_converter.from_object now takes TypeDefinitions
BryceBeagle Jun 20, 2021
40b0ea4
TypeDefinition.copy_with now returns TypeDefinition
BryceBeagle Jun 20, 2021
dc856a6
Remove unnecessary unpacking
BryceBeagle Jun 26, 2021
0221cd0
Create dynamic types when copying fields of TypeDefinitions
BryceBeagle Jun 26, 2021
a482e48
Create dynamic types when copying ContainerTypes of TypeDefinitions
BryceBeagle Jun 26, 2021
cb58c79
Fix type_params check when ContainerType.of_type is not a StrawberryType
BryceBeagle Jun 26, 2021
abc02c2
Type copy should not have type_params if not generic. Grab fields of …
BryceBeagle Jun 26, 2021
bdd9800
Add __eq__ for StrawberryTypeVar
BryceBeagle Jun 26, 2021
457ad02
Implement StrawberryUnion.copy_with
BryceBeagle Jun 26, 2021
62caccc
Fix typos after generics test refactor
BryceBeagle Jun 26, 2021
3b7487b
Fix TypeDefinition.copy_with names with Union types
BryceBeagle Jun 26, 2021
57fbb76
Make sure types used as Generic parameters are also turned into Straw…
BryceBeagle Jun 26, 2021
6b7dd03
Fix typo after generics test refactor
BryceBeagle Jun 26, 2021
22dae96
Remove unused line
BryceBeagle Jun 26, 2021
ac36f95
Fix mypy
patrick91 Jun 26, 2021
1b0666a
Merge remote-tracking branch 'origin/feature/strawberry_type_system' …
BryceBeagle Jun 27, 2021
9c7794b
Fix issue with LazyTypes
BryceBeagle Jul 1, 2021
53c1187
Fix some mypy issues in annotation.py
BryceBeagle Jul 1, 2021
246ffbf
Fix some issues in arguments.py
BryceBeagle Jul 1, 2021
635013b
Handle dataclasses trying to use the field type prematurely
patrick91 Jul 3, 2021
e7bb6b4
MyPy fixes
patrick91 Jul 3, 2021
d786892
Merge branch 'main' into feature/strawberry_type_system
patrick91 Jul 3, 2021
245a5bc
More MyPy fixes
patrick91 Jul 3, 2021
308f49e
Even more MyPy fixes
patrick91 Jul 3, 2021
a2e41f6
Fix logic
patrick91 Jul 3, 2021
2f5c6cc
Copy base when copying generic
patrick91 Jul 3, 2021
22d1b18
Fix generics
patrick91 Jul 3, 2021
aa3900f
Merge branch 'main' into feature/strawberry_type_system
patrick91 Jul 5, 2021
38288c9
Fix issue on python 3.7
patrick91 Jul 5, 2021
5695198
More tests passing
patrick91 Jul 5, 2021
75a2d4c
WIP
patrick91 Jul 5, 2021
de70661
Fix generics copy
patrick91 Jul 5, 2021
f5d9f25
Make comment clearer
patrick91 Jul 5, 2021
e0fd3b7
Lint
patrick91 Jul 5, 2021
825bc9b
Remove cast
patrick91 Jul 5, 2021
6004626
Merge branch 'main' into feature/strawberry_type_system
BryceBeagle Jul 15, 2021
43a69fb
Add __eq__ to StrawberryAnnotation
BryceBeagle Jul 15, 2021
f17554b
Add __eq__ (and __hash__) to StrawberryUnion
BryceBeagle Jul 15, 2021
9706d9b
Raise exception when trying to resolve StrawberryAnnotation(None)
BryceBeagle Jul 15, 2021
72deb42
Improve comment on Union
BryceBeagle Jul 19, 2021
f62b09d
Make MultipleStrawberryArgumentsError only take argument name for now
BryceBeagle Jul 19, 2021
4382b1e
Tests for StrawberryType refactor (#1063)
BryceBeagle Jul 19, 2021
4cdb284
Remove unused functions
BryceBeagle Jul 20, 2021
e3010e7
Add a RELEASE.md
BryceBeagle Jul 20, 2021
5b0221e
Merge remote-tracking branch 'origin/feature/strawberry_type_system' …
BryceBeagle Jul 20, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,22 +1,23 @@
repos:
- repo: https://github.com/ambv/black
rev: 20.8b1
rev: 21.6b0
hooks:
- id: black

- repo: https://gitlab.com/pycqa/flake8
rev: 3.9.0
rev: 3.9.2
hooks:
- id: flake8
additional_dependencies: ["flake8-eradicate==0.4.0"]

- repo: https://github.com/pre-commit/mirrors-mypy
rev: v0.812
rev: v0.910
hooks:
- id: mypy
args: [--non-interactive, --install-types, --ignore-missing-imports]

- repo: https://github.com/pycqa/isort
rev: 5.8.0
rev: 5.9.1
hooks:
- id: isort

Expand All @@ -32,7 +33,7 @@ repos:
files: '^docs/.*\.mdx?$'

- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v3.4.0
rev: v4.0.1
hooks:
- id: trailing-whitespace
- id: check-merge-conflict
Expand Down
18 changes: 18 additions & 0 deletions RELEASE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
Release type: minor

Refactor of the library's typing internals. Previously, typing was handled
individually by fields, arguments, and objects with a hodgepodge of functions to tie it
together. This change creates a unified typing system that the object, fields, and
arguments each hook into.

Mainly replaces the attributes that were stored on StrawberryArgument and
StrawberryField with a hierarchy of StrawberryTypes.

Introduces `StrawberryAnnotation`, as well as `StrawberryType` and some subclasses,
including `StrawberryList`, `StrawberryOptional`, and `StrawberryTypeVar`.

This is a breaking change if you were calling the constructor for `StrawberryField`,
`StrawberryArgument`, etc. and using arguments such as `is_optional` or `child`.

`@strawberry.field` no longer takes an argument called `type_`. It instead takes a
`StrawberryAnnotation` called `type_annotation`.
2 changes: 1 addition & 1 deletion strawberry/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@
from .field import field
from .lazy_type import LazyType
from .mutation import mutation, subscription
from .object_type import input, interface, type
from .permission import BasePermission
from .private import Private
from .scalars import ID
from .schema import Schema
from .type import input, interface, type
from .union import union


Expand Down
271 changes: 271 additions & 0 deletions strawberry/annotation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,271 @@
import typing
from collections.abc import AsyncGenerator as AsyncGenerator_abc
from enum import Enum
from typing import ( # type: ignore
jkimbo marked this conversation as resolved.
Show resolved Hide resolved
TYPE_CHECKING,
Any,
AsyncGenerator as AsyncGenerator_typing,
Dict,
Optional,
TypeVar,
Union,
_eval_type,
)


try:
from typing import ForwardRef # type: ignore
except ImportError: # pragma: no cover
# ForwardRef is private in python 3.6 and 3.7
from typing import _ForwardRef as ForwardRef # type: ignore

from strawberry.custom_scalar import SCALAR_REGISTRY, ScalarDefinition
from strawberry.enum import EnumDefinition
from strawberry.lazy_type import LazyType
from strawberry.scalars import SCALAR_TYPES
from strawberry.type import (
StrawberryList,
StrawberryOptional,
StrawberryType,
StrawberryTypeVar,
)
from strawberry.types.types import TypeDefinition
from strawberry.utils.typing import is_generic, is_type_var


if TYPE_CHECKING:
from strawberry.union import StrawberryUnion


class StrawberryAnnotation:
def __init__(
self, annotation: Union[object, str], *, namespace: Optional[Dict] = None
):
self.annotation = annotation
self.namespace = namespace

def __eq__(self, other: object) -> bool:
if not isinstance(other, StrawberryAnnotation):
return NotImplemented

return self.resolve() == other.resolve()

def resolve(self) -> Union[StrawberryType, type]:
annotation: object
if isinstance(self.annotation, str):
annotation = ForwardRef(self.annotation)
else:
annotation = self.annotation

evaled_type = _eval_type(annotation, self.namespace, None)
if evaled_type is None:
raise ValueError("Annotation cannot be plain None type")
if self._is_async_generator(evaled_type):
evaled_type = self._strip_async_generator(evaled_type)
if self._is_lazy_type(evaled_type):
return evaled_type

if self._is_generic(evaled_type):
if any(is_type_var(type_) for type_ in evaled_type.__args__):
return evaled_type
return self.create_concrete_type(evaled_type)

# Simply return objects that are already StrawberryTypes
if self._is_strawberry_type(evaled_type):
return evaled_type

# Everything remaining should be a raw annotation that needs to be turned into
# 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_scalar(evaled_type):
return evaled_type
elif self._is_union(evaled_type):
return self.create_union(evaled_type)
elif is_type_var(evaled_type):
return self.create_type_var(evaled_type)

# TODO: Raise exception now, or later?
# ... raise NotImplementedError(f"Unknown type {evaled_type}")
return evaled_type

def create_concrete_type(self, evaled_type: type) -> type:
if _is_object_type(evaled_type):
type_definition: TypeDefinition
type_definition = evaled_type._type_definition # type: ignore
return type_definition.resolve_generic(evaled_type)

raise ValueError(f"Not supported {evaled_type}")

def create_enum(self, evaled_type: Any) -> EnumDefinition:
return evaled_type._enum_definition

def create_list(self, evaled_type: Any) -> StrawberryList:
of_type = StrawberryAnnotation(
annotation=evaled_type.__args__[0],
namespace=self.namespace,
).resolve()

return StrawberryList(of_type)

def create_optional(self, evaled_type: Any) -> StrawberryOptional:
types = evaled_type.__args__
non_optional_types = tuple(
filter(lambda x: x is not type(None), types) # noqa: E721
)

# Note that passing a single type to `Union` is equivalent to not using `Union`
# at all. This allows us to not di any checks for how many types have been
# passed as we can safely use `Union` for both optional types
# (e.g. `Optional[str]`) and optional unions (e.g.
# `Optional[Union[TypeA, TypeB]]`)
child_type = Union[non_optional_types] # type: ignore

of_type = StrawberryAnnotation(
annotation=child_type,
namespace=self.namespace,
).resolve()

return StrawberryOptional(of_type)

def create_type_var(self, evaled_type: TypeVar) -> StrawberryTypeVar:
return StrawberryTypeVar(evaled_type)

def create_union(self, evaled_type) -> "StrawberryUnion":
# Prevent import cycles
from strawberry.union import StrawberryUnion

# TODO: Deal with Forward References/origin
if isinstance(evaled_type, StrawberryUnion):
return evaled_type

types = evaled_type.__args__
union = StrawberryUnion(
type_annotations=tuple(StrawberryAnnotation(type_) for type_ in types),
)
return union

@classmethod
def _is_async_generator(cls, annotation: type) -> bool:
origin = getattr(annotation, "__origin__", None)
if origin is AsyncGenerator_abc:
return True
if origin is AsyncGenerator_typing:
# deprecated in Python 3.9 and above
return True
return False

@classmethod
def _is_enum(cls, annotation: Any) -> bool:
# Type aliases are not types so we need to make sure annotation can go into
# issubclass
if not isinstance(annotation, type):
return False
return issubclass(annotation, Enum)

@classmethod
def _is_generic(cls, annotation: Any) -> bool:
if hasattr(annotation, "__origin__"):
return is_generic(annotation.__origin__)

return False

@classmethod
def _is_lazy_type(cls, annotation: Any) -> bool:
return isinstance(annotation, LazyType)

@classmethod
def _is_optional(cls, annotation: Any) -> bool:
"""Returns True if the annotation is Optional[SomeType]"""

# Optionals are represented as unions
if not cls._is_union(annotation):
return False

types = annotation.__args__

# A Union to be optional needs to have at least one None type
return any(x is type(None) for x in types) # noqa: E721

@classmethod
def _is_list(cls, annotation: Any) -> bool:
"""Returns True if annotation is a List"""

annotation_origin = getattr(annotation, "__origin__", None)

return annotation_origin == list

@classmethod
def _is_scalar(cls, annotation: Any) -> bool:
type_ = getattr(annotation, "__supertype__", annotation)

if type_ in SCALAR_REGISTRY:
return True

if type_ in SCALAR_TYPES:
return True

return hasattr(annotation, "_scalar_definition")

@classmethod
def _is_strawberry_type(cls, evaled_type: Any) -> bool:
# Prevent import cycles
from strawberry.union import StrawberryUnion

if isinstance(evaled_type, EnumDefinition):
return True
elif _is_input_type(evaled_type): # TODO: Replace with StrawberryInputObject
return True
# TODO: add support for StrawberryInterface when implemented
elif isinstance(evaled_type, StrawberryList):
return True
elif _is_object_type(evaled_type): # TODO: Replace with StrawberryObject
return True
elif isinstance(evaled_type, TypeDefinition):
return True
elif isinstance(evaled_type, StrawberryOptional):
return True
elif isinstance(
evaled_type, ScalarDefinition
): # TODO: Replace with StrawberryScalar
return True
elif isinstance(evaled_type, StrawberryUnion):
return True

return False

@classmethod
def _is_union(cls, annotation: Any) -> bool:
"""Returns True if annotation is a Union"""
annotation_origin = getattr(annotation, "__origin__", None)

return annotation_origin is typing.Union

@classmethod
def _strip_async_generator(cls, annotation) -> type:
return annotation.__args__[0]

@classmethod
def _strip_lazy_type(cls, annotation: LazyType) -> type:
return annotation.resolve_type()


################################################################################
# Temporary functions to be removed with new types
################################################################################


def _is_input_type(type_: Any) -> bool:
if not _is_object_type(type_):
return False

return type_._type_definition.is_input


def _is_object_type(type_: Any) -> bool:
# isinstance(type_, StrawberryObjectType) # noqa: E800
return hasattr(type_, "_type_definition")
Loading