You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

为仓库内Python代码构建pip包,兼顾团队内部与外部开源使用

单仓库多子模块打包成独立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-b
    
    然后运行pip install -r requirements-dev.txt,一次性搞定所有子包的可编辑安装。

这样你在本地写代码时,直接import package_aimport 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

火山引擎 最新活动