Skip to content

Commit 21daada

Browse files
feat: error-doc handling in python SDK (#1440)
Lookups now take place against the CDN to find a more detailed error guide. The url to that error guide, and the error guide itself - a markdown document - are included in the SDKError class and the ErrorDetail class.
1 parent 62b3d56 commit 21daada

File tree

2 files changed

+115
-12
lines changed

2 files changed

+115
-12
lines changed

python/looker_sdk/error.py

+106-11
Original file line numberDiff line numberDiff line change
@@ -21,33 +21,128 @@
2121
# THE SOFTWARE.
2222

2323
import attr
24-
from typing import Optional, Sequence
24+
from typing import cast, Dict, Optional, Sequence, Tuple
25+
import requests
26+
import json
27+
import re
2528

2629
"""API error class
2730
"""
2831

32+
2933
@attr.s(auto_attribs=True, kw_only=True)
30-
class ErrorDetail():
34+
class ErrorDetail:
3135
"""Error detail:
32-
documentation_url: documentation link
33-
field: field with error
34-
code: error code
35-
message: error info message
36+
documentation_url: documentation link
37+
field: field with error
38+
code: error code
39+
message: error info message
40+
error_doc_url: URL that may point to additional useful information
41+
error_doc: Markdown doc that may contain additional useful information
3642
"""
43+
3744
documentation_url: str
3845
field: Optional[str] = ""
3946
code: Optional[str] = ""
4047
message: Optional[str] = ""
48+
error_doc_url: str = ""
49+
error_doc: str = ""
50+
51+
def __str__(self):
52+
return f"""
53+
*****
54+
documentation_url: {self.documentation_url}
55+
field: {self.field}
56+
code: {self.code}
57+
message: {self.message}
58+
error_doc_url: {self.error_doc_url}
59+
"""
4160

4261

4362
@attr.s(auto_attribs=True)
4463
class SDKError(Exception):
4564
"""API error class:
46-
message: main error info message
47-
errors: array of error details
48-
documentation_url: documentation link
65+
message: main error info message
66+
errors: array of error details
67+
documentation_url: documentation link
68+
error_doc_url: URL that may point to additional useful information
69+
error_doc: Markdown doc that may contain additional useful information
4970
"""
5071

5172
message: str
52-
errors: Optional[Sequence[ErrorDetail]] = attr.ib(default=[], kw_only=True)
53-
documentation_url: Optional[str] = attr.ib(default="", kw_only=True)
73+
errors: Sequence[ErrorDetail] = attr.ib(default=[], kw_only=True)
74+
documentation_url: str = attr.ib(default="", kw_only=True)
75+
error_doc_url: str = ""
76+
error_doc: str = ""
77+
78+
def __str__(self):
79+
sep = "****\n"
80+
return f"""
81+
message: {self.message}
82+
documentation_url: {self.documentation_url}
83+
error_doc_url: {self.error_doc_url}
84+
error details:
85+
{sep.join(self.errors)}
86+
"""
87+
88+
89+
"""Error Doc Helper class
90+
"""
91+
92+
93+
@attr.s(auto_attribs=True, kw_only=True)
94+
class ErrorDocHelper:
95+
"""Error Doc Helper:
96+
error_doc_url: link
97+
"""
98+
99+
ERROR_CODES_URL: str = "https://static-a.cdn.looker.app/errorcodes/"
100+
lookup_dict: Dict[str, Dict[str, str]] = {}
101+
RE_PATTERN: str = (
102+
"""(https://docs\.looker\.com/r/err/|https://cloud\.google\.com/looker/docs/r/err/)(.*)/(\d{3})(.*)"""
103+
)
104+
pattern = re.compile(RE_PATTERN, flags=re.IGNORECASE)
105+
106+
def get_index(self, url: str = ERROR_CODES_URL) -> None:
107+
r = requests.get(f"{url}index.json")
108+
self.lookup_dict = json.loads(r.text)
109+
110+
def lookup(
111+
self, url: str = ERROR_CODES_URL, code: str = "", path: str = ""
112+
) -> Tuple[str, str]:
113+
if len(self.lookup_dict) == 0:
114+
self.get_index(url=url)
115+
116+
error_doc_url: str = ""
117+
error_doc: str = ""
118+
if path:
119+
try:
120+
error_doc_url = self.lookup_dict[f"{code}{path}"]["url"]
121+
except KeyError:
122+
error_doc = f"### No documentation found for {code}{path}"
123+
if not error_doc_url:
124+
try:
125+
error_doc_url = self.lookup_dict[code]["url"]
126+
except KeyError:
127+
if not error_doc:
128+
error_doc = f"### No documentation found for {code}"
129+
130+
if error_doc_url:
131+
r = requests.get(f"{self.ERROR_CODES_URL}{error_doc_url}")
132+
error_doc = r.text
133+
134+
return (f"{self.ERROR_CODES_URL}{error_doc_url}", error_doc)
135+
136+
def parse_and_lookup(
137+
self, error_url: str, url: str = ERROR_CODES_URL
138+
) -> Tuple[str, str]:
139+
m = re.search(self.RE_PATTERN, error_url)
140+
if not m:
141+
return ("", "")
142+
143+
code: str = cast(Tuple[str, str, str, str], m.groups())[2]
144+
path: str = cast(Tuple[str, str, str, str], m.groups())[3]
145+
try:
146+
return self.lookup(url=url, code=code, path=path)
147+
except requests.exceptions.RequestException:
148+
return ("", "")

python/looker_sdk/rtl/api_methods.py

+9-1
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,15 @@ def _return(self, response: transport.Response, structure: TStructure) -> TRetur
8787
value = response.value.decode(encoding=encoding)
8888
sdk_error: error.SDKError
8989
try:
90-
sdk_error = self.deserialize(data=value, structure=error.SDKError) # type: ignore
90+
sdk_error = self.deserialize(data=value, structure=error.SDKError) # type: ignore
91+
helper = error.ErrorDocHelper()
92+
(sdk_error.error_doc_url, sdk_error.error_doc) = (
93+
helper.parse_and_lookup(sdk_error.documentation_url)
94+
)
95+
for e in sdk_error.errors:
96+
(e.error_doc_url, e.error_doc) = helper.parse_and_lookup(
97+
e.documentation_url
98+
)
9199
except serialize.DeserializeError:
92100
raise error.SDKError(value)
93101
raise sdk_error

0 commit comments

Comments
 (0)