| name | python-coding |
| description | Best practices for writing high quality production grade Python code |
Python Coding
This skill is applicable for writing production intent python code
Core Principles
Applicable to production code. Less applicable to scripts, but use as much as it makes sense balancing cleanliness/simplicity with complexity/additional code
- Prioritize KISS, SOLID and DRY principles
- Utilize
uvfor all project management, venv, package installation, builds, execution etc. - Always use the latest Python practices (current version is 3.14) and packages.
- Write module and function docstrings aiming for 100% coverage (verified by
interrogateautomatically in pre-commit) - Use type annotations maximally and run type checking always (with pyrefly)
- Use logging (with structlog)
- Fail Fast: Validate inputs and data (with pydantic) early at function boundaries
- Write tests and aim for at least 70% coverage (with pytest and coverage)
- Rely on
rufffor formatting, import sorting and linting. - Resource Management Use context managers (
withstatements) for files, connections, locks
- Resource Management Use context managers (
Details on each below:
In-Line Documentation
- Follow Numpy docstring format for function docstrings. Except, do not add parameter types (as we already use type annotation and it would violate DRY causing confusion/errors)
- Module docstring to follow the format:
"""
Title:
Author: < AI Name >
Description:
Usage: <include basic examples>
Notes:
"""
- Be explicit and complete without being overly verbose.
Typing
- Maximize type annotations for all functions/methods
- Use
pyreflyfor type checking
Logging
- Use structured logging with
structlog - Centralize logging configuration with the dictConfig pattern
- Output to both file and terminal
- Follow the basic format: "%(asctime)s - %(levelname)s - %(message)s"
- Color the level name in outputs
- Critical: Deep Red
- Error: Light Red
- Warning: Yellow
- Info: Blue
- Debug: Grey
- Rotate log files by time at the appropriate cadence
- Logger naming: create a hierarchy of loggers using dots in the names
- Logging Security
- Never log sensitive data (passwords, tokens, PII)
- Sanitize or redact sensitive fields before logging
- Be careful with debug logs in production
Data Validation and Input Sanitization
Treat all external input as untrusted (API requests, file uploads, user input)
Use Pydantic models to validate and coerce types at system boundaries
Sanitize inputs before using in:
- SQL queries (use parameterized queries, ORMs)
- Shell commands (avoid
shell=True, use lists) - File paths (validate, use
pathlib.Path.resolve()) - HTML/JavaScript (escape properly)
Aim for early Validation
Validate at system boundaries (API endpoints, file inputs, user inputs). Don't let invalid data propagate through the system
Make validators reusable and composable
Fail fast with clear error messages
Use assertions for internal consistency checks (developer errors, not user errors)
Donts: Validate after processing/storage, Mixing validation with transformation. Over-validating trusted internal data
Error Handling
Use Standard Exceptions First
- Prefer built-in exceptions:
ValueError,TypeError,KeyError,FileNotFoundError - Let third-party exceptions bubble up:
httpx.HTTPError,pydantic.ValidationError
Custom Exceptions: Only When Needed
Create custom exceptions only when:
- You need different handling logic at call sites (different catch blocks → different actions)
- The error represents a specific domain concept (e.g.,
DuplicateArticleError,RateLimitError) - You're building a public API or library where users shouldn't catch internal exceptions
- Keep it simple - avoid deep hierarchies
When to Catch vs Propagate
- Catch when you can handle the error meaningfully (retry, fallback, user-friendly message)
- Propagate when the calling code is better positioned to handle it
- Re-raise with context using
raise NewException(...) from original_exceptionto preserve stack trace
Error Messages
- Include specific field names and invalid values
- Provide actionable guidance when possible
- Example:
ValueError(f"Invalid status '{status}'. Must be one of: {VALID_STATUSES}")
Error Context with Structured Logging
- Use structured logging to capture rich context instead of encoding it in exception types
- Include relevant fields:
url,status_code,article_id,feed_name, etc. - Set
exc_info=Trueto capture full tracebacks
Testing
- Write tests as soon as possible, before code ideally (if following TDD) or after code is written.
- Name tests with the naming convention
test_*.py - Prefer when appropriate, Pytest with parametrized (
@pytest.mark.parametrize) or randomized inputs( with hypothesis) complimentarily:- When to Use Each
Use Parametrize For:
- Known edge cases you must handle
- Regression tests (specific bugs)
- Documentation of expected behavior
- Business rule examples
- Quick debugging (run specific case) Use Hypothesis For:
- Finding unknown edge cases
- Testing invariants (properties that always hold)
- Stress testing with many inputs
- Discovering missing validation
- Practical Workflow
- Start with parametrize for obvious cases
- Add hypothesis to find what you missed
- When hypothesis finds a bug, add it as a parametrized test for regression
- Keep both - parametrize documents intent, hypothesis keeps hunting
- When to Use Each
Use Parametrize For:
- Aim for Minimum 70% coverage (soft guideline), measure with
pytest-cov - Test file naming:
test_<module>.py - Test both valid and invalid cases
- Test boundary conditions explicitly
Security Best Practices
Secret Management
- Never hardcode secrets (API keys, passwords, tokens) in source code
- Use environment variables for configuration:
os.getenv("API_KEY") - Use
.envfiles for local development (add to.gitignore) - For production, use secret management services (Google Secret Manager, AWS Secrets Manager etc.)
- Validate that required secrets are present at startup
SQL Injection Prevention
Always use parameterized queries or ORMs
Never concatenate user input into SQL strings
Example with parameterization:
# UNSAFE cursor.execute(f"SELECT * FROM users WHERE id = {user_id}") # SAFE cursor.execute("SELECT * FROM users WHERE id = ?", (user_id,))
Command Injection Prevention
- Avoid
subprocess.run(shell=True) - Pass commands as lists:
subprocess.run(["ls", "-l", directory]) - If shell=True is necessary, sanitize inputs rigorously
Development Tools
Prefer the following tools
| Tool | Purpose |
|---|---|
| uv | Project management, Python management, Package installation, Virtual Environments, Executing modules/scripts |
| pytest | Testing |
| pyrefly | Type Checking |
| structlog | Logging |
| coverage | Testing Coverage |
| pydantic | Data Validation |
| ruff | Linting, Formatting, Imports Sorting |
| prek | Pre-commit check |
Project management should be done declaratively through uv commands (which in turn will write into pyproject.toml and the uv lock file).
Command Reference
Available here .claude/skills/python-coding/references/commands.md
Package Preferences
Prefer:
httpxoverrequests