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 diff --git a/docs/types/interfaces.rst b/docs/types/interfaces.rst index 8c049a24..21cf2173 100644 --- a/docs/types/interfaces.rst +++ b/docs/types/interfaces.rst @@ -1,60 +1,170 @@ 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) + + +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] } type Human implements Character { - name: String - bornIn: String + id: ID! + name: String! + friends: [Character] + starships: [Starship] + homePlanet: String } + + type Droid implements Character { + id: ID! + name: String! + friends: [Character] + 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.Int(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') + + 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 `_. + +For example, the following query: + +.. code:: + + query HeroForEpisode($episode: Int!) { + 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" + } + } + } + +Resolving data objects to types +------------------------------- + +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). This works +well with ``ObjectType`` and ``Scalar`` fields, 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