2016-09-08 21 views
5

Jaki jest najlepszy sposób korzystania z GraphQL z Django podczas korzystania z zewnętrznej bazy danych do pobierania danych z wielu tabel (np. Tworzenie modelu Django w celu reprezentowania danych nie odpowiada pojedyncza tabela w mojej bazie danych)?GraphQL + Django: rozwiązywanie zapytań przy użyciu surowego zapytania PostgreSQL

Moje podejście polegało na tymczasowym zaniechaniu używania modeli Django, ponieważ nie sądzę, abym jeszcze je w pełni rozumiał. (Jestem całkowicie nowy w Django oraz GraphQL.) Przygotowałem prosty projekt z aplikacją z podłączonym zewnętrznym DB PostgreSQL. Podążyłem za wszystkimi ustawieniami z Graphene Django tutorial, a następnie uderzyłem w blokadę drogi, gdy zdałem sobie sprawę, że stworzony przeze mnie model jest połączeniem kilku stołów.

Mam kwerendę, która odsyła właściwe kolumny odwzorowane na pola w moim modelu, ale nie wiem jak zrobić to dynamiczne połączenie tak, że po moim API jest trafiony, zapytuje mojej bazy danych i mapuje wiersze do schematu modelu zdefiniowanego w Django.

Moje podejście od tego czasu polegało na unikaniu modeli i stosowaniu prostszej metody pokazanej w przemówieniu Stevena Luschera: Zero to GraphQL in 30 Minutes.

TLDR;

Celem jest umożliwienie trafienia w mój punkt końcowy GraphQL, skorzystanie z obiektu kursora z mojego django.db.connection, aby uzyskać listę słowników, które powinny zostać przetłumaczone na GraphQLList typu OrderItemTypes (patrz poniżej).

Problemem jest to, że otrzymuję wartości null dla każdej wartości, kiedy uderzył w następujący końcowy z zapytaniem:

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

Powroty:

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

projektu/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; 
    """) 

W moim terminalu drukuję z metody rozstrzygnięcia QueryType i widzę, że dane pomyślnie powracają z mojego połączenia PostgreSQL. Jednak GraphQL daje mi wartości zerowe, więc musi być w metodzie rozwiązania, że ​​niektóre mapowanie jest wkręcane.

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

Jak poprawnie odwzorować moje dane na pola zdefiniowane w moim ItemItemType?

Oto kilka odnośników:

projekt/main/schematu.py

import graphene 

from project.app.schema import QueryType AppQuery 

class Query(AppQuery): 
    pass 

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

drzewo plik

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

Odpowiedz

6

domyślne przeliczniki na GraphQL Python/grafenu spróbować zrobić rozdzielczość danego FIELD_NAME w obiekcie głównym za pomocą getattr. Tak więc, na przykład, domyślna rezolwer dla pola o nazwie order_items będzie coś takiego:

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

Wiedząc, że kiedy robi getattr w dict, wynik będzie None (dostępu do elementów DICT trzeba będzie używać __getitem__/dict[key]).

Rozwiązanie problemu może być tak proste, jak zmiana z dicts na przechowywanie zawartości na 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; 
    """) 

Mam nadzieję, że to pomoże!

+0

To było niezwykle pomocne, dziękuję za poświęcenie czasu! Jedna mała edycja to lista wierszy powinna być rozłożona przed przekazaniem do nazwanego 'RowType (* row)'. Ogólnie rzecz biorąc, czy jest to dobra praktyka, czy istnieje lepsze podejście do pobierania danych zewnętrznych? –

+0

Jeśli widzisz moją drugą próbę (inna odpowiedź na to pytanie), użyłem przelicznika dla każdego pola, stosując sugerowane przez ciebie podejście dyktowania [klucz]. Z kilku testów, które właśnie uruchomiłem, wydaje się, że obie metody zwracają dane w tym samym czasie. Zastanawiam się, czy nieco powolny czas powrotu z GraphQL jest normalny (1000 rekordów ~ 3-4 sekundy po buforowaniu, a przed cachine zajmuje to około 6 sekund dla tego samego zestawu danych). –

+0

Jeśli używasz wersji rozwojowej, zapytanie zostanie rozwiązane co najmniej 10 razy szybciej (300 ms?). Można go zainstalować za pomocą 'pip install grafhene-django> = 1.0.dev'. Mam nadzieję, że to pomoże! –

0

Oto tymczasowe obejście, choć mam nadzieję, że jest coś czystsze obsłużyć snake_cased fieldnames.

projekt/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() 
+0

Należy zauważyć, że posiadanie wszystkich tych przeliczników sprawia, że ​​GraphQL jest niesamowicie wolny. –

Powiązane problemy