前言
我们经常会需要把自己写的sdk给公司其他同事统一使用,那么这个流程是怎么样的呢?本篇博客就一次性说清楚。
最终目的
-
你写好代码(比如叫 mycompany-sdk)
-
打包后上传到公司私有 PyPI(比如 https://pypi.internal.mycompany.com/simple/)
-
同事只需运行:
pythonpip install mycompany-sdk就能用你的 SDK
总共需要5步
第1步:整理项目结构(5 分钟)
确保你的项目长这样:
python
mycompany-sdk/
├── pyproject.toml ← 核心配置文件(重点!)
├── README.md
├── LICENSE ← 建议加上(MIT 或 Apache 2.0)
└── mycompany_sdk/ ← 注意:目录名用下划线,不能有空格或大写
├── __init__.py
└── client.py ← 你的代码
💡 提示:模块目录名(如 mycompany_sdk)必须和 pyproject.toml 中的 name 对应(但包名可用 -)。
第 2 步:写 pyproject.toml(10 分钟)
在项目根目录创建 pyproject.toml,内容如下(直接复制修改):
toml
[build-system]
requires = ["setuptools>=61.0", "wheel"]
build-backend = "setuptools.build_meta"
[project]
name = "mycompany-sdk" # ← 同事 pip install 时用的名字
version = "0.1.0" # ← 每次更新要改!
description = "MyCompany 内部 SDK"
readme = "README.md"
license = {text = "MIT"} # 或你公司的许可证
authors = [{name = "你的名字", email = "you@mycompany.com"}]
requires-python = ">=3.8"
dependencies = [
"requests>=2.25.0",
# 你的其他依赖
]
[project.urls]
Homepage = "https://gitlab.mycompany.com/your-team/mycompany-sdk"
✅ 关键点:
- name 决定了 pip install xxx 的名字
- version 必须每次更新(否则同事装不上新版本)
- 不需要 setup.py!除非你要编译 C/C++ 扩展
第 3 步:本地打包(2 分钟)
在项目根目录执行:
python
# 安装构建工具(如果还没装)
pip install build
# 构建包
python -m build
✅ 成功后你会看到 dist/ 目录,里面有:
- mycompany_sdk-0.1.0.tar.gz
- mycompany_sdk-0.1.0-py3-none-any.whl
这两个就是你要上传的"成品"。
第 4 步:上传到公司私有镜像(5 分钟)
4.1 安装上传工具
python
pip install twine
4.2 获取公司私有 PyPI 的上传地址和账号
⚠️ 这一步必须问你们 DevOps / 平台团队!常见情况:
上传地址(不是 /simple/!)可能是:
- Artifactory: https://artifactory.mycompany.com/artifactory/api/pypi/pypi-local
- GitLab: https://gitlab.mycompany.com/api/v4/projects/123/packages/pypi
- Nexus: https://nexus.mycompany.com/repository/pypi-internal/
认证方式:通常是用户名 + API Token(比密码安全)
4.3 配置 .pypirc(推荐)
在你电脑的 用户主目录(如 C:\Users\you 或 ~)下创建文件 .pypirc:
python
[distutils]
index-servers = company-pypi
[company-pypi]
repository: https://artifactory.mycompany.com/artifactory/api/pypi/pypi-local
username: your-username
password: your-api-token # ← 从公司平台申请
🔐 安全提示:不要把 token 写进代码!
4.4 上传!
python
twine upload -r company-pypi dist/*
✅ 看到 Upload succeeded 就成功了!
第 5 步:教同事怎么安装(1 分钟)
告诉同事两件事:
方式一:临时指定源(推荐先测试)
python
pip install --index-url https://pypi.internal.mycompany.com/simple/ mycompany-sdk
方式二:永久配置(适合全公司推广)
让同事在自己电脑上创建 ~/.pip/pip.conf(Linux/Mac)或 %APPDATA%\pip\pip.ini(Windows):
pip
[global]
index-url = https://pypi.internal.mycompany.com/simple/
trusted-host = pypi.internal.mycompany.com
然后他们就可以直接:
pip
pip install mycompany-sdk
🔁 后续更新怎么办?
- 修改代码
- 改 pyproject.toml 里的 version(比如 0.1.1)
- 重新 python -m build
- 重新 twine upload ...
- 同事运行 pip install --upgrade mycompany-sdk 即可
详细的理解pyproject.toml
根据上面的操作我们已经可以顺利的把自己的sdk给全公司员工使用了,接下来我们把上面的一些配置进行详细的解释。
pyproject.toml简介
pyproject.toml 是一个由打包工具(如 setuptools、Hatch、Flit 等)以及其它开发工具(如 linters、类型检查器等)使用的配置文件。该文件中可以包含三种 TOML 表(table):
-
build-system\] 表:强烈建议使用。它用于声明你使用的构建后端(build backend)以及构建项目所需的依赖。
-
tool\] 表:包含各工具特定的子表,例如 \[tool.hatch\]、\[tool.black\]、\[tool.mypy\] 等。本文仅简要提及此表,因为其内容完全由各个工具自行定义,请查阅对应工具的文档以了解其支持的配置项。
无论你使用哪种构建后端,[build-system] 表都必须存在,因为它定义了实际用于构建项目的工具。
而 [project] 表虽然被大多数现代构建后端所支持,但某些工具过去曾使用自己的格式。
一个显著的例外是 Poetry:在 2.0 版本(发布于 2025 年 1 月 5 日)之前,Poetry 不使用 [project] 表,而是使用 [tool.poetry] 表。从 2.0 开始,它同时支持两种格式。
此外,setuptools 构建后端也同时支持 [project] 表以及旧式的 setup.cfg 或 setup.py 格式。
对于新项目,推荐使用 [project] 表。只有在需要程序化配置(例如编译 C 扩展)时,才保留 setup.py。setup.cfg 和 setup.py 格式目前仍然有效。详见 Is setup.py deprecated?。
声明构建后端
build-system\] 表包含两个关键字段: * build-backend:指定用于构建项目的后端(例如 "hatchling.build")。 * requires:列出构建项目所需的依赖项列表------通常就是构建后端本身,但也可能包含其他依赖。你可以指定版本约束,例如 requires = \["setuptools \>= 61.0"\]。 通常,你只需复制你所选构建后端官方文档中推荐的配置即可。以下是几个常见构建后端的示例: 1. Hatchling ```toml [build-system] requires = ["hatchling >= 1.26"] build-backend = "hatchling.build" ``` 2. setuptools ```toml [build-system] requires = ["setuptools >= 61.0"] build-backend = "setuptools.build_meta" ``` 3. Flit ```toml [build-system] requires = ["flit_core >= 3.2"] build-backend = "flit_core.buildapi" ``` 4. PDM ```toml [build-system] requires = ["pdm-backend"] build-backend = "pdm.backend" ``` 5. uv-build ```toml [build-system] requires = ["uv-build"] build-backend = "uv_build_backend" ``` ##### 静态 vs 动态元数据 本博客其余部分聚焦于 \[project\] 表。 大多数情况下,你会直接在 \[project\] 中写入字段值,例如: ```toml requires-python = ">= 3.8" version = "1.0" ``` 但在某些场景下,你可能希望由构建后端动态生成元数据。例如:许多构建后端可以从代码中的 **version** 属性、Git 标签等自动读取版本号。此时,应将该字段标记为 动态(dynamic): ```toml [project] dynamic = ["version"] ``` 当某个字段被标记为动态时,由构建后端负责填充其值。具体如何实现,请查阅你所用构建后端的文档。 ##### 基础信息 ###### name(必填) 指定你在 PyPI(或私有仓库)上注册的项目名称。这是唯一不能标记为动态的字段。 ```toml [project] name = "spam-eggs" ``` ###### 命名规则: * 只能包含 ASCII 字母、数字、下划线 _、连字符 - 和点号 .。 * 不能以 _、- 或 . 开头或结尾。 **名称比较不区分大小写**,且任意长度的 _、-、. 序列被视为等价。 例如,若你注册了 cool-stuff,用户可通过以下任意形式安装: * Cool-Stuff * cool.stuff * COOL_STUFF * CoOl__-.-__sTuFF ###### version(必填,但常设为动态) 指定项目版本号: ```toml [project] version = "2020.0.0" ``` 支持复杂的版本标识符,如 2020.0.0a1(alpha 版)。完整语法参见 [PEP 440](https://peps.python.org/pep-0440/)。 虽然该字段是必需的,但通常会标记为动态: ```toml [project] dynamic = ["version"] ``` 这样可实现从 **version** 、Git tag 等自动提取版本。详见 [Single-sourcing the Project Version](https://packaging.python.org/en/latest/discussions/single-source-version/)。 ###### 依赖与要求 dependencies / optional-dependencies 列出项目依赖: ```toml [project] dependencies = [ "httpx", "gidgethub[httpx]>4.0.0", "django>2.1; os_name != 'nt'", "django>2.0; os_name == 'nt'", ] ``` 完整依赖语法参见 [Dependency specifiers](https://packaging.python.org/en/latest/specifications/dependency-specifiers/)。 若某些依赖是可选的(例如仅用于 GUI 功能),请使用 optional-dependencies: ```toml [project.optional-dependencies] gui = ["PyQt5"] cli = [ "rich", "click", ] ``` 用户可通过 pip install your-project\[gui\] 安装带 GUI 支持的版本。 ###### requires-python 声明项目支持的最低 Python 版本: ```toml [project] requires-python = ">= 3.8" ``` > ⚠️ 谨慎使用上限约束(如 \<= 3.10),可能导致未来兼容性问题。 ###### 创建可执行脚本 \[project.scripts
声明命令行入口点:
toml
[project.scripts]
spam-cli = "spam:main_cli"
安装后,用户可直接运行 spam-cli。其效果等价于:
python
import sys
from spam import main_cli
sys.exit(main_cli())
[project.gui-scripts](仅 Windows 有效)
在 Windows 上,若脚本不应弹出终端窗口(例如 GUI 应用),请使用 gui-scripts:
python
[project.gui-scripts]
spam-gui = "spam:main_gui"
此时,从命令行启动会立即返回控制权,脚本在后台运行。
注意:scripts 与 gui-scripts 的区别仅在 Windows 上有意义。
项目相关信息
authors / maintainers
列出作者和维护者(可包含姓名和/或邮箱):
toml
[project]
authors = [
{name = "Pradyun Gedam", email = "pradyun@example.com"},
{name = "Tzu-Ping Chung", email = "tzu-ping@example.com"},
{name = "Another person"},
{email = "different.person@example.com"},
]
maintainers = [
{name = "Brett Cannon", email = "brett@example.com"}
]
description
项目的一句话简介,用于 PyPI 页面标题和搜索结果摘要:
toml
[project]
description = "Lovely Spam! Wonderful Spam!"
readme
项目的详细描述,通常指向 README.md 或 README.rst:
toml
[project]
readme = "README.md"
格式根据扩展名自动识别:
- .md → GitHub 风格 Markdown
- .rst → reStructuredText(不含 Sphinx 扩展)
也可显式指定:
toml
readme = {file = "README.txt", content-type = "text/markdown"}
# 或
readme = {file = "README.txt", content-type = "text/x-rst"}
license 与 license-files (依据 PEP 639)
自 PEP 639 起,许可证应通过两个字段声明:
- license:SPDX 许可证表达式(如 "MIT"、"GPL-3.0-or-later")
- license-files:许可证文件的 glob 模式列表
⚠️ 旧格式(license = {file = "LICENSE"})已弃用。
license
使用标准 SPDX 标识符(完整列表):
toml
[project]
license = "MIT"
# 或组合表达式
license = "MIT AND (Apache-2.0 OR BSD-2-Clause)"
若使用自定义许可证,可使用 LicenseRef- 前缀:
toml
license = "LicenseRef-My-Custom-License"
💡 如果构建时报错 "license should be a dict",说明你的构建后端尚未支持 PEP 639 新格式。
license-files
指定要包含在分发包中的许可证文件:
toml
[project]
license-files = ["LICEN[CS]E*", "vendored/licenses/*.txt", "AUTHORS.md"]
glob 规则:
- 字母、数字、_、-、. 匹配字面量
- 支持 、?、* 和 []
- 路径分隔符必须是 /
- 路径相对于 pyproject.toml 所在目录
- 不允许 ... 或以 / 开头
- 每个 glob 必须匹配至少一个文件
keywords
帮助 PyPI 搜索命中你的项目:
toml
[project]
keywords = ["egg", "bacon", "sausage", "tomatoes", "Lobster Thermidor"]
classifiers
PyPI 分类器列表(用于筛选和展示):
toml
[project]
classifiers = [
"Development Status :: 4 - Beta",
"Intended Audience :: Developers",
"Topic :: Software Development :: Build Tools",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
]
📌 注意:classifiers 仅用于 PyPI 展示和搜索,不影响安装行为。要限制 Python 版本,请使用 requires-python。
若想禁止上传到 PyPI,可添加:
toml
classifiers = ["Private :: Do Not Upload"]
PyPI 会拒绝任何包含 Private :: 前缀的分类器。
urls
项目相关链接,显示在 PyPI 页面侧边栏:
toml
[project.urls]
Homepage = "https://example.com"
Documentation = "https://readthedocs.org"
Repository = "https://github.com/me/spam.git"
Issues = "https://github.com/me/spam/issues"
Changelog = "https://github.com/me/spam/blob/master/CHANGELOG.md"
- 若标签含空格,需加引号:"Bug Tracker" = "..."
- 推荐使用 Well-known labels(如 Homepage、Documentation),以便工具正确识别语义。
高级插件(Entry Points)
某些包(如 pytest、Pygments)支持插件机制。通过 [project.entry-points] 声明:
toml
[project.entry-points."spam.magical"]
tomatoes = "spam:main_tomatoes"
详见 Plugin guide。
完整示例
toml
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[project]
name = "spam-eggs"
version = "2020.0.0"
dependencies = [
"httpx",
"gidgethub[httpx]>4.0.0",
"django>2.1; os_name != 'nt'",
"django>2.0; os_name == 'nt'",
]
requires-python = ">=3.8"
authors = [
{name = "Pradyun Gedam", email = "pradyun@example.com"},
{name = "Tzu-Ping Chung", email = "tzu-ping@example.com"},
{name = "Another person"},
{email = "different.person@example.com"},
]
maintainers = [
{name = "Brett Cannon", email = "brett@example.com"}
]
description = "Lovely Spam! Wonderful Spam!"
readme = "README.rst"
license = "MIT"
license-files = ["LICEN[CS]E.*"]
keywords = ["egg", "bacon", "sausage", "tomatoes", "Lobster Thermidor"]
classifiers = [
"Development Status :: 4 - Beta",
"Programming Language :: Python"
]
[project.optional-dependencies]
gui = ["PyQt5"]
cli = ["rich", "click"]
[project.urls]
Homepage = "https://example.com"
Documentation = "https://readthedocs.org"
Repository = "https://github.com/me/spam.git"
"Bug Tracker" = "https://github.com/me/spam/issues"
Changelog = "https://github.com/me/spam/blob/master/CHANGELOG.md"
[project.scripts]
spam-cli = "spam:main_cli"
[project.gui-scripts]
spam-gui = "spam:main_gui"
[project.entry-points."spam.magical"]
tomatoes = "spam:main_tomatoes"
参考文献
https://packaging.python.org/en/latest/guides/writing-pyproject-toml/