如何将Python包目录外的ABOUT.md正确纳入wheel打包流程
我开发了一款可展示项目ABOUT.md文件内容的Web应用,项目文件结构如下:
project_folder/ main_package/ assets/icon.png __init__.py app.py .gitignore # 及其他配套文件 README.md ABOUT.md setup.cfg setup.py
app.py中的Web服务负责渲染并对外提供ABOUT.md的内容,原有路径读取实现如下:
from pathlib import Path from main_package import __file__ as mpfile # 第一级parent为__init__.py所在的main_package目录 ABOUT_MD = Path(mpfile).parent.parent / 'ABOUT.md'
该逻辑在未构建的本地开发环境可正常运行,但项目打包为wheel安装到其他环境后,会抛出文件找不到的异常,功能失效。
之前曾尝试修改setup.cfg将ABOUT.md纳入包数据范围,配置如下:
[options.package_data] main_package = ../ABOUT.md assets/*
但该配置会将ABOUT.md直接复制到site_packages根目录,不符合包文件存放规范,整洁性差。
核心需求
ABOUT.md保留在项目根目录存放,保证GitHub仓库网页端可直接访问查看- 项目构建、发布后,用户通过pip安装的包可以正常读取该文件,无路径错误
已排除的方案
初步设想
曾考虑修改构建系统逻辑,构建wheel时自动将根目录的ABOUT.md复制到main_package/assets/ABOUT.md路径下,再在app.py中增加分支判断,根据运行环境加载对应路径的文件,但暂不清楚如何配置构建系统实现该自动复制操作。
2022-07-18更新:排除链接类方案的原因
针对硬链接、软链接的实现思路,均不适用:
- 硬链接:链接关系无法通过Git同步,其他设备拉取代码后会被识别为两个独立文件,需要额外配置钩子做内容同步,还会占用双倍磁盘空间
- 软链接(符号链接):磁盘占用低,但Git仓库的Web视图无法跟随软链接读取目标内容,只会显示软链接本身的纯文本路径,导致根目录的
ABOUT.md无法在网页端正常查看
直接采用最初设想的构建时自动复制逻辑即可,不需要引入额外依赖,也没有黑魔法,完全符合所有需求:
1. 配置构建时自动复制逻辑
在setup.py中添加自定义构建命令,构建wheel时临时将根目录的ABOUT.md复制到包内assets目录,构建完成后自动删除本地临时文件,避免污染开发仓库:
import os import shutil from pathlib import Path from setuptools import setup from setuptools.command.build_py import build_py ROOT = Path(__file__).parent class BuildWithAbout(build_py): def run(self): # 构建前复制文件到包内目录 src = ROOT / 'ABOUT.md' target_dir = ROOT / 'main_package' / 'assets' target = target_dir / 'ABOUT.md' target_dir.mkdir(exist_ok=True) shutil.copy2(src, target) # 执行原有构建流程 super().run() # 构建结束删除临时复制的文件,避免开发目录出现冗余文件 if target.exists(): os.remove(target) setup( cmdclass={'build_py': BuildWithAbout}, # 保留原有setup的其他配置参数即可 )
同时修改setup.cfg,删除之前错误的../ABOUT.md配置,调整包数据规则如下:
[options] include_package_data = True [options.package_data] main_package = assets/*
该配置的效果:
- 本地开发时,
main_package/assets下不会存在冗余的ABOUT.md副本,仓库始终只有根目录一份源文件,GitHub网页端可正常访问 - 构建wheel包时,
ABOUT.md会被自动临时复制到包内assets目录,随包一起打包,构建完成后本地临时文件自动清理 - 安装后的包中,
ABOUT.md会存放在main_package/assets/路径下,完全在包目录内部,不会散落到site-packages根目录,符合Python打包规范
2. 调整app.py的路径读取逻辑
替换原有硬编码路径的写法,增加环境兼容判断,优先读取包内打包的文件,fallback到开发环境的根目录路径:
from pathlib import Path from importlib.resources import files import main_package def load_about_path() -> Path: # 优先读取包内资源(生产安装环境) pkg_path = files(main_package) / 'assets' / 'ABOUT.md' if pkg_path.is_file(): return pkg_path # 适配本地开发环境 dev_path = Path(main_package.__file__).parent.parent / 'ABOUT.md' if dev_path.is_file(): return dev_path raise RuntimeError("ABOUT.md 不存在,请检查包安装完整性") ABOUT_MD = load_about_path()
注:如果需要兼容Python 3.9以下版本,安装importlib_resources backport包,将导入语句替换为from importlib_resources import files即可
该实现不需要手动维护多份文件,不需要处理链接的兼容性问题,构建流程无人工操作,完全满足所有需求。
内容的提问来源于stack exchange,提问作者David Davó

