|
1 | 1 | from __future__ import annotations |
2 | 2 |
|
3 | 3 | import re |
| 4 | +import ast |
4 | 5 | from dataclasses import asdict, dataclass |
5 | 6 | from typing import Any |
6 | 7 |
|
@@ -253,11 +254,45 @@ def _error_line_col(error: BaseException) -> tuple[int | None, int | None]: |
253 | 254 |
|
254 | 255 |
|
255 | 256 | def _error_message(error: BaseException) -> str: |
| 257 | + module_attr_message = _module_attribute_error_message(error) |
| 258 | + if module_attr_message is not None: |
| 259 | + return module_attr_message |
256 | 260 | if isinstance(error, draconic.DraconicException): |
257 | 261 | return str(error.msg) |
258 | 262 | return str(error) |
259 | 263 |
|
260 | 264 |
|
| 265 | +def _module_attribute_error_message(error: BaseException) -> str | None: |
| 266 | + if not isinstance(error, draconic.NotDefined): |
| 267 | + return None |
| 268 | + message = str(error.msg) |
| 269 | + attr_match = re.fullmatch(r"'SimpleNamespace' object has no attribute (.+)", message) |
| 270 | + if attr_match is None: |
| 271 | + return None |
| 272 | + |
| 273 | + node = getattr(error, "node", None) |
| 274 | + if not isinstance(node, ast.Attribute): |
| 275 | + return None |
| 276 | + |
| 277 | + module_name = _module_attribute_base_name(node.value, getattr(error, "expr", None)) |
| 278 | + if module_name is None: |
| 279 | + return None |
| 280 | + return f"Module '{module_name}' has no attribute '{attr_match.group(1)}'" |
| 281 | + |
| 282 | + |
| 283 | +def _module_attribute_base_name(node: ast.AST, expr: Any) -> str | None: |
| 284 | + if isinstance(node, ast.Name): |
| 285 | + return node.id |
| 286 | + if isinstance(expr, str): |
| 287 | + segment = ast.get_source_segment(expr, node) |
| 288 | + if segment: |
| 289 | + return segment |
| 290 | + try: |
| 291 | + return ast.unparse(node) |
| 292 | + except Exception: |
| 293 | + return None |
| 294 | + |
| 295 | + |
261 | 296 | def _kind_for_error(error: BaseException) -> str: |
262 | 297 | if _circular_import_details(error) is not None: |
263 | 298 | return "circular_import" |
|
0 commit comments