尝试使用Python的GraphQL库Strawberry

首先

在构建FastAPI的GraphQL服务器时,FastAPI的文档推荐使用名为Strawberry的库。为了学习GraphQL,我尝试了一下它。

草莓是什么意思?

这是一个面向Python的GraphQL库,我认为Graphene在Python中非常有名。它的设计思想类似于FastAPI,并且在使用FastAPI构建GraphQL服务器时推荐使用它(当然,你也可以使用其他库,如Graphene)。它不仅支持FastAPI,还支持其他各种框架,如Django、Flask和Chalice。

执行

我想以任务管理为主题,使用Strawberry来实现类似CRUD的功能。

文件结构

最终的文件结构如下所示。

.
├── main.py
└── tasks
    ├── __init__.py
    ├── inputs.py
    ├── models.py
    ├── repositories.py
    ├── resolvers.py
    ├── services.py
    └── types.py

安装

请安装以下两个库。

pip install 'strawberry-graphql[debug-server]'
pip install 'uvicorn[standard]'

准备工作

GraphQL是一种允许简单实现与许多不相关部分的技术。

import uuid
from dataclasses import dataclass, field
from datetime import datetime
from enum import Enum
from typing import Optional


class Status(Enum):
    TODO = "todo"
    DOING = "doing"
    DONE = "done"


@dataclass
class Task:
    title: str
    id: uuid.UUID = field(default_factory=uuid.uuid4)
    description: Optional[str] = None
    status: Status = Status.TODO
    created_at: datetime = field(default_factory=datetime.utcnow)
    updated_at: datetime = field(default_factory=datetime.utcnow)
from copy import deepcopy
from typing import Any

from tasks.models import Task


class InMemoryRepository:
    _store: dict[str, Any] = {}

    @classmethod
    def find_by_id(cls, id_: str) -> Task:
        task = cls._store.get(id_)
        if task is None:
            raise Exception("Not Found")

        return task

    @classmethod
    def find_all(cls) -> list[Task]:
        tasks = list(cls._store.values())

        return tasks

    @classmethod
    def save(cls, task: Task) -> None:
        cls._store[str(task.id)] = deepcopy(task)

    @classmethod
    def delete(cls, task: Task) -> None:
        del cls._store[str(task.id)]
from datetime import datetime
from typing import Optional

from tasks.models import Status, Task
from tasks.repositories import InMemoryRepository


class TaskService:
    def __init__(self, repo: type[InMemoryRepository]) -> None:
        self._repo = repo

    @property
    def repo(self) -> type[InMemoryRepository]:
        return self._repo

    def find(self, id: str) -> Task:
        task = self.repo.find_by_id(id)

        return task

    def find_all(self) -> list[Task]:
        tasks = self.repo.find_all()

        return tasks

    def create(self, *, title: str, description: Optional[str] = None) -> Task:
        task = Task(title=title, description=description)
        self.repo.save(task)

        return task

    def update(self, *, id: str, status: Status) -> Task:
        task = self.repo.find_by_id(id)
        task.status = status
        task.updated_at = datetime.utcnow()
        self.repo.save(task)

        return task

    def delete(self, id: str) -> Task:
        task = self.repo.find_by_id(id)
        self.repo.delete(task)

        return task

物体类型

在以上所述中,我们已完成前期准备工作。现在我们将开始实施与GraphQL相关的功能。
首先,我们要定义Object Type。有关GraphQL类型和Python类型的对应详细信息,请参阅此处。此外,我们将使用strawberry.enum来定义使用Enum定义的Status的类型。
通过将name=”Task”作为strawberry.type的参数,我们可以将其作为Task来处理。

from datetime import datetime
from typing import Optional

import strawberry

from tasks.models import Status, Task

StatusType = strawberry.enum(Status, name="Status")


@strawberry.type(name="Task")
class TaskType:
    id: strawberry.ID
    title: str
    description: Optional[str]
    status: StatusType
    created_at: datetime
    updated_at: datetime

    @classmethod
    def from_instance(cls, instance: Task) -> "TaskType":
        data = instance.__dict__

        return cls(**data)

输入类型

为了创建和更新任务,我们将使用strawberry.input来定义Input Type。

from typing import Optional

import strawberry

from tasks.types import StatusType


@strawberry.input
class AddTaskInput:
    title: str
    description: Optional[str] = None


@strawberry.input
class UpdateTaskInput:
    id: strawberry.ID
    status: StatusType

解决者 (jiě jué zhě)

定义一个Resolver。函数的参数将反映在GraphQL中。
对于add_task(task_input: AddTaskInput)情况,在GraphQL中将变为taskInput: AddTaskInput!,并且下划线形式将转换为驼峰式。

import strawberry

from tasks.inputs import AddTaskInput, UpdateTaskInput
from tasks.repositories import InMemoryRepository
from tasks.services import TaskService
from tasks.types import TaskType


def get_task(id: strawberry.ID) -> TaskType:
    db = InMemoryRepository
    service = TaskService(db)
    task = service.find(id)

    return TaskType.from_instance(task)


def get_tasks() -> list[TaskType]:
    db = InMemoryRepository
    service = TaskService(db)
    tasks = service.find_all()

    return [TaskType.from_instance(task) for task in tasks]


def add_task(task_input: AddTaskInput) -> TaskType:
    db = InMemoryRepository
    service = TaskService(db)
    task = service.create(**task_input.__dict__)

    return TaskType.from_instance(task)


def update_task(task_input: UpdateTaskInput) -> TaskType:
    db = InMemoryRepository
    service = TaskService(db)
    task = service.update(**task_input.__dict__)

    return TaskType.from_instance(task)


def delete_task(id: strawberry.ID) -> TaskType:
    db = InMemoryRepository
    service = TaskService(db)
    task = service.delete(id)

    return TaskType.from_instance(task)

查询与修改

最后,我们定义了Query和Mutation。这些定义的字段将会在GraphQL中反映出来。Snake Case将被转换为Camel Case,并且类型提示将成为返回值。

import strawberry

from tasks.resolvers import add_task, delete_task, get_task, get_tasks, update_task
from tasks.types import TaskType


@strawberry.type
class Query:
    task: TaskType = strawberry.field(resolver=get_task)
    tasks: list[TaskType] = strawberry.field(resolver=get_tasks)


@strawberry.type
class Mutation:
    task_add: TaskType = strawberry.field(resolver=add_task)
    task_update: TaskType = strawberry.field(resolver=update_task)
    task_delete: TaskType = strawberry.field(resolver=delete_task)


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

启动服务器

使用以下命令启动服务器。访问http://localhost:8000/graphql,将显示GraphiQL界面。

strawberry server main
スクリーンショット 2022-03-08 19.01.33.png

结束

我們這次試著使用草莓構建GraphQL服務器。
由於草莓還處於開發初期,因此在使用時請先參考文件進行確認。

请参考

    • Strawberry

 

    • FastAPI

 

    チュートリアル: GraphQL APIの設計
广告
将在 10 秒后关闭
bannerAds