| name | python-dev-practices |
| description | Python development best practices for modern Python projects. Activated when working with Python files, pyproject.toml, or discussing Python patterns, testing, linting, type hints. |
Python development practices
Purpose
Comprehensive guide for Python development covering modern Python practices, type safety, testing strategies, and coding conventions.
When to use
This skill activates when:
- Creating or modifying Python files
- Working with pyproject.toml configuration
- Writing property-based or fuzz tests
- Handling type hints and type safety
- Setting up development environment
- Configuring linting, formatting, or type checking
Core principles
Minimal dependencies philosophy
CRITICAL: Standard library first, dependencies last.
- ALWAYS prefer standard library solutions
- Evaluate stdlib before considering third-party code
- Implementation over dependencies
Type safety requirements
MANDATORY: Comprehensive type hints for ALL code
from typing import Protocol
from typing_extensions import TypeIs, ReadOnly, TypedDict
class Config(TypedDict):
"""Configuration with immutable fields."""
name: ReadOnly[str]
options: ReadOnly[list[str]]
def is_valid_config(value: object) -> TypeIs[Config]:
"""Type narrowing with TypeIs."""
return isinstance(value, dict) and 'name' in value
Modern Python features
Leverage Python 3.10+ capabilities:
# Pattern matching (3.10+)
match argument:
case {"type": "option", "name": str(name)}:
return process_option(name)
case {"type": "value", "data": list(data)}:
return process_value(data)
case _:
raise ValueError(f"Unknown argument: {argument}")
# Dataclasses with slots and frozen
from dataclasses import dataclass
@dataclass(slots=True, frozen=True)
class Result:
"""Immutable result with memory efficiency."""
success: bool
data: dict[str, Any]
errors: list[str]
Import organization
Follow project conventions with isort/Ruff:
# Standard library imports first
import sys
from collections.abc import Sequence
from dataclasses import dataclass, field
from pathlib import Path
from typing import Any, Protocol
# Third-party (minimal)
from typing_extensions import Doc, ReadOnly, TypeIs
# Local imports
from yourpackage._module import Module
from yourpackage.validation import Validator
Class organization standards
MANDATORY class member ordering:
class Processor:
# 1. Class State: Constants and class attributes
DEFAULT_CONFIG = "default"
RESERVED_NAMES = frozenset(["__init__", "__new__"])
# 2. Lifecycle: __init__ and __new__
def __init__(self, config: Config) -> None:
self._config = config
self._state = ProcessorState()
# 3. Magic Methods: __repr__, __str__, __len__
def __repr__(self) -> str:
return f"Processor(config={self._config!r})"
# 4. Public Interface: Properties, then public methods
@property
def name(self) -> str:
return self._config.get("name", self.DEFAULT_CONFIG)
def process(self, data: Sequence[str]) -> Result:
"""Process data."""
return self._process_impl(list(data))
# 5. Internals: Private/protected methods at bottom
def _process_impl(self, data: list[str]) -> Result:
pass
Testing strategy
Domain-centric test organization
tests/
├── unit/
│ ├── processing/
│ │ ├── test_data_processing.py
│ │ └── test_transformations.py
│ └── validation/
│ └── test_input_validation.py
├── properties/
│ └── test_invariants.py
└── fuzz/
└── test_fuzzing.py
Error handling patterns
class AppError(Exception):
"""Base exception for application."""
pass
class ValidationError(AppError):
"""Raised when validation fails."""
def __init__(self, message: str, field: str | None = None) -> None:
super().__init__(message)
self.field = field
def process_data(data: Sequence[str]) -> Result:
"""Process with clear error messages."""
if not data:
raise ValidationError("No data provided")
try:
return _process_impl(data)
except ValueError as e:
raise ValidationError(f"Invalid data format: {e}") from e
Package management with uv
Essential commands
# CRITICAL: ALWAYS use 'uv run python' for script execution
uv run python scripts/analyze.py
uv run python -m yourpackage
# Install dependencies
uv sync
# Run tools
uv run pytest
uv run ruff check .
uv run basedpyright
Code quality tools
Configuration (pyproject.toml)
[project]
requires-python = ">=3.14"
dependencies = ["typing-extensions>=4.8"]
[tool.basedpyright]
typeCheckingMode = "strict"
pythonVersion = "3.14"
[tool.ruff]
line-length = 88
target-version = "py314"
[tool.ruff.lint]
select = ["ALL"]
ignore = ["D", "ANN101", "ANN102"]
[tool.pytest.ini_options]
testpaths = ["tests"]
addopts = "--strict-markers --cov=src --cov-report=term-missing"
Security practices
import shlex
from pathlib import Path
def validate_input(user_input: str) -> str:
"""Validate and sanitize user input."""
# Prevent command injection
if any(char in user_input for char in [';', '|', '&', '`', '$']):
raise ValueError("Invalid characters in input")
return shlex.quote(user_input)
def safe_path_resolution(user_path: str, base: Path) -> Path:
"""Safely resolve paths to prevent traversal."""
path = (base / user_path).resolve()
if not path.is_relative_to(base):
raise ValueError("Path traversal detected")
return path
Quick reference
Testing checklist
- Unit tests with >95% coverage
- Property-based tests for invariants
- Fuzz tests for robustness
- Benchmarks for critical paths
- Type checking passes (basedpyright)
- Linting passes (ruff)
- Domain-centric test organization
Remember: Prioritize type safety, comprehensive testing, and standard library usage in all development.