数据库后端【免费下载链接】tortoise-ormFamiliar asyncio ORM for python, built with relations in mind项目地址https://gitcode.com/gh_mirrors/to/tortoise-orm点击查看免费下载本篇技术指南围绕 Tortoise ORM 的“应用初始化”这一核心生命周期展开从定义好模型后的Tortoise.init()开始讲解db_url modules、config字典、config_file三种配置方式generate_schemas()建表、close_connections()与run_async()的清理机制以及跨任务场景下的_enable_global_fallback全局上下文回退。读完本文你将掌握 Tortoise ORM 应用从启动、建表到安全退出的完整姿势并能结合源码理解其底层工作原理从容应对 ASGI lifespan、后台任务和并行测试等复杂初始化场景。一、为什么需要显式初始化Tortoise ORM 是一个以模型为中心的 asyncio ORM。当你把全部模型定义好之后ORM 并不会自动工作它还需要一次显式的初始化来发现并加载模型把指定模块中的Model子类注册到应用app的模型注册表中建立模型间的反向关系例如外键、多对多等关系字段的“反向引用”需要在此阶段完成绑定对应源码中的_init_relations见 tortoise/init.py匹配数据库客户端与模型让每个 app 与对应的连接connection关联起来。官方文档对初始化的定义很精炼初始化用于“在模型之间创建反向关系并将你的数据库客户端与合适的模型匹配起来”。这正是 docs/setup.rst 的主题。Tortoise 提供了三种等价的初始化入口你只能选择其中一种config、config_file、(db_url, modules)三选一同时传入多个会直接抛出ConfigurationError——这一点在 tortoise/init.py 中由int(bool(config) bool(config_file) bool(db_url)) ! 1这段校验强制保证。二、方式一db_urlmodules快速初始化最直接、最适合快速原型与小项目的方式是直接传数据库 URL 和模型模块映射from tortoise import Tortoise async def init(): # 这里创建一个使用文件 db.sqlite3 的 SQLite 数据库 # 并指定 app 名为 models它包含来自 app.models 的模型 await Tortoise.init( db_urlsqlite://db.sqlite3, modules{app: [app.models]} ) # 生成表结构 await Tortoise.generate_schemas()这段示例完成了两件事建立 SQLite 数据库连接db_urlsqlite://db.sqlite3指定数据库文件路径相对路径会基于当前工作目录解析如果是绝对路径/data/db.sqlite3URL 应写成sqlite:///data/db.sqlite3注意三个斜杠发现并初始化模型modules{app: [app.models]}把名为app的应用映射到app.models模块ORM 会扫描该模块并注册其中的模型类。modules是一个dict[str, list[str]]键是 app 名称即应用标签值是待发现的模块路径列表。示例中只配置了一个 app但你完全可以配置多个 app 来分组管理模型例如await Tortoise.init( db_urlsqlite://db.sqlite3, modules{ app: [app.models], another_app: [another_app.models], } )generate_schemas()与safe参数generate_schemas()用于在空数据库上按照已注册模型生成表结构。它有一个safe参数当safeTrue时只在表不存在的情况下才创建CREATE TABLE IF NOT EXISTS语义避免重复建表报错await Tortoise.generate_schemas(safeTrue)从源码看generate_schemas(safe: bool True)的默认值就是True见 tortoise/init.py也就是说默认行为即是“安全建表”。它的实现会遍历当前上下文中所有连接为每个连接调用generate_schema_for_client(connection, safe)定义在 tortoise/utils.py由各后端自己的 schema 生成器产出建表 SQL 并执行。注意如果表已经存在且safeFalse建表会失败因此官方建议generate_schemas()不要作为常规业务工作流的一部分生产环境的表结构变更请走迁移migration工具详见 docs/migration.rst。用__models__显式指定模型列表如果你在app.models模块或你指定的任何模型加载模块中定义了一个__models__变量Tortoise 会优先使用这个列表而不是自动扫描模块里的所有类# app/models.py from tortoise.models import Model from tortoise import fields class User(Model): id fields.IntField(primary_keyTrue) name fields.CharField(max_length50) __models__ [User]从实现上看模型发现逻辑位于 tortoise/apps.py 的_discover_models()它先检查模块是否有__models__属性如果没有才回退为遍历dir(module)收集所有Model子类。显式__models__的好处是精准可控——当模块里混有非模型类或抽象基类时可以避免误注册。三、方式二config字典——多连接、多应用的完整配置当你需要为不同应用配置不同的数据库时db_url modules这种单连接写法就不够用了。此时应使用config字典CONFIG { connections: { default: { engine: tortoise.backends.sqlite, credentials: {file_path: default.sqlite3}, }, another_conn: { engine: tortoise.backends.sqlite, credentials: {file_path: another_conn.sqlite3}, }, }, apps: { app: {models: [app.models], default_connection: default}, another_app: {models: [another_app.models], default_connection: another_conn}, }, } await Tortoise.init(configCONFIG)配置结构分两大块connections连接注册表每个连接由一个名称如default、another_conn标识。每个连接项可以用“engine credentials”字典形式也可以用DB URL 字符串形式例如default: sqlite://default.sqlite3、default: postgres://postgres:qwerty123localhost:5432/test。engine取值为后端模块路径如tortoise.backends.sqlite、tortoise.backends.asyncpg、tortoise.backends.mysql、tortoise.backends.psycopg等apps应用注册表每个 app 通过models指定模型模块列表通过default_connection指定它默认使用哪个连接。如果某个 app 不写default_connection则默认取default连接。这种写法天然支持连接路由读写路由、分库分表等需求可以借助 router 机制实现详细说明见 docs/router.rst。此外多种 Web 框架集成FastAPI、Sanic 等都是在此基础上封装的参见 docs/contrib/fastapi.rst 与 docs/contrib/sanic.rst。从源码看config会被解析为类型化的TortoiseConfig对象定义在 tortoise/config.py并在构造时执行严格校验包括连接名必须是非空字符串每个 app 的default_connection必须指向一个已声明的连接否则抛出ConfigurationErrorApp xxx refers to unknown connection yyyconnections与apps都必须是非空字典。这些校验在 tortoise/config.py 中可见能在初始化早期就暴露配置错误而不是等到首次查询才炸。四、方式三config_file——把配置放进文件除了把配置写死在 Python 代码里Tortoise 还支持从JSON 或 YAML 文件加载配置格式与上面的config字典完全一致await Tortoise.init(config_filetortoise_config.json) # 或 await Tortoise.init(config_filetortoise_config.yml)对应的 YAML 示例connections: default: engine: tortoise.backends.sqlite credentials: file_path: db.sqlite3 apps: models: models: - app.models default_connection: default实现上TortoiseConfig.from_config_file()见 tortoise/config.py会根据扩展名选择解析器.yml/.yaml走 PyYAML 的yaml.safe_load需要安装 PyYAML.json走标准库json.load其他扩展名直接报错“只支持 .yml 和 .json”。这种方式的典型用途是把数据库连接串从代码中剥离出来配合环境变量或部署平台注入的配置文件使用。五、Tortoise.init()参数全景与底层原理Tortoise.init()的完整签名见 tortoise/init.py暴露了更多可调参数整理如下参数默认值作用config/config_file/db_urlmodules三选一配置来源只能传其中一组_create_dbFalse为指定连接尝试创建数据库多用于测试use_tzTruedatetime 字段默认是否时区感知timezoneUTC使用的时区routersNone数据库路由列表元素为字符串路径或路由类table_name_generatorNone表名生成回调入参为模型类不传则默认用模型类名小写init_connectionsTrue为False时跳过连接客户端初始化但仍加载 app 并校验连接名_enable_global_fallbackFalse开启全局上下文回退详见本文第八节一个容易忽略的关键事实Tortoise.init()并不会真正建立数据库连接。它只是“加载应用与模型、配置连接”而真正的连接/连接池是在第一次执行查询时惰性建立的源码 docstring 明确写明 does not connect to the database yet. The actual connection or connection pool is established lazily on first query execution。这意味着init()失败不会是因为数据库不可达而是因为配置或模型本身有问题。另一个细节Tortoise.init()会返回当前初始化的TortoiseContext对象返回值类型为TortoiseContext。当你需要在多次asyncio.run()之间复用上下文时可以捕获该返回值并用with ctx:显式维护上下文。DB URL 是如何被展开的db_url之所以能变成连接配置是因为底层调用了expand_db_url()见 tortoise/backends/base/config_generator.py。它通过标准库urllib.parse解析 URL并根据DB_LOOKUP表把 URL 片段映射为引擎与凭据sqlite://...→ 引擎tortoise.backends.sqlite路径映射为file_path默认启用 WAL 日志模式journal_modeWALjournal_size_limit16384postgres://、asyncpg://→ 引擎tortoise.backends.asyncpg端口默认5432psycopg://→ 引擎tortoise.backends.psycopgmysql://→ 引擎tortoise.backends.mysql默认端口3306、charsetutf8mb4mssql://、oracle://分别对应 SQL Server 与 Oracle 后端。URL 查询字符串中的参数如?max_size20会按各自后端的cast表做类型转换后并入 credentials。需要提醒的是如果密码包含特殊字符如%、/应先用urllib.parse.quote_plus做 URL 编码或者干脆改用config字典形式绕开 URL 解析详见 docs/databases.rst。六、generate_schemas()的底层执行链路generate_schemas()并非魔法它的完整链路是Tortoise.generate_schemas(safe) └─ 遍历 get_connections().all() 中的每个连接 └─ generate_schema_for_client(connection, safe) [tortoise/utils.py] └─ client.schema_generator(client) 各后端 schema_generator └─ get_create_schema_sql(safe) 生成建表 SQL └─ generate_from_string(schema) 在数据库上执行 SQL其中get_create_schema_sql(safe)负责把模型元数据Model._meta翻译成各数据库方言的建表语句safe参数控制是否加上IF NOT EXISTS保护。所有后端的 schema 生成器位于tortoise/backends/*/schema_generator.py与 tortoise/migrations/schema_generator/后者是迁移体系使用的状态生成器。另外如果Tortoise.init()尚未被调用就执行generate_schemas()会抛出ConfigurationError: You have to call .init() first before generating schemas。七、连接清理为什么它生死攸关Tortoise ORM 会对外部数据库保持打开的连接。作为一个基于asyncio的库如果这些连接没有被正确关闭Python 解释器可能会一直等待这些连接的完成导致事件循环无法优雅退出——表现为脚本“卡住”不结束。正确的做法是在程序退出前调用await Tortoise.close_connections()close_connections()见 tortoise/init.py会找到当前活跃上下文并关闭其全部连接同时打印一条Tortoise-ORM shutdown日志。对于使用全局回退的场景它还会顺带清除全局上下文详见下一节。对于简单脚本更省心的替代方案是使用辅助函数tortoise.run_async()——它会在协程结束时自动关闭连接from tortoise import Tortoise, run_async async def do_stuff(): await Tortoise.init( db_urlsqlite://db.sqlite3, modules{models: [app.models]} ) # ... 业务逻辑 run_async(do_stuff())run_async()定义于 tortoise/init.py的内部实现是用anyio的阻塞 portal 运行main()在finally中无条件执行connections.close_all(discardTrue)。这就是官方文档所说的“run_async()会在应用终止时自动确保连接被关闭”。仓库中的 examples/basic.py 就采用了这种run_async()写法。在 Web 框架集成中连接清理由框架生命周期托管例如 FastAPI 的RegisterTortoise在__aexit__时调用close_orm()→Tortoise.close_connections()见 tortoise/contrib/fastapi/init.py。八、全局上下文回退Global Context Fallback8.1 问题场景初始化与查询不在同一个任务Tortoise ORM 默认使用 Python 的contextvars来追踪当前活跃上下文_current_context见 tortoise/context.py。当Tortoise.init()与执行查询的代码处于**同一个任务task**时这套机制工作得很好。但在现实项目中初始化经常发生在与查询不同的任务里ASGI lifespan 处理器在后台任务中运行如 FastAPI asgi-lifespan的测试模式框架的启动代码派生出独立的初始化任务测试框架在后台任务中管理应用生命周期。此时初始化任务里设置的contextvars上下文对其他任务不可见查询时就会抛出RuntimeError: No TortoiseContext is currently active8.2 解决方案开启_enable_global_fallback解决方法是使用_enable_global_fallback参数await Tortoise.init( db_urlsqlite://db.sqlite3, modules{models: [app.models]}, _enable_global_fallbackTrue, )开启后Tortoise 除了把上下文写入contextvar还会额外把上下文存入进程级全局变量从而让进程内任何任务都能访问到它。8.3 底层实现在 tortoise/context.py 中有一个模块级变量_global_context。get_current_context()的查找顺序是1. 先从 contextvar _current_context 读取保证隔离性优先 2. 如果为 None再回退到 _global_context全局回退当_enable_global_fallbackTrue时TortoiseContext.init()会调用set_global_context(self)见 tortoise/context.py把当前上下文设置为全局回退。而Tortoise.close_connections()在关闭连接时会检查“全局回退是否就是本上下文”如果是则将其清空见 tortoise/context.py避免进程内残留脏状态。8.4 必须注意的四条约束官方文档明确了开启全局回退后的重要注意事项同时只能存在一个全局回退上下文——全局变量是单例的当已有全局回退被设置时再次开启会抛出ConfigurationErrorGlobal context fallback is already enabled by another Tortoise.init() call...见 tortoise/context.py如果需要多个相互隔离的上下文比如同一进程内并行跑多套数据库配置应改用显式的TortoiseContext()实例而不是全局回退全局回退会在调用Tortoise.close_connections()时自动清除。8.5 框架集成中的默认值该参数同样暴露在TortoiseContext.init()中并被框架集成所使用。最典型的是 FastAPI 的RegisterTortoise——它的_enable_global_fallback默认值为True见 tortoise/contrib/fastapi/init.py因为 FastAPI 的 asgi-lifespan 会在后台任务中运行 lifespan而请求/测试则在另一个任务中执行必须借助全局回退才能让两边共享上下文。相关测试也断言了这一点tests/contrib/test_fastapi.py与 tests/contrib/test_blacksheep.py 都验证了集成层会以_enable_global_fallbackTrue调用Tortoise.init()。反面场景如果同一进程里要跑多个 FastAPI 应用各自独立初始化 Tortoise应把RegisterTortoise的_enable_global_fallback显式设为False否则第二个应用初始化时会因全局回退已被占用而报ConfigurationError。九、进阶补充多上下文隔离与测试理解了全局回退的约束后自然会引出一个更精细的管理手段TortoiseContext显式上下文。每个TortoiseContext实例独立拥有自己的连接处理器ConnectionHandler、应用注册表Apps和初始化状态适用于并行测试执行每个 worker 拥有独立上下文pytest-xdist 安全同一进程内管理多套数据库配置需要自动清理的作用域数据库会话。用法是把初始化放进异步上下文管理器from tortoise.context import TortoiseContext async with TortoiseContext() as ctx: await ctx.init( db_urlsqlite://:memory:, modules{models: [myapp.models]}, ) await ctx.generate_schemas() # 在此作用域内执行查询模型会自动使用该上下文的连接TortoiseContext.__aexit__会依次执行close_connections()、清空应用注册表并恢复上一个上下文见 tortoise/context.py。针对测试场景仓库还提供了tortoise_test_context()见 tortoise/context.py它每次调用都会创建一个全新隔离的上下文、建好全新数据库并生成 schema退出时自动清理是 pytest 测试中推荐的初始化方式。十、实战参考仓库中的可运行示例仓库提供了大量可直接参考的初始化范例examples/basic.py最基础的Tortoise.init()run_async()用法examples/fastapi/config.py用类型化的TortoiseConfigDBUrlConfig组合配置配合RegisterTortoise注入 FastAPI 生命周期examples/fastapi/main.pyFastAPI lifespan 中挂载 ORM 的完整写法examples/migrations_project/包含迁移配置migrations键的多迁移示例迁移命令见 docs/migration.rstexamples/two_databases.py双数据库、多连接场景的初始化示范。小结从Tortoise.init()的三种配置方式到generate_schemas()建表、close_connections()/run_async()清理连接再到_enable_global_fallback解决跨任务上下文不可见问题——Tortoise ORM 的初始化其实是一条完整、可控的生命周期管线。记住三条核心原则配置三选一、连接惰性建立、退出必清理在多任务或多应用场景下先想清楚是用全局回退还是显式TortoiseContext就能避免绝大多数初始化相关的RuntimeError与ConfigurationError。赞分享数据库后端【免费下载链接】tortoise-ormFamiliar asyncio ORM for python, built with relations in mind项目地址https://gitcode.com/gh_mirrors/to/tortoise-orm点击查看免费下载相关推荐Tortoise ORM 初始化与配置指南Tortoise ORM 初始化与配置指南 概述 Tortoise ORM 是一个基于 asyncio 的 Python ORM 框架专为异步应用程序设计。本数据库后端Tortoise ORM 多数据库路由Router实战读写分离与连接选择机制全解析Tortoise ORM 多数据库路由Router实战读写分离与连接选择机制全解析 导读 本文围绕 Tortoise ORM 的多数据库路由机制展开讲数据库后端mall4j 数据库初始化实战yami_shop.sql 导入与 MySQL 连接配置全解析mall4j 数据库初始化实战yami_shop.sql 导入与 MySQL 连接配置全解析 本文围绕 mall4j 电商商城项目的数据库初始化环节展开完整电商后端前端移动开发上一篇Dask故障排除手册快速解决分布式计算中的10大常见问题下一篇V8Js异常处理实战如何优雅处理PHP与JS之间的错误传播创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
阅读完成 · 觉得有帮助?