- 数据库
- 后端
【免费下载链接】tortoise-orm
Familiar asyncio ORM for python, built with relations in mind
本文以 Tortoise-ORM 仓库中 Sanic 集成示例 为主线,系统讲解如何通过tortoise.contrib.sanic.register_tortoise在 Sanic 应用中完成 ORM 的启动初始化、建表与关闭清理。读者读完本文将掌握:示例应用的真实代码结构、register_tortoise全部参数的用法与底层实现、Sanic 多进程下的生命周期钩子原理,以及如何用 sanic-testing 编写集成测试。
一句话理解 register_tortoise
Tortoise-ORM 为 Sanic 提供了一个轻量级集成工具tortoise.contrib.sanic,它只暴露一个函数register_tortoise:在服务启动时(startup)设置好 Tortoise-ORM,在服务关闭时(teardown)自动清理连接。这是 Tortoise-ORM 官方contrib集成体系中与 aiohttp、Starlette、FastAPI、BlackSheep 等方案并列的 Sanic 专用入口,官方使用说明见 docs/contrib/sanic.rst。
示例项目结构
仓库中的 examples/sanic 目录是一个最小但完整可运行的 Sanic 集成示例,包含四个文件:
| 文件 | 作用 |
|---|---|
| examples/sanic/models.py | 定义 Tortoise-ORM 数据模型 |
| examples/sanic/main.py | Sanic 应用入口,调用register_tortoise |
| examples/sanic/_tests.py | 基于 sanic-testing 的集成测试 |
| examples/sanic/README.rst | 示例说明文档 |
启动方式与原文档一致,只需在示例目录下执行:
python3 main.py依赖方面,Sanic 与 sanic-testing 已被列入项目contrib可选依赖组,见 pyproject.toml,安装时使用pip install "tortoise-orm[contrib]"即可一并获得。
数据模型:先定义 Users
models.py 定义了本示例唯一的模型:
from tortoise import Model, fields class Users(Model): id = fields.IntField(primary_key=True) name = fields.CharField(50) def __str__(self): return f"User {self.id}: {self.name}"这里展示了 Tortoise-ORM 建模的两种最基础字段:
fields.IntField(primary_key=True):整数型主键;fields.CharField(50):最大长度 50 的字符串字段,与 Django ORM 的字段风格一致。
__str__方法让模型实例在str(user)时输出User 1: New User这类可读文本,后面的路由与测试都依赖这一输出格式。
应用入口:路由 + register_tortoise
main.py 是集成示例的核心:
import logging from models import Users from sanic import Sanic, response from tortoise.contrib.sanic import register_tortoise logging.basicConfig(level=logging.DEBUG) app = Sanic(__name__) @app.route("/") async def list_all(request): users = await Users.all() return response.json({"users": [str(user) for user in users]}) @app.post("/user") async def add_user(request): user = await Users.create(name="New User") return response.json({"user": str(user)}) register_tortoise( app, db_url="sqlite://db.sqlite3", modules={"models": ["models"]}, generate_schemas=True ) if __name__ == "__main__": app.run(port=5000, debug=True)示例包含两个路由:GET /查询全部用户并以 JSON 返回;POST /user创建一个名为"New User"的用户。注意查询/创建操作直接使用异步 ORM API(await Users.all()、await Users.create(...)),因为 Sanic 的事件循环与 Tortoise-ORM 的异步驱动天然兼容。
关键一行是应用底部对register_tortoise的调用,它使用最简配置组合(db_url, modules)完成初始化:
db_url="sqlite://db.sqlite3":以 DB_URL 字符串形式指向 SQLite 数据库文件;modules={"models": ["models"]}:声明应用名"models"及其需要扫描的模型模块"models"(即本目录下的 models.py);generate_schemas=True:服务启动时自动按模型建表。
register_tortoise 参数完全解读
register_tortoise的完整签名位于 tortoise/contrib/sanic/init.py:
def register_tortoise( app: Sanic, config: dict | None = None, config_file: str | None = None, db_url: str | None = None, modules: dict[str, Iterable[str | ModuleType]] | None = None, generate_schemas: bool = False, _enable_global_fallback: bool = True, ) -> None:三种互斥的配置方式
参数说明明确要求:只能使用config、config_file与(db_url, modules)三组中的一组,它们最终都会透传给 Tortoise.init。
方式一:config(字典配置)
直接传入完整配置字典,支持多连接、多应用与路由配置:
register_tortoise( app, config={ "connections": { # Dict 格式的连接 "default": { "engine": "tortoise.backends.asyncpg", "credentials": { "host": "localhost", "port": "5432", "user": "tortoise", "password": "qwerty123", "database": "test", }, }, # 也可以直接用 DB_URL 字符串作为连接 "default": "postgres://postgres:qwerty123@localhost:5432/events", }, "apps": { "models": { "models": ["__main__"], # 若不指定 default_connection,默认使用名为 "default" 的连接 "default_connection": "default", } }, }, )注意connections字典中"default"键被赋值两次,实际以最后一次为准,此处意在演示同一种连接可用 dict 或 DB_URL 两种写法。底层 Tortoise.init 的 docstring 还补充了可选顶层键:routers(数据库路由)、use_tz(datetime 是否时区感知)与timezone。
方式二:config_file(配置文件)
传入.json或.yml(需已安装 PyYAML)文件路径,文件内容格式与config字典完全一致。
方式三:db_url + modules(最简写法)
本示例采用的形式,适合单连接、单应用的快速起步。modules是{应用名: [模型模块列表]}的映射,模型模块可以是模块名字符串,也可以是ModuleType对象。
generate_schemas:仅限开发环境
generate_schemas=True会在启动时调用Tortoise.generate_schemas()自动建表。官方在 tortoise/init.py#L497-L510 中给出了明确警告:建表在表已存在时会失败,因此不建议在生产工作流中使用。它主要适用于开发环境或 SQLite:memory:内存数据库场景;生产环境应改用 aerich 迁移工具 管理表结构。
_enable_global_fallback:内部开关
以下划线开头属于内部参数,默认True。它控制是否将当前 Tortoise 上下文设置为全局回退上下文(global fallback),相关机制定义在 tortoise/context.py,用于在显式上下文之外也能访问到连接。普通开发者保持默认即可。
底层原理:Sanic 生命周期钩子如何驱动 ORM
register_tortoise的价值在于它把 Tortoise-ORM 的初始化/关闭与 Sanic 的服务器生命周期精确对齐。从 tortoise/contrib/sanic/init.py#L84-L111 可以看到完整实现:
async def tortoise_init() -> None: await Tortoise.init( config=config, config_file=config_file, db_url=db_url, modules=modules, _enable_global_fallback=_enable_global_fallback, ) logger.info("Tortoise-ORM started, %s, %s", get_connections()._get_storage(), Tortoise.apps) if generate_schemas: @app.main_process_start async def init_orm_main(app): await tortoise_init() logger.info("Tortoise-ORM generating schema") await Tortoise.generate_schemas() @app.before_server_start async def init_orm(app): await tortoise_init() if generate_schemas and getattr(app, "_test_manager", None): # Running by sanic-testing await Tortoise.generate_schemas() @app.after_server_stop async def close_orm(app): await Tortoise.close_connections() logger.info("Tortoise-ORM shutdown")整个生命周期分三个阶段:
1. 主进程启动(main_process_start)
仅当generate_schemas=True时注册。Sanic 采用多进程模型,main_process_start在主进程(master)中只执行一次,这里完成 Tortoise 初始化并建表,避免多个 worker 并发建表引发冲突。
2. 每个 worker 启动前(before_server_start)
无论是否建表都会注册。由于每个 Sanic worker 拥有独立的事件循环与连接池,必须在每个 worker 内各自调用一次tortoise_init(),这正是 Tortoise.init 所述的“加载应用与模型、配置连接但不立即连接,首次查询时才惰性建立连接/连接池”。这里还包含一个针对 sanic-testing 的兼容分支:当app._test_manager为真(ReusableClient 不会触发真正的main_process_start)时,在before_server_start中补一次建表。
3. 服务停止后(after_server_stop)
调用Tortoise.close_connections()干净地关闭所有连接。tortoise/init.py#L470-L481 的 docstring 强调:进程退出前必须关闭连接,否则事件循环可能因等待连接关闭而永远无法结束——这正是“teardown 清理”环节存在的意义。
DB_URL 格式速查
示例使用的sqlite://db.sqlite3只是 DB_URL 的一种。Tortoise-ORM 支持更通用的形式(详见 docs/databases.rst):
{DB_TYPE}://{USERNAME}:{PASSWORD}@{HOST}:{PORT}/{DB_NAME}?{PARAM1}=value&{PARAM2}=value常见类型:
sqlite://{DB_FILE}:注意当 DB_FILE 是绝对路径/data/db.sqlite3时需写三个斜杠sqlite:///data/db.sqlite;postgres://postgres:pass@db.host:5432/somedb(asyncpg),亦可显式写作asyncpg://或psycopg://;mysql://myuser:mypass@db.host:3306/somedb;mssql://myuser:mypass@db.host:1433/somedb,可携带 ODBC driver 参数。
另外 docs/databases.rst#L33-L62 特别提醒:密码含%后跟合法十六进制字符时可能被 URL 解析器误判为百分号编码,此时应改用 dict 格式配置以绕开 URL 解析。
用 sanic-testing 验证集成
examples/sanic/_tests.py 展示了如何对 Sanic + Tortoise 应用做端到端测试:
import re from pathlib import Path import pytest from sanic_testing.reusable import ReusableClient try: import main except ImportError: if (cwd := Path.cwd()) == (parent := Path(__file__).parent): dirpath = "." else: dirpath = str(parent.relative_to(cwd)) print(f"You may need to explicitly declare python path:\n\nexport PYTHONPATH={dirpath}\n") raise @pytest.fixture(scope="module") def anyio_backend() -> str: return "asyncio" @pytest.fixture def client(): sanic_app = main.app # make register_tortoise treat this as sanic-testing (ReusableClient doesn't set this flag) sanic_app._test_manager = True client = ReusableClient(sanic_app) with client: yield client def test_basic_test_client(client): request, response = client.get("/") assert response.status == 200 assert b'{"users":[' in response.body request, response = client.post("/user") assert response.status == 200 assert re.match(rb'{"user":"User \d+: New User"}$', response.body)两个值得注意的实践细节:
app._test_manager = True:注释说明了关键点——ReusableClient不会设置该标志,手动置为True后,register_tortoise内部的before_server_start分支才会在测试环境中补建表(对应源码 tortoise/contrib/sanic/init.py#L105-L107);anyio_backend返回"asyncio":测试运行在 asyncio 事件循环上,与 Tortoise-ORM 的异步驱动一致。
测试断言验证了端到端链路:GET /返回空用户列表 JSON,POST /user创建用户并返回User N: New User格式的 JSON,说明初始化、建表、查询、写入、关闭整条生命周期都工作正常。
生产环境落地建议
综合官方文档与源码可以总结出以下实践要点:
- 关闭自动建表:
generate_schemas仅适合开发/内存库,生产请使用 aerich 迁移 管理 schema; - 连接清理交给钩子:不要手动调用
Tortoise.close_connections(),after_server_stop已保证退出时干净关闭,避免事件循环悬挂; - 多 worker 天然安全:
before_server_start在每个 worker 内各自初始化连接池,多进程部署无需额外加锁处理连接初始化; - 复杂配置选 dict/文件:多数据库、连接路由(
routers)、时区设置(use_tz/timezone)等场景请使用config或config_file方式,完整能力参见 Tortoise.init 文档。
相关文档延伸阅读
- Sanic 集成官方说明:docs/contrib/sanic.rst
- 示例文档:docs/examples/sanic.rst
- 数据库支持与 DB_URL 详解:docs/databases.rst
- 连接管理文档:docs/connections.rst
- 迁移工具(生产建表推荐):docs/migration.rst
- 数据库
- 后端
【免费下载链接】tortoise-orm
Familiar asyncio ORM for python, built with relations in mind
相关推荐
Tortoise-ORM 与 Sanic 集成实战:register_tortoise 生命周期接入与配置详解
Tortoise ORM 与 Sanic 集成实战:register_tortoise 生命周期接入与配置详解 本指南基于 Tortoise ORM 官方文档中
数据库后端Tortoise-ORM 与 Quart 集成实战:register_tortoise 生命周期管理与 CLI 全解析
Tortoise ORM 与 Quart 集成实战:register_tortoise 生命周期管理与 CLI 全解析 本指南围绕 Tortoise ORM 官
数据库后端Tortoise-ORM 与 BlackSheep 集成指南:register_tortoise 生命周期管理详解
Tortoise ORM 与 BlackSheep 集成指南:register_tortoise 生命周期管理详解 Tortoise ORM 为 BlackSh
数据库后端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考