Не так давно Facebook представили Graphql. Це гнучкий спосіб доступу до даних. Невдовзі після цього GitHub оголосили, що четверта версія їх API буде на Graphql. Також нову технологію впроваджують в таких компаніях як Meteor, Pinterest, Shopify.
Graphql було розроблено на заміну REST API. Він має чітку специфікацію, тому не буде такого, що кожен розробник розуміє стандарт по-своєму. В graphql вже вбудована документація, також є потужний інструмент для тестування запитів, який підтримує автодоповнення на основі ваших даних — Graphiql.
Початок роботи
В світі 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
, якщо вам потрібно додати додаткову авторизацію.
Ще немає коментарів