
很多 Python 项目在刚开始时,发布流程都比较“原始”:
改版本号
→ git tag
→ python -m build
→ twine upload项目少的时候问题不大。
但只要:
很快就会进入一种状态:
版本号经常忘记改
tag 对不上
PyPI 版本混乱
workflow 不知道为什么不触发最近把自己的 Python 项目完整升级了一遍,做成了:
整套流程。
这一篇把完整配置、踩坑和注意事项全部整理一下。
适合:
项目地址, 文章缺少文件可以从项目中找到。
https://github.com/Mehaei/pydify_plus
这次做自动化发布的项目,是一个完整的 Python SDK。
目录结构如下:
.
├── CHANGELOG.md
├── CONTRIBUTING.md
├── docs
│ └── API_REFERENCE.md
├── examples
│ ├── example_async.py
│ ├── example_sync.py
│ ├── fastapi_example.py
│ └── README.md
├── LICENSE
├── PROJECT_STRUCTURE.md
├── PROJECT_SUMMARY.md
├── pydify_plus
│ ├── __init__.py
│ ├── apis
│ │ ├── __init__.py
│ │ ├── app_config.py
│ │ ├── base.py
│ │ ├── blocks.py
│ │ ├── chat.py
│ │ ├── dataset.py
│ │ ├── documents.py
│ │ ├── feedback.py
│ │ ├── files.py
│ │ ├── models.py
│ │ ├── sessions.py
│ │ ├── tags.py
│ │ ├── textgen.py
│ │ └── workflows.py
│ ├── async_client.py
│ ├── base.py
│ ├── config.py
│ ├── errors.py
│ ├── models.py
│ ├── sync_client.py
│ ├── types.py
│ └── utils.py
├── pyproject.toml
├── pytest.ini
├── README.md
├── RELEASE.md
├── requirements.txt
├── scripts
│ ├── bump_version.py
│ └── release.py
└── tests
├── __init__.py
├── conftest.py
├── test_app_config.py
├── test_base_client.py
├── test_blocks.py
├── test_chat.py
├── test_dataset.py
├── test_documents.py
├── test_feedback.py
├── test_files.py
├── test_models.py
├── test_sessions.py
├── test_streaming.py
├── test_tags.py
├── test_textgen.py
└── test_workflows.py整个项目已经是一个比较标准的 Python SDK 结构。
里面包含:
这也是后面能够顺利做 CI/CD 自动化的基础。
现在整个发布流程已经变成:
开发时:
git commit -m "feat: add cache support"
git pushGitHub Actions 自动:
pytest
black
flake8
mypy
安全扫描如果提交符合版本发布规则:
自动升级版本
自动打 tag
自动创建 Release
自动发布 PyPI整个过程不需要手动改版本号。
这一套主要用了:
功能 | 工具 |
|---|---|
CI/CD | GitHub Actions |
自动版本管理 | python-semantic-release |
自动发布 PyPI | Trusted Publisher |
测试 | pytest |
类型检查 | mypy |
代码格式化 | black |
lint | flake8 |
安全扫描 | bandit + safety |
以前很多 Python 项目:
setup.py
requirements.txt
setup.cfg
MANIFEST.in混在一起。
后面维护起来非常痛苦。
现在更推荐统一:
pyproject.toml整个项目的:
全部集中管理。
这也是现在 Python 官方推荐方案。
项目里主要包含:
[project]
name = "pydify-plus"
description = "Python SDK for Dify API"
requires-python = ">=3.10"[project.optional-dependencies]
dev = [
"pytest",
"black",
"flake8",
"mypy"
]这样 workflow 里:
pip install ".[dev]"就能一次性安装所有开发工具。
[tool.semantic_release]
branch = "master"
tag_format = "v{version}"这个后面会自动:
以前很多教程都还是:
PYPI_API_TOKEN方式。
现在已经不太推荐了。
原因很简单:
现在更推荐:
Trusted Publisher(OIDC)即:
GitHub Actions
→ GitHub 身份认证
→ PyPI 信任仓库
→ 自动发布整个过程:
不需要任何 PyPI Token也是目前 PyPI 官方推荐方案。
打开:
PyPI 官方网站
https://pypi.org
需要提前注册账号 进入:
Manage
→ Publishing添加:
Trusted Publisher这里几个字段非常重要。
必须和:
[project]
name = "pydify-plus"完全一致。
GitHub 用户名。
Mehaei仓库完整连接。
https://github.com/Mehaei/pydify_plus填写:
release.yml
必须和:
.github/workflows/release.yml
一致。否则 GitHub Actions 发布时会报:
No matching trusted publisher这是最常见的问题之一。
release
可以留空。点击:
Add这是目前项目里实际使用的 workflow。
相比网上很多“玩具教程”,这个版本已经可以直接用于生产。
on:
push:
branches: [master, develop]
pull_request:
branches: [master]
workflow_dispatch:这里做了三件事:
推送代码自动执行 CI。
Pull Request 自动测试。
允许手动触发。 GitHub 页面可以直接点击运行。 这个功能非常实用。
这是最常见的问题。
比如:
branches:
- main但实际仓库:
master那么:
workflow 永远不会执行我一开始也踩了这个坑。
后来统一改成:
branches: [master, develop]就正常了。
测试部分:
strategy:
matrix:
python-version: ["3.10", "3.11", "3.12"]作用是:
自动测试多个 Python 版本避免:
本地能跑
线上炸了pip install ".[dev]"这里依赖:
[project.optional-dependencies]
dev = [
"pytest",
"black",
"flake8",
"mypy"
]这样开发依赖统一维护。
不用 workflow 里手写一堆 pip install。
项目 tests 目录里已经拆分得比较完整:
tests/
├── test_chat.py
├── test_dataset.py
├── test_documents.py
├── test_feedback.py
├── test_files.py
├── test_models.py
├── test_sessions.py
├── test_streaming.py
├── test_tags.py
├── test_textgen.py
└── test_workflows.pyworkflow 中:
pytest --cov=pydify_plus --cov-report=xml这里顺便加了:
coverage并上传:
codecov/codecov-action这样每次提交:
都能自动统计。
项目里同时用了:
black --check pydify_plus tests检查格式。
flake8 --max-line-length 88检查代码规范。
mypy pydify_plus做类型检查。
这个部分很多项目其实都没有。
但非常建议加。
bandit -r pydify_plus扫描:
等安全问题。
safety check检查依赖漏洞。
Build 只在:
master push时运行。
配置:
if: github.event_name == 'push' &&
github.ref == 'refs/heads/master'避免:
develop 分支也发版这一部分是核心。
项目里使用:
python-semantic-release它会根据:
Commit Message自动决定版本号。
例如:
git commit -m "feat: add redis cache"自动:
1.0.0 → 1.1.0git commit -m "fix: websocket reconnect"自动:
1.0.0 → 1.0.1workflow 中:
uses: python-semantic-release/python-semantic-release@v10这里会自动:
这一点很重要。
否则 semantic-release 不会工作。
推荐:
feat: add xxxfix: resolve xxxdocs: update readmerefactor: optimize api发布阶段:
uses: pypa/gh-action-pypi-publish@release/v1注意:
这里没有:
PYPI_API_TOKEN因为项目已经切换到了:
Trusted Publisher这是一个非常重要的实践。
很多教程会:
build + publish写在一起。
但实际上:
分离更稳定因为:
很多 Python SDK 发布后:
只有 README用户其实很难快速上手。
这个项目里专门保留了:
examples/包括:
example_async.py
example_sync.py
fastapi_example.py这样:
都能快速演示。
对 SDK 类型项目来说非常重要。
项目里:
docs/API_REFERENCE.md也是后面做:
的基础。
很多人前期不做 docs,后面维护成本会越来越高。
现在整个流程:
git push
↓
pytest
↓
black
↓
flake8
↓
mypy
↓
bandit
↓
semantic-release
↓
auto tag
↓
build wheel
↓
trusted publisher
↓
publish pypi已经完全自动化。
原因:
branches:
- main但仓库实际:
master原因:
Trusted Publisher workflow 名不一致PyPI 配的是:
publish.yml实际:
release.yml原因:
缺少 __init__.py原因:
多个 Python 环境混用Mac 上尤其容易出现。
后来统一:
python -m xxx问题基本解决。
目前这套组合已经比较稳定:
功能 | 推荐 |
|---|---|
环境管理 | uv |
打包 | setuptools |
自动版本 | semantic-release |
自动发布 | Trusted Publisher |
测试 | pytest |
lint | black + flake8 |
类型检查 | mypy |
安全扫描 | bandit + safety |
Python 项目做到后面,其实核心已经不是:
功能开发而是:
工程化包括:
这些东西一开始看起来复杂。
但真正搭起来之后,后续维护成本会低很多。
尤其是:
自动版本 + 自动发布一旦习惯之后,基本就回不去了。