Разработка API GraphQL в Django с помощью Strawberry

В постоянно меняющемся ландшафте веб-разработки предоставление эффективных и гибких API имеет решающее значение. В то время как RESTful API были основным продуктом на протяжении многих лет, GraphQL стал мощной альтернативой, предлагая большую гибкость и эффективность при поиске данных. Позволяя клиентам запрашивать именно то, что им нужно, GraphQL сокращает избыточную и недостаточную выборку данных, повышая производительность и удобство использования ваших приложений.

Среди многочисленных доступных библиотек GraphQL, Strawberry GraphQL выделяется своей простотой и удобством использования. Созданный с акцентом на безопасность типов и современные возможности Python, Strawberry использует подсказки по типам Python для создания схем GraphQL без особых усилий. А поддержка множества интеграций, позволяющая использовать его с вашим любимым веб-фреймворком, делает его отличным выбором для разработчиков на Python, которые хотят интегрировать GraphQL в свои проекты.

С другой стороны, Django, высокоуровневый веб-фреймворк на Python, стал популярным выбором для разработчиков благодаря своему надежному набору функций и философии "без батареек". Масштабируемость Django, функции безопасности и простота использования делают его идеальной платформой для создания веб-приложений любого размера.

В этом руководстве мы рассмотрим, как интегрировать Strawberry с Django для создания мощного и гибкого API. Мы рассмотрим настройку проекта Django, определение моделей и создание запросов GraphQL и мутаций с помощью Strawberry. К концу этого руководства у вас будет функциональный GraphQL API на базе Django, готовый точно и эффективно обслуживать ваши потребности в данных.

Давайте начнем!

Содержимое

Почему Strawberry

Strawberry - это современная библиотека Python, предназначенная для упрощения создания API-интерфейсов GraphQL.

Вот несколько причин, по которым Strawberry является отличным выбором для создания GraphQL API:

  1. Безопасность типов: Strawberry использует подсказки по типам в Python для создания схем GraphQL. Это помогает обеспечить типобезопасность вашего кода, что может уменьшить количество ошибок и улучшить удобство сопровождения.
  2. Простота и удобоиспользование: Strawberry разработан с учетом простоты. Его API интуитивно понятен и легок в освоении, что делает его доступным как для начинающих, так и для опытных разработчиков.
  3. Современные возможности Python: Strawberry в полной мере использует современные возможности Python, такие как классы данных и синтаксис async/await. Это позволяет создавать более чистый и удобочитаемый код.
  4. Интеграция с популярными фреймворками Python: Strawberry предлагает полную интеграцию с популярными фреймворками Python, такими как Django, Flask и т.д., с примерами для начала работы. Это позволяет легко добавлять возможности GraphQL практически во все существующие веб-приложения.
  5. Сообщество и поддержка: У Strawberry активное и растущее сообщество. Разработчики отзывчивы, и доступно множество ресурсов, которые помогут вам начать работу и устранить неполадки.

Выбирая Strawberry, вы выбираете мощную и удобную в использовании библиотеку, которая упрощает создание и поддержку ваших API-интерфейсов GraphQL.

Почему именно Django

Django - это веб-фреймворк высокого уровня на Python, который способствует быстрой разработке и чистому, прагматичному дизайну.

Вот несколько причин, по которым Django является предпочтительным выбором для веб-разработки:

  1. Батарейки в комплекте: Django поставляется с множеством встроенных функций, таких как ORM, аутентификация, панель администратора и многое другое. Это сокращает количество времени, которое вы тратите на разработку шаблонного кода, и позволяет вам сосредоточиться на создании вашего приложения.
  2. Масштабируемость: Django разработан для работы с сайтами с высокой посещаемостью и может эффективно масштабироваться по мере роста вашего проекта. Он используется некоторыми из крупнейших веб-сайтов в мире, демонстрируя свою масштабируемость и надежность.
  3. Безопасность: Django предоставляет надежные функции безопасности "из коробки", защищая ваше приложение от распространенных угроз безопасности, таких как внедрение SQL, межсайтовый скриптинг (XSS) и подделка межсайтовых запросов (CSRF).
  4. Сообщество и документация: У Django большое и активное сообщество. Обширная документация и многочисленные учебные пособия облегчают разработчикам процесс обучения и получения поддержки.
  5. Модульность: Модульная структура Django позволяет использовать только те компоненты, которые вам нужны. Это делает его достаточно гибким для адаптации к различным требованиям проекта.

Объединяя Django с Strawberry, вы используете сильные стороны обоих фреймворков для создания надежного, масштабируемого и гибкого API. Эта интеграция позволяет вам воспользоваться преимуществами развитой экосистемы Django, одновременно используя эффективность и гибкость Graphql для обработки запросов к данным.

Что Мы Строим?

В этом руководстве мы создадим простое приложение для каталога книг, используя Django и Strawberry. Это приложение позволит пользователям управлять коллекцией книг и запрашивать информацию о ней, включая такие сведения, как название, автор и дата публикации.

Вот краткий обзор того, что мы рассмотрим:

  • Определение модели: Мы определим модель Book с полями для title, author, и published_date.
  • Схема GraphQL: Используя Strawberry, мы создадим схему GraphQL для отображения данных нашей книги. Схема будет включать в себя:
    • Введите Query, чтобы получить список книг.
    • A Mutation введите, чтобы добавить новые книги в каталог.
  • Запросы и изменения в GraphQL: Мы реализуем запросов для получения сведений о книге и мутаций для добавления новых книг.
  • Тестирование: Мы напишем тесты, чтобы убедиться, что наши запросы GraphQL и мутации работают должным образом, используя pytest.

К концу этого урока у вас будет функциональный GraphQL API на базе Django, способный точно и эффективно управлять коллекцией книг.

Начальная настройка

Самым первым шагом является настройка среды Python и установка Django.

Создайте новую папку проекта, создайте и активируйте виртуальную среду и установите Django:

$ mkdir strawberry_tut && cd strawberry_tut
$ python3 -m venv .env
$ source .env/bin/activate
(.env)$ pip install django==5.0.6

Не стесняйтесь заменить virtualenv и Pip на Poetry или Pipenv. Для получения дополнительной информации ознакомьтесь с Современными средами Python.

Запускаем новый проект на django:

(.env)$ django-admin startproject strawberry_django_tut
(.env)$ cd strawberry_django_tut

Создайте приложение django:

(.env)$ python manage.py startapp book

Чтобы убедиться, что все работает как надо, запустите сервер Django:

(.env) python manage.py runserver

Перейдите к http://127.0.0.1:8000/, чтобы убедиться, что отображается домашняя страница приветствия Django.

Модель Django

Давайте определим нашу модель книги с соответствующими полями. В файле strawberry_django_tut/book/models.py добавьте следующий код

from django.db import models


class Book(models.Model):
    title = models.CharField(max_length=100)
    author = models.CharField(max_length=100)
    published_date = models.DateField()

Добавьте приложение book в список INSTALLED_APPS в strawberry_django_tut/strawberry_django_tut/settings.py:

INSTALLED_APPS = [
    "django.contrib.admin",
    "django.contrib.auth",
    "django.contrib.contenttypes",
    "django.contrib.sessions",
    "django.contrib.messages",
    "django.contrib.staticfiles",

    "book",
]

Создайте и примените миграции:

(.env)$ python manage.py makemigrations
(.env)$ python manage.py migrate

Интеграция Strawberry

Чтобы добавить Strawberry в наше приложение, начните с установки strawberry-django:

(.env)$ pip install strawberry-graphql-django==0.44.2

После установки добавьте strawberry_django в список INSTALLED_APPS в файле настроек.

Затем создайте новый файл с именем strawberry_django_tut/book/schema.py:

from typing import List

import strawberry
import strawberry_django
from strawberry import auto

from .models import Book


@strawberry_django.type(Book)
class BookType:
    id: auto
    title: auto
    author: auto
    published_date: auto


@strawberry.type
class Query:
    books: List[BookType] = strawberry_django.field()


schema = strawberry.Schema(query=Query)

Примечания:

  1. Мы преобразовали нашу модель Book в тип Strawberry, сопоставив поля модели с полями GraphQL с помощью @strawberry_django.type декоратора. Утилита auto, предоставляемая Strawberry, используется для автоматического определения типов полей, обеспечивая согласованность между моделью Django и схемой GraphQL.
  2. Класс Query определяет тип корневого запроса для нашего GraphQL API. Тип Query используется для определения того, какие запросы клиенты могут выполнять к нашим данным. В этом случае у него есть только поле books, которое при запросе возвращает список из BookType объектов. Поскольку мы сопоставили тип BookType с моделью Book, запрос books автоматически запросит все объекты Book из базы данных.

Создайте другой файл с именем urls.py в "strawberry_django_tut/book":

from django.urls import path
from strawberry.django.views import GraphQLView

from .schema import schema


urlpatterns = [
    path("graphql/", GraphQLView.as_view(schema=schema), name="graphql"),
]

Наконец, обновите конфигурацию URL-адреса в strawberry_django_tut/strawberry_django_tut/urls.py:

from django.contrib import admin
from django.urls import path, include


urlpatterns = [
    path("admin/", admin.site.urls),
    path("", include("book.urls")),
]

Добавление мутаций

Помимо запроса данных, GraphQL позволяет изменять данные на сервере с помощью мутаций. Strawberry обрабатывает вводимые данные, позволяя вам определять типы ввода, которые определяют структуру данных, которые могут быть отправлены в ваши мутации GraphQL. Типы ввода в Strawberry создаются с помощью @strawberry_django.input декоратора для моделей Django или @strawberry.input для пользовательских типов ввода. В нашем приложении для каталога книг мы добавим мутацию, которая позволит пользователям добавлять новые книги в каталог.

В нашем schema.py файле давайте определим тип BookInput, который будет соответствовать модели Book. Он будет включать поля title, author и published_date:

@strawberry_django.input(Book)
class BookInput:
    title: auto
    author: auto
    published_date: auto

Если вам нужно больше контролировать тип ввода или если он напрямую не привязан к модели Django, вы можете определить тип ввода с помощью @strawberry.input декоратора:

@strawberry.input
class BookInput:
    title: str
    author: str
    published_date: str

Этот подход особенно полезен, когда ваш тип ввода должен отличаться от модели или когда вы не используете модели Django.

Давайте используем тип ввода в нашей мутации. Мутация примет аргумент этого типа ввода и будет использовать предоставленные данные для выполнения необходимых действий, таких как создание новой книги в базе данных.

Интеграция strawberry с django дает вам возможность напрямую использовать модель для создания без написания дополнительного кода.

В файле schema.py добавьте следующий код:

@strawberry.type
class Mutation:
    create_book: BookType = strawberry_django.mutations.create(BookInput)

Затем добавьте мутацию в strawberry.Schema:

schema = strawberry.Schema(query=Query, mutation=Mutation)

Если вы хотите обновить и удалить все значения в базе данных, вы можете использовать встроенную функцию обновления и удаления мутаций, предоставляемую strawberry-django.

Добавьте ввод для strawberry-django с необязательными полями:

@strawberry_django.input(Book)
class BookUpdateInput:
    title: str | None = None
    author: str | None = None
    published_date: str | None = None

В вашем классе Mutation добавьте следующий метод:

@strawberry.mutation
def update_book(self, book_id: int, data: BookUpdateInput) -> BookType:
    try:
        book = Book.objects.get(id=book_id)
        for key, value in asdict(data).items():
            if value is not None:
                setattr(book, key, value)

        book.save()

        return book
    except Book.DoesNotExist:
        raise Exception("Not Found")

Не забудьте импортировать:

from dataclasses import asdict

Давайте также добавим мутацию для удаления:

@strawberry.mutation
def delete_book(self, book_id: int) -> bool:
    try:
        book = Book.objects.get(pk=book_id)
        book.delete()

        return True
    except Book.DoesNotExist:
        raise Exception("Not Found")

Почему мы не можем просто вернуть книгу через BookType, как мы делали в update_book?

Проверка настройки

Давайте протестируем это на платформе GraphQL Playground. Запустив сервер разработки Django, перейдите к http://127.0.0.1:8000/graphql/.

Выполните следующие запросы и изменения:

mutation CREATE_BOOKS {
  createBook(data: {
    title: "My New Book",
    author: "Oluwole",
    publishedDate: "2024-05-31"
  }) {
    id
    title
  }
}

query GET_BOOKS {
  books {
    id
    title
    author
    publishedDate
  }
}

mutation UPDATE_BOOK {
  updateBook(bookId:1, data: {
    title: "My Biography"
  }){
    id
    title
  }
}

mutation DELETE_BOOK {
  deleteBook(bookId: 1)
}

Обработка ошибок

В наших примерах, приведенных выше, мы обработали недопустимый bookId's, вызвав исключение. Strawberry предлагает нам лучший способ обработки ошибок, возвращая типы объединения, которые либо представляют ошибку, либо отвечают успешно. Затем ваш клиент может просмотреть __typename результата, чтобы определить реакцию.

Мы можем добавить сообщение об ошибке Strawberry с полем message в поле schema.py:

@strawberry.type
class Error:
    message: str

Давайте также добавим тип успешной клубники с полем под названием result:

@strawberry.type
class Success:
    result: bool

Затем создайте объединяющий ответ для обновления и удаления мутаций:

Response = Annotated[
    Union[BookType, Error],
    strawberry.union("BookResponse")
]

DeleteResponse = Annotated[
    Union[Success, Error],
    strawberry.union("DeleteResponse")
]

Добавьте импортные данные:

from typing import Annotated, List, Union

Теперь обновите класс Mutation следующим образом:

@strawberry.type
class Mutation:
    create_book: List[BookType] = strawberry_django.mutations.create(BookInput)

    @strawberry.mutation
    def update_book(self, book_id: int, data: BookUpdateInput) -> Response:
        try:
            book = Book.objects.get(id=book_id)
            for key, value in asdict(data).items():
                if value is not None:
                    setattr(book, key, value)

            book.save()

            return book
        except Book.DoesNotExist:
            return Error(message="Not Found")
        except Exception as e:
            return Error(f"An error occurred: {str(e)}")

    @strawberry.mutation
    def delete_book(self, book_id: int) -> DeleteResponse:
        try:
            book = Book.objects.get(pk=book_id)
            book.delete()

            return Success(result=True)
        except Book.DoesNotExist:
            return Error(message="Not Found")
        except Exception as e:
            return Error(f"An error occurred: {str(e)}")

Чтобы проверить это, в GraphQL Playground запустите следующие изменения:

mutation UPDATE_BOOK {
  updateBook(bookId: 2, data: {
    title: "Updated Title Book"
  }){
    __typename
    ... on BookType {
      id
      title
    }
    ... on Error {
      message
    }
  }
}

mutation DELETE_BOOK {
  deleteBook(bookId: 2){
    __typename
    ... on Success {
      result
    }
    ... on Error {
      message
    }
  }
}

Это упрощает обработку конкретных ошибок в нашем коде.

Тестирование с помощью pytest

Для написания тестов установите pytest и pytest-django:

(.env)$ pip install pytest==8.2.2 pytest-django==4.8.0

Далее, чтобы настроить pytest для вашего проекта Django, создайте файл с именем pytest.ini в корне вашего проекта:

[pytest]
DJANGO_SETTINGS_MODULE = strawberry_django_tut.settings

Создайте папку с именем "тесты" в "strawberry_django_tut" и в этой папке создайте файл с именем test_graphql.py:

import pytest
from django.test import Client
from django.urls import reverse

from book.models import Book


@pytest.fixture
def client():
    return Client()

@pytest.fixture
def create_books(db):
    Book.objects.create(title="Book 1", author="Author 1", published_date="2023-01-01")
    Book.objects.create(title="Book 2", author="Author 2", published_date="2023-01-02")

Здесь мы создали приспособления для добавления книг в нашу тестовую базу данных.

Продолжайте и добавляйте тесты, обязательно просматривая каждый из них:

@pytest.mark.django_db
def test_books_query(client, create_books):
    query = """
    query {
        books {
            title
            author
            publishedDate
        }
    }
    """
    response = client.post(reverse("graphql"), {"query": query}, content_type="application/json")
    assert response.status_code == 200
    data = response.json()["data"]["books"]
    assert len(data) == 2
    assert data[0]["title"] == "Book 1"
    assert data[1]["title"] == "Book 2"


@pytest.mark.django_db
def test_add_book_mutation(client):
    mutation = """
    mutation {
        createBook(data: {title: "New Book", author: "New Author", publishedDate: "2023-05-01"}) {
            title
            author
            publishedDate
        }
    }
    """
    response = client.post(reverse("graphql"), {"query": mutation}, content_type="application/json")
    assert response.status_code == 200
    data = response.json()["data"]["createBook"][0]
    assert data["title"] == "New Book"
    assert data["author"] == "New Author"
    assert data["publishedDate"] == "2023-05-01"
    assert Book.objects.filter(title="New Book").exists()


@pytest.mark.django_db
def test_update_book_mutation(client, create_books):
    book = Book.objects.first()
    mutation = f"""
    mutation {{
        updateBook(bookId: {book.id}, data: {{title: "Updated Book"}}) {{
            __typename
            ... on BookType {{
                title
                author
                publishedDate
            }}
            ... on Error {{
                message
            }}
        }}
    }}
    """
    response = client.post(reverse("graphql"), {"query": mutation}, content_type="application/json")
    assert response.status_code == 200
    data = response.json()["data"]["updateBook"]
    assert data["title"] == "Updated Book"
    assert data["author"] == book.author
    assert data["publishedDate"] == str(book.published_date)
    assert Book.objects.filter(title="Updated Book").exists()
    assert not Book.objects.filter(title="Book 1").exists()
    assert Book.objects.filter(title="Book 2").exists()
    assert Book.objects.count() == 2


@pytest.mark.django_db
def test_delete_book_mutation(client, create_books):
    book = Book.objects.first()
    mutation = f"""
    mutation {{
        deleteBook(bookId: {book.id}) {{
            __typename
            ... on Success {{
                result
            }}
            ... on Error {{
                message
            }}
        }}
    }}
    """
    response = client.post(reverse("graphql"), {"query": mutation}, content_type="application/json")
    assert response.status_code == 200
    data = response.json()["data"]["deleteBook"]
    assert data["result"]
    assert not Book.objects.filter(title=book.title).exists()
    assert Book.objects.count() == 1

Итак, мы добавили тесты для получения всех книг, добавления книги, а также обновления и удаления книги.

Убедитесь, что они соответствуют:

(.env)$ python -m pytest

Заключение

В этом руководстве мы рассмотрели, как интегрировать Strawberry с Django для создания надежного GraphQL API для приложения каталога книг. Мы рассмотрели следующие ключевые шаги:

  1. Настройка: Установка необходимых пакетов и настройка проекта Django с помощью Strawberry.
  2. Определение моделей: Создание модели Django для книг.
  3. Создание схем: Определение типов и запросов в схеме GraphQL с помощью Strawberry.
  4. Добавление мутаций: Реализация мутаций для добавления и обновления записей в книге.
  5. Обработка ошибок: Как лучше обрабатывать ошибки с помощью strawberry union.
  6. Тестирование: Написание тестов с использованием pytest, чтобы убедиться, что запросы GraphQL и мутации работают должным образом.

Strawberry в сочетании с Django предлагает простой способ создания типобезопасного и эффективного GraphQL API. Гибкость определения схем и мощные возможности Django делают это сочетание привлекательным для современной веб-разработки. Следуя описанным шагам, вы сможете быстро создать и протестировать GraphQL API, что упростит структурированное управление вашими данными и выполнение запросов к ним.

С помощью этой основы вы сможете еще больше расширить функциональность вашего приложения для каталога книг, добавив больше моделей, запросов и изменений. Кроме того, вы можете изучить расширенные возможности Strawberry, такие как подписки, пользовательские скаляры и промежуточное программное обеспечение, чтобы расширить возможности вашего API. Счастливого кодирования!

Вернуться на верх