举例 PyInstaller 打包 Tortoise ORM 遇到的问题及解决方案
本文总结了在使用 PyInstaller 打包 Tortoise ORM 时可能遇到的问题及对应解决方案,适用于处理动态模块加载失败、数据库后端模块未包含等常见情况。
1. 问题描述
在使用 PyInstaller 打包基于 Tortoise ORM 的项目时,常见错误包括:
-
缺少元数据文件:
原因:PyInstaller 默认不会将importlib.metadata.PackageNotFoundError: tortoise-orm
tortoise-orm
的元数据(如METADATA
文件)打包。 -
缺少数据库后端模块:
原因:Tortoise ORM 的后端模块(如 SQLite、MySQL、PostgreSQL 等)通常通过动态加载实现,PyInstaller 无法自动识别这些模块。ModuleNotFoundError: No module named 'tortoise.backends.sqlite'
-
未加载动态模块: 某些扩展功能或动态模块(如
tortoise.contrib.fastapi
、tortoise.transactions
)未被打包,导致运行时失败。
2. 解决方案
2.1 元数据文件(如 METADATA
)是 importlib.metadata
用于获取包信息的重要部分。如果未包含该文件,会导致 PackageNotFoundError
。
方法1: :collect_all 一站式收集 同时获取包的 元数据(datas)、二进制文件(binaries) 和 隐藏导入项(hiddenimports)。 (推荐
)√√√
from PyInstaller.utils.hooks import collect_all
# 一次性获取 tortoise-orm 的所有资源
tortoise_datas, tortoise_binaries, tortoise_hiddenimports = collect_all('tortoise')
# 或使用 PyPI 包名:collect_all('tortoise-orm')(两者均可,优先用模块名)
最终加入到
a = Analysis(
['main.py'],
pathex=[],
datas=tortoise_datas, # 包含元数据和数据文件
binaries=tortoise_binaries, # 包含二进制依赖(如有)
hiddenimports=tortoise_hiddenimports, # 包含所有子模块(如数据库后端)
hookspath=[],
excludes=[],
runtime_hooks=[],
cipher=block_cipher,
)
方法 2:使用 copy_metadata(单独引推荐) √
在 .spec
文件中添加:
from PyInstaller.utils.hooks import copy_metadata
datas = copy_metadata('tortoise-orm')
.spec文件如何生成?
pyinstaller --name xxx main.py --onefile --specpath .
方法 3:手动添加元数据路径 不推荐 ×
如果 copy_metadata
不适用,可以手动指定 tortoise-orm
的元数据路径:
datas = [
('D:/Python/Lib/site-packages/tortoise-orm-0.18.0.dist-info', 'tortoise-orm-0.18.0.dist-info'),
]
2.2 包含数据库后端模块
- 一站式收集:同时获取包的 元数据(datas)、二进制文件(binaries) 和 隐藏导入项(hiddenimports)。推荐 一站式方案 同上已处理
- Tortoise ORM 的功能模块较多,推荐使用
collect_submodules
自动收集模块。推荐
from PyInstaller.utils.hooks import collect_submodules
hiddenimports = collect_submodules('tortoise')
如果是多个包 循环加入
modules_to_collect = ['tortoise', 'pydantic', 'fastapi']
hiddenimports = []
for module in modules_to_collect:
hiddenimports += collect_submodules(module)
手动添加到 hiddenimports
。以下是 Tortoise 支持的常见后端模块:繁琐不推荐 ×
hiddenimports=[
'tortoise.backends.sqlite', # SQLite
'tortoise.backends.mysql', # MySQL
'tortoise.backends.asyncpg', # PostgreSQL
'tortoise.backends.aioodbc', # ODBC
'tortoise.backends.db_url', # 动态数据库 URL 解析
]
最终的.spec文件 (可以直接抄作业)
一站式加载方案
# -*- mode: python ; coding: utf-8 -*-
from PyInstaller.utils.hooks import collect_all
block_cipher = None
# ----------------------
# 1. 使用 collect_all 收集 Tortoise ORM 资源
# ----------------------
tortoise_datas, tortoise_binaries, tortoise_hiddenimports = collect_all('tortoise')
# ----------------------
# 2. 分析入口文件及依赖(合并资源)
# ----------------------
a = Analysis(
['main.py'],
pathex=[],
datas=tortoise_datas, # 包含元数据和数据文件
binaries=tortoise_binaries, # 包含二进制依赖(如有)
hiddenimports=tortoise_hiddenimports, # 包含所有子模块(如数据库后端)
hookspath=[],
excludes=[],
runtime_hooks=[],
cipher=block_cipher,
)
# ----------------------
# 3. 生成可执行文件(其他配置不变)
# ----------------------
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
exe = EXE(
pyz,
a.scripts,
name='tortoise_app',
upx=True,
console=True,
)
分开加载方案
from PyInstaller.utils.hooks import copy_metadata
from PyInstaller.utils.hooks import collect_submodules
# -*- mode: python ; coding: utf-8 -*-
block_cipher = None
a = Analysis(
['main.py'],
pathex=[],
binaries=[],
datas=copy_metadata('tortoise-orm'),
hiddenimports=collect_submodules('tortoise'),
hookspath=[],
hooksconfig={},
runtime_hooks=[],
excludes=[],
win_no_prefer_redirects=False,
win_private_assemblies=False,
cipher=block_cipher,
noarchive=False,
)
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
exe = EXE(
pyz,
a.scripts,
a.binaries,
a.zipfiles,
a.datas,
[],
name='main',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
upx_exclude=[],
runtime_tmpdir=None,
console=True,
disable_windowed_traceback=False,
argv_emulation=False,
target_arch=None,
codesign_identity=None,
entitlements_file=None,
)