Schema๐
In this section, we'll cover how you can set up entrypoints to you GraphQL schema for executing operations in Undine.
RootTypes๐
A GraphQL schema defines a RootType for each kind of operation that it supports.
In GraphQL terms, a RootType is just a regular ObjectType that just happens
to be the root of the GraphQL Schema.
Let's take a look at this example from the Tutorial.
Here you've created the Query RootType. In Undine, the Query RootType is
required to exist for a schema to be created. Each RootType must also have at
least one Entrypoint in its class body.
As the name implies, the Query RootType is for querying data.
For mutating data, you'd create a Mutation RootType.
The Mutation RootType is optional, but if created, it must also include at least
one Entrypoint, just like the Query RootType.
For Subscription RootTypes, see the Subscriptions section.
Schema name๐
By default, the name of the generated GraphQL ObjectType from a RootType class
is the name of the RootType class. If you need to change the name separately,
you can do so by providing the schema_name argument.
Description๐
To provide a description for the RootType, you can add a docstring to the class.
Directives๐
You can add directives to the RootType by providing them using the directives argument.
The directive must be usable in the OBJECT location.
You can also add them using decorator syntax.
See the Directives section for more details on directives.
GraphQL extensions๐
You can provide custom extensions for the RootType by providing a
extensions argument with a dictionary containing them. These can then be used
however you wish to extend the functionality of the RootType.
RootType extensions are made available in the GraphQL ObjectType extensions
after the schema is created. The RootType itself is found in the GraphQL ObjectType extensions
under a key defined by the ROOT_TYPE_EXTENSIONS_KEY
setting.
Entrypoints๐
Entrypoints can be thought of as the "API endpoints inside the GraphQL schema"
from which you can execute operations like queries or mutations.
In GraphQL terms, they are the fields on the ObjectType created from a RootType
An Entrypoint always requires a reference from which it will create the
proper GraphQL resolver, output type, and arguments for the operation.
Function references๐
Using a function/method as a reference is the most basic way of creating an Entrypoint.
Function references can be used for both query and mutation Entrypoints.
See the example from the Tutorial.
With a function reference, the Entrypoint will use the decorated function as its GraphQL resolver.
The function's return type will be used as the Entrypoint's output type, so typing it is required.
You can even use a TypedDict to return an object with multiple fields.
About method signature
A method decorated with @Entrypoint is treated as a static method by the Entrypoint.
The self argument is not an instance of the RootType,
but root argument of the GraphQLField resolver. To clarify this,
it's recommended to change the argument's name to root,
as defined by the RESOLVER_ROOT_PARAM_NAME
setting.
The value of the root argument for an Entrypoint is None by default,
but can be configured using the ROOT_VALUE
setting if desired.
The info argument can be left out, but if it's included, it should always
have the GQLInfo type annotation.
You can add arguments to the Entrypoint by adding them to the function signature.
Typing these arguments is required to determine their input type.
This will add a non-null name string argument to the Entrypoint.
Note that non-null arguments are required by GraphQL, so if you wanted to make the argument
optional, you'd need to make it nullable (in which case it will be None by default)
or add a default value ourselves.
You can add a description to the Entrypoint by adding a docstring to the method.
If the method has arguments, you can add descriptions to those arguments by using
reStructuredText docstrings format.
What about other docstring formats?
Other types of docstrings can be used by parsed by providing a custom parser to the
DOCSTRING_PARSER setting that conforms to the
DocstringParserProtocol from undine.typing.
QueryType references๐
A QueryType represents a GraphQL ObjectType for querying data from a Django Model.
You can read more on QueryTypes in the Queries section.
This section will only cover using them in Entrypoints.
To create an Entrypoint for querying a single Model instance by its primary key,
simply use the QueryType class as the reference for the Entrypoint.
This would create the following field in the Query RootType:
To crete an Entrypoint for listing all instances of the Model,
add the many argument to the Entrypoint.
This would create the following field in the Query RootType:
With a list Entrypoint, if a FilterSet or an OrderSet
has been added to your QueryType, they will show up as arguments on the Entrypoint.
MutationType references๐
A MutationType represents a possible mutation operation based on a Django Model.
You can read more on MutationTypes in the Mutations section.
This section will only cover using them in Entrypoints.
To create an Entrypoint for mutating a single Model instance (a create mutation in this example),
simply use the MutationType class as the reference for the Entrypoint.
This would create the following field in the Mutation RootType:
To create an Entrypoint for mutating multiple Model instances in bulk,
add the many argument to the Entrypoint.
This would create the following field in the Mutation RootType:
Note that the total amount of objects that can be mutated in a bulk mutation is limited by the
MUTATION_INSTANCE_LIMITsetting.
Nullable๐
By default, all Entrypoints are non-null (except for function references,
which determine nullability from the function's signature). However, you can
make an Entrypoint nullable explicitly by using the nullable argument.
Limit๐
The limit argument is used by Entrypoints based on either QueryTypes,
UnionTypes, or InterfaceTypes that return
a list of items (i.e. many=True) to limit the number of objects that are fetched.
By default, this is set by the LIST_ENTRYPOINT_LIMIT setting.
Permissions๐
To add permission checks to your Entrypoint, use the @<entrypoint_name>.permissions decorator.
Note that permissions for Entrypoints based on QueryTypes or MutationTypes
are checked using that QueryType's or MutationType's permissions if no permission checks
have been defined on the Entrypoint.
Custom resolver๐
You can override the resolver for an Entrypoint by decorating
a method using the @<entrypoint_name>.resolve decorator. This
can be used, e.g., to add special-case Entrypoints for QueryTypes.
About method signature
The decorated method is treated as a static method by the Entrypoint.
The self argument is not an instance of the RootType,
but root argument of the GraphQLField resolver. To clarify this,
it's recommended to change the argument's name to root,
as defined by the RESOLVER_ROOT_PARAM_NAME
setting.
The value of the root argument for an Entrypoint is None by default,
but can be configured using the ROOT_VALUE
setting if desired.
The info argument can be left out, but if it's included, it should always
have the GQLInfo type annotation.
Note that when using this decorator, you'll override the resolver
and arguments based on the reference used in the Entrypoint.
Arguments will be taken from the additional arguments passed to the resolver,
e.g., "name" in the example above.
When overriding the resolver for Entrypoints based on QueryTypes,
the QueryType's FilterSet and OrderSet
will not be available on the Entrypoint
Overriding the resolver for Entrypoints using MutationTypes is not recommended,
as it bypasses the whole mutation process and many MutationType functions will not work.
If the resolver returns a Django Model that resolves using QueryType,
you should call the optimizer in the resolver using optimize_sync or optimize_async,
like in the above example, so that queries are optimized.
Schema name๐
By default, the name of the ObjectType field generated from an Entrypoint is the same
as the name of the Entrypoint on the RootType class (converted to camelCase if
CAMEL_CASE_SCHEMA_FIELDS is enabled).
If you want to change the name of the ObjectType field separately,
you can do so by setting the schema_name argument:
This can be useful when the desired name of the ObjectType field is a Python keyword
and cannot be used as the Entrypoint attribute name.
Description๐
By default, an Entrypoint is able to determine its description based on its reference.
For example, for a QueryType, the description is taken from the class docstring.
If the reference has no description, or you wish to add a different one,
you can provide a description in one of two ways:
1) By setting the description argument.
2) As class attribute docstrings, if ENABLE_CLASS_ATTRIBUTE_DOCSTRINGS is enabled.
When using function references, instead of a class attribute docstring, you add a docstring to the function/method used as the reference instead.
Deprecation reason๐
A deprecation_reason can be provided to mark the Entrypoint as deprecated.
This is for documentation purposes only and does not affect the use of the Entrypoint.
Complexity๐
The complexity value of an Entrypoint is used by Undine to calculate how expensive a given query
to the schema would be. Queries are rejected by Undine if they would exceed the maximum allowed complexity,
as set by the MAX_QUERY_COMPLEXITY setting.
Usually, complexity is set by QueryType Fields, but you can also set
complexity on the Entrypoint itself. This can be useful for declaring complexity of
Entrypoints not based on QueryTypes. Note that when the Entrypoint is based on a QueryType,
this complexity adds to any complexity calculated from the QueryType's Fields.
Directives๐
You can add directives to the Entrypoint by providing them using the directives argument.
The directive must be usable in the FIELD_DEFINITION location.
You can also add them using the @ operator (which kind of looks like GraphQL syntax):
See the Directives section for more details on directives.
Visibility๐
This is an experimental feature that needs to be enabled using the
EXPERIMENTAL_VISIBILITY_CHECKSsetting.
You can hide an Entrypoint from certain users by decorating a method with the
<entrypoint_name>.visible decorator. Hiding an Entrypoint 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 Entrypoint didn't exist in the first place.
About method signature
The decorated method is treated as a static method by the Entrypoint.
The self argument is not an instance of the RootType,
but the instance of the Entrypoint that is being used.
Since visibility checks occur in the validation phase of the GraphQL request,
GraphQL resolver info is not yet available. However, you can access the
Django request object using the request argument.
From this, you can, e.g., access the request user for permission checks.
When using visibility checks, you should also disable "did you mean" suggestions using the
ALLOW_DID_YOU_MEAN_SUGGESTIONSsetting. Otherwise, a hidden field might show up in them.
GraphQL extensions๐
You can provide custom extensions for the Entrypoint by providing a extensions
argument with a dictionary containing them. These can then be used however you wish to
extend the functionality of the Entrypoint.
Entrypoint extensions are made available in the GraphQL ObjectType field extensions
after the schema is created. The Entrypoint itself is found in the GraphQL field extensions
under a key defined by the ENTRYPOINT_EXTENSIONS_KEY
setting.
Schema export๐
Undine includes a management command to export your GraphQL schema.
It prints the schema to STDOUT, which can be redirected to a file like so: