在使用 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 与我联系。
源代码在这里。
参考文档: