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 достает данные, а клиент выбирает нужные поля

Подписаться
Уведомить о
guest
0 комментариев
Старые
Новые Популярные

Настройки cookie

0
Оставьте комментарий! Напишите, что думаете по поводу статьи.x