Unions
In this section, we'll cover how GraphQL Unions work in Undine.
Unions are abstract GraphQL types that represent a group of ObjectTypes
that need to be returned together, e.g. for a search result.
UnionType
In Undine, a GraphQL Union between two or more QueryTypes is implemented using a UnionType.
The QueryTypes in the Union should be added as generic type parameters to the UnionType.
| from undine import QueryType, UnionType
from .models import Project, Task
class TaskType(QueryType[Task]): ...
class ProjectType(QueryType[Project]): ...
class SearchObjects(UnionType[TaskType, ProjectType]): ...
|
Usage in Entrypoints
An Entrypoint created using a UnionType as the reference will return
all instances of all QueryTypes it contains.
| from undine import Entrypoint, QueryType, RootType, UnionType
from .models import Project, Task
class TaskType(QueryType[Task]): ...
class ProjectType(QueryType[Project]): ...
class SearchObjects(UnionType[TaskType, ProjectType]): ...
class Query(RootType):
search_objects = Entrypoint(SearchObjects, many=True)
|
This Entrypoint can be queried like this:
| query {
searchObjects {
... on TaskType {
name
}
... on ProjectType {
name
}
__typename
}
}
|
Filtering
By default, an Entrypoint for a UnionType will return all instances of all QueryTypes it contains.
However, if those QueryTypes implement a FilterSet or an
OrderSet, those will also be available on the UnionType Entrypoint.
| from undine import Entrypoint, FilterSet, OrderSet, QueryType, RootType, UnionType
from .models import Project, Task
class TaskFilterSet(FilterSet[Task]): ...
class TaskOrderSet(OrderSet[Task]): ...
@TaskFilterSet
@TaskOrderSet
class TaskType(QueryType[Task]): ...
class ProjectFilterSet(FilterSet[Project]): ...
class ProjectOrderSet(OrderSet[Project]): ...
@ProjectFilterSet
@ProjectOrderSet
class ProjectType(QueryType[Project]): ...
class SearchObjects(UnionType[TaskType, ProjectType]): ...
class Query(RootType):
search_objects = Entrypoint(SearchObjects, many=True)
|
This creates the following Entrypoint:
| type Query {
searchObjects(
filterTask: TaskFilterSet
orderByTask: [TaskOrderSet!]
filterProject: ProjectFilterSet
orderByProject: [ProjectOrderSet!]
): [Commentable!]!
}
|
This allows filtering and ordering the different types of models in the UnionType separately.
To filter and order across different Models in the Union, you can implement
a FilterSet or an OrderSet
for the same Models as the QueryTypes in the UnionType and add it to the UnionType.
| from undine import Entrypoint, FilterSet, OrderSet, QueryType, RootType, UnionType
from .models import Project, Task
class TaskType(QueryType[Task]): ...
class ProjectType(QueryType[Project]): ...
class SearchObjectsFilterSet(FilterSet[Task, Project]): ...
class SearchObjectsOrderSet(OrderSet[Task, Project]): ...
@SearchObjectsFilterSet
@SearchObjectsOrderSet
class SearchObjects(UnionType[TaskType, ProjectType]): ...
class Query(RootType):
search_objects = Entrypoint(SearchObjects, many=True)
|
This creates the following Entrypoint:
| type Query {
searchObjects(
filter: SearchObjectsFilterSet
orderBy: [SearchObjectsOrderSet!]
): [Commentable!]!
}
|
Note that a FilterSet or OrderSet created for multiple Models like this
should only contain Filters and Orders which will work on all Models in the UnionType.
For example, a "name" Filter can be added to the FilterSet if all Models contain
a "name" field of type CharField.
UnionTypes can be paginated just like any QueryType.
| from undine import Entrypoint, QueryType, RootType, UnionType
from undine.relay import Connection
from .models import Project, Task
class TaskType(QueryType[Task]): ...
class ProjectType(QueryType[Project]): ...
class SearchObjects(UnionType[TaskType, ProjectType]): ...
class Query(RootType):
search_objects = Entrypoint(Connection(SearchObjects))
|
See the Pagination section for more details on pagination.
Schema name
By default, the name of the generated GraphQL Union for a UnionType class
is the name of the UnionType class. If you want to change the name separately,
you can do so by setting the schema_name argument:
| from undine import QueryType, UnionType
from .models import Project, Task
class TaskType(QueryType[Task]): ...
class ProjectType(QueryType[Project]): ...
class SearchObjects(UnionType[TaskType, ProjectType], schema_name="Search"): ...
|
Description
A description for a UnionType can be provided as a docstring.
| from undine import QueryType, UnionType
from .models import Project, Task
class TaskType(QueryType[Task]): ...
class ProjectType(QueryType[Project]): ...
class SearchObjects(UnionType[TaskType, ProjectType]):
"""Description"""
|
Directives
You can add directives to the UnionType by providing them using the directives argument.
The directive must be usable in the UNION location.
| from graphql import DirectiveLocation
from undine import QueryType, UnionType
from undine.directives import Directive
from .models import Project, Task
class MyDirective(Directive, locations=[DirectiveLocation.UNION]): ...
class TaskType(QueryType[Task]): ...
class ProjectType(QueryType[Project]): ...
class SearchObjects(UnionType[TaskType, ProjectType], directives=[MyDirective()]): ...
|
You can also add directives using decorator syntax.
| from graphql import DirectiveLocation
from undine import QueryType, UnionType
from undine.directives import Directive
from .models import Project, Task
class MyDirective(Directive, locations=[DirectiveLocation.UNION]): ...
class TaskType(QueryType[Task]): ...
class ProjectType(QueryType[Project]): ...
@MyDirective()
class SearchObjects(UnionType[TaskType, ProjectType]): ...
|
See the Directives section for more details on directives.
Visibility
This is an experimental feature that needs to be enabled using the
EXPERIMENTAL_VISIBILITY_CHECKS setting.
You can hide a UnionType from certain users by using the __is_visible__ method.
Hiding the UnionType means that it will not be included in introspection queries,
and trying to use it in operations will result in an error that looks exactly like
the UnionType didn't exist in the first place.
| from undine import QueryType, UnionType
from undine.typing import DjangoRequestProtocol
from .models import Project, Task
class TaskType(QueryType[Task]): ...
class ProjectType(QueryType[Project]): ...
class SearchObjects(UnionType[TaskType, ProjectType]):
@classmethod
def __is_visible__(cls, request: DjangoRequestProtocol) -> bool:
return request.user.is_superuser
|
When using visibility checks, you should also disable "did you mean" suggestions
using the ALLOW_DID_YOU_MEAN_SUGGESTIONS setting.
Otherwise, a hidden field might show up in them.
GraphQL Extensions
You can provide custom extensions for the UnionType by providing an
extensions argument with a dictionary containing them.
| from undine import QueryType, UnionType
from .models import Project, Task
class TaskType(QueryType[Task]): ...
class ProjectType(QueryType[Project]): ...
class SearchObjects(UnionType[TaskType, ProjectType], extensions={"foo": "bar"}): ...
|
UnionType extensions are made available in the GraphQL Union extensions
after the schema is created. The UnionType itself is found in the GraphQL Union extensions
under a key defined by the UNION_TYPE_EXTENSIONS_KEY
setting.