Python development best practices - type safety, async patterns, testing, and Pythonic code for production-ready applications
Resources
3Install
npx skillscat add wizact/dotclaude/python-developer Install via the SkillsCat registry.
Python Developer
Production-ready Python development guidelines covering type safety, async patterns, testing, and Pythonic best practices. Contains 28 rules across 9 categories, prioritized by impact.
When to Apply
Reference these guidelines when:
- Writing new Python packages or services
- Implementing async/await patterns
- Setting up Python project structure
- Writing tests for Python code
- Reviewing code for best practices
- Refactoring existing Python code
Rule Categories by Priority
| Priority | Category | Impact | Prefix | Count |
|---|---|---|---|---|
| 1 | Type Safety | CRITICAL | type- |
2 |
| 2 | Async Patterns | CRITICAL | async- |
3 |
| 3 | Error Handling | HIGH | error- |
2 |
| 4 | Testing | HIGH | test- |
3 |
| 5 | Code Style | MEDIUM | style- |
5 |
| 6 | Patterns | MEDIUM | pattern- |
6 |
| 7 | Performance | LOW | perf- |
2 |
| 8 | Documentation | LOW | doc- |
1 |
| 9 | Setup & Tooling | LOW | setup- |
4 |
Quick Reference
1. Type Safety (CRITICAL)
type-hints-always- Always use type hints on functions, enforce with mypy strict modetype-immutability- Use frozen dataclasses and Pydantic models to prevent mutation bugs
2. Async Patterns (CRITICAL)
async-io-patterns- Use aiofiles and AsyncIterator for non-blocking I/Oasync-cpu-threadpool- Run CPU-bound operations in thread pool with asyncio.to_threadasync-concurrency- Use asyncio.gather for concurrent I/O operations
3. Error Handling (HIGH)
error-exception-chaining- Chain exceptions withfrom errto preserve contexterror-guard-clauses- Use early returns for error cases, keep happy path un-nested
4. Testing (HIGH)
test-organization- Organize tests into unit/, integration/, fixtures/ directoriestest-async-tests- Use pytest.mark.asyncio for async function teststest-mocking-strategy- Mock only slow/expensive operations, use real components
5. Code Style (MEDIUM)
style-naming- Follow PEP 8: snake_case functions, PascalCase classes, UPPER_SNAKE_CASE constantsstyle-import-organization- Organize imports: stdlib, third-party, localstyle-eafp-over-lbyl- "Easier to Ask Forgiveness" - use try/except over pre-checksstyle-dataclasses- Use dataclasses instead of manual init boilerplatestyle-explicit-better- Explicit types and behavior over implicit
6. Patterns (MEDIUM)
pattern-dependency-injection- Pass dependencies through constructorspattern-streaming-batching- Process large datasets with AsyncIterator batchingpattern-context-managers- Use context managers for automatic resource cleanuppattern-list-comprehensions- Use for simple transforms, explicit loops for complex logicpattern-builtin-functions- Prefer sum(), filter() over manual loopspattern-no-mutable-defaults- Use None instead of [] or {} as default arguments
7. Performance (LOW)
perf-generator-expressions- Use generator expressions for memory-efficient iterationperf-slots- Use slots for classes with many instances (>10k)
8. Documentation (LOW)
doc-google-style- Use Google-style docstrings for public APIs
9. Setup & Tooling (LOW)
setup-uv-package-manager- Use uv for faster dependency management (10-100x faster than pip)setup-pyproject-toml- Use pyproject.toml as single source of truth (PEP 621)setup-virtual-env- Always use virtual environments, never install globallysetup-tool-config- Configure ruff/mypy/pytest in pyproject.toml
Scalable Architecture
Structure adapts to project complexity while principles remain constant:
Small (< 1K lines): Single module organization
app.py
models.py # Domain models
repository.py # Data access
service.py # Business logicMedium (1K-10K lines): Package-based structure
src/
package_name/
__init__.py
models.py # Domain types
repository.py # Ports
service.py # Business logic
postgres.py # Adapters
api.py # HTTP handlers
tests/
unit/
integration/Large (10K+ lines): Full layer separation
src/
package_name/
domain/
models.py
application/
service.py
adapters/
postgres_repo.py
http_controller.py
ports/
repository.pyPragmatic Guidelines
When to Use Type Hints?
Always. Type hints are CRITICAL for:
- Static analysis (mypy)
- IDE support
- Documentation
- Preventing runtime errors
Use mypy strict mode for production code.
When to Use Async?
Use async/await when ANY apply:
- I/O-bound operations (API calls, database, files)
- Need concurrent operations
- Building web services/APIs
- Processing streams of data
Don't use async for:
- CPU-bound operations (use asyncio.to_thread instead)
- Simple scripts with no concurrency
- Synchronous libraries (wrap with asyncio.to_thread)
When to Create Abstractions?
Create interface/abstraction if ANY apply:
- Multiple implementations exist or likely will
- Need to test without external dependency
- Crossing architectural boundaries
Don't create for:
- Single implementation unlikely to change
- Internal helpers
- Simple utilities
Start simple, refactor when complexity demands it.
How to Use
Reference individual rule files for detailed explanations and code examples:
Type Safety (CRITICAL):
Async Patterns (CRITICAL):
Error Handling (HIGH):
Testing (HIGH):
Code Style (MEDIUM):
Patterns (MEDIUM):
- Dependency Injection
- Streaming with Batching
- Context Managers
- List Comprehensions
- Built-in Functions
- No Mutable Defaults
Performance (LOW):
Documentation (LOW):
Setup & Tooling (LOW):
Each rule file contains:
- Why it matters explanation
- Incorrect code example with explanation
- Correct code example with explanation
- Benefits and additional context
Full Compiled Document
For complete guide with all rules expanded: REFERENCE.md
Anti-Patterns to Avoid
Blocking the Event Loop:
# ❌ BAD: Blocks event loop
async def process():
with open('file.txt') as f: # Blocking I/O
data = f.read()
result = expensive_cpu_work(data) # Blocks other tasks
return result
# ✅ GOOD: Non-blocking
async def process():
async with aiofiles.open('file.txt') as f: # Async I/O
data = await f.read()
result = await asyncio.to_thread(expensive_cpu_work, data) # In thread
return resultMissing Type Hints:
# ❌ BAD: No type information
def process(data):
return [x for x in data if x]
# ✅ GOOD: Clear types
def process(data: list[str]) -> list[str]:
return [x for x in data if x]Mutable Default Arguments:
# ❌ BAD: Shared mutable default
def add_item(items: list[str] = []):
items.append("new")
return items
# ✅ GOOD: Safe default
def add_item(items: list[str] | None = None):
if items is None:
items = []
items.append("new")
return itemsQuick Checklist
Before submitting Python code:
Type Safety:
- All functions have type hints
- Mypy strict mode passes
- Domain models are frozen dataclasses/Pydantic
Async Patterns:
- I/O uses async libraries (aiofiles, httpx, etc.)
- CPU-bound work in thread pool (asyncio.to_thread)
- Concurrent operations use asyncio.gather
Error Handling:
- Exceptions chained with
from err - Guard clauses used (early returns)
- EAFP over LBYL
Testing:
- Tests organized (unit/, integration/)
- Async tests use pytest.mark.asyncio
- Minimal mocking (only external dependencies)
Code Quality:
- Imports organized (stdlib, third-party, local)
- Dataclasses used instead of manual init
- No mutable default arguments
- Built-in functions used (sum, filter, etc.)
- Context managers for resources
Setup:
- Virtual environment active
- Dependencies in pyproject.toml
- Using uv for package management
- Tools configured (ruff, mypy, pytest)