|
| 1 | +# Error Handling |
| 2 | + |
| 3 | +Understanding and handling errors in python-typemap. |
| 4 | + |
| 5 | +## Exception Types |
| 6 | + |
| 7 | +### TypeMapError |
| 8 | + |
| 9 | +Base exception for all typemap errors. |
| 10 | + |
| 11 | +```python |
| 12 | +from typemap import TypeMapError |
| 13 | + |
| 14 | +try: |
| 15 | + result = eval_typing(expr) |
| 16 | +except TypeMapError as e: |
| 17 | + print(f"Type evaluation failed: {e}") |
| 18 | +``` |
| 19 | + |
| 20 | +### StuckException |
| 21 | + |
| 22 | +Raised when type evaluation cannot proceed because a type variable hasn't been resolved. |
| 23 | + |
| 24 | +```python |
| 25 | +from typemap.type_eval import StuckException |
| 26 | + |
| 27 | +try: |
| 28 | + # T is still a TypeVar, can't evaluate |
| 29 | + result = eval_typing(KeyOf[T]) |
| 30 | +except StuckException: |
| 31 | + # T needs to be bound to a concrete type |
| 32 | + result = eval_typing(KeyOf[ConcreteType]) |
| 33 | +``` |
| 34 | + |
| 35 | +**Common causes:** |
| 36 | +- Passing a TypeVar directly to an operator |
| 37 | +- Using unevaluated generics |
| 38 | +- Circular type references |
| 39 | + |
| 40 | +### RecursionError |
| 41 | + |
| 42 | +Python's built-in recursion limit exceeded during type evaluation. |
| 43 | + |
| 44 | +```python |
| 45 | +# Recursive type that doesn't terminate |
| 46 | +type Infinite = list[Infinite] |
| 47 | + |
| 48 | +try: |
| 49 | + eval_typing(Partial[Infinite]) |
| 50 | +except RecursionError: |
| 51 | + print("Type is infinitely recursive") |
| 52 | +``` |
| 53 | + |
| 54 | +## Common Errors |
| 55 | + |
| 56 | +### 1. Type Variable Not Bound |
| 57 | + |
| 58 | +```python |
| 59 | +from typing import TypeVar |
| 60 | + |
| 61 | +T = TypeVar('T') |
| 62 | + |
| 63 | +# Wrong - T is not bound |
| 64 | +KeyOf[T] # StuckException |
| 65 | + |
| 66 | +# Correct - T is bound to a concrete type |
| 67 | +KeyOf[User] |
| 68 | +``` |
| 69 | + |
| 70 | +### 2. Missing Type Annotation |
| 71 | + |
| 72 | +```python |
| 73 | +class MissingAnnotation: |
| 74 | + name: str |
| 75 | + data # No annotation! |
| 76 | + |
| 77 | +# May cause evaluation issues |
| 78 | +eval_typing(Attrs[MissingAnnotation]) |
| 79 | +# May raise: AttributeError or StuckException |
| 80 | +``` |
| 81 | + |
| 82 | +### 3. Invalid Type Expression |
| 83 | + |
| 84 | +```python |
| 85 | +# Wrong - this is not a valid type expression |
| 86 | +eval_typing("User") # String, not a type |
| 87 | + |
| 88 | +# Correct |
| 89 | +eval_typing(User) |
| 90 | +eval_typing(KeyOf[User]) |
| 91 | +``` |
| 92 | + |
| 93 | +### 4. Union with Non-Types |
| 94 | + |
| 95 | +```python |
| 96 | +# Wrong |
| 97 | +Union[str, 123] # 123 is not a type |
| 98 | + |
| 99 | +# Correct |
| 100 | +Union[str, int] |
| 101 | +``` |
| 102 | + |
| 103 | +## Debugging Tips |
| 104 | + |
| 105 | +### Enable Context Manager |
| 106 | + |
| 107 | +```python |
| 108 | +from typemap.type_eval import _ensure_context |
| 109 | + |
| 110 | +with _ensure_context() as ctx: |
| 111 | + result = _eval_types_impl(expr, ctx) |
| 112 | + print(f"Resolved: {ctx.resolved}") |
| 113 | + print(f"Seen: {ctx.seen}") |
| 114 | +``` |
| 115 | + |
| 116 | +### Check What Was Resolved |
| 117 | + |
| 118 | +```python |
| 119 | +ctx = EvalContext() |
| 120 | +result = eval_typing(expr, ensure_context=False) |
| 121 | + |
| 122 | +# Inspect cache |
| 123 | +for type_obj, resolved in ctx.resolved.items(): |
| 124 | + print(f"{type_obj} -> {resolved}") |
| 125 | +``` |
| 126 | + |
| 127 | +### Verify Type Structure |
| 128 | + |
| 129 | +```python |
| 130 | +import typing |
| 131 | + |
| 132 | +def inspect_type(t: type) -> dict: |
| 133 | + """Debug type structure.""" |
| 134 | + info = { |
| 135 | + 'is_generic': isinstance(t, typing._GenericAlias), |
| 136 | + 'is_union': hasattr(t, '__origin__') and t.__origin__ is Union, |
| 137 | + 'args': getattr(t, '__args__', ()), |
| 138 | + 'origin': getattr(t, '__origin__', None), |
| 139 | + } |
| 140 | + return info |
| 141 | +``` |
| 142 | + |
| 143 | +## Error Recovery |
| 144 | + |
| 145 | +### Fallback to Default |
| 146 | + |
| 147 | +```python |
| 148 | +def safe_eval(expr, default=None): |
| 149 | + """Evaluate with fallback on error.""" |
| 150 | + try: |
| 151 | + return eval_typing(expr) |
| 152 | + except (StuckException, TypeMapError) as e: |
| 153 | + warnings.warn(f"Evaluation failed: {e}") |
| 154 | + return default |
| 155 | +``` |
| 156 | + |
| 157 | +### Partial Evaluation |
| 158 | + |
| 159 | +```python |
| 160 | +def try_partial_eval(cls: type, operators: list): |
| 161 | + """Try applying operators, collecting failures.""" |
| 162 | + results = {} |
| 163 | + errors = [] |
| 164 | + |
| 165 | + for op in operators: |
| 166 | + try: |
| 167 | + results[op] = eval_typing(op[cls]) |
| 168 | + except Exception as e: |
| 169 | + errors.append((op, str(e))) |
| 170 | + |
| 171 | + return results, errors |
| 172 | +``` |
| 173 | + |
| 174 | +## Validation Before Evaluation |
| 175 | + |
| 176 | +### Check for TypeVars |
| 177 | + |
| 178 | +```python |
| 179 | +from typing import TypeVar, get_args, get_origin |
| 180 | + |
| 181 | +def has_unbound_typevars(t: type) -> bool: |
| 182 | + """Check if type has unresolved TypeVars.""" |
| 183 | + if isinstance(t, TypeVar): |
| 184 | + return True |
| 185 | + if hasattr(t, '__args__'): |
| 186 | + return any(has_unbound_typevars(arg) for arg in t.__args__) |
| 187 | + return False |
| 188 | + |
| 189 | +# Before evaluation |
| 190 | +if has_unbound_typevars(MyType): |
| 191 | + raise ValueError("Type has unbound TypeVars") |
| 192 | +``` |
| 193 | + |
| 194 | +### Validate Type Structure |
| 195 | + |
| 196 | +```python |
| 197 | +def validate_for_eval(t: type) -> tuple[bool, str]: |
| 198 | + """Validate type can be evaluated.""" |
| 199 | + if t is None: |
| 200 | + return False, "Type is None" |
| 201 | + if isinstance(t, str): |
| 202 | + return False, "Type is a string, not a type" |
| 203 | + if isinstance(t, TypeVar): |
| 204 | + return False, f"TypeVar {t} is unbound" |
| 205 | + if not callable(t): |
| 206 | + return False, f"{t} is not callable" |
| 207 | + return True, "OK" |
| 208 | +``` |
| 209 | + |
| 210 | +## Logging |
| 211 | + |
| 212 | +```python |
| 213 | +import logging |
| 214 | + |
| 215 | +logging.basicConfig(level=logging.DEBUG) |
| 216 | +logger = logging.getLogger('typemap') |
| 217 | + |
| 218 | +# Enable debug logging |
| 219 | +logger.setLevel(logging.DEBUG) |
| 220 | +``` |
| 221 | + |
| 222 | +## Common Error Messages |
| 223 | + |
| 224 | +| Error | Cause | Solution | |
| 225 | +|-------|-------|----------| |
| 226 | +| `StuckException: cannot evaluate KeyOf[T]` | TypeVar not bound | Provide concrete type | |
| 227 | +| `RecursionError` | Infinite type recursion | Break recursive cycle | |
| 228 | +| `TypeError: not a type` | Invalid type passed | Check type annotation | |
| 229 | +| `AttributeError: no __args__` | Non-generic used as generic | Use correct type | |
| 230 | +| `KeyError` | Type not in namespace | Provide namespace context | |
| 231 | + |
| 232 | +## Best Practices |
| 233 | + |
| 234 | +1. **Always bind TypeVars** before passing to operators |
| 235 | +2. **Use concrete types** not type aliases when possible |
| 236 | +3. **Catch specific exceptions** not just `Exception` |
| 237 | +4. **Validate inputs** before expensive evaluations |
| 238 | +5. **Cache results** to avoid repeated evaluations |
| 239 | +6. **Provide namespace** for custom type resolution |
0 commit comments