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.
233 lines
4.7 KiB
233 lines
4.7 KiB
# 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
|
|
```
|
|
|