基于Python的FastAPI后端开发框架如何使用PyInstaller 进行打包与部署
我在随笔《WxPython跨平台开发框架之使用PyInstaller 进行打包处理》中介绍过如何使用PyInstaller 进行打包处理的一些过程和事项。我们基于Python的FastAPI后端应用,在实际开发的时候,直接运行main.py 进行调试即可,但是部署的时候,我们就需要把它们进行打包处理,这里首选PyInstaller 进行打包。本文详细介绍了 如何使用 PyInstaller 对基于 Python 的 FastAPI 后端项目进行打包与部署,使其能够在目标环境中以独立可执行文件的形式运行,无需安装 Python 解释器或额外依赖。文章面向希望将 FastAPI 服务打包为独立运行服务的开发者,特别适用于企业内部系统或需要简化部署的场景。
一、背景与目标
-
说明为什么需要将 FastAPI 项目打包为可执行文件。
-
对比传统部署方式(如
uvicorn main:app
)与 PyInstaller 打包方式的区别。 -
适用场景:企业内网部署、Windows 服务、无 Python 环境的服务器等。
二、环境准备
-
Python 版本要求(推荐 Python 3.12.4+)。
-
FastAPI 与依赖(
fastapi
,uvicorn
,pydantic
,sqlalchemy
, 等)。 -
安装 PyInstaller:
pip install pyinstaller
三、FastAPI 项目结构示例
展示一个典型的 FastAPI 项目结构:
并说明 main.py
中如何启动服务,例如:
import uvicorn from app.main import appif __name__ == "__main__":uvicorn.run(app, host="0.0.0.0", port=8000)
四、使用 PyInstaller 打包
PyInstaller是目前最流行的Python打包工具之一。它可以将Python脚本打包成独立的可执行文件,支持Windows、Linux和macOS平台。
PyInstaller 有丰富的文档,提供了详细的使用说明和常见问题解答,你可以通过以下链接访问:
- PyInstaller 官方文档:https://pyinstaller.readthedocs.io
- GitHub 代码库:https://github.com/pyinstaller/pyinstaller
这些文档和资源能帮助你深入了解 PyInstaller 的使用方式,并解决在打包过程中可能遇到的问题。
打包后的可执行文件可以在没有 Python 环境的机器上运行。PyInstaller 会自动分析程序的依赖关系,并将所有必要的库和资源打包到一个文件或者一个文件夹中。
打包过程中,PyInstaller 会生成一个 .spec
文件。这个文件包含了 PyInstaller 的配置信息,其中包含了构建过程的所有配置信息。你可以修改这个文件来定制打包过程。
如果我们执行下面代码
pyinstaller main.py
或者指定更多的参数的代码
pyinstaller --onefile --icon=your_icon.ico main.py
PyInstaller 都会生成一个 .spec
文件,然后可以编辑 main.spec
文件,以便进行更好的控制管理打包文件。
虽然原则上.spec文件支持跨平台的配置,不过我们在实际中往往根据不同的平台配置特定的.spec文件。
你可以手动修改 .spec
文件来添加资源文件、修改导入模块、定制输出路径等。
你可以通过编辑.spec
文件,在EXE、COLLECT和BUNDLE块下添加一个name=
,为PyInstaller提供一个更好的名字,以便为应用程序(和dist
文件夹)使用。
EXE下的名字是可执行文件的名字,BUNDLE下的名字是应用程序包的名字。
import sys import os from pathlib import Path# 本文件用于Window平台下打包整个项目,生成一个独立的exe文件,依赖文件松散组合 # 执行命令:pyinstaller main_my.spec # 打包后生成文件:dist\fastapi_app\fastapi_app.exe # 运行后,会在当前目录生成一个 dist 文件夹,里面有 fastapi_app.exe 文件,在命令行窗口运行该文件即可启动服务。if sys.platform == "win32":icon = "app/images/app.ico" elif sys.platform == "darwin":icon = "app/images/app.icns"block_cipher = None# 导入 PyInstaller 模块 from PyInstaller.building.build_main import Analysis from PyInstaller.building.build_main import PYZ from PyInstaller.building.build_main import EXE from PyInstaller.building.build_main import COLLECT# Analysis: PyInstaller Analysis object a = Analysis(["app/main.py"],pathex=[],binaries=[],datas=[ ("app/uvicorn_config.json", "app"),("app/.env", "."),("app/images/*", "app/images"),("app/templates/*", "app/templates"),("app/uploadfiles/*", "app/uploadfiles"),("app/logs/*", "app/logs"),],hiddenimports=["uvicorn", "fastapi", "pydantic", "aiomysql", 'asyncio', # 确保依赖被正确包含 ],hookspath=[],hooksconfig={},runtime_hooks=[],excludes=[],win_no_prefer_redirects=False,win_private_assemblies=False,cipher=block_cipher,noarchive=False,optimize=0, )# PYZ: PyInstaller PYZ object pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
修改完成后,执行以下命令来重新打包:
pyinstaller main_my.spec
如果我们想在Windows平台生成的dist目录中生成一个启动exe,和其他相关的Lib依赖库目录,那么我们可以适当调整下.spec文件,让它可以生成松散结构的文件目录包。
exe = EXE(pyz,a.scripts,[],exclude_binaries=True,name="fastapi_app",debug=False,bootloader_ignore_signals=False,strip=False,upx=True,upx_exclude=[],runtime_tmpdir=None,console=True, # True = 有控制台输出(调试方便),False = 静默运行onefile=False, # <-- False取消、True使用 onefile 模式icon=icon, # <-- 图标路径disable_windowed_traceback=False,argv_emulation=False,target_arch=None,codesign_identity=None,entitlements_file=None, )coll = COLLECT(exe, a.binaries,a.zipfiles,a.datas,strip=False,upx=True,name='fastapi_app' )
相当于之前在exe包中的a.binaries 和 a.datas从EXE 构造函数中移到了Collect的构造函数里面了。这样会生成下面的目录结构。
其中_internal目录包含程序的相关依赖包和文件资源。
由于打包的.spec文件指定的目录结构为松散结构(使用了COLLECT构造),那么可以看到 _internal / app目录下有下面的目录结构。
也就是我们前面通过 Analysis 模块指定的datas集合路径的内容。
解决常见问题
-
缺少依赖库:如果打包后运行时出现缺少模块的错误,可以尝试将缺少的模块加入到
hiddenimports
中,或者通过--hidden-import
选项指定: - 大文件:如果使用
--onefile
时打包后的文件太大,考虑使用--onedir
或通过压缩文件等方法进行优化。 - 处理资源文件:如果你的应用程序包含非 Python 代码的资源(如图像、配置文件、数据文件等),你需要通过
--add-data
选项指定资源文件的路径,或者在.spec
文件中修改datas
选项。 -
动态链接库,如果你的应用程序依赖于特定的动态链接库(如 DLL 文件或
.so
文件),你需要将这些库包含到打包中。可以在.spec
文件的binaries
选项中指定: -
多平台支持:PyInstaller 支持 Windows、Linux 和 macOS 等多个平台,但需要在相应的平台上打包。例如,如果你要为 Windows 用户创建可执行文件,最好在 Windows 上运行 PyInstaller 来生成 Windows 的
.exe
文件。如果在 macOS 上打包,生成的文件只能在 macOS 上运行。
在使用 PyInstaller 打包 FastAPI(或其他 Python 应用)时,两个最常见、最容易混淆的参数就是:
-
--add-data
(或.spec
文件中的datas
) -
--hidden-import
(或.spec
文件中的hiddenimports
)
当 PyInstaller 打包时,它默认只会分析 Python 代码的依赖模块,而不会自动包含图片、HTML 模板、配置文件等静态资源。
这时,就需要用 --add-data
(或在 .spec
文件的 datas
中定义)告诉它要额外打包哪些文件或目录。
这里不介绍命令行的方式,只介绍.spec
文件写法:
PyInstaller 在打包时,会分析你的 Python 源代码(AST)来判断使用了哪些模块。但有些模块是动态导入的(例如通过 importlib
或字符串导入),它就可能漏掉。
解决办法:用 --hidden-import
或者.spec文件中指定 hiddenimports 集合,告诉 PyInstaller 把这些模块也打包进去,如上所示。
总结起来就是:
-
datas
= “我还有额外的文件要带上”。 -
hiddenimports
= “我还有额外的模块要带上”。
五、FastAPI 项目打包的处理
前面介绍了一个简单的fastapi的项目结构和启动,一般我们在开发的时候,启动fastapi,直接调用python解析器运行main.py文件即可启动,常规来说,main.py的启动部分函数代码如下。
if __name__ == "__main__":# 日志配置路径config_path = resource_path("app/uvicorn_config.json")# 运行 uvicorntry:config = uvicorn.Config(app = socket_app,reload=True,host=settings.SERVER_IP,log_config = config_config, # 日志配置 )server = uvicorn.Server(config)server.run()except Exception as e:raise e
上面就是我实际项目简化版本的main.py函数的启动内容,正常开发环境,测试是正常的。但是通过pyinstall打包完成,并运行fastapi_app.exe的时候,提示找不到配置文件uvicorn_config.json。
FileNotFoundError: [Errno 2] No such file or directory: 'app/uvicorn_config.json'
这个原因是打包后执行exe文件的当前路径改变了,打包进去的 exe 并没有找到这个文件。首先:修改 .spec
文件,确保文件被打包进去,在 datas
里加这一行 👇
(假设文件路径是 app/uvicorn_config.json
)
datas = [ ("app/uvicorn_config.json", "app"),("app/templates/*", "app/templates"),("app/static/*", "app/static"),("app/images/*", "app/images"), ]
这一步确保 exe 中确实包含了你的 uvicorn_config.json
文件。
其次:在 main.py
中使用通用的路径函数resource_path:
def resource_path(relative_path: str) -> str:"""获取资源文件真实路径,支持:- 开发模式- PyInstaller onefile 模式- PyInstaller COLLECT (_internal) 模式"""if hasattr(sys, '_MEIPASS'): # onefile 模式# exe 解压临时目录base_path = sys._MEIPASSelse:# 在松散模式下,_internal 目录才是真正的数据存放处base_path = os.path.dirname(sys.executable) # exe 所在目录internal_path = os.path.join(base_path, "_internal")if os.path.exists(internal_path):# 松散打包目录base_path = internal_pathelse:# 直接开发运行时base_path = os.path.abspath(".")return os.path.join(base_path, relative_path)
然后修改你的 uvicorn.Config
代码
替换硬编码路径为:
import uvicornconfig_path = resource_path("app/uvicorn_config.json")config = uvicorn.Config(app=socket_app,host=settings.SERVER_IP,port=settings.SERVER_PORT,log_config=config_path, # ✅ 动态获取正确路径 )server = uvicorn.Server(config) server.run()
上面启动后,fastapi 配置文件定位到了,但是可能还会产生新的问题
你可能会发现 app/uvicorn_config.json 里面配置的日志文件路径和实际不对。
FileNotFoundError: [Errno 2] No such file or directory: '.../app/logs/log.log'
Uvicorn 在加载 uvicorn_config.json
时的日志路径是 相对进程工作目录,
而不是相对 uvicorn_config.json
文件本身的路径 ——
这正是为什么你配置 "filename": "app/logs/log.log"
仍然报错的根本原因。
我们需要,在运行前动态修正 log_config.json 内部的路径
我们在加载 JSON 后,动态修改其中 "filename"
字段的路径为打包后正确的绝对路径。
修正代码后如下所示。
if __name__ == "__main__":# 动态解析日志配置路径config_path = resource_path("app/uvicorn_config.json")# 加载并修改日志配置,主要对日志文件路径进行修正with open(config_path, "r", encoding="utf-8") as f:log_config = json.load(f)# 找到其中的 file handler,改写 filename 为绝对路径for handler in log_config.get("handlers", {}).values():if "filename" in handler:log_file = handler["filename"]abs_log_path = resource_path(log_file)os.makedirs(os.path.dirname(abs_log_path), exist_ok=True)handler["filename"] = abs_log_path # 替换为绝对路径# 运行 uvicorn(传入已修改的 log_config dict)try:config = uvicorn.Config(app = socket_app,reload=True,host=settings.SERVER_IP,log_config = log_config, # 日志配置,修正方式见上 )server = uvicorn.Server(config)server.run()except Exception as e:raise e
至此,所有问题都顺利解决,能够正常运行起来了,我们来看看FastAPI顺利启动后的效果。复制松散文件夹到服务器上双击运行即可,需要也可以修改配置文件.env实现相关修改。
✅ 如果运行打包的exe 提示Missing command.
其实是 uvicorn
的提示,不是 PyInstaller 本身的报错。可能是你的app设置上的问题,你在 main.py
里可能用了这种启动方式:
uvicorn.run("app.main:app", host="0.0.0.0", port=8000)
解决方法 改成直接传入 app 对象,而不是字符串路径:
# ✅ 改成直接传 app 对象uvicorn.run(app, host="0.0.0.0", port=8000)
这样 uvicorn
就不会去找字符串形式的 module:app
,而是直接运行你传进去的 FastAPI
实例。 打包后的 exe 就能正常运行。
✅ 如果提示No module named 'aiomysql'
这个问题其实是 PyInstaller 没有把 aiomysql
打包进去,因为它是动态导入的,PyInstaller 静态分析不到。
方法 A:命令行添加 hidden-import
pyinstaller --onefile --name fastapi_app --hidden-import aiomysql app/main.py
方法 B:在 .spec
文件里加 hiddenimports
找到 .spec
文件里的 Analysis
,改成:
a = Analysis(['app/main.py'],pathex=[],binaries=[],datas=datas,hiddenimports=["uvicorn","fastapi","pydantic","aiomysql" # 👈 加上这里 ],hookspath=[],runtime_hooks=[],excludes=[],win_no_prefer_redirects=False,win_private_assemblies=False,cipher=block_cipher,noarchive=False, )
FastAPI + 数据库常用依赖很多(如 sqlalchemy[asyncio]
、asyncpg
、aiomysql
等),有些也可能被漏掉。做法同样:把缺失的库加到 hidden-import。
hiddenimports=["uvicorn","fastapi","pydantic","aiomysql","asyncpg","sqlalchemy.ext.asyncio", ]
✅ Data内容的写法
("app/images/*", "app/images")
会把 app/images
下的所有文件 放到 exe 解压后的目录里,路径是 app/images/...
如果代码里是这样写的:

转载请注明出处:撰写人:伍华聪 http://www.iqidi.com
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/934706.shtml
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!