使用PyInstaller打包Streamlit应用时组件资源缺失问题求助
问题背景
打包依赖streamlit==1.3.0、streamlit-authenticator==0.3.0及其他第三方Streamlit组件的应用为单可执行文件时,使用PyInstaller构建后运行报错:
File "extra_streamlit_components/TabBar/init.py", line 10, in
File "streamlit/components/v1/components.py", line 320, in declare_component
File "streamlit/components/v1/components.py", line 360, in register_component
streamlit.errors.StreamlitAPIException: No such component directory: '/tmp/_MEIHN61O8/extra_streamlit_components/TabBar/frontend/build'
问题源于组件TabBar/__init__.py中通过本地路径加载前端资源:
absolute_path = os.path.dirname(os.path.abspath(__file__)) build_path = os.path.join(absolute_path, "frontend/build") _component_func = components.declare_component("tab_bar", path=build_path)
PyInstaller默认不会打包非Python文件(如组件的frontend/build目录),导致运行时找不到资源。
解决方案
1. 将组件前端目录添加到PyInstaller打包内容
方法一:使用.spec文件配置(推荐)
- 生成.spec文件:
pyi-makespec your_streamlit_app.py - 打开生成的
your_streamlit_app.spec,修改datas列表,添加需要打包的组件前端资源路径(替换python3.x为你的Python版本):datas = [ # TabBar组件的前端资源 ('venv/lib/python3.x/site-packages/extra_streamlit_components/TabBar/frontend/build', 'extra_streamlit_components/TabBar/frontend/build'), # 其他组件同理添加,比如: # ('venv/lib/python3.x/site-packages/streamlit_authenticator/xxx/frontend/build', 'streamlit_authenticator/xxx/frontend/build'), ] - 基于.spec文件打包:
pyinstaller your_streamlit_app.spec
方法二:命令行直接指定
打包时通过--add-data参数逐个添加资源目录,注意不同系统的路径分隔符:
- Linux/macOS:
pyinstaller --add-data "venv/lib/python3.x/site-packages/extra_streamlit_components/TabBar/frontend/build:extra_streamlit_components/TabBar/frontend/build" your_streamlit_app.py - Windows:
pyinstaller --add-data "venv/lib/python3.x/site-packages/extra_streamlit_components/TabBar/frontend/build;extra_streamlit_components/TabBar/frontend/build" your_streamlit_app.py
多个组件需重复添加--add-data参数。
2. 让PyInstaller运行环境正常解析组件路径
PyInstaller打包后,程序运行在临时目录,原有的os.path.abspath无法正确获取资源路径,需改用PyInstaller提供的sys._MEIPASS变量:
修改组件的TabBar/__init__.py代码:
import sys import os import streamlit.components.v1 as components def get_resource_path(relative_path): try: # 打包后的运行环境,获取临时资源目录 base_path = sys._MEIPASS except Exception: # 开发环境,使用本地路径 base_path = os.path.dirname(os.path.abspath(__file__)) return os.path.join(base_path, relative_path) # 使用新的路径获取方式 build_path = get_resource_path("frontend/build") _component_func = components.declare_component("tab_bar", path=build_path)
如果有多个类似组件,需对每个组件的__init__.py做相同修改,确保打包后能正确定位到前端资源。
内容的提问来源于stack exchange,提问作者Pollastre

