diff --git a/.alexrc b/.alexrc index 587b769682..ea3756ce5a 100644 --- a/.alexrc +++ b/.alexrc @@ -12,6 +12,6 @@ "special", "primitive", "invalid", - "crash", + "crash" ] } diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 556ad1132b..6c4163edbd 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -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/ @@ -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 @@ -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] diff --git a/RELEASE.md b/RELEASE.md new file mode 100644 index 0000000000..fedbf0c263 --- /dev/null +++ b/RELEASE.md @@ -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 +``` diff --git a/docs/errors/not-a-strawberry-enum.md b/docs/errors/not-a-strawberry-enum.md deleted file mode 100644 index ceaf00c53d..0000000000 --- a/docs/errors/not-a-strawberry-enum.md +++ /dev/null @@ -1,62 +0,0 @@ ---- -title: Not a Strawberry Enum Error ---- - -# Not a Strawberry Enum Error - -## Description - -This error is thrown when trying to use an enum that is not a Strawberry enum, -for example the following code will throw this error: - -```python -import strawberry - - -# note the lack of @strawberry.enum here: -class IceCreamFlavour(Enum): - VANILLA = strawberry.enum_value("vanilla") - STRAWBERRY = strawberry.enum_value( - "strawberry", - description="Our favourite", - ) - CHOCOLATE = "chocolate" - - -@strawberry.type -class Query: - field: IceCreamFlavour - - -schema = strawberry.Schema(query=Query) -``` - -This happens because Strawberry expects all enums to be decorated with -`@strawberry.enum`. - -## How to fix this error - -You can fix this error by making sure the enum you're using is decorated with -`@strawberry.enum`. For example, the following code will fix this error: - -```python -import strawberry - - -@strawberry.enum -class IceCreamFlavour(Enum): - VANILLA = strawberry.enum_value("vanilla") - STRAWBERRY = strawberry.enum_value( - "strawberry", - description="Our favourite", - ) - CHOCOLATE = "chocolate" - - -@strawberry.type -class Query: - field: IceCreamFlavour - - -schema = strawberry.Schema(query=Query) -``` diff --git a/docs/general/subscriptions.md b/docs/general/subscriptions.md index 6363fc3051..25675c08d0 100644 --- a/docs/general/subscriptions.md +++ b/docs/general/subscriptions.md @@ -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 diff --git a/docs/types/enums.md b/docs/types/enums.md index cb2e99d216..743e91f80b 100644 --- a/docs/types/enums.md +++ b/docs/types/enums.md @@ -42,6 +42,17 @@ class IceCreamFlavour(Enum): CHOCOLATE = "chocolate" ``` + + +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). + + + Let's see how we can use Enums in our schema. ```python diff --git a/strawberry/annotation.py b/strawberry/annotation.py index 86d06d73e5..dff708a1a1 100644 --- a/strawberry/annotation.py +++ b/strawberry/annotation.py @@ -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, @@ -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 @@ -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) diff --git a/strawberry/exceptions/__init__.py b/strawberry/exceptions/__init__.py index 7d3b3ccb1b..ee331af721 100644 --- a/strawberry/exceptions/__init__.py +++ b/strawberry/exceptions/__init__.py @@ -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 @@ -174,7 +173,6 @@ class StrawberryGraphQLError(GraphQLError): "UnresolvedFieldTypeError", "PrivateStrawberryFieldError", "MultipleStrawberryArgumentsError", - "NotAStrawberryEnumError", "ScalarAlreadyRegisteredError", "WrongNumberOfResultsReturned", "FieldWithResolverAndDefaultValueError", diff --git a/strawberry/exceptions/not_a_strawberry_enum.py b/strawberry/exceptions/not_a_strawberry_enum.py deleted file mode 100644 index dd0c94d48f..0000000000 --- a/strawberry/exceptions/not_a_strawberry_enum.py +++ /dev/null @@ -1,38 +0,0 @@ -from __future__ import annotations - -from functools import cached_property -from typing import TYPE_CHECKING, Optional - -from .exception import StrawberryException -from .utils.source_finder import SourceFinder - -if TYPE_CHECKING: - from enum import EnumMeta - - from .exception_source import ExceptionSource - - -class NotAStrawberryEnumError(StrawberryException): - def __init__(self, enum: EnumMeta) -> None: - self.enum = enum - - self.message = f'Enum "{enum.__name__}" is not a Strawberry enum.' - self.rich_message = ( - f"Enum `[underline]{enum.__name__}[/]` is not a Strawberry enum." - ) - self.suggestion = ( - "To fix this error you can declare the enum using `@strawberry.enum`." - ) - - self.annotation_message = "enum defined here" - - super().__init__(self.message) - - @cached_property - def exception_source(self) -> Optional[ExceptionSource]: - if self.enum is None: - return None # pragma: no cover - - source_finder = SourceFinder() - - return source_finder.find_class_from_object(self.enum) diff --git a/tests/enums/test_enum.py b/tests/enums/test_enum.py index 2189fba3d6..c0dea47813 100644 --- a/tests/enums/test_enum.py +++ b/tests/enums/test_enum.py @@ -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 @@ -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): @@ -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"