GraphQL + Django:使用原始的PostgreSQL查询解析查询

9
当使用外部数据库从多个表中获取数据时(即创建一个Django模型来表示数据不对应于我的数据库中的单个表),最佳方法是如何使用GraphQL与Django一起使用?
我的方法是暂时放弃使用Django模型,因为我认为我还没有完全理解它们。(我对Django和GraphQL都是完全新手。)我设置了一个简单的项目,其中包含一个连接的外部Postgres DB的应用程序。我按照Graphene Django教程中的所有设置进行了设置,然后遇到了一个障碍,当我意识到我创建的模型是几个表的综合体时。
我有一个查询,将正确的列映射到我模型中的字段,但我不知道如何使这成为动态连接,这样当我的API被命中时,它会查询我的数据库并将行映射到我在Django中定义的模型架构。
自那以后,我的方法是避免使用模型,并使用Steven Luscher的演讲中演示的更简单的方法:30分钟内零到GraphQL

简而言之;

目标是能够访问我的GraphQL端点,使用来自django.db.connection的游标对象获取一个字典列表,这些字典应该解析为OrderItemTypes的GraphQLList(见下文)。

问题是当我使用查询命中以下端点时,每个值都返回null:

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

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

project/main/app/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的resolve方法中打印并且可以看到数据成功地从我的Postgres连接返回。然而,GraphQL给了我null,所以必须是在resolve方法中出了问题导致了某些映射出错。
[ { 'uuid': u'7584aac3-ab39-4a56-9c78-e3bb1e02dfc1', 'order_id': 25624320, 'date': datetime.datetime(2016, 1, 30, 16, 39, 40, 573400, tzinfo=<UTC>) }, ... ]

我该如何将我的数据正确映射到我在OrderItemType中定义的字段?
以下是一些更多的参考资料:

project/main/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
3个回答

10

GraphQL Python/Graphene中的默认解析器尝试使用getattr在根对象中解析给定field_name的解析。例如,对于名为order_items的字段,其默认解析器将类似于:

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

了解到,在字典中进行getattr操作时,结果将会是None(如果要访问字典项,则必须使用__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;
    """)
希望这能帮到你!

非常有帮助,感谢您抽出时间!一个小修改是在传递给namedtuple RowType(*row)之前应该展开行列表。一般来说,这是一个好的做法吗?还是有更好的获取外部数据的方法? - John William Domingo
如果你看到我的第二次尝试(发布给这个问题的其他答案),我使用了你建议的dict [key]方法为每个字段使用解析器。从我刚刚进行的几项测试中,似乎两种方法在相同的时间内返回数据。我想知道GraphQL的有些缓慢的返回时间是否正常(1000条记录在缓存后约为3-4秒;在缓存之前,对于相同的数据集需要约6秒)。 - John William Domingo
如果您使用开发版本,查询速度将至少快10倍(300ms?)。您可以通过pip install graphene-django>=1.0.dev进行安装。 希望这能帮到您! - Syrus Akbary Nieto

1
这里有一个临时解决方案,虽然我希望有更简洁的方法来处理蛇形命名的字段名。

project/main/app/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()

值得注意的是,拥有所有这些解析器会使GraphQL变得非常缓慢。 - John William Domingo

0

我们也可以更改 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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接