Почему при выполнении запроса я получаю ошибку "Cannot return null for non-nullable field"?

** СТРУКТУРА ПАПКИ **

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
  }
}

Создал модели с именами Episode, Character и Director. Написал типы, запросы и мутации, которые прекрасно работали до использования dataloader, все еще прекрасно работают при возврате набора запросов в resolver, но выдают какую-то ошибку.

Ожидаемым результатом было использование dataloader для повышения производительности, метод resole возвращает объект Promise, а запрос после выполнения показывает вышеприведенный вывод

ИСПРАВЛЕНО ПУТЕМ РЕДАКТИРОВАНИЯ RESOLVER И LOADER И ИЗМЕНЕНИЯ ВЕРСИИ GRAPHENE И DJANGO

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]
        )

Разрешитель

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 
Вернуться на верх