GraphQL в світі Python. Що і як

7 хв. читання

Не так давно Facebook представили Graphql. Це гнучкий спосіб доступу до даних. Невдовзі після цього GitHub оголосили, що четверта версія їх API буде на Graphql. Також нову технологію впроваджують в таких компаніях як Meteor, Pinterest, Shopify.

Graphql було розроблено на заміну REST API. Він має чітку специфікацію, тому не буде такого, що кожен розробник розуміє стандарт по-своєму. В graphql вже вбудована документація, також є потужний інструмент для тестування запитів, який підтримує автодоповнення на основі ваших даних — Graphiql.

GraphQL в світі Python. Що і як

Початок роботи

В світі Python всю Graphql-магію виконує одна маленька бібліотека — graphene. Але перш ніж ми з нею познайомимось, розберемося з основами Graphql.

  • Модель (model) — це об'єкт, що оголошений схемою (schema) Graphql.

  • Схема описує моделі та їх атрибути.

  • Кожен атрибут моделі має власну функцію (resolver), що відповідає за повернення значення цього атрибуту.

  • Запит (query) — це те, що ви використовуєте для отримання даних в Graphql.

  • Мутації (mutation) — запити, що дозволяють вам змінювати дані.

  • Graphiql — візуальний інтерфейс, який ви використовуєте при взаємодії з Graphql-сервером. Саме він зображений на скріншоті вище.

І тут в гру вступає Python

Інтеграція Python та Graphql проводиться завдяки трьом інструментам. Graphene, graphene-плагін для вашої ORM, та graphene-плагін для вашого веб-фреймворку. Graphene дозволяє вам оголошувати власні моделі, їх атрибути та займається обробкою Graphql-запитів.

Плагін для ORM перетворює ваші моделі з SQLAlchemy та Django в об'єкти Graphql.

Досліджуємо Graphene

Встановлюється Graphene як і більшість бібліотек, через pip та підтримує Python як третьої, так і другої версії.

pip install graphene

Після встановлення імпортуйте її, напишіть просту модель та отримайте її, ось так:

import graphene  # 1

class Query(graphene.ObjectType):  # 2

    hello = graphene.String(description='A typical hello world')  # 3

    def resolve_hello(self, args, context, info):  # 4
        return 'World'

schema = graphene.Schema(query=Query)  # 5

query = '''
    query {
      hello
    }
'''  # 6
result = schema.execute(query) # 7

В 1 ми імпортуємо Graphene. У 2 оголошуємо клас для нашого запиту. Зауважте, що всі запити повинні бути успадковані від класу graphene.ObjectType. Ви можете вбудовувати запити в запити, це досить корисно при розділені додатку на модулі. Але всі, навіть найскладніші, об'єкти повинні бути нащадками graphene.ObjectType.

Цей клас зараз містить всі моделі. На даний момент це лише одна модель hello, яка є звичайним рядком. Так, може це й не сильно вражає, але для прикладу згодиться.

В 3 ми додаємо об'єкт до схеми, в даному випадку це просто рядок. В 4 ми оголосили резолвер для нашої моделі. Ми познайомимося з ними ближче трохи пізніше.

В 5 ми просто створюємо схему на основі нашого запиту.

Потів ми створюємо текст нашого запиту в 6 і в 7 виконуємо його. Ось такий результат ми отримаємо:

In [6]: result = schema.execute(query)

In [7]: type(result)
Out[7]: graphql.execution.base.ExecutionResult

In [8]: result.data
Out[8]: OrderedDict([('hello', 'world')])

Результат має три головні атрибути:

In [12]: result.data
Out[12]: OrderedDict([('hello', 'world')])

In [13]: result.errors

In [14]: result.invalid
Out[14]: False

В data зберігається результат виконання, в errors — помилки що сталися під час виконання, а invalid вказує на те, що сам запит складено неправильно.

Базові типи

Тепер, коли ви знайомі з базовими речами, на яких і побудована Graphql, ми можемо перейти до більш серйозних прикладів використання. Перш за все потрібно познайомитись з базовими типами. Про це вже багато написано і я не хочу переписувати все ще раз. Перш за все, почитайте документацію.

Типи поділяються на скалярні та не скалярні. До скалярних належать такі базові типи як числа, текст, булеві значення тощо. До не скалярних належать складніші типи даних, які часто є контейнерами для скалярних даних, наприклад, graphene.List. Вони також можуть бути інтерфейсами, що успадковані від graphene.ObjectType, та, звісно, мутаціями.

Інтеграція з ORM

Graphene, насправді, це лише комбінація серіалізаторів та інтерпретатора Graphql-запитів. Справді потужною вона стає в поєднанні з ORM. На момент перекладу вона підтримує Django, SQLAlchemy та Google App Engine. І все це дуже просто інтегрується. В більшості випадків вам потрібно лише оголосити метаатрибут, який буде вказувати на потрібну модель.

Django

from django.db import models

from graphene_django import DjangoObjectType

class Account(models.Model):

    birth_date = models.DateField(db_column='personbirthdate', null=True)
    created_date = models.DateTimeField(blank=True, null=True, db_column='createddate')
    is_paying_customer = models.NullBooleanField(db_column='iscustomer')
    country = models.CharField(db_column='country', max_length=3, null=True, blank=True)
    customer_number = models.CharField( db_column='cnumber', unique=True, max_length=255,
        blank=True, null=True, editable=False)

    class Meta:
        managed = False
        db_table ='accountinfo'


class AccountType(DjangoObjectType):
    class Meta:
        model = Account

Тепер ви можете використовувати AccountType як і будь-який інший тип даних. В більшості випадків вам не потрібно в ручну писати все в query-об'єкти. Якщо в вас встановлено django-filter, ви можете все значно спростити, додавши graphene.Node до списку інтерфейсів певного типу. Це дозволить просто і гнучко інтегрувати ваш тип з запитом, використовуючи DjangoConnectedFilterField.

class AccountNode(DjangoObjectType):
    class Meta:
        model = Account
        interfaces = (graphene.Node, )
        filter_fields = [
            'customer_number',
            'is_paying_customer',
        ]

І обєкт запиту:

from graphene_django.filter import DjangoConnectedFilterField

class AccountQuery(graphene.AbstractType):
    # Дає доступ до певного акаунта
    account = graphene.Node.Field(AccountNode)

    # Всі доступні акаунти
    all_accounts = DjangoFilterConnectionField(AccountNode, order_by='-customer_number')

Це значно полегшує написання запитів і дозволяє тримати функціональними ваші ORM-моделі, а не Graphene-моделі. Зауважте, що тут ми використовуємо graphene.AbstractType, тому що я планую використовувати AccountQuery як домішок (mixin) для головного об'єкта запиту.

from .queries import AccountQuery


class Query(AccountQuery, graphene.ObjectType):
    pass

schema = graphene.Schema(query=Query)

Таким чином ваш головний запит буде невеличким за розміром. Але не забудьте додати graphene.ObjectType останнім аргументом, інакше магія не спрацює.

SQLAlchemy та інші

Інші ORM інтегруються схожим чином. З SQLAlchemy ви просто використовуєте SQLAlchemyObjectType замість DjangoObjectType. Ви так само можете додати інтерфейс Node та користуватися SQLAlchemyConnectionField. Те ж саме й з Google App Engine. Підтримка Peewee тільки в планах.

Інтеграція з веб-фреймворками

Як ви могли здогадатися, Graphene підтримує деякі популярні веб-фреймворки. На даний момент підтримуються Django та Flask. А ось так виглядає Graphql-додаток:

from flask import Flask
from flask_graphql import GraphQLView

from models import db_session
from schema import schema, Department

app = Flask(__name__)
app.debug = True

app.add_url_rule(
    '/graphql',
    view_func=GraphQLView.as_view(
        'graphql',
        schema=schema,
        graphiql=True
    )
)

@app.teardown_appcontext
def shutdown_session(exception=None):
    db_session.remove()

if __name__ == '__main__':
    app.run()

Звісно, вам потрібно розділяти маршрути для вашого graphiql та звичайних HTTP-запитів. Також ви можете написати субклас GraphQLView, якщо вам потрібно додати додаткову авторизацію.

Помітили помилку? Повідомте автору, для цього достатньо виділити текст з помилкою та натиснути Ctrl+Enter
Codeguida 5.6K
Приєднався: 8 місяців тому
Коментарі (0)

    Ще немає коментарів

Щоб залишити коментар необхідно авторизуватися.

Вхід / Реєстрація