# Python-Specific Patterns

## Singleton Pattern

Use module-level instances for shared resources:

```python
# database.py
class MotionDatabase:
    def __init__(self, db_path: str = config.DATABASE_PATH):
        self.db_path = db_path
        self._init_database()
    
    def _init_database(self):
        # Initialize tables on first instantiation
        ...

# Bottom of file - the singleton
db = MotionDatabase()
```

**Usage across the codebase:**
```python
# In other modules
from database import db

def some_function():
    motions = db.get_filtered_motions(limit=10)
    return motions
```

Similarly for other singletons:
```python
# summarizer.py
class MotionSummarizer:
    def __init__(self):
        pass  # Stateless
    
    def generate_layman_explanation(self, title: str, body: str) -> str:
        ...

summarizer = MotionSummarizer()
```

## Dataclass Config Pattern

Use dataclass for configuration with environment variable support:

```python
# config.py
from dataclasses import dataclass
from typing import List
import os

@dataclass
class Config:
    # Database settings
    DATABASE_PATH = "data/motions.db"
    
    # API settings
    TWEEDE_KAMER_ODATA_API = "https://gegevensmagazijn.tweedekamer.nl/OData/v4/2.0"
    API_TIMEOUT = 30
    API_BATCH_SIZE = 250
    
    # AI settings
    OPENROUTER_API_KEY = os.getenv("OPENROUTER_API_KEY")
    OPENROUTER_BASE_URL = "https://openrouter.ai/api/v1"
    QWEN_MODEL = "qwen/qwen-2.5-72b-instruct"
    
    # App settings
    DEFAULT_MOTION_COUNT = 10
    SESSION_TIMEOUT_DAYS = 30
    
    # Policy areas
    POLICY_AREAS: List[str] = None
    def __post_init__(self):
        self.POLICY_AREAS = [
            "Alle", "Economie", "Klimaat", "Immigratie", 
            "Zorg", "Onderwijs", "Defensie", "Sociale Zaken", "Algemeen"
        ]

config = Config()
```

**Usage:**
```python
from config import config

# Access as attributes
timeout = config.API_TIMEOUT
areas = config.POLICY_AREAS
```

## DuckDB Connection Pattern

Short-lived connections with explicit cleanup:

```python
class MotionDatabase:
    def get_motion(self, motion_id: int) -> Optional[Dict]:
        conn = duckdb.connect(self.db_path)
        try:
            result = conn.execute(
                "SELECT * FROM motions WHERE id = ?", 
                (motion_id,)
            ).fetchone()
            return result
        finally:
            conn.close()
    
    def get_filtered_motions(self, **kwargs) -> List[Dict]:
        conn = duckdb.connect(self.db_path)
        try:
            rows = conn.execute(query, params).fetchall()
            return rows
        except Exception:
            return []  # Safe fallback
        finally:
            conn.close()
```

**Context manager alternative (preferred when applicable):**
```python
def some_operation(self):
    with duckdb.connect(self.db_path) as conn:
        result = conn.execute("SELECT ...").fetchall()
    return result
```

## Try/Except with Fallback Pattern

Always provide safe fallbacks:

```python
def get_motion_or_default(self, motion_id: int) -> Dict:
    try:
        conn = duckdb.connect(self.db_path)
        result = conn.execute("SELECT * FROM motions WHERE id = ?", (motion_id,)).fetchone()
        conn.close()
        return result if result else {}
    except Exception:
        return {}
```

## Optional Import Pattern

Handle optional dependencies gracefully:

```python
try:
    import duckdb
except Exception:  # pragma: no cover
    duckdb = None

class MotionDatabase:
    def __init__(self, db_path: str = config.DATABASE_PATH):
        self._file_mode = duckdb is None
        ...
```

## Property Pattern

Lazy initialization of expensive resources:

```python
class MotionDatabase:
    def __init__(self, db_path: str = config.DATABASE_PATH):
        self.db_path = db_path
        self._session_cache = None
    
    @property
    def session(self):
        """Lazy-load expensive resources."""
        if self._session_cache is None:
            self._session_cache = self._create_session()
        return self._session_cache
```

## Type Annotation Patterns

```python
from typing import Dict, List, Optional, Tuple, Any

# Optional with None default
def get_motion(self, motion_id: Optional[int] = None) -> Optional[Dict]:
    ...

# Multiple return types
def parse_vote(self, vote_str: str) -> Tuple[bool, str]:
    """Returns (success, error_message)"""
    ...

# Generic types
def get_batch(self, ids: List[int]) -> Dict[str, Any]:
    ...
```
