🔧 Git + Python 项目工作流最佳实践:pre-commit、CI、CHANGELOG 自动化

🌸你好呀!我是 lbb小魔仙
🌟 感谢陪伴~ 小白博主在线求友
🌿 跟着小白学Linux/Java/Python
📖 专栏汇总:
《Linux》专栏 | 《Java》专栏 | 《Python》专栏

- [🔧 Git + Python 项目工作流最佳实践:pre-commit、CI、CHANGELOG 自动化](#🔧 Git + Python 项目工作流最佳实践:pre-commit、CI、CHANGELOG 自动化)
-
- 摘要
- 内容
- 一、为什么需要工程化工作流?
-
- [1.1 团队协作的痛点](#1.1 团队协作的痛点)
- [1.2 理想的工作流](#1.2 理想的工作流)
- [二、🔗 pre-commit:本地提交的第一道防线](#二、🔗 pre-commit:本地提交的第一道防线)
-
- [2.1 pre-commit 是什么?](#2.1 pre-commit 是什么?)
- [2.2 安装与初始化](#2.2 安装与初始化)
- [2.3 推荐的 Python 项目配置](#2.3 推荐的 Python 项目配置)
- [2.4 各钩子的职责说明](#2.4 各钩子的职责说明)
- [2.5 跳过钩子的正确姿势](#2.5 跳过钩子的正确姿势)
- [2.6 CI 中验证 pre-commit](#2.6 CI 中验证 pre-commit)
- [三、📋 Conventional Commits:让提交信息有意义](#三、📋 Conventional Commits:让提交信息有意义)
-
- [3.1 标准格式](#3.1 标准格式)
- [3.2 优秀 vs 糟糕的提交信息](#3.2 优秀 vs 糟糕的提交信息)
- [3.3 使用 commitizen 规范提交](#3.3 使用 commitizen 规范提交)
- [3.4 自动化校验](#3.4 自动化校验)
- [四、📝 CHANGELOG 自动化](#四、📝 CHANGELOG 自动化)
-
- [4.1 使用 git-cliff 生成 CHANGELOG](#4.1 使用 git-cliff 生成 CHANGELOG)
- [4.2 配置文件](#4.2 配置文件)
- [4.3 使用方式](#4.3 使用方式)
- [4.4 生成效果示例](#4.4 生成效果示例)
- [4.5 版本发布工作流整合](#4.5 版本发布工作流整合)
- [五、🤖 CI/CD 集成](#五、🤖 CI/CD 集成)
-
- [5.1 Python 项目 CI 模板(GitHub Actions)](#5.1 Python 项目 CI 模板(GitHub Actions))
- [5.2 工作流说明](#5.2 工作流说明)
- [5.3 自动化依赖更新(Dependabot)](#5.3 自动化依赖更新(Dependabot))
- [六、🔒 安全与凭证管理](#六、🔒 安全与凭证管理)
-
- [6.1 使用 .gitignore 防止误提交](#6.1 使用 .gitignore 防止误提交)
- [6.2 使用 pre-commit 检测凭证泄露](#6.2 使用 pre-commit 检测凭证泄露)
- [6.3 Git Hooks 防御链](#6.3 Git Hooks 防御链)
- [七、🏗️ Branch Strategy 与工作流](#七、🏗️ Branch Strategy 与工作流)
-
- [7.1 推荐结构:GitHub Flow(简化版)](#7.1 推荐结构:GitHub Flow(简化版))
- [7.2 PR 模板](#7.2 PR 模板)
- [八、📦 pyproject.toml 统一配置](#八、📦 pyproject.toml 统一配置)
- [九、🚀 快速初始化项目脚本](#九、🚀 快速初始化项目脚本)
- 十、常见问题与最佳实践
-
- [10.1 pre-commit 运行太慢怎么办?](#10.1 pre-commit 运行太慢怎么办?)
- [10.2 如何回退被 pre-commit 修改的代码?](#10.2 如何回退被 pre-commit 修改的代码?)
- [10.3 CI 和本地环境不一致怎么办?](#10.3 CI 和本地环境不一致怎么办?)
- [10.4 多项目如何管理 pre-commit 配置?](#10.4 多项目如何管理 pre-commit 配置?)
- 十一、总结
摘要
代码风格不统一、调试代码被误提交、CI 反复失败、CHANGELOG 靠手动整理------这些团队协作中的常见痛点,根源在于缺乏工程化的自动化工作流。本文从 pre-commit 本地钩子配置开始,系统讲解 Git + Python 项目的全链路工程化实践,涵盖 Conventional Commits 规范提交、commitizen 交互式工具、git-cliff CHANGELOG 自动生成、GitHub Actions CI/CD 流水线配置、Dependabot 依赖更新、安全凭证检测、分支策略和 PR 模板规范等内容,附一键项目初始化脚本,帮助团队建立从本地到线上的全自动质量保障体系。
🚀 个人主页 : 有点流鼻涕 · CSDN
💬 座右铭 : "向光而行,沐光而生。"
内容
一、为什么需要工程化工作流?
1.1 团队协作的痛点
在实际项目中,下面这些场景你是否遇到过?
- 代码风格不统一:有人用 2 空格缩进,有人用 4 空格,有人用 Tab;字符串引号单双混用
- 调试代码被提交 :
print()或breakpoint()进入生产分支 - CI 反复失败:本地明明能跑,推到 CI 就红,原因是依赖没锁死或格式没检查
- CHANGELOG 靠记忆:发版时翻 commit log 手动整理,漏掉重要变更
- 安全凭证泄露 :
.env或密钥文件被误提交到仓库
1.2 理想的工作流
本地开发 → pre-commit 钩子自动检查 → 提交 → CI 自动验证 → Code Review → Merge → 自动生成 CHANGELOG
▲ │
└────────────────────────────────────── 持续迭代 ────────────────────────────────────────────────┘
🔑 核心理念 :把一切能自动化的事情都交给机器。人的精力应该集中在代码逻辑 和架构设计上,而不是格式、漏测、版本号这些重复劳动。
二、🔗 pre-commit:本地提交的第一道防线
2.1 pre-commit 是什么?
pre-commit 是一个多语言框架,用于管理 Git 钩子。它允许你在每次 git commit 之前自动执行一系列检查和格式化操作。
与传统 Git 钩子的区别:
| 特性 | 手动 .git/hooks/pre-commit |
pre-commit 框架 |
|---|---|---|
| 是否版本控制 | ❌ .git/ 目录不被跟踪 |
✅ 通过 .pre-commit-config.yaml 管理 |
| 团队成员同步 | 手动复制脚本 | ✅ 自动安装相同版本 |
| 钩子来源 | 自己写脚本 | ✅ 社区 3000+ 现成钩子 |
| 语言无关 | 通常 Shell | ✅ 支持 Python/Node/Ruby/Go 等 |
| 环境隔离 | 全局环境 | ✅ 每个钩子独立运行 |
2.2 安装与初始化
bash
# 安装 pre-commit
pip install pre-commit
# 在项目根目录创建配置文件
touch .pre-commit-config.yaml
# 安装 Git 钩子(会在 .git/hooks/ 中注册)
pre-commit install
# (可选)对已有仓库运行一次全量检查
pre-commit run --all-files
2.3 推荐的 Python 项目配置
yaml
# .pre-commit-config.yaml
repos:
# === 通用基础检查 ===
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.6.0
hooks:
- id: trailing-whitespace # 修剪行尾多余空格
- id: end-of-file-fixer # 确保文件末尾有换行
- id: check-yaml # 检查 YAML 文件语法
- id: check-json # 检查 JSON 文件语法
- id: check-toml # 检查 TOML 文件语法
- id: check-added-large-files # 阻止大文件被提交(默认 500KB)
args: ["--maxkb=1024"]
- id: check-merge-conflict # 检查是否有未解决的合并冲突
- id: detect-private-key # 检查是否意外提交了私钥
- id: check-case-conflict # 检查是否存在大小写冲突的文件名(Windows/macOS 相关)
- id: mixed-line-ending # 统一换行符(跨平台相关)
# === Python 格式化(自动修复)===
- repo: https://github.com/psf/black
rev: 24.4.2
hooks:
- id: black
args: ["--line-length=88", "--target-version=py312"]
language_version: python3.12
# === import 排序(自动修复)===
- repo: https://github.com/PyCQA/isort
rev: 5.13.2
hooks:
- id: isort
args: ["--profile=black", "--line-length=88"]
# === 代码静态检查 ===
- repo: https://github.com/PyCQA/flake8
rev: 7.1.0
hooks:
- id: flake8
args: ["--max-line-length=88", "--extend-ignore=E203,W503"]
additional_dependencies: [flake8-docstrings]
# === 类型检查(可选,运行时较慢)===
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.10.0
hooks:
- id: mypy
args: ["--strict", "--ignore-missing-imports"]
additional_dependencies: [types-requests, types-pyyaml]
# 类型检查较慢,设为手动触发
stages: [manual]
# === 安全扫描 ===
- repo: https://github.com/Yelp/detect-secrets
rev: v1.4.0
hooks:
- id: detect-secrets
args: ["--baseline", ".secrets.baseline"]
# 基线文件管理:detect-secrets scan > .secrets.baseline
# === Dockerfile 检查(如果有 Docker)===
- repo: https://github.com/hadolint/hadolint
rev: v2.12.0
hooks:
- id: hadolint-docker
2.4 各钩子的职责说明
| 钩子 | 类型 | 作用 | 能否自动修复 |
|---|---|---|---|
trailing-whitespace |
格式 | 删除行尾多余空格 | ✅ |
end-of-file-fixer |
格式 | 确保文件末尾空行 | ✅ |
check-added-large-files |
安全 | 阻止 >500KB 的文件 | ❌ 阻止提交 |
detect-private-key |
安全 | 检测私钥文件 | ❌ 阻止提交 |
black |
格式 | Python 代码格式化 | ✅ |
isort |
格式 | 自动排序 import | ✅ |
flake8 |
质量 | 代码规范检查 | ❌ 提示修改 |
mypy |
类型 | 静态类型检查 | ❌ 提示修改 |
detect-secrets |
安全 | 检测凭证泄露 | ❌ 阻止提交 |
2.5 跳过钩子的正确姿势
bash
# 紧急情况(不推荐滥用)
git commit --no-verify -m "fix: hotfix for production issue"
# 只跳过特定钩子
SKIP=flake8,black git commit -m "feat: wip"
# 运行特定钩子
pre-commit run black --all-files
# 只对暂存的文件运行
pre-commit run --show-diff-on-failure
⚠️ 重要 :
--no-verify只应在生产热修复等极少数紧急场景使用。滥用它会破坏整个质量保障体系。
2.6 CI 中验证 pre-commit
在 CI 中运行 pre-commit,确保即使有人本地跳过了钩子,CI 也会拦住:
yaml
# .github/workflows/ci.yml
name: CI
on: [push, pull_request]
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Install pre-commit
run: pip install pre-commit
- name: Run pre-commit
run: pre-commit run --all-files --show-diff-on-failure
三、📋 Conventional Commits:让提交信息有意义
3.1 标准格式
一个规范的提交信息包含以下结构:
<type>(<scope>): <subject>
<body>
<footer>
Type 类型标准(Angular 规范):
| Type | 含义 | 是否出现在 CHANGELOG | 版本影响 |
|---|---|---|---|
feat |
新功能 | ✅ | 次版本号+1 |
fix |
Bug 修复 | ✅ | 修订号+1 |
docs |
文档变更 | ✅ | - |
style |
代码格式变更 | ❌ | - |
refactor |
重构 | ❌ | - |
perf |
性能优化 | ✅ | - |
test |
测试相关 | ❌ | - |
build |
构建/依赖变更 | ❌ | - |
ci |
CI 配置变更 | ❌ | - |
chore |
杂项 | ❌ | - |
revert |
回滚 | ✅ | - |
3.2 优秀 vs 糟糕的提交信息
text
# ❌ 糟糕(看不出意图)
git commit -m "fix bug"
git commit -m "update"
git commit -m "改了"
# ❌ 过于冗长
git commit -m "修复了当用户输入为空字符串时程序崩溃的问题,
之前因为没做空值校验导致...(写了 500 字)"
# ✅ 优秀的提交
git commit -m "fix: 处理用户名为空时的输入验证异常
当用户名字段为空字符串时,validate_username() 会抛出
ValueError 而非返回 False,导致 HTTP 500。
- 在入口处增加空字符串前置校验
- 补充对应的单元测试用例
- Closes #1234"
3.3 使用 commitizen 规范提交
bash
# 安装
pip install commitizen
# 交互式生成规范提交信息
cz commit
# 输出示例
# ? Select the type of change you're committing: feat
# ? What is the scope of this change: auth
# ? Write a short description: 添加 JWT Token 刷新接口
# ? Provide a longer description: ...
# ? Are there any breaking changes: No
3.4 自动化校验
在 pre-commit 中增加对提交信息的校验:
yaml
# 添加到 .pre-commit-config.yaml
- repo: https://github.com/commitizen-tools/commitizen
rev: v3.27.0
hooks:
- id: commitizen
stages: [commit-msg]
四、📝 CHANGELOG 自动化
4.1 使用 git-cliff 生成 CHANGELOG
git-cliff 是一个基于 Conventional Commits 自动生成 CHANGELOG 的工具。
bash
# 安装
cargo install git-cliff
# 或使用 pip
pip install git-cliff
4.2 配置文件
toml
# cliff.toml
[changelog]
header = "# 📋 CHANGELOG\n\n此文件记录了项目的所有重要变更。\n"
body = """
{% for group, commits in commits | group_by(attribute="group") %}
### {{ group | upper_first }}
{% for commit in commits %}
- {{ commit.message | upper_first }}\
{% if commit.breaking %} (⚠️ 破坏性变更){% endif %}\
{% if commit.scope %} ({{ commit.scope }}){% endif %}
{% endfor %}
{% endfor %}
"""
footer = "---\n*自动生成于 {{ timestamp }}*\n"
# 禁止词
trim = true
[git]
conventional_commits = true
filter_unconventional = true
# 按类型分组
[commit_parser]
message = "^(:?feat|fix|docs|perf|revert|breaking):\\s?(.*)"
body = ".*"
[links]
# 可选的链接配置(GitHub Issues)
# reference = "https://github.com/你的用户名/仓库名/issues/{id}"
4.3 使用方式
bash
# 基于所有标签生成(完整 CHANGELOG)
git-cliff -o CHANGELOG.md
# 只生成最新未发布的变更
git-cliff --unreleased -o CHANGELOG.md
# 筛选特定版本范围
git-cliff --tag v1.0.0..v1.1.0 -o CHANGELOG.md
# 追加到已有 CHANGELOG
git-cliff --prepend CHANGELOG.md
4.4 生成效果示例
markdown
# 📋 CHANGELOG
### Features
- 添加 JWT Token 刷新接口 (auth)
- 支持批量导出功能 (export)
- 新增数据看板组件 (dashboard)
### Bug Fixes
- 修复用户名为空时的输入验证异常 (auth)
- 修复分页组件在数据不足时的显示异常 (ui)
### Performance
- 优化大列表渲染性能,减少 60% 重绘 (perf)
### ⚠️ Breaking Changes
- API 响应格式从 XML 变更为 JSON (api)
---
*自动生成于 2025-01-15*
4.5 版本发布工作流整合
bash
#!/bin/bash
# release.sh - 一键发布脚本
set -e
# 1. 确认当前分支
BRANCH=$(git rev-parse --abbrev-ref HEAD)
if [ "$BRANCH" != "main" ] && [ "$BRANCH" != "master" ]; then
echo "❌ 请在 main/master 分支上执行发布"
exit 1
fi
# 2. 检查是否有未提交的变更
if [ -n "$(git status --porcelain)" ]; then
echo "❌ 存在未提交的变更,请先提交或暂存"
exit 1
fi
# 3. 读取当前版本号(假设使用 semver)
CURRENT_VERSION=$(git describe --tags --abbrev=0 2>/dev/null || echo "0.0.0")
echo "📦 当前版本: $CURRENT_VERSION"
# 4. 基于最近的 commit 类型自动升级版本
# feat → minor, fix → patch, breaking → major
LATEST_COMMIT=$(git log -1 --pretty=%B)
if echo "$LATEST_COMMIT" | grep -q "BREAKING\|!"; then
# major bump
NEW_VERSION=$(echo "$CURRENT_VERSION" | awk -F. '{print $1+1".0.0"}')
elif echo "$LATEST_COMMIT" | grep -q "^feat"; then
# minor bump
NEW_VERSION=$(echo "$CURRENT_VERSION" | awk -F. '{print $1"."$2+1".0"}')
else
# patch bump
NEW_VERSION=$(echo "$CURRENT_VERSION" | awk -F. '{print $1"."$2"."$3+1}')
fi
echo "🎯 新版本: $NEW_VERSION"
# 5. 生成 CHANGELOG
git cliff --tag "$NEW_VERSION" --prepend CHANGELOG.md
# 6. 创建 tag 并推送
git add CHANGELOG.md
git commit -m "chore: release v$NEW_VERSION"
git tag -a "v$NEW_VERSION" -m "Release v$NEW_VERSION"
git push && git push --tags
echo "✅ 发布完成!版本 v$NEW_VERSION"
五、🤖 CI/CD 集成
5.1 Python 项目 CI 模板(GitHub Actions)
yaml
# .github/workflows/ci.yml
name: Python CI
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Run pre-commit
uses: pre-commit/action@v3.0.1
test:
needs: lint
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.11", "3.12"]
os: [ubuntu-latest, windows-latest]
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
cache: "pip"
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -e ".[dev]"
- name: Run tests with coverage
run: |
pytest --cov=src --cov-report=xml --cov-report=term-missing
- name: Upload coverage
uses: codecov/codecov-action@v4
with:
file: ./coverage.xml
fail_ci_if_error: false
security:
needs: lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Install bandit
run: pip install bandit
- name: Run bandit security scan
run: bandit -r src/ -f json -o bandit_report.json || true
- name: Upload security report
uses: actions/upload-artifact@v4
with:
name: bandit-report
path: bandit_report.json
5.2 工作流说明
| Job | 依赖 | 并行策略 | 超时设置 |
|---|---|---|---|
| lint | 无 | 单版本 | 5 min |
| test | lint ✅ | 多 Python 版本 × 多 OS | 15 min |
| security | lint ✅ | 单次扫描 | 5 min |
设计原则:
- lint 最快执行:格式问题不值得跑完整测试
- test 矩阵化:确保在所有目标环境通过
- 安全扫描独立:不影响测试流程,发现问题时不会阻塞其他检查
- lint 失败则跳过 test:节省 CI 资源
5.3 自动化依赖更新(Dependabot)
yaml
# .github/dependabot.yml
version: 2
updates:
- package-ecosystem: "pip"
directory: "/"
schedule:
interval: "weekly"
day: "monday"
open-pull-requests-limit: 10
labels:
- "dependencies"
- "automated"
# 对 dev 依赖分组(减少 PR 数量)
groups:
dev-dependencies:
patterns:
- "pytest*"
- "flake8"
- "black"
- "isort"
- "mypy"
- "pre-commit"
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "monthly"
六、🔒 安全与凭证管理
6.1 使用 .gitignore 防止误提交
gitignore
# .gitignore
# === 环境与凭证 ===
.env
.env.*
*.key
*.pem
credentials.json
service-account.json
# === 虚拟环境 ===
.venv/
venv/
env/
# === Python 构建产物 ===
__pycache__/
*.py[cod]
*.egg-info/
dist/
build/
# === IDE ===
.idea/
.vscode/
*.swp
*.swo
# === 操作系统 ===
.DS_Store
Thumbs.db
# === 其他 ===
*.log
.coverage
htmlcov/
6.2 使用 pre-commit 检测凭证泄露
yaml
- repo: https://github.com/Yelp/detect-secrets
rev: v1.4.0
hooks:
- id: detect-secrets
args: ["--baseline", ".secrets.baseline"]
首次使用:
bash
# 扫描当前项目,生成基线文件
detect-secrets scan > .secrets.baseline
# 检查基线中是否有误报,手动编辑 .secrets.baseline 标记
# 将 "is_secret": true 改为 false
# 提交基线文件
git add .secrets.baseline
6.3 Git Hooks 防御链
本地修改 → 检测到文件变更
↓
.gitignore 过滤敏感文件
↓
pre-commit 钩子
├─ detect-secrets → 扫描凭证
├─ check-added-large-files → 阻止大文件
└─ detect-private-key → 检测私钥
↓
提交成功 → 推送到远程
↓
GitHub Secret Scanning(自动)
↓
CI 中的安全扫描(bandit / truffleHog)
七、🏗️ Branch Strategy 与工作流
7.1 推荐结构:GitHub Flow(简化版)
main ─── feat-A ── feat-A ── feat-A ── merge ──────────── main ── ...
↑ 创建 ↑ 开发 ↑ PR ↑ 合并部署
bash
# 完整流程
git checkout -b feat/user-authentication
# 多次小提交(每次 pre-commit 都检查)
git commit -m "feat: 添加用户模型"
git commit -m "feat: 实现 JWT 生成逻辑"
git commit -m "feat: 添加登录接口"
# 推送到远程,创建 PR
git push -u origin feat/user-authentication
# PR 通过后,在 GitHub 上 squash merge
# 然后删除远程分支
git branch -d feat/user-authentication
git push origin --delete feat/user-authentication
7.2 PR 模板
markdown
<!-- .github/PULL_REQUEST_TEMPLATE.md -->
## 📝 描述
请简要描述本次 PR 的内容和动机。
Closes #(issue)
## 🎯 变更类型
- [ ] feat: 新功能
- [ ] fix: Bug 修复
- [ ] refactor: 重构
- [ ] perf: 性能优化
- [ ] docs: 文档
- [ ] test: 测试
- [ ] chore: 杂项
## ✅ 检查清单
- [ ] 我的代码遵循项目代码规范
- [ ] 我已经运行了 `pre-commit run --all-files`
- [ ] 我已经添加/更新了相关测试
- [ ] 所有测试均已通过
- [ ] 我已经更新了相关文档
- [ ] 我已经在 CHANGELOG 中添加了变更记录
## 🧪 测试说明
如何验证本次变更?
八、📦 pyproject.toml 统一配置
把工具配置集中到 pyproject.toml,避免项目根目录散落配置文件:
toml
# pyproject.toml
[build-system]
requires = ["setuptools>=68.0"]
build-backend = "setuptools.backends._legacy:_Backend"
[project]
name = "my-project"
version = "0.1.0"
requires-python = ">=3.11"
dependencies = [
"requests>=2.31",
"pydantic>=2.0",
]
[project.optional-dependencies]
dev = [
"pytest>=8.0",
"pytest-cov>=5.0",
"black>=24.0",
"isort>=5.13",
"flake8>=7.0",
"mypy>=1.10",
"pre-commit>=3.7",
"commitizen>=3.27",
]
# === Black 配置 ===
[tool.black]
line-length = 88
target-version = ["py312"]
include = '\.pyi?$'
extend-exclude = '''
/(
\.eggs
| \.git
| \.mypy_cache
| \.tox
| \.venv
| _build
| buck-out
| build
| dist
)/
'''
# === isort 配置 ===
[tool.isort]
profile = "black"
line_length = 88
multi_line_output = 3
include_trailing_comma = true
force_grid_wrap = 0
use_parentheses = true
ensure_newline_before_comments = true
# === flake8 配置 ===
[tool.flake8]
max-line-length = 88
extend-ignore = ["E203", "W503"]
max-complexity = 10
docstring-convention = "google"
# === mypy 配置 ===
[tool.mypy]
python_version = "3.12"
strict = true
ignore_missing_imports = true
disallow_untyped_defs = true
disallow_any_unimported = false
warn_return_any = true
warn_unused_configs = true
# === pytest 配置 ===
[tool.pytest.ini_options]
minversion = "8.0"
testpaths = ["tests"]
python_files = ["test_*.py"]
addopts = "-v --tb=short --strict-markers"
markers = [
"slow: 慢速测试(默认跳过)",
"integration: 集成测试(需要外部服务)",
]
# === coverage 配置 ===
[tool.coverage.run]
source = ["src"]
omit = ["tests/*", "**/__init__.py"]
[tool.coverage.report]
exclude_lines = [
"pragma: no cover",
"def __repr__",
"if __name__ == .__main__.",
"raise AssertionError",
"raise NotImplementedError",
]
九、🚀 快速初始化项目脚本
结合本文所有内容,这里提供一个一键初始化脚本:
python
#!/usr/bin/env python3
"""快速初始化 Python 项目的标准化工程结构"""
import subprocess
import sys
from pathlib import Path
PROJECT_NAME = sys.argv[1] if len(sys.argv) > 1 else "my-python-project"
# 目录结构
DIRS = [
"src",
"src/your_package",
"tests",
"docs",
"scripts",
]
# 核心文件
FILES = {
"README.md": f"# {PROJECT_NAME}\n\n项目描述\n",
".gitignore": (
".env\n.venv/\n__pycache__/\n*.pyc\ndist/\nbuild/\n"
"*.egg-info/\n.idea/\n.vscode/\n*.log\n"
),
"pyproject.toml": (
"[build-system]\n"
'requires = ["setuptools>=68.0"]\n'
'build-backend = "setuptools.build_meta"\n\n'
"[project]\n"
f'name = "{PROJECT_NAME}"\n'
'version = "0.1.0"\n'
'requires-python = ">=3.11"\n'
'dependencies = []\n\n'
"[project.optional-dependencies]\n"
'dev = [\n'
' "pytest>=8.0",\n'
' "pytest-cov>=5.0",\n'
' "black>=24.0",\n'
' "isort>=5.13",\n'
' "flake8>=7.0",\n'
' "mypy>=1.10",\n'
' "pre-commit>=3.7",\n'
' "commitizen>=3.27",\n'
"]\n"
),
}
# 创建目录
for d in DIRS:
Path(d).mkdir(parents=True, exist_ok=True)
# 创建文件
for path, content in FILES.items():
Path(path).write_text(content)
# 初始化 git
subprocess.run(["git", "init"])
subprocess.run(["git", "add", "."])
# 安装 pre-commit
subprocess.run(["pip", "install", "pre-commit"])
# 生成 .pre-commit-config.yaml(可在此基础上增加其他钩子)
pre_commit_config = """
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.6.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
- id: check-added-large-files
- id: detect-private-key
- repo: https://github.com/psf/black
rev: 24.4.2
hooks:
- id: black
- repo: https://github.com/PyCQA/isort
rev: 5.13.2
hooks:
- id: isort
- repo: https://github.com/PyCQA/flake8
rev: 7.1.0
hooks:
- id: flake8
- repo: https://github.com/commitizen-tools/commitizen
rev: v3.27.0
hooks:
- id: commitizen
stages: [commit-msg]
"""
Path(".pre-commit-config.yaml").write_text(pre_commit_config.strip())
subprocess.run(["pre-commit", "install"])
print(f"✅ 项目 {PROJECT_NAME} 初始化完成!")
print("包含:pyproject.toml / pre-commit / Git / 目录结构")
十、常见问题与最佳实践
10.1 pre-commit 运行太慢怎么办?
bash
# 只检查暂存的文件(默认行为)
git commit
# 更新钩子版本可以跳过缓存
pre-commit autoupdate
# 将耗时的检查(如 mypy)设为手动
# 在配置中添加 stages: [manual]
# 仅某些提交时运行:
pre-commit run --hook-stage manual mypy
10.2 如何回退被 pre-commit 修改的代码?
bash
# pre-commit 自动修改后,如果不想提交:
git checkout -- <file>
# 或者查看具体修改:
git diff
10.3 CI 和本地环境不一致怎么办?
yaml
# 方案一:在 CI 中也运行 pre-commit
# 方案二:使用相同的 Python 版本和依赖版本
# 方案三:锁定依赖版本(推荐)
# requirements.txt 或
# pyproject.toml 中锁定版本
dependencies = [
"requests==2.31.0",
"pydantic==2.5.0",
]
10.4 多项目如何管理 pre-commit 配置?
pre-commit 支持从远端仓库引用配置:
yaml
# 中央仓库维护一套通用配置
repos:
- repo: https://github.com/your-org/pre-commit-configs
rev: v1.0.0
hooks:
- id: org-python-hooks
十一、总结
完整工作流全景图
┌──────────────────────┐
│ 代码编写阶段 │
│ - 遵循项目结构 │
│ - 编写测试 │
└────────┬─────────────┘
↓
┌──────────────────────┐
│ git add │
└────────┬─────────────┘
↓
┌──────────────────────────┐
│ pre-commit 自动运行 │
│ ├─ black 格式化 │
│ ├─ isort 排序 │
│ ├─ flake8 检查 │
│ ├─ detect-secrets │
│ └─ 其他钩子 │
└────────┬─────────────────┘
↓ (通过/失败)
┌──────────────────────────┐
│ git commit │
│ └─ commitizen 规范信息 │
└────────┬─────────────────┘
↓
┌──────────────────────────┐
│ git push │
└────────┬─────────────────┘
↓
┌──────────────────────────┐
│ CI 自动运行 │
│ ├─ lint (pre-commit) │
│ ├─ test (pytest) │
│ ├─ security 扫描 │
│ └─ coverage 报告 │
└────────┬─────────────────┘
↓ (通过)
┌──────────────────────────┐
│ PR Review / Merge │
└────────┬─────────────────┘
↓
┌──────────────────────────┐
│ 版本发布 │
│ ├─ git tag │
│ ├─ git-cliff CHANGELOG │
│ └─ 部署 │
└──────────────────────────┘
核心收益
| 环节 | 自动化前 | 自动化后 |
|---|---|---|
| 代码格式 | Code Review 时被人指出来 | commit 时自动修复 |
| import 顺序 | 手动整理,每次提交很乱 | isort 自动排序 |
| 安全凭证 | 不小心提交到仓库才被发现 | pre-commit 直接拦住 |
| 提交信息 | 随心所欲,CHANGELOG 难产 | commitizen 规范化 |
| CHANGELOG | 发版前手动翻 commit log | git-cliff 自动生成 |
| CI 流程 | 手动触发,配置分散 | GitHub Actions 一键完成 |
📌 如果本文对你有帮助,欢迎点赞收藏,关注博主获取更多 Python + AI 实战教程!
📕个人领域 :Linux/C++/java/AI🚀 个人主页 :有点流鼻涕 · CSDN
💬 座右铭 : "向光而行,沐光而生。"
