1# The MIT License (MIT)
2#
3# Copyright (c) 2019 Looker Data Sciences, Inc.
4#
5# Permission is hereby granted, free of charge, to any person obtaining a copy
6# of this software and associated documentation files (the "Software"), to deal
7# in the Software without restriction, including without limitation the rights
8# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9# copies of the Software, and to permit persons to whom the Software is
10# furnished to do so, subject to the following conditions:
11#
12# The above copyright notice and this permission notice shall be included in
13# all copies or substantial portions of the Software.
14#
15# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21# THE SOFTWARE.
22
23import attr
24from typing import cast, Dict, Optional, Sequence, Tuple
25import requests
26import json
27import re
28
29"""API error class
30"""
31
32
33@attr.s(auto_attribs=True, kw_only=True)
34class ErrorDetail:
35 """Error detail:
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
42 """
43
44 documentation_url: str
45 field: Optional[str] = ""
46 code: Optional[str] = ""
47 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 """
60
61
62@attr.s(auto_attribs=True)
63class SDKError(Exception):
64 """API error class:
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
70 """
71
72 message: str
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(str(error_details) for error_details in self.errors)}
86 """
87
88
89"""Error Doc Helper class
90"""
91
92
93@attr.s(auto_attribs=True, kw_only=True)
94class 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 r'(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 ("", "")