# Type Hint Constraints ## Core Rule **Use type hints on all public functions and methods** ## Function Type Hints ### Required on Public APIs ```python # GOOD - complete type hints def get_motion(self, motion_id: int) -> Optional[Dict]: ... def get_filtered_motions( self, policy_area: str = "Alle", limit: int = 10 ) -> List[Dict]: ... def calculate_similarity(self, motion_a: int, motion_b: int) -> float: ... ``` ### Optional Parameters Use `Optional[X]` or `X | None`: ```python # Both forms are acceptable def get_motion(self, motion_id: Optional[int] = None) -> Optional[Dict]: ... def get_motion(self, motion_id: int | None = None) -> dict | None: ... ``` ### Multiple Return Types Use `Union[X, Y]` or `|` operator: ```python # Acceptable forms def parse_value(self, value: str) -> Union[bool, str, None]: ... def parse_value(self, value: str) -> bool | str | None: ... ``` ### Generic Types Use `List[X]`, `Dict[K, V]`, `Tuple[X, Y]`: ```python from typing import Dict, List, Optional, Tuple def get_motions(self, ids: List[int]) -> Dict[int, Dict]: """Map motion_id -> motion data.""" ... def process_batch(self, items: List[str]) -> Tuple[List[str], List[str]]: """Returns (successes, failures).""" ... ``` ## Collection Types Prefer specific types over bare `list`/`dict`: ```python # GOOD - specific types def get_votes(self) -> List[str]: ... def get_metadata(self) -> Dict[str, Any]: ... # ACCEPTABLE - for truly generic collections def merge_dicts(*dicts: dict) -> dict: ... ``` ## DuckDB Result Types DuckDB returns tuples/lists - document expected structure: ```python def get_motion(self, motion_id: int) -> Optional[Tuple]: """Returns (id, title, description, date, ...) or None.""" conn = duckdb.connect(self.db_path) try: result = conn.execute( "SELECT * FROM motions WHERE id = ?", (motion_id,) ).fetchone() return result finally: conn.close() # Or use Dict for clarity def get_motion_as_dict(self, motion_id: int) -> Optional[Dict]: """Returns motion dict or None.""" conn = duckdb.connect(self.db_path) try: row = conn.execute( "SELECT * FROM motions WHERE id = ?", (motion_id,) ).fetchone() if row: return { "id": row[0], "title": row[1], "description": row[2], ... } return None finally: conn.close() ``` ## Class/Instance Types Use `Self` for methods returning instance type: ```python from typing import Self class MotionDatabase: def with_connection(self, path: str) -> Self: """Return new instance with different path.""" return MotionDatabase(db_path=path) ``` ## Callback/Function Types Use `Callable` for function parameters: ```python from typing import Callable def process_motions( motions: List[Dict], processor: Callable[[Dict], Any] ) -> List[Any]: return [processor(m) for m in motions] ``` ## Type Aliases Define clear type aliases for domain concepts: ```python from typing import Dict, List, TypedDict, Literal # Vote values VoteValue = Literal["Voor", "Tegen", "Onthouden", "Geen stem", "Afwezig"] # Policy areas PolicyArea = Literal["Alle", "Economie", "Klimaat", "Immigratie", ...] # Motion dict class MotionDict(TypedDict): id: int title: str description: Optional[str] date: Optional[str] policy_area: Optional[str] voting_results: Optional[str] # JSON string winning_margin: Optional[float] def get_motion(self, motion_id: int) -> Optional[MotionDict]: ... ``` ## Avoid `Any` Use `Any` sparingly - prefer specific types: ```python # AVOID - too vague def process(data: Any) -> Any: ... # PREFER - specific types def process(motion: MotionDict) -> Optional[SimilarityResult]: ... ``` ## Inline Type Hints For simple cases, inline hints are fine: ```python def get_count(self) -> int: ... def is_empty(self) -> bool: ... ``` ## Docstring Type Hints For complex types, include in docstrings: ```python def get_party_positions(self, window_id: str) -> Dict[str, List[float]]: """Get party positions in political space. Args: window_id: Time window (e.g., "2024-Q1") Returns: Dict mapping party_name -> [x, y] coordinates Example: >>> positions = db.get_party_positions("2024-Q1") >>> positions["VVD"] [0.5, -0.3] """ ... ``` ## Type Checking For runtime type checking, use runtime checks: ```python def set_count(self, count: int) -> None: if not isinstance(count, int): raise TypeError(f"Expected int, got {type(count).__name__}") self._count = count ```