You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
196 lines
4.4 KiB
196 lines
4.4 KiB
# 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]:
|
|
...
|
|
```
|
|
|