From 63594feda29ed73cf4bfd2555c3db41f6c29a29f Mon Sep 17 00:00:00 2001 From: Dusty Phillips Date: Mon, 30 Oct 2023 11:46:41 -0300 Subject: [PATCH] fix: Remove GlobalID type from relay schema. Having this type in the schema violates the relay spec. The GlobalID is technically an interpretation of the ID type, and the schema should always return ID for that. I created a new scalar type called ID. It's unclear how this collides with the strawberry.ID, but given that strawberry.ID isn't actually a scalar type, I am hoping it is ok. --- docs/guides/relay.md | 13 +++++++----- strawberry/relay/types.py | 14 +++++++++++-- tests/relay/snapshots/schema.gql | 6 +++--- .../snapshots/schema_future_annotations.gql | 6 +++--- tests/relay/test_fields.py | 7 ++----- tests/relay/test_schema.py | 21 ++++++------------- 6 files changed, 34 insertions(+), 33 deletions(-) diff --git a/docs/guides/relay.md b/docs/guides/relay.md index 24dbf8f1f8..264b458aa4 100644 --- a/docs/guides/relay.md +++ b/docs/guides/relay.md @@ -97,10 +97,9 @@ class Query: This will generate a schema like this: ```graphql -scalar GlobalID interface Node { - id: GlobalID! + id: ID! } type PageInfo { @@ -111,7 +110,7 @@ type PageInfo { } type Fruit implements Node { - id: GlobalID! + id: ID! name: String! weight: Float! } @@ -127,7 +126,7 @@ type FruitConnection { } type Query { - node(id: GlobalID!): Node! + node(id: ID!): Node! fruits( before: String = null after: String = null @@ -199,7 +198,7 @@ It can be defined in in the `Query` objects in 4 ways: a `Node` instance. This is the most basic way to define it. - `node: Optional[Node]`: The same as `Node`, but if the given object doesn't exist, it will return `null`. -- `node: List[Node]`: This will define a field that accepts `[GlobalID!]!` and +- `node: List[Node]`: This will define a field that accepts `[ID!]!` and returns a list of `Node` instances. They can even be from different types. - `node: List[Optional[Node]]`: The same as `List[Node]`, but the returned list can contain `null` values if the given objects don't exist. @@ -407,6 +406,10 @@ similar use case, like SQLAlchemy, etc. The `GlobalID` scalar is a special object that contains all the info necessary to identify and retrieve a given object that implements the `Node` interface. +It constrains the existing `strawberry.ID!` type, which can be any string. Under +relay, the GlobalID is composed of the type and the internal ID, encoded as a base64 +string. + It can for example be useful in a mutation, to receive and object and retrieve it in its resolver. For example: diff --git a/strawberry/relay/types.py b/strawberry/relay/types.py index 2db9242a4c..0d6c386058 100644 --- a/strawberry/relay/types.py +++ b/strawberry/relay/types.py @@ -16,6 +16,7 @@ Iterable, Iterator, List, + NewType, Optional, Sequence, Type, @@ -26,6 +27,7 @@ ) from typing_extensions import Annotated, Literal, Self, TypeAlias, get_args, get_origin +from strawberry.custom_scalar import scalar from strawberry.field import field from strawberry.lazy_type import LazyType from strawberry.object_type import interface, type @@ -41,7 +43,6 @@ from .utils import from_base64, to_base64 if TYPE_CHECKING: - from strawberry.scalars import ID from strawberry.utils.await_maybe import AwaitableOrValue _T = TypeVar("_T") @@ -298,6 +299,15 @@ def resolve_node_sync(self, info, *, required=False, ensure_type=None) -> Any: return node +ID = scalar(NewType("ID", GlobalID), + description="ID in the Global ID format, a base64 encoded string that" + "wraps the type and the internal id of the object." + "This is a specialization of the strawberry.ID type, which can be any string.", + specified_by_url="https://graphql.org/learn/global-object-identification/", + serialize=str, + parse_value=GlobalID.from_id +) + class NodeIDPrivate(StrawberryPrivate): """Annotate a type attribute as its id. @@ -355,7 +365,7 @@ class Node: @field(name="id", description="The Globally Unique ID of this object") @classmethod - def _id(cls, root: Node, info: Info) -> GlobalID: + def _id(cls, root: Node, info: Info) -> ID: # NOTE: root might not be a Node instance when using integrations which # return an object that is compatible with the type (e.g. the django one). # In that case, we can retrieve the type itself from info diff --git a/tests/relay/snapshots/schema.gql b/tests/relay/snapshots/schema.gql index 45eb0a8640..04f6f36d3d 100644 --- a/tests/relay/snapshots/schema.gql +++ b/tests/relay/snapshots/schema.gql @@ -4,7 +4,7 @@ type CreateFruitPayload { type Fruit implements Node { """The Globally Unique ID of this object""" - id: GlobalID! + id: ID! name: String! color: String! } @@ -19,7 +19,7 @@ type FruitAlikeConnection { type FruitAsync implements Node { """The Globally Unique ID of this object""" - id: GlobalID! + id: ID! name: String! color: String! } @@ -81,7 +81,7 @@ type Mutation { """An object with a Globally Unique ID""" interface Node { """The Globally Unique ID of this object""" - id: GlobalID! + id: ID! } """Information to aid in pagination.""" diff --git a/tests/relay/snapshots/schema_future_annotations.gql b/tests/relay/snapshots/schema_future_annotations.gql index 45eb0a8640..04f6f36d3d 100644 --- a/tests/relay/snapshots/schema_future_annotations.gql +++ b/tests/relay/snapshots/schema_future_annotations.gql @@ -4,7 +4,7 @@ type CreateFruitPayload { type Fruit implements Node { """The Globally Unique ID of this object""" - id: GlobalID! + id: ID! name: String! color: String! } @@ -19,7 +19,7 @@ type FruitAlikeConnection { type FruitAsync implements Node { """The Globally Unique ID of this object""" - id: GlobalID! + id: ID! name: String! color: String! } @@ -81,7 +81,7 @@ type Mutation { """An object with a Globally Unique ID""" interface Node { """The Globally Unique ID of this object""" - id: GlobalID! + id: ID! } """Information to aid in pagination.""" diff --git a/tests/relay/test_fields.py b/tests/relay/test_fields.py index 76ceca95b3..58e4dbbc5b 100644 --- a/tests/relay/test_fields.py +++ b/tests/relay/test_fields.py @@ -1497,7 +1497,7 @@ class Query: expected = ''' type Fruit implements Node { """The Globally Unique ID of this object""" - id: GlobalID! + id: ID! } """A connection to a list of items.""" @@ -1518,13 +1518,10 @@ class Query: node: Fruit! } - """__GLOBAL_ID_DESC__""" - scalar GlobalID @specifiedBy(url: "https://relay.dev/graphql/objectidentification.htm") - """An object with a Globally Unique ID""" interface Node { """The Globally Unique ID of this object""" - id: GlobalID! + id: ID! } """Information to aid in pagination.""" diff --git a/tests/relay/test_schema.py b/tests/relay/test_schema.py index 4f727e8a33..5fc7f98bf1 100644 --- a/tests/relay/test_schema.py +++ b/tests/relay/test_schema.py @@ -52,7 +52,7 @@ def fruits(self) -> List[Fruit]: ''' type Fruit implements Node { """The Globally Unique ID of this object""" - id: GlobalID! + id: ID! } """A connection to a list of items.""" @@ -73,13 +73,10 @@ def fruits(self) -> List[Fruit]: node: Fruit! } - """__GLOBAL_ID_DESC__""" - scalar GlobalID @specifiedBy(url: "https://relay.dev/graphql/objectidentification.htm") - """An object with a Globally Unique ID""" interface Node { """The Globally Unique ID of this object""" - id: GlobalID! + id: ID! } """Information to aid in pagination.""" @@ -163,7 +160,7 @@ def fruits(self) -> List[Fruit]: ''' type Fruit implements Node { """The Globally Unique ID of this object""" - id: GlobalID! + id: ID! } """A connection to a list of items.""" @@ -184,13 +181,10 @@ def fruits(self) -> List[Fruit]: node: Fruit! } - """__GLOBAL_ID_DESC__""" - scalar GlobalID @specifiedBy(url: "https://relay.dev/graphql/objectidentification.htm") - """An object with a Globally Unique ID""" interface Node { """The Globally Unique ID of this object""" - id: GlobalID! + id: ID! } """Information to aid in pagination.""" @@ -274,7 +268,7 @@ def fruits(self) -> List[Fruit]: ''' type Fruit implements Node { """The Globally Unique ID of this object""" - id: GlobalID! + id: ID! } """A connection to a list of items.""" @@ -295,13 +289,10 @@ def fruits(self) -> List[Fruit]: node: Fruit! } - """__GLOBAL_ID_DESC__""" - scalar GlobalID @specifiedBy(url: "https://relay.dev/graphql/objectidentification.htm") - """An object with a Globally Unique ID""" interface Node { """The Globally Unique ID of this object""" - id: GlobalID! + id: ID! } """Information to aid in pagination."""