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]): ...
|
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
:
| 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:
| 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.
| 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
.
| 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.
| 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.
| 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.
| 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.
| 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.")
|
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 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.