mcpField
P
python-dev

python-dev

github.com/tenequm开发4099 次安装5 天前更新
代码仓库

关于 python-dev

python-dev 是用于创建或现代化 Python 项目的配置 Skill,规定依赖管理、类型检查、代码检查、测试和任务命令所用工具。它还提供配置模板和迁移指引。

主要功能

  1. 01基于 uv 的项目与依赖配置
  2. 02ty 类型检查配置
  3. 03ruff 代码检查与格式化
  4. 04pytest 与 just 工作流
  5. 05lefthook Git 钩子配置

适用场景

  1. 01配置新的 Python 项目
  2. 02将现有项目迁移到该工具栈
  3. 03为开发工作流添加检查和测试

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/tenequm/skills --skill python-dev

安装后在 Claude Code 中输入/python-dev确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

CHANGELOG.md
LICENSE.txt
references/
justfile-reference.md
pytest-reference.md
ruff-reference.md
ty-reference.md
uv-reference.md

详细介绍

Python 开发配置

注重实践、可用于生产环境的 Python 开发技术栈。无需选择,直接使用即可。

适用场景

  • 开始一个新的 Python 项目
  • 现代化改造现有项目(从 pip/poetry/mypy/black/flake8 迁移)
  • 设置 lint、格式化、类型检查或测试
  • 为项目命令创建 Justfile
  • 将 pyproject.toml 配置为唯一可信来源

技术栈

工具用途替代工具
uv 0.12+包管理器、Python 版本管理器、运行器pip、poetry、pyenv、virtualenv
ty(测试版)类型检查器(Astral,Rust)mypy、pyright
ruffLinter + 格式化工具flake8、black、isort、pyupgrade
pytest测试unittest
just命令运行器make
lefthook 2.1+Git 钩子(单个二进制文件,并行运行)pre-commit

关于 ty 的说明:ty 目前处于测试阶段(0.0.x),没有稳定的 API,推断行为也可能在任意两个版本间发生变化,因此请锁定版本。Pydantic 已不再是合理的反对理由:从 0.0.57 起,ty 已为其推出专门的库支持路线(构造函数、model_config、BaseSettings、RootModel、严格与宽松模式)。Django 和 SQLAlchemy 仍不受此类支持,因此很可能产生误报。在更换整个检查器之前,可以先使用 [tool.ty.analysis] replace-imports-with-any = ["sqlalchemy.**"],这样只会屏蔽一个有问题的依赖,而不是全部依赖。如果你现在确实需要稳如磐石的检查,请将 ty 换成 pyright,其余技术栈保持不变。

快速开始:新建项目

# 1. Create project with src layout (uv 0.12+ packages by default; --package is redundant)
uv init my-project
cd my-project

# 2. Pin Python version
uv python pin 3.13

# 3. Add dev dependencies
uv add --dev ruff ty pytest pytest-asyncio lefthook

# 4. Create Justfile and lefthook.yml (see templates below)
# 5. Configure pyproject.toml (see template below)
# 6. Install git hooks
uv run lefthook install

# 7. Run checks
just check

pyproject.toml 模板

这是唯一的配置文件。复制它并调整 [project] 字段。

[project]
name = "my-project"
version = "0.1.0"
description = "Project description"
readme = "README.md"
requires-python = ">=3.13"
license = {text = "MIT"}
dependencies = []

[project.scripts]
my-project = "my_project:main"   # CLI: `uv run my-project` -> main() in src/my_project/__init__.py

[dependency-groups]
dev = [
    "ruff>=0.16.6",          # 0.16 changed the default rule set - see the lint note below
    "ty>=0.0.79",            # beta: pin tight, inference changes between minors
    "pytest>=9.1.1",         # 9.1 fixed addopts strictness being ignored
    "pytest-asyncio>=1.4.0", # 1.4 added the loop-factories hook
    "lefthook>=2.1.12",
]

# uv 0.12+ generates `uv_build` here instead. Keep that unless you need a hatchling
# plugin - this block is a deliberate override, not what `uv init` gives you.
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[tool.hatch.build.targets.wheel]
packages = ["src/my_project"]

# =============================================================================
# RUFF - Loose, helpful rules only
# =============================================================================
[tool.ruff]
target-version = "py313"
line-length = 100

[tool.ruff.lint]
# Ruff 0.16 enables 413 rules by default (up from 59). Do NOT write a `select`
# list here unless you mean to shrink that - `select = ["E","F","I","UP"]` now
# makes ruff weaker than no config at all. Narrow with `extend-select`/`ignore`.
ignore = [
    "E501",   # line too long - formatter handles it
    "UP007",  # X | Y unions - Optional[X] is more readable
]
exclude = [".git", ".venv", "__pycache__", "build", "dist"]

[tool.ruff.format]
quote-style = "double"
indent-style = "space"
line-ending = "lf"
# Ruff 0.16 formats Python blocks inside Markdown by default. Drop this line
# only if you want README code fences reformatted too.
exclude = ["*.md"]

# =============================================================================
# TY - Type Checker
# =============================================================================
[tool.ty.environment]
python-version = "3.13"

[tool.ty.src]
include = ["src"]

# =============================================================================
# PYTEST
# =============================================================================
[tool.pytest.ini_options]
testpaths = ["tests"]
python_files = ["test_*.py"]
python_classes = ["Test*"]
python_functions = ["test_*"]
asyncio_mode = "auto"
# pytest 9.0 silently ignored --strict-markers/--strict-config passed via
# addopts. Use the ini keys, which work on 9.0 and 9.1 alike. `strict = true`
# is the shorthand and also covers strict_xfail + strict_parametrization_ids.
strict = true
addopts = ["-ra"]

上面的配置有意不设置 asyncio_default_fixture_loop_scope;pytest-asyncio 每次运行时都会对此发出警告。将其设为 "function" 即可消除警告并固定行为。

Justfile 模板

# Check types, lint, and formatting (non-mutating; mirrors CI)
check:
    uv run ty check
    uv run ruff check
    uv run ruff format --check

# Run tests
test *ARGS:
    uv run pytest {{ARGS}}

# Run tests with coverage
test-cov:
    uv run pytest --cov=src --cov-report=term-missing

# Auto-fix and format
fix:
    uv run ruff check --fix
    uv run ruff format

# Install/sync all dependencies
install:
    uv sync --all-groups
    uv run lefthook install

# Update all dependencies
update:
    uv lock --upgrade
    uv sync --all-groups

# Clean build artifacts
clean:
    rm -rf dist/ build/ .pytest_cache/ .ruff_cache/ htmlcov/
    find . -type d -name __pycache__ -exec rm -rf {} + 2>/dev/null || true

Lefthook 配置

这里用 Lefthook 替代 pre-commit,原因与 go-dev 使用它相同:只有一个二进制文件、钩子并行运行,而且——这在 Python 中最重要——钩子运行的是来自 uv.lock 的你的 ruff,而不是在钩子配置中单独锁定的另一个副本。它以平台 wheel 的形式从 PyPI 安装,因此 uv add --dev lefthook 就完成了全部安装;无需 Go 工具链。

uv run lefthook install     # writes .git/hooks/pre-commit and pre-push
# lefthook.yml
assert_lefthook_installed: true

pre-commit:
  piped: true   # a failed job stops the rest; lint must fix before format runs
  jobs:
    - name: guards
      group:
        parallel: true
        jobs:
          - name: private-key
            run: "! grep -lE 'BEGIN [A-Z ]*PRIVATE KEY' {staged_files}"
          - name: merge-conflict
            run: "! grep -lE '^(<<<<<<<|>>>>>>>) ' {staged_files}"
          - name: large-files
            exclude: ["uv.lock"]
            run: "! find {staged_files} -type f -size +1000k | grep ."
    - name: ruff-check
      glob: "*.{py,pyi,ipynb}"
      run: uv run ruff check --force-exclude --fix {staged_files}
      stage_fixed: true
    - name: ruff-format
      glob: "*.{py,pyi,ipynb}"
      run: uv run ruff format --force-exclude {staged_files}
      stage_fixed: true
    - name: ty
      run: uv run ty check   # no glob - see the glob trap below

pre-push:
  jobs:
    - name: test
      run: uv run pytest

--force-exclude 是必需项,不是装饰。 对于通过命令行显式传入的路径,Ruff 会忽略自己的 exclude 配置,而 {staged_files} 会显式传入路径。没有这个标志时,[tool.ruff] exclude 下的暂存文件仍会被 lint——经验证:加上这个标志,ruff check --force-exclude src/generated/gen.py 会报告 All checks passed;不加时,同一条命令会发现错误。pre-commit 用户从未遇到这个问题,因为 ruff-pre-commit 已将此标志写入钩子条目;使用 lefthook 时,需要你自己记住加上它。

stage_fixed: true 是相比 pre-commit 更方便之处。 pre-commit 在钩子改写文件时会使提交失败,并要求你重新暂存并再次运行。lefthook 会对修复后的文件重新运行 git add,然后继续提交。从 2.1.12 起,如果 git add 失败,钩子也会失败,因此修复结果绝不会在未暂存的情况下混过去。

设置 piped: true 的原因是执行顺序。 Ruff 自己建议先运行带修复功能的 lint,再进行格式化,因为 --fix 生成的代码之后还需要重新格式化。启用 piped 也意味着先运行各项防护检查,发现泄露的密钥时就会在任何工具耗费时间之前阻止提交。

编辑此配置前,值得了解的注意事项:

  • 当没有任何文件匹配时,glob 会静默跳过整个任务——这是门禁漏洞,而非便利功能。即使某个任务配置了 glob,但 run 中没有 {staged_files},它仍会受该 glob 过滤。因此,在只修改了 pyproject.toml 的提交中,配置了 glob: "*.{py,pyi}" 的 ty 会被跳过——而 [tool.ty] 和依赖版本固定信息恰好就在这个文件里。已验证:此类提交会显示 ty (skip) no matching staged files,且完全不会执行类型检查。任何检查整个项目而非暂存文件的任务都不能配置 glob。上面的 ruff 任务保留了 glob,因为它们只针对 {staged_files} 操作。
  • ** 匹配一个或多个目录,而不是零个或多个。 glob: "src/**/*.py" 不会匹配 src/main.py。使用 glob_matcher: doublestar,即可获得其他工具都有的行为。
  • 配置文件的位置至关重要。 lefthook 只会自动发现仓库根目录或 .config/ 下的配置(从 v1.11.12 起支持后者)。放在其他位置时,提交会静默停止运行钩子,因为 git 会直接调用钩子,任何任务运行器配方都无法拦截。
  • 绝不要在 pre-push 中配置会修改内容的任务。 在那里重写文件的任务会导致推送失败,并留下未提交的修改。--fix 应放在 pre-commit 中,由 stage_fixed 处理;pre-push 应保持只读,像上面的 pytest 任务一样。
  • 可以使用 file_types,以应对 glob 过于宽泛的情况——支持 text、binary、executable、symlink 以及包括 text/x-python 在内的 MIME 类型。
  • 已删除的文件不会出现在 {staged_files} 中,相应任务会因 no files for inspection 而跳过,因此只删除文件的提交不会报错。
  • 钩子运行期间,未暂存的修改会被隐藏,并在运行后恢复,因此门禁检查的是你实际要提交的内容,而不是有未提交修改的工作区。已在 2.1.12 上验证:暂存一个干净文件,同时将有问题的副本留在未暂存状态,检查会通过,提交干净版本,并恢复未暂存的修改。
  • lefthook 在安装前不会运行。 assert_lefthook_installed: true 会将缺少二进制文件视为失败,避免钩子静默不运行。应将 lefthook install 纳入新成员上手流程。
  • lefthook validate 会在 CI 中检查配置是否格式错误;lefthook dump 会打印合并后的有效配置。被 git 忽略的 lefthook-local.yml 允许开发者添加或跳过任务,而不会强加给队友。

离开 pre-commit 后你会失去什么。 pre-commit-hooks 库没有 lefthook 的对应版本。其中有三个钩子值得保留,已在上面的 guards 组中手动实现:detect-private-key、check-merge-conflict 和 check-added-large-files。另外四个没有复刻:check-yaml、check-toml、end-of-file-fixer、trailing-whitespace 和 mixed-line-ending:ruff 的格式化工具已经会处理 Python 文件的空白和末尾换行,其余钩子则只用于非 Python 文件。如果你的仓库中有很多手动编辑的 YAML 文件,可以将它们作为 shell 任务加回来。你还会失去 pre-commit autoupdate 和托管服务 pre-commit.ci——作为交换,现在工具版本只需在一个地方更新:uv lock --upgrade。

项目结构

始终使用 src 布局:

my-project/
  src/
    my_project/
      __init__.py
      cli.py
      models.py
  tests/
    conftest.py
    test_models.py
  pyproject.toml
  Justfile
  uv.lock
  .python-version
  lefthook.yml
  .gitignore

日常工作流

just check          # Type check + lint + format
just test           # Run tests
just test -x        # Stop on first failure
just fix            # Auto-fix lint issues
uv add httpx        # Add a dependency
uv add --dev hypothesis  # Add dev dependency
uv sync             # main deps + dev (dev is in default-groups)
uv sync --all-groups  # everything in [dependency-groups]
uv run python -m my_project  # Run the project

CI(GitHub Actions)

在 CI 中运行与 just check + just test 相同的检查。在 .github/workflows/ci.yml 中添加以下内容:

name: CI
on: [push, pull_request]

jobs:
  check:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7
      - uses: astral-sh/[email protected]   # pin the full version - see below
      - run: uv sync --all-groups
      - run: uv run ty check
      - run: uv run ruff check --output-format github
      - run: uv run ruff format --check
      - run: uv run pytest
      - run: uv run lefthook validate

astral-sh/setup-uv 会安装 uv、管理 .python-version 中指定的 Python 安装,并缓存解析器。无需单独添加 setup-python 步骤。

关于该版本固定,需要注意两点:

  • 固定完整版本,不要使用 @v10。 从 v8 开始,setup-uv 不再发布这两种浮动标签——“为进一步提高安全性,我们将停止发布次要版本标签。你将无法再使用 @v8 或 @v8.0。” 已确认:v6 和 v7 引用有效,v8/v9/v10 则返回 404。只有精确到补丁版本的标签存在,因此请固定到其中一个版本或提交 SHA。
  • 不要设置 enable-cache: true。 默认值为 auto。从 v10 开始,该选项会在 pull_request_target、workflow_run 和 release 中刻意禁用缓存,以防止缓存投毒。强制启用会关闭这项保护。

迁移现有项目

# 1. Install uv if not present
brew install uv

# 2. Convert requirements.txt to pyproject.toml deps
uv add -r requirements.txt

# 3. Replace mypy with ty
uv remove --dev mypy
uv add --dev ty

# 4. Replace black/flake8/isort with ruff
uv remove --dev black flake8 isort
uv add --dev ruff

# 5. Replace pre-commit with lefthook
uv run pre-commit uninstall      # while it can still read its own config
uv remove --dev pre-commit
rm .pre-commit-config.yaml
uv add --dev lefthook
uv run lefthook install

# 6. Apply pyproject.toml config sections from template above
# 7. Create Justfile and lefthook.yml from templates above
# 8. Run: just check

lefthook install 不会覆盖现有钩子,而是将其重命名为 .git/hooks/pre-commit.old 并告知你。先运行 pre-commit uninstall,可以免去删除遗留文件的步骤。

参考文档

references/ 中提供了各工具的详细指南:

  • uv-reference.md - 项目初始化、依赖项、锁定/同步、Python 版本、构建/发布
  • ty-reference.md - 配置、规则、CLI 标志、已知限制
  • ruff-reference.md - 规则集、格式化选项、按文件忽略规则、CI 集成
  • pytest-reference.md - 插件、夹具、异步测试、conftest 模式
  • justfile-reference.md - 语法、变量、参数、shebang 配方、设置

资源

评论

支持 Markdown