为仓库内Python代码构建pip包,兼顾团队内部与外部开源使用
我来分享下我在单仓库多子包打包上的实践经验,刚好踩过类似的坑,完美匹配你的需求——既要把每个子模块做成独立的pip包,又不影响本地开发时的代码导入,还能兼顾内部团队分发和公开OSS发布。
1. 先搞定合理的仓库结构
这是基础,建议采用src布局+独立子包目录的结构,既避免本地导入冲突,又能让每个子包的边界清晰:
my-monorepo/ ├── pyproject.toml # 根目录可选配置,用于统一管理开发环境 ├── package-a/ │ ├── pyproject.toml # 子包A的专属打包配置 │ ├── README.md # 子包A的说明文档 │ ├── src/ │ │ └── package_a/ # 注意这里用下划线,符合Python包命名规范 │ │ ├── __init__.py │ │ └── core.py ├── package-b/ │ ├── pyproject.toml │ ├── README.md │ ├── src/ │ │ └── package_b/ │ │ ├── __init__.py │ │ └── utils.py └── tests/ # 统一测试目录,方便跨子包测试 ├── test_package_a.py └── test_package_b.py
为什么用src布局?因为它能避免本地目录下的文件和包名冲突,同时让打包工具只关注src目录里的代码,更规范。
2. 每个子包的独立打包配置
每个子包都要有自己的pyproject.toml,明确打包规则、依赖、元信息。以package-a为例:
[build-system] requires = ["setuptools>=61.0"] build-backend = "setuptools.build_meta" [project] name = "package-a" # 这是pip上的包名,要唯一 version = "0.1.0" # 版本号,建议用语义化版本 authors = [ { name = "Your Team", email = "team@example.com" } ] description = "Subpackage A for our core project" readme = "README.md" requires-python = ">=3.8" dependencies = [ # 处理子包间依赖的技巧: # 本地开发时,用相对路径引用其他子包: # "package-b @ file://../package-b" # 正式发布时,换成PyPI上的版本号: # "package-b>=0.1.0" ] [project.urls] "Homepage" = "https://github.com/your-org/my-monorepo" "Bug Tracker" = "https://github.com/your-org/my-monorepo/issues"
重点说下子包依赖:开发阶段用本地路径安装,确保修改实时生效;发布前把依赖换成正式版本号,这样用户安装时会从PyPI(或内部镜像)拉取对应版本。
3. 本地开发:保持便捷的代码导入
要让未打包的本地代码能直接导入,最省心的方式是安装子包的可编辑版本:
- 进入每个子包目录,运行:
pip install -e . - 或者在根目录创建
requirements-dev.txt,把所有子包的可编辑路径列进去:
然后运行-e ./package-a -e ./package-bpip install -r requirements-dev.txt,一次性搞定所有子包的可编辑安装。
这样你在本地写代码时,直接import package_a或import package_b就和安装了正式包一样,而且修改代码后不需要重新安装,实时生效。
4. 打包与发布:兼顾内部和OSS
打包操作
每个子包单独打包,进入子包目录运行:
python -m build
这会在子包的dist目录生成两种格式的包:.tar.gz(源码包)和.whl(wheel包)。
内部团队分发
如果你们有内部PyPI镜像(比如DevPI、Artifactory),用twine上传:
twine upload --repository internal dist/*
团队成员安装时,只要把pip源指向内部镜像即可:
pip install package-a --index-url https://your-internal-pypi.com/simple/
公开OSS发布
确认子包没有内部敏感信息后,直接上传到PyPI:
twine upload dist/*
注意:PyPI上的包名是全局唯一的,提前查好有没有重名。
版本管理小技巧
- 如果子包关联性强,建议用统一版本号(比如所有子包同步升级到v0.2.0),方便管理;如果子包独立迭代,可以用独立版本号,但要在每个子包的README或CHANGELOG里明确更新内容。
- 发布时给仓库打标签,比如
package-a-v0.1.0,方便追踪每个子包的发布版本,也便于CI/CD自动触发发布。
5. 进阶优化:统一CI/CD与共享配置
- CI/CD自动化:用GitHub Actions或GitLab CI,自动检测子包的代码变化(比如用
git diff或工具changesets),只要某个子包的代码有修改,就自动打包并发布对应的子包,减少手动操作。 - 共享配置:如果多个子包有相同的配置(比如build-system、作者信息),可以在根目录的
pyproject.toml定义公共配置,子包通过tool.setuptools.dynamic继承(需要setuptools>=62.0支持),避免重复配置。
这样一套流程下来,既能满足你拆分独立pip包的需求,又能让本地开发和平时一样顺畅,不管是内部分发还是公开OSS发布都能轻松搞定。如果有具体的细节问题,比如版本管理工具的选择,或者CI配置,可以再细化讨论。
内容的提问来源于stack exchange,提问作者Mark LeMoine




