GraphQL + Django: разрешать запросы с помощью необработанного запроса PostgreSQL

Как лучше всего использовать GraphQL с Django при использовании внешней базы данных для извлечения данных из нескольких таблиц (т. е. создание модели Django для представления данных не будет соответствовать одной таблице в моей базе данных)?

Мой подход заключался в том, чтобы временно отказаться от использования моделей Django, поскольку я не думаю, что еще полностью их понимаю. (Я совершенно новичок в Django, а также в GraphQL.) Я создал простой проект с приложением с подключенной внешней базой данных Postgres. Я выполнил все настройки из учебника Graphene Django, а затем наткнулся на препятствие, когда Я понял, что созданная мной модель представляет собой смесь нескольких таблиц.

У меня есть запрос, который отправляет обратно правильные столбцы, сопоставленные с полями в моей модели, но я не знаю, как сделать это динамическое соединение, чтобы при попадании в мой API он запрашивал мою базу данных и сопоставлял строки с моделью. схему, которую я определил в Django.

С тех пор мой подход заключался в том, чтобы избегать моделей и использовать более простой метод, продемонстрированный в выступлении Стивена Люшера: От нуля до GraphQL за 30 минут.

TL; DR;

Цель состоит в том, чтобы иметь возможность попасть в мою конечную точку GraphQL, использовать объект курсора из моего django.db.connection, чтобы получить список словарей, которые должны разрешаться в GraphQLList из OrderItemTypes (см. Ниже).

Проблема в том, что я получаю нули для каждого значения, когда попадаю в следующую конечную точку с запросом:

localhost:8000/api?query={orderItems{date,uuid,orderId}}

возвращает:

{ "data":{ "orderItems":[ {"date":null, "uuid":null, "orderId":null }, ... ] } }

проект / основной / приложение / schema.py

import graphene
from django.db import connection


class OrderItemType(graphene.ObjectType):
    date = graphene.core.types.custom_scalars.DateTime()
    order_id = graphene.ID()
    uuid = graphene.String()

class QueryType(graphene.ObjectType):
    name = 'Query'
    order_items = graphene.List(OrderItemType)

    def resolve_order_items(root, args, info):
        data = get_order_items()

        # data prints out properly in my terminal
        print data
        # data does not resolve properly
        return data


def get_db_dicts(sql, args=None):
    cursor = connection.cursor()
    cursor.execute(sql, args)
    columns = [col[0] for col in cursor.description]
    data = [
        dict(zip(columns, row))
        for row in cursor.fetchall() ]

    cursor.close()
    return data

def get_order_items():
    return get_db_dicts("""
        SELECT j.created_dt AS date, j.order_id, j.uuid
        FROM job AS j
        LIMIT 3;
    """)

В своем терминале я печатаю из метода разрешения QueryType и вижу, что данные успешно возвращаются из моего соединения Postgres. Однако GraphQL дает мне нули, поэтому в методе разрешения должно быть какое-то сопоставление.

[ { 'uuid': u'7584aac3-ab39-4a56-9c78-e3bb1e02dfc1', 'order_id': 25624320, 'date': datetime.datetime(2016, 1, 30, 16, 39, 40, 573400, tzinfo=<UTC>) }, ... ]

Как мне правильно сопоставить мои данные с полями, которые я определил в моем OrderItemType?

Вот еще несколько ссылок:

проект / основной / schema.py

import graphene

from project.app.schema import QueryType AppQuery

class Query(AppQuery):
    pass

schema = graphene.Schema(
    query=Query, name='Pathfinder Schema'
)

файловое дерево

|-- project
    |-- manage.py
    |-- main
        |-- app
            |-- models.py
            |-- schema.py
        |-- schema.py
        |-- settings.py
        |-- urls.py

person John William Domingo    schedule 08.09.2016    source источник


Ответы (3)


Решатели по умолчанию на GraphQL Python / Graphene пытаются выполнить разрешение заданного field_name в корневом объекте с помощью getattr. Так, например, преобразователь по умолчанию для поля с именем order_items будет выглядеть примерно так:

def resolver(root, args, context, info):
    return getattr(root, 'order_items', None)

Зная, что при выполнении getattr в dict результат будет None (для доступа к элементам dict вам придется использовать __getitem__ / dict[key]).

Таким образом, решить вашу проблему можно так же просто, как перейти с dicts на сохранение содержимого в namedtuples.

import graphene
from django.db import connection
from collections import namedtuple


class OrderItemType(graphene.ObjectType):
    date = graphene.core.types.custom_scalars.DateTime()
    order_id = graphene.ID()
    uuid = graphene.String()

class QueryType(graphene.ObjectType):
    class Meta:
        type_name = 'Query' # This will be name in graphene 1.0

    order_items = graphene.List(OrderItemType)

    def resolve_order_items(root, args, info):
        return get_order_items()    


def get_db_rows(sql, args=None):
    cursor = connection.cursor()
    cursor.execute(sql, args)
    columns = [col[0] for col in cursor.description]
    RowType = namedtuple('Row', columns)
    data = [
        RowType(*row) # Edited by John suggestion fix
        for row in cursor.fetchall() ]

    cursor.close()
    return data

def get_order_items():
    return get_db_rows("""
        SELECT j.created_dt AS date, j.order_id, j.uuid
        FROM job AS j
        LIMIT 3;
    """)

Надеюсь это поможет!

person Syrus Akbary Nieto    schedule 09.09.2016
comment
Это было невероятно полезно, спасибо, что нашли время! Одно небольшое изменение заключается в том, что список строк должен быть расширен перед передачей именованному кортежу RowType(*row). В общем, это хорошая практика или есть лучший подход к извлечению внешних данных? - person John William Domingo; 09.09.2016
comment
Если вы видите мою вторую попытку (другой ответ на этот вопрос), я использовал преобразователь для каждого поля, используя предложенный вами подход dict [key]. Из нескольких тестов, которые я только что провел, кажется, что оба метода возвращают данные за одинаковое количество времени. Интересно, является ли несколько медленное время возврата из GraphQL нормальным (1000 записей ~ 3-4 секунды после кеширования; до cachine для того же набора данных требуется ~ 6 секунд). - person John William Domingo; 09.09.2016
comment
Если вы используете версию для разработки, запрос будет решен как минимум в 10 раз быстрее (300 мс?). Вы можете установить его с помощью pip install graphene-django>=1.0.dev. Надеюсь это поможет! - person Syrus Akbary Nieto; 09.09.2016

Вот временное решение, хотя я надеюсь, что есть что-то более чистое для обработки имен полей snake_cased.

проект / основной / приложение / schema.py

from graphene import (
    ObjectType, ID, String, Int, Float, List
)
from graphene.core.types.custom_scalars import DateTime
from django.db import connection

''' Generic resolver to get the field_name from self's _root '''
def rslv(self, args, info):
    return self.get(info.field_name)


class OrderItemType(ObjectType):
    date = DateTime(resolver=rslv)
    order_id = ID()
    uuid = String(resolver=rslv)
    place_id = ID()

    ''' Special resolvers for camel_cased field_names '''
    def resolve_order_id(self, args, info):
    return self.get('order_id')

    def resolve_place_id(self, args, info):
        return self.get('place_id')

class QueryType(ObjectType):
    name = 'Query'
    order_items = List(OrderItemType)

    def resolve_order_items(root, args, info):
        return get_order_items()
person John William Domingo    schedule 08.09.2016
comment
Следует отметить, что наличие всех этих преобразователей делает GraphQL невероятно медленным. - person John William Domingo; 09.09.2016

Также можно просто изменить преобразователь по умолчанию для graphene.object.

Я считаю, что после реструктуризации сработает следующее.

from graphene.types.resolver import dict_resolver

class OrderItemType(ObjectType):

    class Meta:
        default_resolver = dict_resolver

    date = DateTime()
    order_id = ID()
    uuid = String()
    place_id = ID()

Даже если этот метод не применим напрямую к вышеупомянутому вопросу, я обнаружил этот вопрос, когда исследовал, как это сделать.

dict_resolver

person jayreed1    schedule 06.03.2020