引言
我最近开发并发布了我的第一个 Python 库到 PyPI。本文并不是关于库本身的技术细节,而是记录了从一无所知到发布它的过程。
已发布的库:
- fastapi-websocket-stabilizer
- PyPI: https://pypi.org/project/fastapi-websocket-stabilizer/
- GitHub: https://github.com/yuuichieguchi/fastapi-websocket-stabilizer
我希望这对任何想要创建库但不知道从何开始的人有所帮助。
原因
在使用 FastAPI 开发实时通信应用时,我遇到了 WebSocket 连接不稳定的问题。
具体来说:
- 意外的连接中断
- 没有心跳机制
- 僵尸连接的积累
- 复杂的重连处理
- 繁琐的优雅关闭实现
我不断编写类似的代码来解决这些问题,当我查看 GitHub Issues 和 Stack Overflow 时,发现许多开发者面临着相同的挑战。
“那么,也许将其作为一个通用库发布是有价值的。”
这时我开始了开发。
项目结构
第一个挑战是决定项目结构。经过研究,我采用了这个标准结构:
project-root/
├── src/
│ └── fastapi_websocket_stabilizer/
│ ├── __init__.py
│ ├── manager.py
│ ├── config.py
│ └── ...
├── tests/
├── README.md
├── LICENSE
└── pyproject.toml
关键点:
- 使用
src布局(在测试期间使用已安装的包) - 包名使用连字符,模块名使用下划线
- 选择 MIT 许可证(以便广泛采用)
pyproject.toml 配置
现代 Python 项目使用 pyproject.toml 代替 setup.py 进行配置。以下是我使用的配置:
[build-system]
requires = ["setuptools>=70.0.0", "wheel"]
build-backend = "setuptools.build_meta"
[project]
name = "fastapi-websocket-stabilizer"
version = "0.1.0"
description = "一个为 FastAPI 应用程序提供生产就绪的 WebSocket 稳定层"
readme = "README.md"
requires-python = ">=3.10"
license = "MIT"
authors = [
{name = "FastAPI WebSocket 稳定器贡献者"}
]
keywords = [
"fastapi",
"websocket",
"连接管理",
"心跳",
"优雅关闭",
]
classifiers = [
"开发状态 :: 4 - 测试版",
"环境 :: Web 环境",
"目标受众 :: 开发者",
"操作系统 :: 操作系统无关",
以下是您提供的英文技术文章内容的中文翻译:
"编程语言 :: Python",
"编程语言 :: Python :: 3",
"编程语言 :: Python :: 3.10",
"编程语言 :: Python :: 3.11",
"编程语言 :: Python :: 3.12",
"主题 :: 互联网 :: WWW/HTTP",
"主题 :: 软件开发 :: 库 :: Python 模块",
]
依赖项 = [
"fastapi>=0.95.0",
]
[项目.可选依赖项]
开发 = [
"pytest>=7.0",
"pytest-asyncio>=0.21.0",
"pytest-cov>=4.0",
"black>=23.0",
"ruff>=0.1.0",
"mypy>=1.0",
"uvicorn>=0.20.0",
]
[project.urls]
主页 = "https://github.com/yuuichieguchi/fastapi-websocket-stabilizer"
仓库 = "https://github.com/yuuichieguchi/fastapi-websocket-stabilizer"
文档 = "https://github.com/yuuichieguchi/fastapi-websocket-stabilizer#readme"
问题 = "https://github.com/yuuichieguchi/fastapi-websocket-stabilizer/issues"
[tool.setuptools]
packages = ["fastapi_websocket_stabilizer"]
package-dir = {"" = "src"}
[tool.black]
line-length = 100
target-version = ["py310", "py311", "py312"]
[tool.ruff]
line-length = 100
target-version = "py310"
select = ["E", "F", "W", "I"]
[tool.mypy]
python_version = "3.10"
warn_return_any = true
warn_unused_configs = true
disallow_untyped_defs = true
disallow_untyped_calls = true
[tool.pytest.ini_options]
asyncio_mode = "auto"
testpaths = ["tests"]
python_files = ["test_*.py"]
python_classes = ["Test*"]
python_functions = ["test_*"]
要点:
classifiers指定 PyPI 上的分类optional-dependencies分离开发工具tool.*部分集成了 linter、formatter 和测试配置
设置 PyPI 账户
创建账户
- 在 https://pypi.org 创建一个账户
- 验证您的电子邮件地址
- 启用双因素身份验证(推荐)
生成 API 令牌
出于安全考虑,请使用 API 令牌而不是密码。
- 登录 PyPI
- 转到账户设置 → API 令牌
- 点击“添加 API 令牌”
- 选择范围:“整个账户”(用于初始发布)
- 安全存储生成的令牌
重要: 令牌只显示一次,请确保保存。
配置身份验证
创建一个 ~/.pypirc 文件:
[pypi]
username = __token__
password = pypi-AgEIcHlwaS5vcmc... (你的令牌)
构建包
安装所需工具
pip install --upgrade build twine setuptools wheel
### 构建
python -m build
成功后,将在 dist/ 目录中生成以下文件:
.whl文件(wheel 格式).tar.gz文件(源代码分发格式)
故障排除:InvalidDistribution 错误
在我第一次构建时,遇到了这个错误:
InvalidDistribution: 未识别或格式错误的字段 'license-file'
原因
这是由于 twine 和 packaging 库之间的版本冲突造成的。
解决方案
# 更新相关工具
pip install --upgrade packaging build setuptools wheel twine
# 清除缓存
rm -rf dist build *.egg-info
# 重新构建
python -m build
这解决了问题。
上传到 PyPI
使用 TestPyPI 进行测试(推荐)
在上传到生产环境的 PyPI 之前,我建议先在 TestPyPI 上进行测试:
python -m twine upload --repository testpypi dist/*
测试PyPI: https://test.pypi.org
上传到生产环境
如果一切看起来都很好,请上传到生产环境:
python -m twine upload dist/*
验证
pip install fastapi-websocket-stabilizer
现在世界上任何人都可以使用此命令进行安装。
发布后的维护
版本管理
遵循语义版本控制:
- 重大: 破坏性变更
- 次要: 向后兼容的功能添加
- 修补: 向后兼容的错误修复
示例: 0.1.0 → 0.1.1 (错误修复) → 0.2.0 (新功能) → 1.0.0 (稳定版)
更新过程
- 修改代码
- 在
pyproject.toml中更新版本 - 更新
CHANGELOG.md - 重新构建并重新上传
python -m build
python -m twine upload dist/*
为了用户,我准备了:
- 在 README.md 中的使用示例和 API 参考
- 用于 IDE 补全的类型提示
- 通过文档字符串提供的函数描述
反思
技术学习
通过库的开发,我学到了:
- Python 打包机制
- 类型提示的重要性
- 维护测试覆盖率
- 设置 CI/CD
给那些创建他们第一个库的人
如果你在想“我想创建一个库,但这似乎很艰巨”,以下是我的建议:
- 从小开始:你不需要一开始就构建一个庞大的项目
- 解决真实问题:解决你所面临的问题的库是有价值的
- 在 TestPyPI 上练习:你可以在上线之前在一个预发布环境中进行测试
- 编写良好的文档:清晰的使用说明能吸引用户
- 欢迎反馈:问题是改进的机会
技术障碍比你想象的要低。更大的障碍可能是“发布的勇气”。
结论
发布我的第一个库是一次宝贵的经历,不仅在技术学习上有所收获,也让我参与到了开源软件(OSS)社区中。
如果你在想“我想试着做一个”,我鼓励你迈出第一步。
参考文献:
- Python 打包用户指南: https://packaging.python.org/
- PyPI 官方文档: https://pypi.org/help/
- fastapi-websocket-stabilizer: https://github.com/yuuichieguchi/fastapi-websocket-stabilizer

