---
description: "Python development: modern Python, async/await, FastAPI, Django, data science, and ML patterns"
globs: ["**/*.py", "**/pyproject.toml", "**/requirements.txt", "**/setup.py", "**/*.ipynb", "**/Pipfile"]
alwaysApply: false
---
# Python Development Patterns
Modern Python (3.10+) best practices for web development, data science, and general programming.
## CRITICAL: Agentic-First Python Development
### Pre-Development Verification (MANDATORY)
Before writing ANY Python code:
```
1. CHECK PYTHON INSTALLATION
→ run_terminal_cmd("python3 --version")
→ run_terminal_cmd("pip --version")
→ run_terminal_cmd("uv --version") # Modern package manager
2. VERIFY CURRENT VERSIONS (use web_search)
→ web_search("Python stable version December 2024")
→ web_search("FastAPI latest version 2024")
→ web_search("Django LTS version 2024")
3. CHECK EXISTING PROJECT
→ Does pyproject.toml/requirements.txt exist? Read it first!
→ Is there a virtual environment?
→ What dependencies are already installed?
4. VIRTUAL ENVIRONMENT (ALWAYS USE)
→ run_terminal_cmd("python3 -m venv .venv")
→ run_terminal_cmd("source .venv/bin/activate")
```
### CLI-First Python Development
**ALWAYS use CLI for package management:**
```bash
# Virtual environment (ALWAYS create first)
python3 -m venv .venv
source .venv/bin/activate # Unix
.venv\Scripts\activate # Windows
# Modern: uv (10-100x faster than pip)
uv venv
uv pip install fastapi uvicorn
uv pip sync requirements.txt
# Traditional: pip
pip install fastapi uvicorn
pip install -r requirements.txt
pip freeze > requirements.txt
# Framework CLIs (use these for scaffolding)
# Django
django-admin startproject myproject
python manage.py startapp myapp
# FastAPI with cookiecutter
pip install cookiecutter
cookiecutter https://github.com/tiangolo/full-stack-fastapi-template
# Poetry (if project uses it)
poetry init
poetry add fastapi
poetry install
```
### Post-Edit Verification
After ANY Python code changes:
```bash
# Type checking
mypy src/
# Linting
ruff check .
ruff format --check .
# Or older tools
flake8 .
black --check .
# Tests
pytest
pytest --cov=src
# Verify imports work
python -c "import mypackage"
```
### Common Python Syntax Traps (Avoid These!)
```python
# WRONG: Mutable default argument
def append_to(item, target=[]): # Bug! List is shared!
target.append(item)
return target
# CORRECT: Use None as default
def append_to(item, target=None):
if target is None:
target = []
target.append(item)
return target
# WRONG: Not using context managers for files
f = open('file.txt')
data = f.read()
f.close() # Might not run if exception occurs!
# CORRECT: Always use with statement
with open('file.txt') as f:
data = f.read()
# WRONG: Bare except clause
try:
risky_operation()
except: # Catches EVERYTHING including KeyboardInterrupt!
pass
# CORRECT: Catch specific exceptions
try:
risky_operation()
except (ValueError, TypeError) as e:
logger.error(f"Operation failed: {e}")
# WRONG: Late binding in closures
funcs = [lambda: i for i in range(3)]
[f() for f in funcs] # Returns [2, 2, 2], not [0, 1, 2]!
# CORRECT: Capture value as default argument
funcs = [lambda i=i: i for i in range(3)]
[f() for f in funcs] # Returns [0, 1, 2]
# WRONG: Using is for value comparison
if x is 1: # Works sometimes, but wrong!
pass
# CORRECT: Use == for values, is for identity
if x == 1: # Value comparison
pass
if x is None: # Identity (OK for None, True, False)
pass
```
### Python Version-Specific Features
Check Python version before using new features:
```python
# Python 3.10+ Pattern Matching
match command:
case ["quit"]:
return "Goodbye"
case _:
return "Unknown"
# Python 3.9+ Built-in Generic Types
def process(items: list[str]) -> dict[str, int]:
pass
# Python 3.8+ Walrus Operator
if (n := len(data)) > 10:
print(f"Processing {n} items")
# Check version in code if needed
import sys
if sys.version_info >= (3, 10):
# Use match statement
else:
# Use if/elif chain
```
---
## Modern Python Syntax
### Type Hints
```python
# Basic types
def greet(name: str) -> str:
return f"Hello, {name}"
# Collections (Python 3.9+)
def process(items: list[str]) -> dict[str, int]:
return {item: len(item) for item in items}
# Optional and Union
def find_user(id: int) -> User | None:
return db.get(id)
# Generic types
from typing import TypeVar, Generic
T = TypeVar('T')
class Repository(Generic[T]):
def get(self, id: int) -> T | None: ...
def save(self, item: T) -> T: ...
```
### Pattern Matching (3.10+)
```python
match command:
case ["quit"]:
return "Goodbye"
case ["load", filename]:
return load_file(filename)
case ["save", filename, *options]:
return save_file(filename, options)
case _:
return "Unknown command"
# With guards
match user:
case User(role="admin"):
return full_access()
case User(role="user", verified=True):
return limited_access()
case _:
return no_access()
```
### Dataclasses
```python
from dataclasses import dataclass, field
from datetime import datetime
@dataclass
class User:
id: int
name: str
email: str
created_at: datetime = field(default_factory=datetime.now)
tags: list[str] = field(default_factory=list)
def __post_init__(self):
self.email = self.email.lower()
# Immutable
@dataclass(frozen=True)
class Config:
host: str
port: int
```
---
## Async/Await Patterns
### Basic Async
```python
import asyncio
async def fetch_data(url: str) -> dict:
async with aiohttp.ClientSession() as session:
async with session.get(url) as response:
return await response.json()
# Parallel execution
async def fetch_all(urls: list[str]) -> list[dict]:
tasks = [fetch_data(url) for url in urls]
return await asyncio.gather(*tasks)
# With error handling
async def safe_fetch(url: str) -> dict | None:
try:
return await fetch_data(url)
except aiohttp.ClientError as e:
logger.error(f"Failed to fetch {url}: {e}")
return None
```
### Async Context Managers
```python
from contextlib import asynccontextmanager
@asynccontextmanager
async def get_db_connection():
conn = await database.connect()
try:
yield conn
finally:
await conn.close()
# Usage
async with get_db_connection() as conn:
result = await conn.fetch("SELECT * FROM users")
```
---
## FastAPI Patterns
### Application Structure
```
app/
__init__.py
main.py # FastAPI app instance
config.py # Settings
models/ # Pydantic models
routers/ # Route handlers
services/ # Business logic
repositories/ # Data access
dependencies.py # Dependency injection
```
### Route Handlers
```python
from fastapi import FastAPI, HTTPException, Depends, status
from pydantic import BaseModel
app = FastAPI()
class UserCreate(BaseModel):
name: str
email: str
class UserResponse(BaseModel):
id: int
name: str
email: str
class Config:
from_attributes = True
@app.post("/users", response_model=UserResponse, status_code=status.HTTP_201_CREATED)
async def create_user(
user: UserCreate,
db: Database = Depends(get_db)
) -> UserResponse:
db_user = await db.users.create(user.model_dump())
return db_user
@app.get("/users/{user_id}", response_model=UserResponse)
async def get_user(user_id: int, db: Database = Depends(get_db)):
user = await db.users.get(user_id)
if not user:
raise HTTPException(status_code=404, detail="User not found")
return user
```
### Dependency Injection
```python
from fastapi import Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
async def get_current_user(token: str = Depends(oauth2_scheme)) -> User:
user = await verify_token(token)
if not user:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid authentication credentials",
)
return user
async def get_admin_user(user: User = Depends(get_current_user)) -> User:
if user.role != "admin":
raise HTTPException(status_code=403, detail="Admin access required")
return user
```
### Pydantic Validation
```python
from pydantic import BaseModel, EmailStr, Field, field_validator
class UserCreate(BaseModel):
name: str = Field(min_length=1, max_length=100)
email: EmailStr
age: int = Field(ge=0, le=150)
@field_validator('name')
@classmethod
def name_must_not_be_empty(cls, v: str) -> str:
if not v.strip():
raise ValueError('Name cannot be empty')
return v.strip()
```
---
## Django Patterns
### Model Design
```python
from django.db import models
from django.contrib.auth.models import AbstractUser
class User(AbstractUser):
bio = models.TextField(blank=True)
avatar = models.ImageField(upload_to='avatars/', null=True)
class Post(models.Model):
author = models.ForeignKey(User, on_delete=models.CASCADE, related_name='posts')
title = models.CharField(max_length=200)
content = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
ordering = ['-created_at']
def __str__(self):
return self.title
```
### Views (Class-Based)
```python
from django.views.generic import ListView, DetailView, CreateView
from django.contrib.auth.mixins import LoginRequiredMixin
class PostListView(ListView):
model = Post
paginate_by = 10
context_object_name = 'posts'
class PostCreateView(LoginRequiredMixin, CreateView):
model = Post
fields = ['title', 'content']
def form_valid(self, form):
form.instance.author = self.request.user
return super().form_valid(form)
```
### QuerySet Optimization
```python
# Avoid N+1 queries
posts = Post.objects.select_related('author').prefetch_related('comments')
# Only fetch needed fields
users = User.objects.only('id', 'name', 'email')
# Efficient counting
count = Post.objects.filter(published=True).count()
# Bulk operations
Post.objects.filter(draft=True).update(published=True)
Post.objects.bulk_create([Post(title=t) for t in titles])
```
---
## Data Science Patterns
### Pandas Best Practices
```python
import pandas as pd
# Method chaining
df = (
pd.read_csv('data.csv')
.dropna(subset=['important_column'])
.assign(
new_col=lambda x: x['col1'] + x['col2'],
date=lambda x: pd.to_datetime(x['date_str'])
)
.query('value > 0')
.groupby('category')
.agg({'value': 'sum', 'count': 'size'})
.reset_index()
)
# Efficient operations
# Bad: iterating rows
for idx, row in df.iterrows():
df.loc[idx, 'new'] = row['a'] + row['b']
# Good: vectorized
df['new'] = df['a'] + df['b']
# Memory efficiency
df = pd.read_csv('large.csv',
dtype={'id': 'int32', 'category': 'category'},
usecols=['id', 'category', 'value']
)
```
### NumPy Patterns
```python
import numpy as np
# Vectorized operations
result = np.sum(arr1 * arr2, axis=1)
# Broadcasting
normalized = (data - data.mean(axis=0)) / data.std(axis=0)
# Efficient conditionals
result = np.where(condition, value_if_true, value_if_false)
```
---
## Testing with pytest
### Test Structure
```python
import pytest
from myapp import calculate_total, UserService
class TestCalculateTotal:
def test_empty_list_returns_zero(self):
assert calculate_total([]) == 0
def test_sums_positive_numbers(self):
assert calculate_total([1, 2, 3]) == 6
def test_raises_on_invalid_input(self):
with pytest.raises(ValueError):
calculate_total(None)
# Fixtures
@pytest.fixture
def db_session():
session = create_test_session()
yield session
session.rollback()
@pytest.fixture
def user_service(db_session):
return UserService(db_session)
def test_create_user(user_service):
user = user_service.create(name="Test", email="test@example.com")
assert user.id is not None
```
### Async Tests
```python
import pytest
@pytest.mark.asyncio
async def test_fetch_user():
user = await fetch_user(1)
assert user.name == "Expected Name"
```
### Parametrized Tests
```python
@pytest.mark.parametrize("input,expected", [
([], 0),
([1], 1),
([1, 2, 3], 6),
([-1, 1], 0),
])
def test_sum(input, expected):
assert sum(input) == expected
```
---
## Error Handling
### Custom Exceptions
```python
class AppError(Exception):
"""Base exception for application errors."""
pass
class NotFoundError(AppError):
"""Resource not found."""
def __init__(self, resource: str, id: int):
self.resource = resource
self.id = id
super().__init__(f"{resource} with id {id} not found")
class ValidationError(AppError):
"""Validation failed."""
def __init__(self, field: str, message: str):
self.field = field
self.message = message
super().__init__(f"{field}: {message}")
```
### Context Managers
```python
from contextlib import contextmanager
@contextmanager
def transaction(db):
try:
yield db
db.commit()
except Exception:
db.rollback()
raise
```
---
## Package Management
### pyproject.toml (Modern Standard)
```toml
[project]
name = "myproject"
version = "0.1.0"
description = "My Python project"
requires-python = ">=3.10"
dependencies = [
"fastapi>=0.100.0",
"pydantic>=2.0.0",
"sqlalchemy>=2.0.0",
]
[project.optional-dependencies]
dev = [
"pytest>=7.0.0",
"black>=23.0.0",
"ruff>=0.1.0",
]
[tool.ruff]
line-length = 100
select = ["E", "F", "I"]
[tool.pytest.ini_options]
testpaths = ["tests"]
asyncio_mode = "auto"
```
### uv (Fast Package Manager)
```bash
# Install dependencies
uv pip install -r requirements.txt
# Add dependency
uv pip install fastapi
# Create virtual environment
uv venv
# Sync dependencies
uv pip sync requirements.txt
```