使用 Blacksheep 和 Piccolo ORM 构建简单 REST API | Python

在使用 Django 和 FastAPI 一段时间之后,我在 awsome fastapi 发现了 BlackSheep 这个异步网络框架。因此来翻译一篇快速的 Demo 来入门 Blacksheep 框架。

原文链接:A simple REST API with Blacksheep and Piccolo ORM | Python

作者:Carlos Armando Marcano Vargas

Blacksheep 和 Piccolo ORM 介绍

根据其官方文档介绍:

BlackSheep 是一个异步 Web 框架,用于使用 Python 构建基于事件的 Web 应用程序。它的灵感来自 Flask、ASP.NET Core 和 Yury Selivanov 的工作。 特点包括:

  • 丰富的代码 API
  • 基于依赖注入,受到 Flask 和 ASP.NET Core 的启发
  • 编码友好的代码库
  • 通过 IDE 编码时的提示,实现舒适的开发体验
  • 内置 OpenAPI 文档生成,支持版本 3、YAML和 JSON
  • 一个跨平台框架,使用最现代版本的 Python
  • 良好的性能,正如 TechEmpower 基准测试的结果所证明的那样

使用方式

BlackSheep 文档非常容易理解,与 Flask 一样,可以用它构建 API。

pip install blacksheep

一个简单的 Hello World 输出:

python 复制代码
from datetime import datetime

from blacksheep import Application, get


app = Application()

@get("/")
async def home():
    return f"Hello, World! {datetime.utcnow().isoformat()}"

使用数据库

下一步是使用数据库,我在 BlackSheep 的文档中找到了 Piccolo ORM

Piccolo 是一个快速、用户友好的 ORM 和查询构建器,支持 asyncio。有如下特点:

  • 支持同步和异步
  • 内置 playground ,让学习变得轻而易举
  • Tab 补全支持 - 与 iPython 和 VSCode 配合得很好
  • 组件支持:用户模型、身份验证、迁移、管理界面 GUI
  • 现代 Python - 完全类型注释
  • 使用 Piccolo 应用程序(类似于 Django 应用程序)使您的代码库模块化且可扩展

我们将使用这些工具构建基本的 CRUD API,但首先,我们设置环境

bash 复制代码
mkdir api_tutorial 
cd api_tutorial

然后新建一个虚拟环境 py -m venv ./myvenv, 进行激活:

bash 复制代码
cd myenv/Scripts
activate

然后我们返回到 api_tutorial 文件夹,进行 Piccolo ORM 的安装:

bash 复制代码
pip install "piccolo[postgres]"

然后我们创建一个新的 piccolo 应用程序,在根文件夹中运行以下命令:

bash 复制代码
piccolo app new sql_app

最后一个命令将在我们的根文件夹中生成一个新文件夹,其结构如下:

bash 复制代码
sql_app/
    __init__.py
    piccolo_app.py
    piccolo_migrations/
        __init__.py
    tables.py

然后,我们必须创建一个新的 Piccolo 项目,在我们的根文件夹中运行:

bash 复制代码
piccolo project new

执行上述命令后,会生成 piccolo_conf.py 文件,其中包含以下代码:

python 复制代码
from piccolo.conf.apps import AppRegistry
from piccolo.engine.postgres import PostgresEngine


DB = PostgresEngine(config={})


# A list of paths to piccolo apps
# e.g. ['blog.piccolo_app']
APP_REGISTRY = AppRegistry(apps=[])

我们必须在 piccolo_conf.py 中编写数据库的配置:

python 复制代码
# piccolo_conf.py
from piccolo.conf.apps import AppRegistry
from piccolo.engine.postgres import PostgresEngine


DB = PostgresEngine(config={
        "database": "api_project",
        "user": "postgres",
        "password": "your password",
        "host": "localhost",
        "port": 5432,
})


APP_REGISTRY = AppRegistry(apps=['sql_app.piccolo_app'])

然后我们在位于 sql_app 文件夹中的 tables.py 中创建表:

python 复制代码
# tables.py
from piccolo.table import Table
from piccolo.columns import Varchar, Integer

class Expense(Table):
    amount = Integer()
    description = Varchar()

piccolo_app.py 中,我们注册应用程序的表。我们将使用 table_finder 从给定的模块列表中自动导入任何 Table 子类。更多细节可以点此处

python 复制代码
# piccolo_app.py
import os
from piccolo.conf.apps import AppConfig, table_finder


CURRENT_DIRECTORY = os.path.dirname(os.path.abspath(__file__))


APP_CONFIG = AppConfig(
    app_name="sql_app",
    migrations_folder_path=os.path.join(
        CURRENT_DIRECTORY, "piccolo_migrations"
    ),
    table_classes=table_finder(modules=["sql_app.tables"], 
    exclude_imported=True),
    migration_dependencies=[],
    commands=[],
)

我们使用 Postgres,我们需要创建数据库,因此,打开命令行并创建数据库:

bash 复制代码
psql -U <your user>
create database api_project;

创建数据库后,我们需要运行迁移,在根文件夹中运行:

arduino 复制代码
piccolo migrations new sql_app --auto
piccolo migrations forwards sql_app

当我们运行 piccolo migrations new sql_app 时,我们应该在命令行中看到下一条消息:

bash 复制代码
- Creating new migration ...
- Created tables                           1
- Dropped tables                           0
- Renamed tables                           0
- Created table columns                    2
- Dropped columns                          0
- Columns added to existing tables         0
- Renamed columns                          0
- Altered columns                          0`

当我们运行 piccolo migrationsforwards sql_app 时,我们应该看到下一条消息:

bash 复制代码
👍 1 migration already complete

⏩ 1 migration not yet run

🚀 Running 1 migration:
  - 2022-06-04T15:29:20:540168 [forwards]... ok! ✔️

此外,它应该是迁移文件夹中带有时间戳的新文件。

现在是时候安装 Blacksheep 了:

bash 复制代码
pip install blacksheep uvicorn

正如 Blacksheep 的文档中提到的,"BlackSheep 属于 ASGI Web 框架类别,因此它需要 ASGI HTTP 服务器才能运行,例如 uvicorn 或 hypercorn"。在它的教程中使用了 uvicorn,所以我们也将使用 uvicorn。

在根文件夹中,我们创建 main.py 并编写以下代码:

python 复制代码
if __name__ == "__main__":

    import uvicorn

    uvicorn.run("app:app", reload=True)

现在,要运行我们的服务器,我们只需要在命令行中编写 py main.py,而不是 uvicorn server:app --reload

是时候编写端点了。首先,我们将编写三个端点,两个 GET 端点和一个 POST 端点。

我们创建一个新文件 app.py 来编写端点。

python 复制代码
import typing

from piccolo.utils.pydantic import create_pydantic_model
from piccolo.engine import engine_finder

from blacksheep import Application, FromJSON, json, Response, Content

from sql_app.tables import Expense

ExpenseModelIn: typing.Any = create_pydantic_model(table=Expense, model_name=" ExpenseModelIn")

ExpenseModelOut: typing.Any = create_pydantic_model(table=Expense, include_default_columns=True, model_name=" ExpenseModelIn")

ExpenseModelPartial: typing.Any = create_pydantic_model(
    table=Expense, model_name="ExpenseModelPartial", all_optional=True
)


app = Application()



@app.router.get("/expenses")
async def expenses():
    try:
        expense = await Expense.select()
        return expense
    except:
        return Response(404, content=Content(b"text/plain", b"Not Found"))

我们创建了三个 pydantic 模型来验证我们的数据。 ExpenseModelIn 是我们在使用 POST 方法时向数据库添加新数据时使用的模型或结构。如果我们尝试输入没有表中指定字段的数据,它将返回错误。例如:

python 复制代码
This is correct and will pass.
{"amount":20, "description":"Food"}

Any json without the fields "amount" and "description" will not pass.

ExpenseModelPartial 是当我们想要修改数据库中的数据时(例如,使用 PATCH 方法时)函数作为参数接收的模型。

ExpenseModelOut 是函数在使用任何端点时返回的模型,包含默认列,例如 id。

我们通过调用 Application 类来初始化我们的应用程序。然后,我们使用装饰器 @app.router.get() 和"/expenses"作为路由参数。接下来,我们使用 Piccolo ORM 中的 select() 方法定义一个返回数据库中所有条目的函数。

GET:

python 复制代码
@app.router.get("/expense/{id}")
async def expense(id: int):
    expense = await Expense.select().where(id==Expense.id)
    if not expense:
        return Response(404, content=Content(b"text/plain", b"Id not Found"))
    return expense

该另一个端点使用 GET 方法返回按其 id 选择的特定条目。我们定义一个以 id 作为参数的函数。然后,我们使用 select()where()。如果该 id 不在数据库中,则会返回状态码 404。

POST:

python 复制代码
@app.router.post("/expense")
async def create_expense(expense_model: FromJSON[ExpenseModelIn]):
    try:
        expense = Expense(**expense_model.value.dict())
        await expense.save()
        return ExpenseModelOut(**expense.to_dict())  
    except:
        return Response(400, content=Content(b"text/plain", b"Bad Request"))

我们使用装饰器 @app.router.post() 和 ("/expense") 作为参数。我们定义一个函数来在数据库中创建一个新条目,该条目以 ExpenseModelIn 作为参数。该函数将字典中转换的 JSON 数据作为关键字参数传递给 Expense 构造函数并保存。并返回带有 id(ExpenseModelOut) 的数据。如果请求没有在 ExpenseModelIn 中定义相同的字段,它将发送一个 Bad 请求作为响应。

在我们的 post 路由下面,初始化我们的数据库并使用连接池进行查询。

python 复制代码
async def open_database_connection_pool(application):
    try:
        engine = engine_finder()
        await engine.start_connection_pool()
    except Exception:
        print("Unable to connect to the database")


async def close_database_connection_pool(application):
    try:
        engine = engine_finder()
        await engine.close_connection_pool()
    except Exception:
        print("Unable to connect to the database")


app.on_start += open_database_connection_pool
app.on_stop += close_database_connection_pool

现在我们可以运行服务器:

css 复制代码
py main.py

PATCH:

python 复制代码
@app.router.patch("expense/{id}")
async def patch_expense(
        id: int, expense_model: FromJSON[ExpenseModelPartial]
):
    expense = await Expense.objects().get(Expense.id == id)
    if not expense:
        return Response(404, content=Content(b"text/plain", b"Id not Found"))

    for key, value in expense_model.value.dict().items():
        if value is not None:
            setattr(expense, key, value)

    await expense.save()
    return ExpenseModelOut(**expense.to_dict())

在 patch 方法中,我们使用 id 和 ExpenseModelPartial 作为参数,它将用我们想要的 id 更新条目,保存它,并发送更新后的条目的 JSON。否则,它将发送错误和"Id Not Found"作为响应。

PUT:

python 复制代码
@app.router.put("/expense/{id}")
async def put_expense(
        id: int, expense_model: FromJSON[ExpenseModelIn]
):
    expense = await Expense.objects().get(Expense.id == id)
    if not expense:
        return Response(404, content=Content(b"text/plain", b"Id Not Found"))

    for key, value in expense_model.value.dict().items():
        if value is not None:
            setattr(expense, key, value)

    await expense.save()
    return ExpenseModelOut(**expense.to_dict())

在 put 方法中,我们使用 id 和 ExpenseModelIn 作为参数,它将用我们想要的 id 更新条目,保存它,并发送更新后的条目的 JSON。否则,它将发送错误和"Id Not Found"作为响应。

DELETE:

python 复制代码
@app.router.delete("/expense/{id}")
async def delete_expense(id: int):
    expense = await Expense.objects().get(Expense.id == id)
    if not expense:
        return Response(404, content=Content(b"text/plain", b"Id Not Found"))
    await expense.remove()
    return json({"message":"Expense deleted"})

对于 DELETE 方法,我们传递要删除的条目的 id 并检查它是否在数据库中,然后使用 Piccolo ORM 中的 remove() 方法将其删除。否则,它将发送错误和 "Id Not Found" 作为响应。

总结

老实说,这是第一次尝试除了 Django 之外的 Python 中的另一个 Web 框架。我不会比较它们,我认为它们是不同的。另外,这是第一次使用 Piccolo 和 Postgres,我一直在使用 SQLite,但是我在 Piccolo 的 GitHub 上看到一个问题,说 SQLite 中没有启用连接池。对我来说,学习 Blacksheep 和 Piccolo 很有趣,我将开始学习更多内容并与它们一起构建项目。

如果您对其他软件包、架构、如何改进我的代码、我的英语或任何其他方面有任何建议;请发表评论或通过 Twitter、LinkedIn 与我联系。

源代码在这里

参考文档:

  1. Blacksheep documentation
  2. Piccolo documentation
相关推荐
Shy9604187 分钟前
Bert完形填空
python·深度学习·bert
上海_彭彭18 分钟前
【提效工具开发】Python功能模块执行和 SQL 执行 需求整理
开发语言·python·sql·测试工具·element
zhongcx0134 分钟前
使用Python查找大文件的实用脚本
python
Yaml441 分钟前
智能化健身房管理:Spring Boot与Vue的创新解决方案
前端·spring boot·后端·mysql·vue·健身房管理
yyfhq1 小时前
sdnet
python
测试19982 小时前
2024软件测试面试热点问题
自动化测试·软件测试·python·测试工具·面试·职场和发展·压力测试
love_and_hope2 小时前
Pytorch学习--神经网络--搭建小实战(手撕CIFAR 10 model structure)和 Sequential 的使用
人工智能·pytorch·python·深度学习·学习
小码编匠2 小时前
一款 C# 编写的神经网络计算图框架
后端·神经网络·c#
AskHarries2 小时前
Java字节码增强库ByteBuddy
java·后端