Skip to content

Commit

Permalink
Add support for using raw Python enum types in schema (#3639)
Browse files Browse the repository at this point in the history
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Patrick Arminio <patrick.arminio@gmail.com>
  • Loading branch information
3 people authored Oct 9, 2024
1 parent 596461b commit 0d880b7
Show file tree
Hide file tree
Showing 10 changed files with 94 additions and 129 deletions.
2 changes: 1 addition & 1 deletion .alexrc
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,6 @@
"special",
"primitive",
"invalid",
"crash",
"crash"
]
}
6 changes: 3 additions & 3 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.6.8
rev: v0.6.9
hooks:
- id: ruff-format
exclude: ^tests/\w+/snapshots/
Expand All @@ -20,7 +20,7 @@ repos:
files: '^docs/.*\.mdx?$'

- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.6.0
rev: v5.0.0
hooks:
- id: trailing-whitespace
- id: check-merge-conflict
Expand All @@ -31,7 +31,7 @@ repos:
args: ["--branch", "main"]

- repo: https://github.com/adamchainz/blacken-docs
rev: 1.18.0
rev: 1.19.0
hooks:
- id: blacken-docs
args: [--skip-errors]
Expand Down
25 changes: 25 additions & 0 deletions RELEASE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
Release type: patch

This release adds support for using raw Python enum types in your schema
(enums that are not decorated with `@strawberry.enum`)

This is useful if you have enum types from other places in your code
that you want to use in strawberry.
i.e
```py
# somewhere.py
from enum import Enum


class AnimalKind(Enum):
AXOLOTL, CAPYBARA = range(2)


# gql/animals
from somewhere import AnimalKind


@strawberry.type
class AnimalType:
kind: AnimalKind
```
62 changes: 0 additions & 62 deletions docs/errors/not-a-strawberry-enum.md

This file was deleted.

2 changes: 1 addition & 1 deletion docs/general/subscriptions.md
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,7 @@ const subscriber = client.subscribe({query: ...}).subscribe({...})
subscriber.unsubscribe();
```

Strawberry can easily capture when a subscriber unsubscribes using an
Strawberry can capture when a subscriber unsubscribes using an
`asyncio.CancelledError` exception.

```python
Expand Down
11 changes: 11 additions & 0 deletions docs/types/enums.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,17 @@ class IceCreamFlavour(Enum):
CHOCOLATE = "chocolate"
```

<Note>

In some cases you already have an enum defined elsewhere in your code. You can
safely use it in your schema and strawberry will generate a default graphql
implementation of it.

The only drawback is that it is not currently possible to configure it
(documentation / renaming or using `strawberry.enum_value` on it).

</Note>

Let's see how we can use Enums in our schema.

```python
Expand Down
4 changes: 2 additions & 2 deletions strawberry/annotation.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
)
from typing_extensions import Annotated, Self, get_args, get_origin

from strawberry.exceptions.not_a_strawberry_enum import NotAStrawberryEnumError
from strawberry.types.base import (
StrawberryList,
StrawberryObjectDefinition,
Expand All @@ -30,6 +29,7 @@
has_object_definition,
)
from strawberry.types.enum import EnumDefinition
from strawberry.types.enum import enum as strawberry_enum
from strawberry.types.lazy_type import LazyType
from strawberry.types.private import is_private
from strawberry.types.scalar import ScalarDefinition
Expand Down Expand Up @@ -187,7 +187,7 @@ def create_enum(self, evaled_type: Any) -> EnumDefinition:
try:
return evaled_type._enum_definition
except AttributeError:
raise NotAStrawberryEnumError(evaled_type)
return strawberry_enum(evaled_type)._enum_definition

def create_list(self, evaled_type: Any) -> StrawberryList:
item_type, *_ = get_args(evaled_type)
Expand Down
2 changes: 0 additions & 2 deletions strawberry/exceptions/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
from .missing_dependencies import MissingOptionalDependenciesError
from .missing_field_annotation import MissingFieldAnnotationError
from .missing_return_annotation import MissingReturnAnnotationError
from .not_a_strawberry_enum import NotAStrawberryEnumError
from .object_is_not_a_class import ObjectIsNotClassError
from .object_is_not_an_enum import ObjectIsNotAnEnumError
from .private_strawberry_field import PrivateStrawberryFieldError
Expand Down Expand Up @@ -174,7 +173,6 @@ class StrawberryGraphQLError(GraphQLError):
"UnresolvedFieldTypeError",
"PrivateStrawberryFieldError",
"MultipleStrawberryArgumentsError",
"NotAStrawberryEnumError",
"ScalarAlreadyRegisteredError",
"WrongNumberOfResultsReturned",
"FieldWithResolverAndDefaultValueError",
Expand Down
38 changes: 0 additions & 38 deletions strawberry/exceptions/not_a_strawberry_enum.py

This file was deleted.

71 changes: 51 additions & 20 deletions tests/enums/test_enum.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

import strawberry
from strawberry.exceptions import ObjectIsNotAnEnumError
from strawberry.exceptions.not_a_strawberry_enum import NotAStrawberryEnumError
from strawberry.types.base import get_object_definition
from strawberry.types.enum import EnumDefinition


Expand Down Expand Up @@ -120,25 +120,6 @@ class IceCreamFlavour(Enum):
assert definition.values[2].description is None


@pytest.mark.raises_strawberry_exception(
NotAStrawberryEnumError, match='Enum "IceCreamFlavour" is not a Strawberry enum'
)
def test_raises_error_when_using_enum_not_decorated():
class IceCreamFlavour(Enum):
VANILLA = strawberry.enum_value("vanilla")
STRAWBERRY = strawberry.enum_value(
"strawberry",
description="Our favourite",
)
CHOCOLATE = "chocolate"

@strawberry.type
class Query:
flavour: IceCreamFlavour

strawberry.Schema(query=Query)


def test_can_use_enum_values():
@strawberry.enum
class TestEnum(Enum):
Expand Down Expand Up @@ -169,3 +150,53 @@ class TestEnum(IntEnum):
assert TestEnum.D.value == 4

assert [x.value for x in TestEnum.__members__.values()] == [1, 2, 3, 4]


def test_default_enum_implementation() -> None:
class Foo(Enum):
BAR = "bar"
BAZ = "baz"

@strawberry.type
class Query:
@strawberry.field
def foo(self, foo: Foo) -> Foo:
return foo

schema = strawberry.Schema(Query)
res = schema.execute_sync("{ foo(foo: BAR) }")
assert not res.errors
assert res.data
assert res.data["foo"] == "BAR"


def test_default_enum_reuse() -> None:
class Foo(Enum):
BAR = "bar"
BAZ = "baz"

@strawberry.type
class SomeType:
foo: Foo
bar: Foo

definition = get_object_definition(SomeType, strict=True)
assert definition.fields[1].type is definition.fields[1].type


def test_default_enum_with_enum_value() -> None:
class Foo(Enum):
BAR = "bar"
BAZ = strawberry.enum_value("baz")

@strawberry.type
class Query:
@strawberry.field
def foo(self, foo: Foo) -> str:
return foo.value

schema = strawberry.Schema(Query)
res = schema.execute_sync("{ foo(foo: BAZ) }")
assert not res.errors
assert res.data
assert res.data["foo"] == "baz"

0 comments on commit 0d880b7

Please sign in to comment.