含多子项目的Python项目依赖管理最佳实践(无需Poetry/Conda)
多子项目pyproject.toml依赖管理与打包最佳实践(无Poetry/Conda)
方案1:用setuptools直接关联子项目依赖
这是最贴合标准打包流程的方案,父项目通过本地路径引用子项目,自动继承子项目的所有依赖,不用手动复制。
- 先把每个子项目的pyproject.toml配置成可独立安装的包,比如子项目
subproj_a的配置:[build-system] requires = ["setuptools>=61.0"] build-backend = "setuptools.build_meta" [project] name = "subproj-a" version = "0.1.0" dependencies = ["requests>=2.28.0"] - 父项目的pyproject.toml里,把每个子项目作为本地依赖引入:
[build-system] requires = ["setuptools>=61.0"] build-backend = "setuptools.build_meta" [project] name = "parent-proj" version = "0.1.0" dependencies = [ "subproj-a @ file://./subproj_a", "subproj-b @ file://./subproj_b", # 其他子项目按这个格式添加 ] - 执行
pip install .在父项目根目录,会自动安装所有子项目及它们的依赖;要生成本地wheel,直接运行python -m build就行,父项目的wheel会包含所有子项目的依赖声明,后续上传私有仓库也没问题。
方案2:写轻量Python脚本自动同步依赖
如果不想在父项目里硬编码子项目路径,写个小脚本遍历子项目,自动把所有子项目的依赖同步到父项目的pyproject.toml里,适配打包需求。
示例脚本:
import tomllib import tomli_w import os # 改成你实际的子项目目录列表 SUBPROJECTS = ["subproj_a", "subproj_b", "subproj_c"] PARENT_PYPROJECT = "pyproject.toml" def get_subproject_deps(subproj_dir): pyproject_path = os.path.join(subproj_dir, "pyproject.toml") if not os.path.exists(pyproject_path): return [] with open(pyproject_path, "rb") as f: data = tomllib.load(f) return data.get("project", {}).get("dependencies", []) def update_parent_pyproject(): # 读取父项目配置 with open(PARENT_PYPROJECT, "rb") as f: parent_data = tomllib.load(f) # 收集所有子项目依赖并去重 all_deps = set() for subproj in SUBPROJECTS: deps = get_subproject_deps(subproj) all_deps.update(deps) # 更新父项目的依赖列表 parent_data["project"]["dependencies"] = list(all_deps) # 写回父项目的pyproject.toml with open(PARENT_PYPROJECT, "wb") as f: tomli_w.dump(parent_data, f) if __name__ == "__main__": update_parent_pyproject() print("父项目依赖已同步完成")
- 子项目依赖变更时跑一遍这个脚本,就能自动更新父项目的依赖。之后执行
python -m build生成的wheel,会包含所有子项目的依赖,直接上传私有仓库或者本地使用都可以。
方案3:用requirements文件统一管理依赖
如果习惯用requirements文件,也可以写个脚本提取所有子项目的依赖,生成统一的requirements.txt,再让父项目引用这个文件打包。
示例bash脚本(需要先装yq工具:pip install yq):
#!/bin/bash # 清空现有requirements.txt > requirements.txt # 遍历子项目提取依赖 for subproj in subproj_a subproj_b subproj_c; do if [ -f "$subproj/pyproject.toml" ]; then tomlq -r '.project.dependencies[]' "$subproj/pyproject.toml" >> requirements.txt fi done # 去重 sort -u requirements.txt -o requirements.txt # 可选:直接安装依赖 # pip install -r requirements.txt
- 然后在父项目的pyproject.toml里配置动态读取这个文件的依赖:
[project] name = "parent-proj" version = "0.1.0" dynamic = ["dependencies"] [tool.setuptools.dynamic] dependencies = { file = ["requirements.txt"] } - 这样打包父项目时,setuptools会自动把requirements.txt里的依赖纳入wheel的声明中。
打包到本地或私有仓库的补充
不管用哪种方案,打包流程都很统一:
- 每个子项目单独生成wheel:进入子项目目录执行
python -m build,生成的wheel可以存本地或者上传私有仓库。 - 父项目打包时,如果子项目是本地路径引用,生成的wheel会包含子项目的依赖声明;如果子项目已经上传私有仓库,父项目直接写子项目的包名和版本就行,比如
"subproj-a>=0.1.0"。 - 上传私有仓库用
twine upload --repository-url <你的私有仓库地址> dist/*,提前配置好仓库的认证信息即可。
内容的提问来源于stack exchange,提问作者Valeria
相关产品推荐
相关产品推荐

