☰
Tortoise-ORM 与 Sanic 集成实战:register_tortoise 生命周期管理全解析
2026/10/12 6:02:21 网站建设 项目流程
  • 数据库
  • 后端

【免费下载链接】tortoise-orm

Familiar asyncio ORM for python, built with relations in mind

项目地址:https://gitcode.com/gh_mirrors/to/tortoise-orm
点击查看免费下载

本文以 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.pySanic 应用入口,调用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)

两个值得注意的实践细节:

  1. app._test_manager = True:注释说明了关键点——ReusableClient不会设置该标志,手动置为True后,register_tortoise内部的before_server_start分支才会在测试环境中补建表(对应源码 tortoise/contrib/sanic/init.py#L105-L107);
  2. 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

项目地址:https://gitcode.com/gh_mirrors/to/tortoise-orm
点击查看免费下载
上一篇:Carbon-3B部署指南:如何在本地环境快速运行这个基因组基础模型的完整教程
下一篇:Video2X终极指南:专业视频超分辨率与帧插值解决方案

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询