Interfaces🔗

In this section, we'll cover how GraphQL Interfaces work in Undine. Interfaces are abstract GraphQL types that represent a group of fields that an ObjectType can implement.

InterfaceType🔗

In Undine, a GraphQL Interface is implemented using the InterfaceType class and defining a number of InterfaceFields in its class body.

1
2
3
4
5
6
7
from graphql import GraphQLNonNull, GraphQLString

from undine import InterfaceField, InterfaceType


class Named(InterfaceType):
    name = InterfaceField(GraphQLNonNull(GraphQLString))

QueryTypes can implement InterfaceTypes by adding them to the QueryType using the interfaces argument in their class definition.

from graphql import GraphQLNonNull, GraphQLString

from undine import InterfaceField, InterfaceType, QueryType

from .models import Task


class Named(InterfaceType):
    name = InterfaceField(GraphQLNonNull(GraphQLString))


class TaskType(QueryType[Task], interfaces=[Named]): ...

InterfaceTypes can also implement other InterfaceTypes.

from graphql import GraphQLInt, GraphQLNonNull, GraphQLString

from undine import InterfaceField, InterfaceType


class Named(InterfaceType):
    name = InterfaceField(GraphQLNonNull(GraphQLString))


class Person(InterfaceType, interfaces=[Named]):
    age = InterfaceField(GraphQLNonNull(GraphQLInt))

Usage in Entrypoints🔗

By default, an Entrypoint for an InterfaceType can be created that returns all implementations of the InterfaceType.

from graphql import GraphQLNonNull, GraphQLString

from undine import Entrypoint, InterfaceField, InterfaceType, QueryType, RootType

from .models import Step, Task


class Named(InterfaceType):
    name = InterfaceField(GraphQLNonNull(GraphQLString))


class TaskType(QueryType[Task], interfaces=[Named]): ...


class StepType(QueryType[Step], interfaces=[Named]): ...


class Query(RootType):
    named = Entrypoint(Named, many=True)

This Entrypoint can be queried like this:

query {
  named {
    name
    ... on TaskType {
      createdAt
    }
    ... on StepType {
      done
    }
    __typename
  }
}

Filtering🔗

By default, the InterfaceType will return all instances of the QueryTypes that implement it. However, if those QueryTypes implement a FilterSet or an OrderSet, those will also be available on the InterfaceType Entrypoint.

from graphql import GraphQLNonNull, GraphQLString

from undine import Entrypoint, FilterSet, InterfaceField, InterfaceType, OrderSet, QueryType, RootType

from .models import Step, Task


class Named(InterfaceType):
    name = InterfaceField(GraphQLNonNull(GraphQLString))


class TaskFilterSet(FilterSet[Task]): ...


class TaskOrderSet(OrderSet[Task]): ...


class TaskType(QueryType[Task], interfaces=[Named], filterset=TaskFilterSet, orderset=TaskOrderSet): ...


class StepFilterSet(FilterSet[Step]): ...


class StepOrderSet(OrderSet[Step]): ...


class StepType(QueryType[Step], interfaces=[Named], filterset=StepFilterSet, orderset=StepOrderSet): ...


class Query(RootType):
    named = Entrypoint(Named, many=True)

This creates the following Entrypoint:

1
2
3
4
5
6
7
8
type Query {
  named(
    filterTask: TaskFilterSet
    orderByTask: [TaskOrderSet!]
    filterStep: StepFilterSet
    orderByStep: [StepOrderSet!]
  ): [Named!]!
}

This allows filtering the different types of models in the InterfaceType separately.

The InterfaceType also provides a __process_results__ method that can be used to filter the results of the union after everything has been fetched.

from graphql import GraphQLNonNull, GraphQLString

from undine import Entrypoint, GQLInfo, InterfaceField, InterfaceType, QueryType, RootType

from .models import Step, Task


class Named(InterfaceType):
    name = InterfaceField(GraphQLNonNull(GraphQLString))

    @classmethod
    def __process_results__(cls, instances: list[Task | Step], info: GQLInfo) -> list[Task | Step]:
        return sorted(instances, key=lambda x: x.name)


class TaskType(QueryType[Task], interfaces=[Named]): ...


class StepType(QueryType[Step], interfaces=[Named]): ...


class Query(RootType):
    named = Entrypoint(Named, many=True)

By default, the number of items returned is limited per model implementing the InterfaceType. This is set by the ENTRYPOINT_LIMIT_PER_MODEL setting, but can also be changed per Entrypoint using the limit argument:

from graphql import GraphQLNonNull, GraphQLString

from undine import Entrypoint, InterfaceField, InterfaceType, QueryType, RootType

from .models import Step, Task


class Named(InterfaceType):
    name = InterfaceField(GraphQLNonNull(GraphQLString))


class TaskType(QueryType[Task], interfaces=[Named]): ...


class StepType(QueryType[Step], interfaces=[Named]): ...


class Query(RootType):
    named = Entrypoint(Named, many=True, limit=10)
What about pagination?

Pagination of InterfaceTypes is not supported yet.

Schema name🔗

By default, the name of the generated Interface is the same as the name of the InterfaceType class. If you want to change the name, you can do so by setting the schema_name argument:

1
2
3
4
5
6
7
from graphql import GraphQLNonNull, GraphQLString

from undine import InterfaceField, InterfaceType


class Named(InterfaceType, schema_name="HasName"):
    name = InterfaceField(GraphQLNonNull(GraphQLString))

Description🔗

To provide a description for the InterfaceType, you can add a docstring to the class.

1
2
3
4
5
6
7
8
9
from graphql import GraphQLNonNull, GraphQLString

from undine import InterfaceField, InterfaceType


class Named(InterfaceType):
    """Description."""

    name = InterfaceField(GraphQLNonNull(GraphQLString))

GraphQL Extensions🔗

You can provide custom extensions for the InterfaceType by providing a extensions argument with a dictionary containing them. These can then be used however you wish to extend the functionality of the InterfaceType.

1
2
3
4
5
6
7
from graphql import GraphQLNonNull, GraphQLString

from undine import InterfaceField, InterfaceType


class Named(InterfaceType, extensions={"foo": "bar"}):
    name = InterfaceField(GraphQLNonNull(GraphQLString))

InterfaceType extensions are made available in the GraphQL Interface extensions after the schema is created. The InterfaceType itself is found in the extensions under a key defined by the INTERFACE_TYPE_EXTENSIONS_KEY setting.

InterfaceField🔗

The Fields required by an interface are defined using InterfaceFields. Minimally, an InterfaceField requires the output type.

1
2
3
4
5
6
7
from graphql import GraphQLNonNull, GraphQLString

from undine import InterfaceField, InterfaceType


class Named(InterfaceType):
    name = InterfaceField(GraphQLNonNull(GraphQLString))

Optionally, you can define arguments that the InterfaceField requires.

from graphql import GraphQLArgument, GraphQLNonNull, GraphQLString

from undine import InterfaceField, InterfaceType


class Named(InterfaceType):
    name = InterfaceField(
        GraphQLNonNull(GraphQLString),
        args={"name": GraphQLArgument(GraphQLNonNull(GraphQLString))},
    )

Schema name🔗

A schema_name can be provided to override the name of the InterfaceField in the GraphQL schema. This can be useful for renaming fields for the schema, or when the desired name is a Python keyword and cannot be used as the InterfaceField attribute name.

1
2
3
4
5
6
7
from graphql import GraphQLNonNull, GraphQLString

from undine import InterfaceField, InterfaceType


class Named(InterfaceType):
    name = InterfaceField(GraphQLNonNull(GraphQLString), schema_name="name")

Description🔗

A description for a field can be provided in one of two ways:

1) By setting the description argument.

1
2
3
4
5
6
7
from graphql import GraphQLNonNull, GraphQLString

from undine import InterfaceField, InterfaceType


class Named(InterfaceType):
    name = InterfaceField(GraphQLNonNull(GraphQLString), description="The name of the object.")

2) As class attribute docstrings.

1
2
3
4
5
6
7
8
from graphql import GraphQLNonNull, GraphQLString

from undine import InterfaceField, InterfaceType


class Named(InterfaceType):
    name = InterfaceField(GraphQLNonNull(GraphQLString))
    """The name of the object."""

Deprecation reason🔗

A deprecation_reason can be provided to mark the InterfaceField as deprecated. This is for documentation purposes only, and does not affect the use of the InterfaceField.

1
2
3
4
5
6
7
from graphql import GraphQLNonNull, GraphQLString

from undine import InterfaceField, InterfaceType


class Named(InterfaceType):
    name = InterfaceField(GraphQLNonNull(GraphQLString), deprecation_reason="Use `title` instead.")

GraphQL Extensions🔗

You can provide custom extensions for the InterfaceField by providing a extensions argument with a dictionary containing them. These can then be used however you wish to extend the functionality of the InterfaceField.

1
2
3
4
5
6
7
from graphql import GraphQLNonNull, GraphQLString

from undine import InterfaceField, InterfaceType


class Named(InterfaceType):
    name = InterfaceField(GraphQLNonNull(GraphQLString), extensions={"foo": "bar"})

InterfaceField extensions are made available in the GraphQL InterfaceField extensions after the schema is created. The InterfaceField itself is found in the extensions under a key defined by the INTERFACE_FIELD_EXTENSIONS_KEY setting.