| name | python-development |
| description | Python development — use when the user works with .py files, pyproject.toml, uv, ruff, mypy, pytest, async/await, MicroPython, CLI tools, or PyPI publishing. Covers modern tooling, best practices, library architecture, functional patterns, and production workflows. NOT for TypeScript or JavaScript development (use typescript-development), NOT for React component patterns (use react-development). |
Python Development - Comprehensive Skill
Expert guidance for modern Python development (Python 3.11-3.14+), covering everything from simple scripts to production-grade systems, library architecture, and embedded development.
When to Use This Skill
Activate this skill when:
- Working with
.py files, pyproject.toml, or uv.lock
- Writing, testing, or reviewing Python code
- Managing Python dependencies or virtual environments
- Using commands like
uv, pytest, ruff, or mypy
- Building CLI applications with Typer/Click
- Designing Python libraries or packages
- Working with async/await patterns
- Developing for embedded systems (MicroPython, RP2350)
- Setting up CI/CD for Python projects
- Publishing packages to PyPI
When NOT to Use This Skill
- TypeScript or JavaScript development → use
typescript-development
- React component patterns or hooks → use
react-development
- TDD methodology (Red-Green-Refactor cycle) → use
test-driven-development (this skill covers pytest patterns but not the full TDD workflow)
- Django/Flask framework-specific patterns → this skill covers Python fundamentals; dedicated framework plugins provide deeper coverage
Decision Trees
Tier Selection
What are you building?
│
├─ Single-file script, one-off utility, quick data processing
│ └─ MINIMAL tier: PEP 723 inline metadata, no project scaffolding
│
├─ Multi-file project, team development, anything with tests
│ └─ STANDARD tier (default): src/ layout + uv + ruff + mypy + pytest
│
└─ PyPI package, production system requiring CI/CD
└─ FULL tier: Complete tooling + bandit + CI/CD + release workflow
Default to Standard — it covers most use cases.
Async vs Sync Decision
Does the task involve I/O-bound operations (HTTP, DB, file, network)?
│
├─ No → Use synchronous code (simpler, easier to debug)
│
└─ Yes → Are operations concurrent (many requests at once)?
│
├─ No → Simple async/await with httpx.AsyncClient
│
└─ Yes → asyncio.Semaphore per resource + httpx.AsyncClient
with connection pooling + retry logic
Protocol vs ABC Decision
Do you need structural subtyping (duck typing with type safety)?
│
├─ Yes → Protocol
│ └─ Enables dependency injection, avoids inheritance coupling
│
└─ No → Do you need runtime type checking or enforced method implementation?
│
├─ Yes → ABC with @abstractmethod
│
└─ No → Protocol (lighter weight, preferred default)
Library vs Application Architecture Decision
Will this code be imported by other projects?
│
├─ Yes → Library architecture:
│ - Protocol-based API surface
│ - py.typed marker
│ - Minimal public exports (__all__)
│ - Stable deprecation policy
│ - pyproject.toml with [project.optional-dependencies]
│
└─ No → Application architecture:
- Direct implementation (no Protocol overhead)
- Simpler project structure
- pyproject.toml with [project.scripts]
Quick Start
Standard Project Setup
curl -LsSf https://astral.sh/uv/install.sh | sh
uv init my-project
cd my-project
uv add requests pydantic httpx
uv add --dev pytest pytest-cov ruff mypy
uv run ruff check .
uv run ruff format .
uv run mypy .
uv run pytest --cov
Core Principles
- Use uv for everything: 10-100x faster than pip, all-in-one tool
- Type hints everywhere: Use type annotations for all functions and classes
- Ruff for quality: Single tool for linting and formatting (replaces Black, flake8, isort)
- Test with pytest: Comprehensive tests with fixtures and parametrization
- Lock dependencies: Always maintain
uv.lock for reproducible builds
- PEP 723 for scripts: Use inline script metadata for single-file scripts
Modern Python Toolchain
Package Management: uv
Use current tool documentation when exact flags matter; uv, ruff, mypy, and pytest move quickly. The defaults below are stable project-starting patterns, not a substitute for checking release notes during migrations.
uv init my-project && cd my-project
uv add requests pandas numpy
uv add --dev pytest ruff mypy
uv sync
uv lock --upgrade
uv run python script.py
uv run pytest
Code Quality: ruff
ruff check .
ruff check --fix .
ruff format .
Type Checking: mypy
mypy .
mypy --strict .
Project Structure
myproject/
├── src/
│ └── myproject/
│ ├── __init__.py
│ ├── main.py
│ └── utils.py
├── tests/
│ ├── __init__.py
│ ├── conftest.py
│ ├── test_main.py
│ └── test_utils.py
├── docs/
├── .gitignore
├── .python-version
├── pyproject.toml
├── uv.lock
└── README.md
pyproject.toml Configuration
[project]
name = "myproject"
version = "0.1.0"
requires-python = ">=3.11"
dependencies = ["requests>=2.31.0"]
[project.optional-dependencies]
dev = ["pytest>=8.0.0", "ruff>=0.8.0", "mypy>=1.13.0"]
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[tool.ruff]
line-length = 100
target-version = "py311"
[tool.ruff.lint]
select = ["E", "F", "I", "N", "W", "B", "Q"]
[tool.mypy]
python_version = "3.11"
strict = true
[tool.pytest.ini_options]
testpaths = ["tests"]
Anti-Patterns to Avoid
| Anti-Pattern | Problem | Solution |
|---|
Mutable default args def f(lst=[]) | Default value is shared across all calls; mutations persist unexpectedly | def f(lst: list | None = None) then lst = lst or [] inside |
requests.get in async | Blocks the event loop; all concurrent tasks stall | httpx.AsyncClient for async HTTP; requests only in sync code |
| Classes for data bags | Boilerplate __init__, mutable by default, no equality | @dataclass(frozen=True) for immutable value objects, or NamedTuple for lightweight records |
| Deep inheritance hierarchies | Tight coupling, fragile base class, hard to test | Protocol + composition; define behavior by what it does, not what it is |
| Mutating function args | Side effects make code unpredictable and hard to test | Return new values; use frozen dataclasses; prefer pure functions |
try/except Exception | Swallows all errors including KeyboardInterrupt, SystemExit | Catch specific types: except (ValueError, KeyError) |
| Blocking in async | time.sleep(), requests.get(), subprocess.run() freeze the event loop | await asyncio.to_thread(fn) or asyncio.sleep() for non-blocking alternatives |
from module import * | Pollutes namespace, makes dependencies invisible, breaks linters | Explicit imports: from module import ClassA, func_b |
Bare except: | Catches everything including SystemExit/KeyboardInterrupt; hides bugs | except (SpecificError, AnotherError): |
Using List[str] etc. | Legacy typing; deprecated since Python 3.9 | Use modern syntax: list[str], dict[str, int], tuple[int, ...] |
| Ignoring mypy errors | Type errors at compile time become runtime crashes | Fix the type; use # type: ignore[specific-code] with comment explaining why |
| Testing with production URLs | Tests hit real APIs, flaky, slow, depend on network | Mock HTTP calls with pytest-httpx or respx; test against fixtures |
Quality Gates
Every Python task must pass:
- Format-first:
uv run ruff format .
- Linting:
uv run ruff check .
- Type checking:
uv run mypy .
- Tests:
uv run pytest (>80% coverage)
- Modern patterns: No legacy typing (use
list[str] not List[str])
For critical code (payments, auth, security):
- Coverage >95%
- Security scan:
uv run bandit -r src/
See Extended Patterns for detailed code examples, testing patterns, async patterns, itertools/functools usage, library architecture, workflow routing, and complete reference file listings.
Official Documentation