前言
我们经常会需要把自己写的sdk给公司其他同事统一使用,那么这个流程是怎么样的呢?本篇博客就一次性说清楚。
最终目的
- 你写好代码(比如叫 mycompany-sdk)
- 打包后上传到公司私有 PyPI(比如 https://pypi.internal.mycompany.com/simple/)
- 同事只需运行:
就能用你的 SDKpip install mycompany-sdk
总共需要5步
第1步:整理项目结构(5 分钟)
确保你的项目长这样:
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,内容如下(直接复制修改):
[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 分钟)
在项目根目录执行:
# 安装构建工具(如果还没装)
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 安装上传工具
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:
[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 上传!
twine upload -r company-pypi dist/*
✅ 看到 Upload succeeded 就成功了!
第 5 步:教同事怎么安装(1 分钟)
告诉同事两件事:
方式一:临时指定源(推荐先测试)
pip install --index-url https://pypi.internal.mycompany.com/simple/ mycompany-sdk
方式二:永久配置(适合全公司推广)
让同事在自己电脑上创建 ~/.pip/pip.conf(Linux/Mac)或 %APPDATA%\pip\pip.ini(Windows):
[global]
index-url = https://pypi.internal.mycompany.com/simple/
trusted-host = pypi.internal.mycompany.com
然后他们就可以直接:
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)以及构建项目所需的依赖。
- [project] 表:这是大多数构建后端用来指定项目基本元数据(如依赖项、作者信息等)的标准格式。
- [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”]。
通常,你只需复制你所选构建后端官方文档中推荐的配置即可。以下是几个常见构建后端的示例:
- Hatchling
[build-system]
requires = ["hatchling >= 1.26"]
build-backend = "hatchling.build"
- setuptools
[build-system]
requires = ["setuptools >= 61.0"]
build-backend = "setuptools.build_meta"
- Flit
[build-system]
requires = ["flit_core >= 3.2"]
build-backend = "flit_core.buildapi"
- PDM
[build-system]
requires = ["pdm-backend"]
build-backend = "pdm.backend"
- uv-build
[build-system]
requires = ["uv-build"]
build-backend = "uv_build_backend"
静态 vs 动态元数据
本博客其余部分聚焦于 [project] 表。
大多数情况下,你会直接在 [project] 中写入字段值,例如:
requires-python = ">= 3.8"
version = "1.0"
但在某些场景下,你可能希望由构建后端动态生成元数据。例如:许多构建后端可以从代码中的 version 属性、Git 标签等自动读取版本号。此时,应将该字段标记为 动态(dynamic):
[project]
dynamic = ["version"]
当某个字段被标记为动态时,由构建后端负责填充其值。具体如何实现,请查阅你所用构建后端的文档。
基础信息
name(必填)
指定你在 PyPI(或私有仓库)上注册的项目名称。这是唯一不能标记为动态的字段。
[project]
name = "spam-eggs"
命名规则:
- 只能包含 ASCII 字母、数字、下划线 _、连字符 - 和点号 .。
- 不能以 _、- 或 . 开头或结尾。
名称比较不区分大小写,且任意长度的 _、-、. 序列被视为等价。
例如,若你注册了 cool-stuff,用户可通过以下任意形式安装:
- Cool-Stuff
- cool.stuff
- COOL_STUFF
- CoOl__-.-__sTuFF
version(必填,但常设为动态)
指定项目版本号:
[project]
version = "2020.0.0"
支持复杂的版本标识符,如 2020.0.0a1(alpha 版)。完整语法参见 PEP 440。
虽然该字段是必需的,但通常会标记为动态:
[project]
dynamic = ["version"]
这样可实现从 version、Git tag 等自动提取版本。详见 Single-sourcing the Project Version。
依赖与要求
dependencies / optional-dependencies
列出项目依赖:
[project]
dependencies = [
"httpx",
"gidgethub[httpx]>4.0.0",
"django>2.1; os_name != 'nt'",
"django>2.0; os_name == 'nt'",
]
完整依赖语法参见 Dependency specifiers。
若某些依赖是可选的(例如仅用于 GUI 功能),请使用 optional-dependencies:
[project.optional-dependencies]
gui = ["PyQt5"]
cli = [
"rich",
"click",
]
用户可通过 pip install your-project[gui] 安装带 GUI 支持的版本。
requires-python
声明项目支持的最低 Python 版本:
[project]
requires-python = ">= 3.8"
⚠️ 谨慎使用上限约束(如 <= 3.10),可能导致未来兼容性问题。
创建可执行脚本
[project.scripts]
声明命令行入口点:
[project.scripts]
spam-cli = "spam:main_cli"
安装后,用户可直接运行 spam-cli。其效果等价于:
import sys
from spam import main_cli
sys.exit(main_cli())
[project.gui-scripts](仅 Windows 有效)
在 Windows 上,若脚本不应弹出终端窗口(例如 GUI 应用),请使用 gui-scripts:
[project.gui-scripts]
spam-gui = "spam:main_gui"
此时,从命令行启动会立即返回控制权,脚本在后台运行。
注意:scripts 与 gui-scripts 的区别仅在 Windows 上有意义。
项目相关信息
authors / maintainers
列出作者和维护者(可包含姓名和/或邮箱):
[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 页面标题和搜索结果摘要:
[project]
description = "Lovely Spam! Wonderful Spam!"
readme
项目的详细描述,通常指向 README.md 或 README.rst:
[project]
readme = "README.md"
格式根据扩展名自动识别:
- .md → GitHub 风格 Markdown
- .rst → reStructuredText(不含 Sphinx 扩展)
也可显式指定:
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 标识符(完整列表):
[project]
license = "MIT"
# 或组合表达式
license = "MIT AND (Apache-2.0 OR BSD-2-Clause)"
若使用自定义许可证,可使用 LicenseRef- 前缀:
license = "LicenseRef-My-Custom-License"
💡 如果构建时报错 “license should be a dict”,说明你的构建后端尚未支持 PEP 639 新格式。
license-files
指定要包含在分发包中的许可证文件:
[project]
license-files = ["LICEN[CS]E*", "vendored/licenses/*.txt", "AUTHORS.md"]
glob 规则:
- 字母、数字、_、-、. 匹配字面量
- 支持 、?、* 和 []
- 路径分隔符必须是 /
- 路径相对于 pyproject.toml 所在目录
- 不允许 … 或以 / 开头
- 每个 glob 必须匹配至少一个文件
keywords
帮助 PyPI 搜索命中你的项目:
[project]
keywords = ["egg", "bacon", "sausage", "tomatoes", "Lobster Thermidor"]
classifiers
PyPI 分类器列表(用于筛选和展示):
[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,可添加:
classifiers = ["Private :: Do Not Upload"]
PyPI 会拒绝任何包含 Private :: 前缀的分类器。
urls
项目相关链接,显示在 PyPI 页面侧边栏:
[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] 声明:
[project.entry-points."spam.magical"]
tomatoes = "spam:main_tomatoes"
详见 Plugin guide。
完整示例
[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/
780

被折叠的 条评论
为什么被折叠?



