-
-
Notifications
You must be signed in to change notification settings - Fork 516
/
exception.py
120 lines (91 loc) · 3.36 KB
/
exception.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
from abc import ABC, abstractmethod
from typing import TYPE_CHECKING, Optional
from strawberry.utils.cached_property import cached_property
from strawberry.utils.str_converters import to_kebab_case
from .exception_source import ExceptionSource
if TYPE_CHECKING:
from rich.console import RenderableType
class UnableToFindExceptionSource(Exception):
"""Internal exception raised when we can't find the exception source."""
class StrawberryException(Exception, ABC):
message: str
rich_message: str
suggestion: str
annotation_message: str
def __init__(self, message: str) -> None:
self.message = message
def __str__(self) -> str:
return self.message
@property
def documentation_path(self) -> str:
return to_kebab_case(self.__class__.__name__.replace("Error", ""))
@property
def documentation_url(self) -> str:
prefix = "https://errors.strawberry.rocks/"
return prefix + self.documentation_path
@cached_property
@abstractmethod
def exception_source(self) -> Optional[ExceptionSource]:
return None
@property
def __rich_header__(self) -> "RenderableType":
return f"[bold red]error: {self.rich_message}"
@property
def __rich_body__(self) -> "RenderableType":
assert self.exception_source
return self._get_error_inline(self.exception_source, self.annotation_message)
@property
def __rich_footer__(self) -> "RenderableType":
return (
f"{self.suggestion}\n\n"
"Read more about this error on [bold underline]"
f"[link={self.documentation_url}]{self.documentation_url}"
).strip()
def __rich__(self) -> Optional["RenderableType"]:
from rich.box import SIMPLE
from rich.console import Group
from rich.panel import Panel
if self.exception_source is None:
raise UnableToFindExceptionSource() from self
content = (
self.__rich_header__,
"",
self.__rich_body__,
"",
"",
self.__rich_footer__,
)
return Panel.fit(
Group(*content),
box=SIMPLE,
)
def _get_error_inline(
self, exception_source: ExceptionSource, message: str
) -> "RenderableType":
source_file = exception_source.path
relative_path = exception_source.path_relative_to_cwd
error_line = exception_source.error_line
from rich.console import Group
from .syntax import Syntax
path = f"[white] @ [link=file://{source_file}]{relative_path}:{error_line}"
prefix = " " * exception_source.error_column
caret = "^" * (
exception_source.error_column_end - exception_source.error_column
)
message = f"{prefix}[bold]{caret}[/] {message}"
error_line = exception_source.error_line
line_annotations = {error_line: message}
return Group(
path,
"",
Syntax(
code=exception_source.code,
highlight_lines={error_line},
line_offset=exception_source.start_line - 1,
line_annotations=line_annotations,
line_range=(
exception_source.start_line - 1,
exception_source.end_line,
),
),
)