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.
| 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]): ...
|
You can also use decorator syntax to add an InterfaceType to a QueryType.
| from graphql import GraphQLNonNull, GraphQLString
from undine import InterfaceField, InterfaceType, QueryType
from .models import Task
class Named(InterfaceType):
name = InterfaceField(GraphQLNonNull(GraphQLString))
@Named
class TaskType(QueryType[Task]): ...
|
Note that 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
An Entrypoint created using an InterfaceType as the reference will return
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, an InterfaceType Entrypoint 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]): ...
@Named
@TaskFilterSet
@TaskOrderSet
class TaskType(QueryType[Task]): ...
class StepFilterSet(FilterSet[Step]): ...
class StepOrderSet(OrderSet[Step]): ...
@Named
@StepFilterSet
@StepOrderSet
class StepType(QueryType[Step]): ...
class Query(RootType):
named = Entrypoint(Named, many=True)
|
This creates the following Entrypoint:
| type Query {
named(
filterTask: TaskFilterSet
orderByTask: [TaskOrderSet!]
filterStep: StepFilterSet
orderByStep: [StepOrderSet!]
): [Named!]!
}
|
This allows filtering and ordering the different types of models in the InterfaceType separately.
To filter and order across the different Models that implement the InterfaceType, you can implement
a FilterSet or an OrderSet
for the same Models as the QueryTypes implementing the InterfaceType and add it to the InterfaceType.
| from graphql import GraphQLNonNull, GraphQLString
from undine import Entrypoint, Filter, FilterSet, InterfaceField, InterfaceType, Order, OrderSet, QueryType, RootType
from .models import Step, Task
class NamedFilterSet(FilterSet[Task, Step], auto=False):
name = Filter()
name_contains = Filter(lookup="icontains", field_name="name")
class NamedOrderSet(OrderSet[Task, Step], auto=False):
name = Order()
@NamedFilterSet
@NamedOrderSet
class Named(InterfaceType):
name = InterfaceField(GraphQLNonNull(GraphQLString))
@Named
class TaskType(QueryType[Task]): ...
@Named
class StepType(QueryType[Step]): ...
class Query(RootType):
named = Entrypoint(Named, many=True)
|
This creates the following Entrypoint:
| type Query {
named(
filter: NamedFilterSet
orderBy: [NamedOrderSet!]
): [Named!]!
}
|
Note that a FilterSet or OrderSet created for multiple Models like this
should only contain Filters and Orders which will work on all Models that
implement the InterfaceType. For this reason, each Filter and Order
attached to the InterfaceType must reference a field declared as an
InterfaceField on the InterfaceType since those are the only fields guaranteed
to exist on all implementing QueryTypes.
InterfaceTypes can be paginated just like any QueryType.
| from graphql import GraphQLNonNull, GraphQLString
from undine import Entrypoint, InterfaceField, InterfaceType, QueryType, RootType
from undine.relay import Connection
from .models import Step, Task
class Named(InterfaceType):
name = InterfaceField(GraphQLNonNull(GraphQLString))
@Named
class TaskType(QueryType[Task]): ...
@Named
class StepType(QueryType[Step]): ...
class Query(RootType):
named = Entrypoint(Connection(Named))
|
See the Pagination section for more details on pagination.
Schema name
By default, the name of the generated GraphQL Interface for a InterfaceType class
is the name of the InterfaceType class. If you want to change the name separately,
you can do so by setting the schema_name argument:
| from graphql import GraphQLNonNull, GraphQLString
from undine import InterfaceField, InterfaceType
class Named(InterfaceType, schema_name="HasName"):
name = InterfaceField(GraphQLNonNull(GraphQLString))
|
Description
You can provide a description for the InterfaceType by adding a docstring to the class.
| from graphql import GraphQLNonNull, GraphQLString
from undine import InterfaceField, InterfaceType
class Named(InterfaceType):
"""Description."""
name = InterfaceField(GraphQLNonNull(GraphQLString))
|
Caching
You can set custom caching rules for InterfaceTypes using the cache_time
and cache_per_user arguments.
| from graphql import GraphQLNonNull, GraphQLString
from undine import InterfaceField, InterfaceType
class Named(InterfaceType, cache_time=10, cache_per_user=True):
name = InterfaceField(GraphQLNonNull(GraphQLString))
|
See the Entrypoint caching section for more details.
Directives
You can add directives to the InterfaceType by providing them using the directives argument.
The directive must be usable in the INTERFACE location.
| from graphql import DirectiveLocation, GraphQLNonNull, GraphQLString
from undine import Directive, InterfaceField, InterfaceType
class MyDirective(Directive, locations=[DirectiveLocation.INTERFACE]): ...
class Named(InterfaceType, directives=[MyDirective()]):
name = InterfaceField(GraphQLNonNull(GraphQLString))
|
You can also add directives using decorator syntax.
| from graphql import DirectiveLocation, GraphQLNonNull, GraphQLString
from undine import Directive, InterfaceField, InterfaceType
class MyDirective(Directive, locations=[DirectiveLocation.INTERFACE]): ...
@MyDirective()
class Named(InterfaceType):
name = InterfaceField(GraphQLNonNull(GraphQLString))
|
See the Directives section for more details on directives.
GraphQL Extensions
You can provide custom extensions for the InterfaceType by providing an
extensions argument with a dictionary containing them. These can then be used
however you wish to extend the functionality of the InterfaceType.
| 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 GraphQL Interface extensions
under a key defined by the INTERFACE_TYPE_EXTENSIONS_KEY
setting.
InterfaceField
When a QueryType implements an InterfaceType, all of the InterfaceFields on
the InterfaceType are converted to Fields on the QueryType. The converted Field must
correspond to a Model field on the QueryType Model, and the InterfaceField output type
must match the GraphQL output type converted from Model field. In other words, all InterfaceFields
must correspond to Model fields when implemented on a QueryType.
An InterfaceField always requires its desired GraphQL output type to be defined.
| 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.
If defined, these must also match the Model field of the implementing QueryType.
| from graphql import GraphQLArgument, GraphQLNonNull, GraphQLString
from undine import InterfaceField, InterfaceType
class Named(InterfaceType):
name = InterfaceField(
GraphQLNonNull(GraphQLString),
args={"name": GraphQLArgument(GraphQLNonNull(GraphQLString))},
)
|
Field name
By default, the name of the field in the Django model is the same as the name of the InterfaceField.
If you want to change the name of the field in the Django model separately,
you can do so by setting the field_name argument:
| from graphql import GraphQLNonNull, GraphQLString
from undine import InterfaceField, InterfaceType
class Named(InterfaceType):
name = InterfaceField(GraphQLNonNull(GraphQLString), field_name="name")
|
Schema name
By default, the name of the Interface field generated from a InterfaceField is the same
as the name of the InterfaceField on the InterfaceType class (converted to camelCase if
CAMEL_CASE_SCHEMA_FIELDS is enabled).
If you want to change the name of the Interface field separately,
you can do so by setting the schema_name argument:
| from graphql import GraphQLNonNull, GraphQLString
from undine import InterfaceField, InterfaceType
class Named(InterfaceType):
name = InterfaceField(GraphQLNonNull(GraphQLString), schema_name="name")
|
This can be useful when the desired name of the Interface field is a Python keyword
and cannot be used as the Field attribute name.
Description
A description for a field can be provided in one of two ways:
1) By setting the description argument.
| 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, if ENABLE_CLASS_ATTRIBUTE_DOCSTRINGS is enabled.
| 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.
| from graphql import GraphQLNonNull, GraphQLString
from undine import InterfaceField, InterfaceType
class Named(InterfaceType):
name = InterfaceField(GraphQLNonNull(GraphQLString), deprecation_reason="Use `title` instead.")
|
Caching
You can set custom caching rules for InterfaceFields using the cache_time
and cache_per_user arguments.
| from graphql import GraphQLNonNull, GraphQLString
from undine import InterfaceField, InterfaceType
class Named(InterfaceType):
name = InterfaceField(GraphQLNonNull(GraphQLString), cache_time=10, cache_per_user=True)
|
See the Entrypoint caching section for more details.
Directives
You can add directives to the IntefaceField by providing them using the directives argument.
The directive must be usable in the FIELD_DEFINITION location.
| from graphql import DirectiveLocation, GraphQLNonNull, GraphQLString
from undine import Directive, InterfaceField, InterfaceType
class MyDirective(Directive, locations=[DirectiveLocation.FIELD_DEFINITION]): ...
class Named(InterfaceType):
name = InterfaceField(GraphQLNonNull(GraphQLString), directives=[MyDirective()])
|
You can also add them using the @ operator (which kind of looks like GraphQL syntax):
| from graphql import DirectiveLocation, GraphQLNonNull, GraphQLString
from undine import Directive, InterfaceField, InterfaceType
class MyDirective(Directive, locations=[DirectiveLocation.FIELD_DEFINITION]): ...
class Named(InterfaceType):
name = InterfaceField(GraphQLNonNull(GraphQLString)) @ MyDirective()
|
See the Directives section for more details on directives.
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.
| 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 Interface field extensions
after the schema is created. The InterfaceField itself is found in the GraphQL Interface field extensions
under a key defined by the INTERFACE_FIELD_EXTENSIONS_KEY
setting.