/
context.py
159 lines (129 loc) · 4.45 KB
/
context.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
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
import enum
import json
import sys
from dataclasses import dataclass, field
from io import BytesIO
from typing import Any, Callable, Dict, List, Optional
from urllib.error import HTTPError
if sys.version_info >= (3, 9):
from zoneinfo import ZoneInfo
else:
from backports.zoneinfo import ZoneInfo
from forestadmin.datasource_toolkit.exceptions import BusinessError, ForbiddenError, UnprocessableError, ValidationError
class RequestMethod(enum.Enum):
GET = "GET"
POST = "POST"
PUT = "PUT"
DELETE = "DELETE"
OPTIONS = "OPTIONS"
@dataclass
class User:
rendering_id: int
user_id: int
tags: Dict[str, Any]
email: str
first_name: str
last_name: str
team: str
timezone: ZoneInfo
# permission_level
# role
class Request:
method: RequestMethod
body: Optional[Dict[str, Any]] = None
query: Optional[Dict[str, Any]] = None
headers: Optional[Dict[str, str]] = None
user: Optional[User] = None
client_ip: Optional[str] = None
def __init__(
self,
method: RequestMethod,
body: Optional[Dict[str, Any]] = None,
query: Optional[Dict[str, Any]] = None,
headers: Optional[Dict[str, str]] = None,
user: Optional[User] = None,
client_ip: Optional[str] = None,
):
self.method = method
self.body = body
self.query = query
self.headers = headers
self.user = user
self.client_ip = client_ip
@dataclass
class Response:
status: int
body: Optional[str] = None
headers: Dict[str, str] = field(default_factory=lambda: {})
@dataclass
class FileResponse:
file: Optional[BytesIO]
name: str
mimetype: str
headers: Dict[str, str] = field(default_factory=lambda: {})
class HttpResponseBuilder:
_ERROR_MESSAGE_CUSTOMIZER: Callable[[Exception], str] = None
@classmethod
def setup_error_message_customizer(cls, customizer_function: Callable[[Exception], str]):
cls._ERROR_MESSAGE_CUSTOMIZER = customizer_function
@staticmethod
def build_json_response(status: int, body: Dict[str, Any]) -> Response:
return Response(status, json.dumps(body), headers={"content-type": "application/json"})
@staticmethod
def build_client_error_response(reasons: List[Exception]) -> Response:
errors = []
for error in reasons:
tmp = {
"name": HttpResponseBuilder._get_error_name(error),
"detail": HttpResponseBuilder._get_error_message(error),
"status": HttpResponseBuilder._get_error_status(error),
}
if hasattr(error, "data") and getattr(error, "data") is not None:
tmp["data"] = error.data
errors.append(tmp)
return HttpResponseBuilder.build_json_response(
HttpResponseBuilder._get_error_status(reasons[0]),
{"errors": errors},
)
@staticmethod
def build_csv_response(body: str, filename: str) -> Response:
return Response(
200, body, headers={"content-type": "text/csv", "Content-Disposition": f'attachment; filename="{filename}"'}
)
@staticmethod
def build_success_response(body: Dict[str, Any]) -> Response:
return HttpResponseBuilder.build_json_response(200, body)
@staticmethod
def build_unknown_response() -> Response:
return Response(404)
@staticmethod
def build_no_content_response() -> Response:
return Response(204)
@staticmethod
def build_method_not_allowed_response() -> Response:
return Response(405)
@staticmethod
def _get_error_status(error: Exception):
if isinstance(error, ValidationError):
return 400
if isinstance(error, ForbiddenError):
return 403
if isinstance(error, UnprocessableError):
return 422
if isinstance(error, HTTPError):
return error.code
return 500
@staticmethod
def _get_error_message(error: Exception):
if isinstance(error, BusinessError):
return str(error.args[0][3:])
if HttpResponseBuilder._ERROR_MESSAGE_CUSTOMIZER is not None:
return HttpResponseBuilder._ERROR_MESSAGE_CUSTOMIZER(error)
else:
return str(error)
@staticmethod
def _get_error_name(error: Exception):
if hasattr(error, "name") and getattr(error, "name") is not None:
return error.name
else:
return error.__class__.__name__