tenequm/claude-plugins/python-dev

Claude Code and agent skill by tenequm/claude-plugins

Trust tier 3
  • community
  • ext:[{"type":"hook","description":"Pre/post lifecycle hooks"}]
Source check pending

Checking that the SKILL.md file is still in the repository.

Opinionated Python development setup with uv, ty, ruff, pytest, lefthook, and just. Use when creating a new Python project, writing or fixing pyproject.toml, or configuring linting, formatting, type checking, testing, git hooks, or CI.

Install

npx vskill@latest install tenequm/claude-plugins/python-dev

Semi-extensibleMentions customization, but needs skill-specific knowledge. Read the extensible skills standard

SKILL.md

Python Development Setup

Opinionated, production-ready Python development stack. No choices to make - just use this.

When to Use

  • Starting a new Python project
  • Modernizing an existing project (migrating from pip/poetry/mypy/black/flake8)
  • Setting up linting, formatting, type checking, or testing
  • Creating a Justfile for project commands
  • Configuring pyproject.toml as the single source of truth

The Stack

ToolRoleReplaces
uv 0.12+Package manager, Python versions, runnerpip, poetry, pyenv, virtualenv
ty (beta)Type checker (Astral, Rust)mypy, pyright
ruffLinter + formatterflake8, black, isort, pyupgrade
pytestTestingunittest
justCommand runnermake
lefthook 2.1+Git hooks (single binary, parallel)pre-commit
Note on ty: ty is in beta (0.0.x) - no stable API, and inference can change between any
two versions, so pin it. Pydantic is no longer a fair complaint: ty has shipped a dedicated
library-support track for it since 0.0.57 (constructors, model_config, BaseSettings,
RootModel, strict vs lax). Django and SQLAlchemy still have no such support and remain the
likely source of false positives. Before swapping the whole checker, reach for
[tool.ty.analysis] replace-imports-with-any = ["sqlalchemy.**"], which silences one bad
dependency instead of all of them. If you do need rock-solid checking today, swap ty for
pyright and keep the rest of the stack unchanged.

Quick Start: New Project

bash
# 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 Template

This is the single config file. Copy this and adjust [project] fields.

toml
[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 is intentionally unset above; pytest-asyncio warns about that on every run. Set it to "function" to silence the warning and lock the behavior in.

Justfile Template

just
# 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 Config

Lefthook replaces pre-commit here for the same reason go-dev uses it: one binary, hooks in parallel, and - the reason that matters most in Python - the hook runs your ruff from uv.lock instead of a second copy pinned separately in a hook config. It installs from PyPI as a platform wheel, so uv add --dev lefthook is the whole install; no Go toolchain.

bash
uv run lefthook install # writes .git/hooks/pre-commit and pre-push
yaml
# 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` is mandatory, not decoration. Ruff ignores its own exclude config for paths passed explicitly on the command line, and {staged_files} passes paths explicitly. Without the flag a staged file under [tool.ruff] exclude gets linted anyway - verified: with the flag ruff check --force-exclude src/generated/gen.py reports All checks passed, without it the same call finds errors. pre-commit users never met this because ruff-pre-commit bakes the flag into its hook entry; on lefthook it is yours to remember.

`stage_fixed: true` is the ergonomic win over pre-commit. pre-commit fails the commit when a hook rewrites a file and makes you re-stage and re-run. lefthook re-runs git add on the fixed files and the commit proceeds. Since 2.1.12 a failing git add fails the hook, so a fix can never slip through unstaged.

Ordering is why `piped: true` is set. Ruff's own guidance is lint-with-fix before format, because --fix emits code that then needs reformatting. Piped also means the guards run first and a leaked key stops the commit before any tool burns time.

Notes worth knowing before editing this config:

  • A `glob` silently skips the whole job when nothing matches - and that is a gate hole, not

a convenience. A job with a glob but no {staged_files} in its run is still filtered by that glob, so ty with glob: "*.{py,pyi}" is skipped on a commit that changes only pyproject.toml - exactly where [tool.ty] and your dependency pins live. Verified: such a commit prints ty (skip) no matching staged files and records with no type check at all. Any job that checks the project rather than the staged files must carry no glob. The ruff jobs above keep theirs because they act on {staged_files} and nothing else.

  • ` matches one or more directories, not zero or more.** glob: "src/**/.py"` does not*

match src/main.py. Use glob_matcher: doublestar for the behavior every other tool has.

  • Config location is load-bearing. lefthook auto-discovers only the repo root or .config/

(the latter since v1.11.12). Anywhere else and commits silently stop running hooks, because git invokes the hook directly and no task-runner recipe can intercept that.

  • Never put a mutating job in `pre-push`. A job that rewrites files there fails the push and

leaves you with uncommitted edits. --fix belongs in pre-commit, where stage_fixed handles it; pre-push stays read-only, like the pytest job above.

  • `file_types` is available when a glob is too blunt - text, binary, executable,

symlink, and MIME types including text/x-python.

  • Deleted files drop out of `{staged_files}` and the job is skipped with

no files for inspection, so a deletion-only commit does not error.

  • Unstaged changes are hidden for the hook's duration and restored after, so the gate judges

what you are actually committing, not your dirty worktree. Verified on 2.1.12: staging a clean file while leaving a broken copy unstaged passes, commits the clean version, and gives the unstaged edit back.

  • lefthook is dormant until installed. assert_lefthook_installed: true turns a missing

binary into a failure instead of hooks silently not firing. Make lefthook install part of onboarding.

  • lefthook validate catches a malformed config in CI; lefthook dump prints the merged

effective config. A gitignored lefthook-local.yml lets one developer add or skip jobs without imposing it on teammates.

What you give up by leaving pre-commit. The pre-commit-hooks library has no lefthook equivalent. Three of its hooks were worth keeping and are hand-rolled in the guards group above: detect-private-key, check-merge-conflict and check-added-large-files. Four are not replicated - check-yaml, check-toml, end-of-file-fixer, trailing-whitespace and mixed-line-ending: ruff's formatter already handles whitespace and final newlines for Python files, and the rest only ever covered non-Python files. Add them back as shell jobs if your repo carries a lot of hand-edited YAML. You also lose pre-commit autoupdate and hosted pre-commit.ci - in exchange, uv lock --upgrade is now the one place tool versions move.

Project Structure

Always use src layout:

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

Daily Workflow

This is the start of the file. Read the full SKILL.md on GitHub.

Security

  • Security scanPassed, 100 of 100Pattern checks on the skill's files.
  • Model reviewPassed

Quality evaluation

  • Task evaluationNot runNo evaluation yet. Run one in Skill Studio.

Source

Popularity

Repository stars
18
7-day trend score
15

Stars and forks are for the whole GitHub repository, not this skill alone. Refreshed 17 Mar 2026, 7 months ago.

Extension points

  • hookPre/post lifecycle hooks

Works with

Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.

  • Claude Code
  • Cursor
  • GitHub Copilot
  • Windsurf
  • Codex
  • Gemini CLI
  • Cline
  • Amp
  • Roo Code
  • Goose
  • Continue
  • Kilo Code
  • Junie
  • Trae
  • OpenHands
  • Qwen Code
  • AI Agent
  • Tabnine Chat
  • Sourcegraph Cody
  • Amazon Q
  • Firebase Genkit
  • Sweep
  • Devin
  • Phind
  • Mentat
  • Claude Desktop
  • Aider
  • GPT Pilot
  • Plandex
  • bolt.new
  • v0
  • Replit Agent
  • Anse
  • TypingMind
  • BetterChatGPT
  • LibreChat
  • Warp AI
  • Pieces
  • Fig AI
Embed badge
vskill VERIFIED badge
[![vskill VERIFIED](https://verified-skill.com/api/v1/skills/tenequm/claude-plugins/python-dev/badge)](https://verified-skill.com/skills/tenequm/claude-plugins/python-dev)

Something wrong with this skill or its page? Tell us.