| name | pyhd |
| description | Modern Python development workflow, project architecture, and code quality guidelines using uv, Ruff, and pytest. Enforces strict virtual environment isolation with uv, modern Python 3.10+ typing, AST-safe linting and formatting with Ruff, and automated pytest verification loops. Activate when creating, refactoring, testing, or building Python code, managing dependencies with uv, or configuring pyproject.toml.
|
| license | Apache-2.0 |
| metadata | {"category":"coding","tags":"python, uv, ruff, formatting, linting, testing","author":"Daniela Petruzalek (daniela@danicat.dev)","version":"0.2.0","catalog":"https://skills.danicat.dev"} |
Modern Python Development (Pyhd)
This guide establishes the standard development workflow, project structure, and code quality gates for Python projects using uv for fast environment and dependency management, ruff for linting and formatting, and pytest for test verification.
1. Project Scaffolding & Architecture
Prefer the standard src/ layout for Python packages to avoid accidental imports of uninstalled local code and ensure test parity with installed packages.
Standard Project Layout
my-project/
├── .venv/ # Managed isolated virtual environment (gitignored)
├── pyproject.toml # Unified project metadata, dependencies, and tool config
├── README.md # Project documentation
├── src/
│ └── my_package/
│ ├── __init__.py # Package export root
│ ├── core.py # Domain logic
│ └── py.typed # PEP 561 marker for type checkers
└── tests/
├── conftest.py # Shared test fixtures and pytest hooks
├── unit/ # Fast, isolated unit tests
└── integration/ # Multi-module integration tests
Dependency Management with uv
Manage dependencies deterministically using uv commands:
uv init --app my-project
uv init --lib my-library
uv add requests pydantic
uv add --dev ruff pytest pytest-cov
uv sync
uv lock
Self-Contained Scripts (PEP 723)
For standalone automation or utility scripts, declare dependencies directly inline using PEP 723 script metadata:
import httpx
from rich import print
response = httpx.get("https://httpbin.org/get")
print(response.json())
Execute single-file scripts with automated dependency isolation:
uv run script.py
2. Plan-Validate-Execute (Refactoring)
When performing complex or multi-file Python refactoring:
- Plan: Scan the workspace with
grep_search to identify all call-sites, import statements, and references to target symbols.
- Validate: Verify compatibility of planned changes against dependent modules and type signatures.
- Execute: Modify files incrementally, running the Code Verification Loop after each change.
3. Code Verification Loop
After modifying any Python file, execute this verification loop before completing tasks:
graph TD
A[Start: Modify Code] --> B[Lint: uv run ruff check --fix]
B --> C{Lint Clean?}
C -->|No: Manual fixes needed| D[Fix Violations]
D --> B
C -->|Yes| E[Format: uv run ruff format]
E --> F[Test: uv run pytest]
F --> G{Tests Pass?}
G -->|No| D
G -->|Yes| H[Loop Complete]
Step-by-Step Verification Commands
- Lint & Auto-Fix: Resolve syntax, style, and import sorting issues:
uv run ruff check --fix
- Format Code: Ensure consistent code style:
uv run ruff format
- Run Unit & Integration Tests: Verify zero regressions:
uv run pytest
uv run pytest tests/unit/test_core.py -k "test_process_data"
4. Virtual Environment Isolation
[!IMPORTANT]
Virtual Environment Isolation: Always prefix Python commands, test runners, and formatting tools with uv run (e.g. uv run python script.py, uv run ruff check, uv run pytest). This guarantees execution within the local .venv/ interpreter, preventing system package contamination and missing import errors.
5. Gotchas & Edge Cases
- Circular Imports in Type Annotations: When module
A imports type B purely for type annotations, prevent runtime import cycles by using typing.TYPE_CHECKING:
from __future__ import annotations
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from my_package.service import DatabaseService
- Mutable Default Arguments: Never use mutable objects (
list, dict, set) as default parameter values. Use None as a sentinel:
def append_item(item: str, target: list[str] = []) -> list[str]:
target.append(item)
return target
def append_item(item: str, target: list[str] | None = None) -> list[str]:
if target is None:
target = []
target.append(item)
return target
- Explicit Exception Chaining: When catching and re-raising errors as domain-specific exceptions, preserve root-cause tracebacks using
from err:
try:
data = parse_payload(raw)
except json.JSONDecodeError err:
ValidationError() err
6. 📚 Progressive Disclosure & References
- Python Best Practices Guide:
references/best_practices.md — Idiomatic Python patterns, type annotations, Ruff configuration, and pytest fixture guidelines.