Python
GraphQL на Python: первый пример с Graphene
GraphQL на Python удобно попробовать через библиотеку Graphene. Вы описываете схему, поля и resolver-функции, а клиент запрашивает только те данные, которые ему нужны. Для первого знакомства не нужен сервер: схему можно выполнить прямо в обычном Python-файле
В этом примере мы сделаем простой query, добавим тип Task, вернем список задач и покажем mutation
По этой теме полезно отдельно посмотреть Составьте еженедельное расписание занятий, чтобы расширить контекст и сравнить подходы
По этой теме полезно отдельно посмотреть Аннотации типов для декораторов в Python, чтобы расширить контекст и сравнить подходы
Установка
pip install graphene
Создайте файл app.py и запускайте примеры командой:
python app.py
Первый query
import graphene
class Query(graphene.ObjectType):
hello = graphene.String(name=graphene.String(default_value='мир'))
def resolve_hello(root, info, name):
return f'Привет, {name}'
schema = graphene.Schema(query=Query)
result = schema.execute('{ hello(name: "Анна") }')
print(result.data)
Результат:
{'hello': 'Привет, Анна'}
Query описывает доступные поля. Поле hello принимает аргумент name. Метод resolve_hello() возвращает значение для этого поля
В GraphQL resolver — это функция, которая знает, как получить данные для конкретного поля
Запрос без лишних полей
Добавим два поля
import graphene
class Query(graphene.ObjectType):
version = graphene.String()
author = graphene.String()
def resolve_version(root, info):
return '1.0'
def resolve_author(root, info):
return 'Aglamov'
schema = graphene.Schema(query=Query)
result = schema.execute('{ version }')
print(result.data)
Результат:
{'version': '1.0'}
Клиент запросил только version, поэтому author в ответ не попал. В этом смысл GraphQL: клиент сам выбирает форму ответа в рамках схемы
Тип Task
Теперь сделаем тип задачи
import graphene
class Task(graphene.ObjectType):
id = graphene.Int()
title = graphene.String()
done = graphene.Boolean()
TASKS = [
{'id': 1, 'title': 'написать код', 'done': True},
{'id': 2, 'title': 'запустить тесты', 'done': False},
]
class Query(graphene.ObjectType):
tasks = graphene.List(Task)
def resolve_tasks(root, info):
return TASKS
schema = graphene.Schema(query=Query)
result = schema.execute('{ tasks { id title done } }')
print(result.data)
Результат:
{'tasks': [{'id': 1, 'title': 'написать код', 'done': True}, {'id': 2, 'title': 'запустить тесты', 'done': False}]}
В запросе { tasks { id title done } } клиент явно выбирает поля внутри задачи
Если запросить только title, ответ будет короче:
result = schema.execute('{ tasks { title } }')
print(result.data)
Аргумент в поле
Добавим поиск задачи по id
class Query(graphene.ObjectType):
task = graphene.Field(Task, id=graphene.Int(required=True))
def resolve_task(root, info, id):
for task in TASKS:
if task['id'] == id:
return task
return None
Запрос:
result = schema.execute('{ task(id: 1) { title done } }')
print(result.data)
Результат:
{'task': {'title': 'написать код', 'done': True}}
Если задача не найдена, поле вернет None
Mutation
Mutation нужна для изменения данных. Для учебного примера добавим задачу в список в памяти
import graphene
class Task(graphene.ObjectType):
id = graphene.Int()
title = graphene.String()
done = graphene.Boolean()
TASKS = []
class AddTask(graphene.Mutation):
class Arguments:
title = graphene.String(required=True)
task = graphene.Field(Task)
def mutate(root, info, title):
task = {
'id': len(TASKS) + 1,
'title': title,
'done': False,
}
TASKS.append(task)
return AddTask(task=task)
class Query(graphene.ObjectType):
tasks = graphene.List(Task)
def resolve_tasks(root, info):
return TASKS
class Mutation(graphene.ObjectType):
add_task = AddTask.Field()
schema = graphene.Schema(query=Query, mutation=Mutation)
result = schema.execute('''
mutation {
addTask(title: "проверить GraphQL") {
task {
id
title
done
}
}
}
''')
print(result.data)
В Python поле называется add_task, но в GraphQL-запросе оно превращается в addTask. Graphene автоматически использует camelCase
Ошибки в запросе
Если запросить поле, которого нет в схеме, GraphQL вернет ошибку
result = schema.execute('{ unknownField }')
print(result.data)
print(result.errors)
Смотрите не только data, но и errors. В учебных примерах это помогает быстро понять, где неверное имя поля или аргумента
Что важно понять
Схема описывает, какие данные можно запросить
Resolver возвращает данные для конкретного поля
Клиент сам выбирает поля в запросе
Query обычно читает данные
Mutation обычно меняет данные
Graphene помогает описать GraphQL-схему на Python-классах
Частые ошибки
Путать имя поля в Python и имя в GraphQL после преобразования в camelCase
Забывать проверять result.errors
Возвращать из resolver данные не того типа
Пытаться сразу строить большой API без маленького локального примера
Хранить данные в обычном списке в production-коде. Для учебного примера это нормально, для реального проекта нужна база или другой слой хранения
Практические задачи
Добавьте поле version
Добавьте тип User с полями id, name, role
Сделайте запрос пользователя по id
Добавьте mutation для отметки задачи как выполненной
Проверьте result.errors при запросе несуществующего поля
Первый пример GraphQL на Python через Graphene можно сделать без сервера: описать Query, создать Schema, выполнить строку запроса через schema.execute() и посмотреть result.data
После первого query добавьте тип объекта, список, аргументы и mutation. Так вы увидите главную модель GraphQL: схема описывает возможности, resolver достает данные, а клиент выбирает нужные поля