From 43e87768d2a4b9a069681ede6ca97a14e67bd1be Mon Sep 17 00:00:00 2001 From: Jonathan Kim Date: Sat, 16 Jun 2018 15:10:32 +0100 Subject: [PATCH 1/5] Update interface documentation --- docs/types/interfaces.rst | 140 ++++++++++++++++++++++++++++++-------- 1 file changed, 111 insertions(+), 29 deletions(-) diff --git a/docs/types/interfaces.rst b/docs/types/interfaces.rst index 8c049a24..e8bcf1d1 100644 --- a/docs/types/interfaces.rst +++ b/docs/types/interfaces.rst @@ -1,60 +1,142 @@ Interfaces ========== -An Interface contains the essential fields that will be implemented by -multiple ObjectTypes. +An *Interface* is an abstract type that defines a certain set of fields that a +type must include to implement the interface. -The basics: - -- Each Interface is a Python class that inherits from ``graphene.Interface``. -- Each attribute of the Interface represents a GraphQL field. - -Quick example -------------- - -This example model defines a ``Character`` interface with a name. ``Human`` -and ``Droid`` are two implementations of that interface. +For example, you can define an Interface ``Character`` that represents any +character in the Star Wars trilogy: .. code:: python import graphene class Character(graphene.Interface): - name = graphene.String() + id = graphene.ID(required=True) + name = graphene.String(required=True) + friends = graphene.List(lambda: Character) + appears_in = graphene.List(Episode, required=True) + + +Any ObjectType that implements ``Character`` will have these exact fields, with +these arguments and return types. + +For example, here are some types that might implement ``Character``: + +.. code:: python - # Human is a Character implementation class Human(graphene.ObjectType): class Meta: interfaces = (Character, ) - born_in = graphene.String() + starships = graphene.List(Starship) + home_planet = graphene.String() - # Droid is a Character implementation class Droid(graphene.ObjectType): class Meta: interfaces = (Character, ) - function = graphene.String() + primary_function = graphene.String() -``name`` is a field on the ``Character`` interface that will also exist on both -the ``Human`` and ``Droid`` ObjectTypes (as those implement the ``Character`` -interface). Each ObjectType may define additional fields. +Both of these types have all of the fields from the ``Character`` interface, +but also bring in extra fields, ``home_planet``, ``starships`` and +``primary_function``, that are specific to that particular type of character. -The above types have the following representation in a schema: +The full GraphQL schema defition will look like this: .. code:: interface Character { - name: String - } - - type Droid implements Character { - name: String - function: String + id: ID! + name: String! + friends: [Character] + appearsIn: [Episode]! } type Human implements Character { - name: String - bornIn: String + id: ID! + name: String! + friends: [Character] + appearsIn: [Episode]! + starships: [Starship] + homePlanet: String + } + + type Droid implements Character { + id: ID! + name: String! + friends: [Character] + appearsIn: [Episode]! + primaryFunction: String + } + +Interfaces are useful when you want to return an object or set of objects, +which might be of several different types. + +For example, you can define a field ``hero`` that resolves to any +``Character``, depending on the episode, like this: + +.. code:: python + + class Query(graphene.ObjectType): + hero = graphene.Field( + Character, + required=True, + episode=graphene.Field(Episode, required=True) + ) + + def resolve_hero(_, info, episode): + # Luke is the hero of Episode V + if episode == 5: + return get_human(name='Luke Skywalker') + return get_droid(name='R2-D2') + +This allows you to directly query for fields that exist on the Character interface +as well as selecting specific fields on any type that implments the interface +using `inline fragments `_. + +For example, the following query: + +.. code:: + + query HeroForEpisode($episode: Episode!) { + hero(episode: $episode) { + __typename + name + ... on Droid { + primaryFunction + } + ... on Human { + homePlanet + } + } + } + +Will return the following data with variables ``{ "episode": 4 }``: + +.. code:: json + + { + "data": { + "hero": { + "__typename": "Droid", + "name": "R2-D2", + "primaryFunction": "Astromech" + } + } + } + +And different data with the variables ``{ "episode": 5 }``: + +.. code:: json + + { + "data": { + "hero": { + "__typename": "Human", + "name": "Luke Skywalker", + "homePlanet": "Tatooine" + } + } } From 3f6c3a7a9959bc3e4be3a0eb6422678347479793 Mon Sep 17 00:00:00 2001 From: Jonathan Kim Date: Sun, 17 Jun 2018 11:23:08 +0100 Subject: [PATCH 2/5] Clean up doc and add resolve_type documentation --- docs/types/interfaces.rst | 35 +++++++++++++++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/docs/types/interfaces.rst b/docs/types/interfaces.rst index e8bcf1d1..113edee5 100644 --- a/docs/types/interfaces.rst +++ b/docs/types/interfaces.rst @@ -83,7 +83,7 @@ For example, you can define a field ``hero`` that resolves to any hero = graphene.Field( Character, required=True, - episode=graphene.Field(Episode, required=True) + episode=graphene.Int(required=True) ) def resolve_hero(_, info, episode): @@ -92,6 +92,8 @@ For example, you can define a field ``hero`` that resolves to any return get_human(name='Luke Skywalker') return get_droid(name='R2-D2') + schema = graphene.Schema(query=Query, types=[Human, Droid]) + This allows you to directly query for fields that exist on the Character interface as well as selecting specific fields on any type that implments the interface using `inline fragments `_. @@ -100,7 +102,7 @@ For example, the following query: .. code:: - query HeroForEpisode($episode: Episode!) { + query HeroForEpisode($episode: Int!) { hero(episode: $episode) { __typename name @@ -140,3 +142,32 @@ And different data with the variables ``{ "episode": 5 }``: } } } + +Resolving data objects to types +------------------------------- + +As you build out your schema in Graphene it is common for your resolvers to +return objects that represent the data backing your GraphQL types rather than +instances of the Graphene types (e.g. Django or SQLAlchemy models). However +when you start using Interfaces you might come across this error: + +.. code:: + + "Abstract type Character must resolve to an Object type at runtime for field Query.hero ..." + +This happens because Graphene doesn't have enough information to convert the +data object into a Graphene type needed to resolve the ``Interface``. To solve +this you can define a ``resolve_type`` class method on the ``Interface`` which +maps a data object to a Graphene type: + +.. code:: python + + class Character(graphene.Interface): + id = graphene.ID(required=True) + name = graphene.String(required=True) + + @classmethod + def resolve_type(cls, instance, info): + if instance.type == 'DROID': + return Droid + return Human From a2db7c5dae36ac3c9b2f1d261a27d1efb0bacbb7 Mon Sep 17 00:00:00 2001 From: Jonathan Kim Date: Sun, 17 Jun 2018 11:24:59 +0100 Subject: [PATCH 3/5] Remove an unnecessary field --- docs/types/interfaces.rst | 4 ---- 1 file changed, 4 deletions(-) diff --git a/docs/types/interfaces.rst b/docs/types/interfaces.rst index 113edee5..a8d06b9a 100644 --- a/docs/types/interfaces.rst +++ b/docs/types/interfaces.rst @@ -15,7 +15,6 @@ character in the Star Wars trilogy: id = graphene.ID(required=True) name = graphene.String(required=True) friends = graphene.List(lambda: Character) - appears_in = graphene.List(Episode, required=True) Any ObjectType that implements ``Character`` will have these exact fields, with @@ -51,14 +50,12 @@ The full GraphQL schema defition will look like this: id: ID! name: String! friends: [Character] - appearsIn: [Episode]! } type Human implements Character { id: ID! name: String! friends: [Character] - appearsIn: [Episode]! starships: [Starship] homePlanet: String } @@ -67,7 +64,6 @@ The full GraphQL schema defition will look like this: id: ID! name: String! friends: [Character] - appearsIn: [Episode]! primaryFunction: String } From e7ebb86e5aa163294ce051883889b15d247d092f Mon Sep 17 00:00:00 2001 From: Jonathan Kim Date: Sun, 17 Jun 2018 11:25:32 +0100 Subject: [PATCH 4/5] Re-order type list --- docs/types/index.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/types/index.rst b/docs/types/index.rst index a49c23e7..acbdb8e8 100644 --- a/docs/types/index.rst +++ b/docs/types/index.rst @@ -8,9 +8,9 @@ Types Reference enums scalars list-and-nonnull - interfaces - abstracttypes - unions objecttypes + interfaces + unions schema mutations + abstracttypes From cc54c76a3e673922e2436d335dae17d0ddbbce33 Mon Sep 17 00:00:00 2001 From: Jonathan Kim Date: Sun, 17 Jun 2018 12:05:34 +0100 Subject: [PATCH 5/5] Improve wording --- docs/types/interfaces.rst | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/types/interfaces.rst b/docs/types/interfaces.rst index a8d06b9a..21cf2173 100644 --- a/docs/types/interfaces.rst +++ b/docs/types/interfaces.rst @@ -142,10 +142,11 @@ And different data with the variables ``{ "episode": 5 }``: Resolving data objects to types ------------------------------- -As you build out your schema in Graphene it is common for your resolvers to +As you build out your schema in Graphene it's common for your resolvers to return objects that represent the data backing your GraphQL types rather than -instances of the Graphene types (e.g. Django or SQLAlchemy models). However -when you start using Interfaces you might come across this error: +instances of the Graphene types (e.g. Django or SQLAlchemy models). This works +well with ``ObjectType`` and ``Scalar`` fields, however when you start using +Interfaces you might come across this error: .. code::