Why am I getting a "Cannot return null for non-nullable field" error when doing a query?
**FOLDER STRUCTURE **
models.py
import graphene
from graphene import ObjectType, relay
from graphene_django import DjangoObjectType
from general.models import Character, Director, Episode
class CharacterType(DjangoObjectType):
pk = graphene.Int(source="pk", required=True)
class Meta:
model = Character
filter_fields = {
"name": ["exact", "icontains", "istartswith"],
"character_species": ["exact", "icontains"],
"location": ["exact"],
"status": ["exact"],
}
fields = "__all__"
interfaces = (relay.Node,)
class EpisodeType(DjangoObjectType):
pk = graphene.Int(source="pk")
class Meta:
model = Episode
filter_fields = {
"name": ["exact", "icontains", "istartswith"],
"directed_by__name": ["exact", "icontains"],
"aired_date": ["exact"],
}
fields = "__all__"
interfaces = (relay.Node,)
class DirectorType(DjangoObjectType):
pk = graphene.Int(source="pk", required=True)
class Meta:
model = Director
filter_fields = {
"name": ["exact", "icontains", "istartswith"],
"first_directed_episode__name": ["exact", "icontains"],
"last_directed_episode__name": ["exact", "icontains"],
"age": ["exact"],
}
fields = "__all__"
interfaces = (relay.Node,)
types.py
import graphene
from graphene import ObjectType, relay
from graphene_django import DjangoObjectType
from general.models import Character, Director, Episode
class CharacterType(DjangoObjectType):
pk = graphene.Int(source="pk", required=True)
class Meta:
model = Character
filter_fields = {
"name": ["exact", "icontains", "istartswith"],
"character_species": ["exact", "icontains"],
"location": ["exact"],
"status": ["exact"],
}
fields = "__all__"
interfaces = (relay.Node,)
class EpisodeType(DjangoObjectType):
pk = graphene.Int(source="pk")
class Meta:
model = Episode
filter_fields = {
"name": ["exact", "icontains", "istartswith"],
"directed_by__name": ["exact", "icontains"],
"aired_date": ["exact"],
}
fields = "__all__"
interfaces = (relay.Node,)
class DirectorType(DjangoObjectType):
pk = graphene.Int(source="pk", required=True)
class Meta:
model = Director
filter_fields = {
"name": ["exact", "icontains", "istartswith"],
"first_directed_episode__name": ["exact", "icontains"],
"last_directed_episode__name": ["exact", "icontains"],
"age": ["exact"],
}
fields = "__all__"
interfaces = (relay.Node,)
loaders.py
from collections import defaultdict
from promise import Promise
from promise.dataloader import DataLoader
from general.models import Episode
class EpisodesByDirectorIdLoader(DataLoader):
def batch_load_fn(self, director_ids):
episodes_by_director_ids = defaultdict(list)
all_episodes_of_directors = Episode.objects.filter(
directed_by__pk__in=director_ids
)
for episode in all_episodes_of_directors.iterator():
episodes_by_director_ids[episode.directed_by.pk].append(episode)
return Promise.resolve(
[
episodes_by_director_ids.get(director_id, [])
for director_id in director_ids
]
)
queries.py
import graphene
from graphene_django.filter import DjangoFilterConnectionField
from general.models import Character, Director, Episode
from .types import EpisodeType
class Query(graphene.ObjectType):
all_episodes = DjangoFilterConnectionField(EpisodeType)
@staticmethod
def resolve_all_episodes(root, info):
# return Episode.objects.all() # RETURNS QUERYSET
return info.context.episodes_by_director_id_loader.load(1) # RETURNS PROMISE OBJECT
QUERY
query{
allEpisodes{
edges{
node{
name
}
}
}
}
OUTPUT
{
"errors": [
{
"message": "Cannot return null for non-nullable field EpisodeTypeConnection.edges.",
"locations": [
{
"line": 26,
"column": 5
}
],
"path": [
"allEpisodes",
"edges"
]
}
],
"data": {
"allEpisodes": null
}
}
Created models named Episode, Character and Director. Wrote types, queries and mutations which worked perfectly before using dataloader, still works perfectly when returning queryset in resolver but shows some kind of error.
The expected result was to use dataloader to improve the performance, the resole method returns Promise object and the query after execution shows the above output
FIXED BY EDITING RESOLVER AND LOADER AND CHANGING THE GRAPHENE AND DJANGO VERSION
loaders.py
from collections import defaultdict
from promise import Promise
from promise.dataloader import DataLoader
from general.models import Episode, Director
class EpisodesByDirectorIdLoader(DataLoader):
def batch_load_fn(self, director_ids):
episodes_directed_by_director_ids = defaultdict(list)
episodes = Episode.objects.filter(directed_by_id__in=director_ids)
for episode in episodes.iterator():episodes_directed_by_director_ids[episode.directed_by_id].append(episode)
return Promise.resolve(
[episodes_directed_by_director_ids.get(director_id, []) for director_id in director_ids]
)
Resolver
class DirectorType(DjangoObjectType):
pk = graphene.Int(source="pk", required=True)
episodes_directed = graphene.List(EpisodeType)
class Meta:
model = Director
filter_fields = {
"name": ["exact", "icontains", "istartswith"],
"first_directed_episode__name": ["exact", "icontains"],
"last_directed_episode__name": ["exact", "icontains"],
"age": ["exact"],
}
only_fields = (
"id",
"name",
"age",
"first_directed_episode",
"last_directed_episode",
)
interfaces = (relay.Node,)
use_connection = True
@staticmethod
def resolve_episodes_directed(root, info, **kwargs):
# return Episode.objects.filter(directed_by_id=root.id)
return info.context.episodes_by_director_id_loader.load(root.id)
requirements.txt
aiodataloader==0.2.1
aniso8601==7.0.0
asgiref==3.5.2
backports.zoneinfo==0.2.1
black==22.10.0
click==8.1.3
Cython==0.29.32
Django==3.2.16
django-debug-toolbar==3.7.0
django-filter==22.1
django-graphiql-debug-toolbar==0.2.0
django-utils-six==2.0
factory-boy==3.2.1
Faker==15.3.2
flake8==5.0.4
graphene==2.1.9
graphene-django==2.2.0
graphql-core==2.3.2
graphql-relay==2.0.1
isort==5.10.1
mccabe==0.7.0
mypy-extensions==0.4.3
pathspec==0.10.1
platformdirs==2.5.2
promise==2.3
psycopg2-binary==2.9.5
pycodestyle==2.9.1
pyflakes==2.5.0
python-dateutil==2.8.2
pytz==2022.6
Rx==1.6.1
singledispatch==3.7.0
six==1.16.0
sqlparse==0.4.3
text-unidecode==1.3
tomli==2.0.1
typing-extensions==4.4.0