# 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]: ... ```