soia-client 1.0.20__tar.gz → 1.0.21__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {soia_client-1.0.20 → soia_client-1.0.21}/PKG-INFO +1 -1
- {soia_client-1.0.20 → soia_client-1.0.21}/pyproject.toml +1 -1
- {soia_client-1.0.20 → soia_client-1.0.21}/soia/__init__.py +3 -3
- soia_client-1.0.21/soia/_impl/service.py +339 -0
- {soia_client-1.0.20 → soia_client-1.0.21}/soia_client.egg-info/PKG-INFO +1 -1
- soia_client-1.0.20/soia/_impl/service.py +0 -205
- {soia_client-1.0.20 → soia_client-1.0.21}/LICENSE +0 -0
- {soia_client-1.0.20 → soia_client-1.0.21}/README.md +0 -0
- {soia_client-1.0.20 → soia_client-1.0.21}/setup.cfg +0 -0
- {soia_client-1.0.20 → soia_client-1.0.21}/soia/_impl/__init__.py +0 -0
- {soia_client-1.0.20 → soia_client-1.0.21}/soia/_impl/arrays.py +0 -0
- {soia_client-1.0.20 → soia_client-1.0.21}/soia/_impl/enums.py +0 -0
- {soia_client-1.0.20 → soia_client-1.0.21}/soia/_impl/function_maker.py +0 -0
- {soia_client-1.0.20 → soia_client-1.0.21}/soia/_impl/keyed_items.py +0 -0
- {soia_client-1.0.20 → soia_client-1.0.21}/soia/_impl/method.py +0 -0
- {soia_client-1.0.20 → soia_client-1.0.21}/soia/_impl/never.py +0 -0
- {soia_client-1.0.20 → soia_client-1.0.21}/soia/_impl/optionals.py +0 -0
- {soia_client-1.0.20 → soia_client-1.0.21}/soia/_impl/primitives.py +0 -0
- {soia_client-1.0.20 → soia_client-1.0.21}/soia/_impl/repr.py +0 -0
- {soia_client-1.0.20 → soia_client-1.0.21}/soia/_impl/serializer.py +0 -0
- {soia_client-1.0.20 → soia_client-1.0.21}/soia/_impl/serializers.py +0 -0
- {soia_client-1.0.20 → soia_client-1.0.21}/soia/_impl/service_client.py +0 -0
- {soia_client-1.0.20 → soia_client-1.0.21}/soia/_impl/structs.py +0 -0
- {soia_client-1.0.20 → soia_client-1.0.21}/soia/_impl/timestamp.py +0 -0
- {soia_client-1.0.20 → soia_client-1.0.21}/soia/_impl/type_adapter.py +0 -0
- {soia_client-1.0.20 → soia_client-1.0.21}/soia/_module_initializer.py +0 -0
- {soia_client-1.0.20 → soia_client-1.0.21}/soia/_spec.py +0 -0
- {soia_client-1.0.20 → soia_client-1.0.21}/soia/reflection.py +0 -0
- {soia_client-1.0.20 → soia_client-1.0.21}/soia_client.egg-info/SOURCES.txt +0 -0
- {soia_client-1.0.20 → soia_client-1.0.21}/soia_client.egg-info/dependency_links.txt +0 -0
- {soia_client-1.0.20 → soia_client-1.0.21}/soia_client.egg-info/top_level.txt +0 -0
- {soia_client-1.0.20 → soia_client-1.0.21}/tests/test_module_initializer.py +0 -0
- {soia_client-1.0.20 → soia_client-1.0.21}/tests/test_serializers.py +0 -0
- {soia_client-1.0.20 → soia_client-1.0.21}/tests/test_timestamp.py +0 -0
@@ -8,7 +8,7 @@ from soia._impl.serializers import (
|
|
8
8
|
optional_serializer,
|
9
9
|
primitive_serializer,
|
10
10
|
)
|
11
|
-
from soia._impl.service import
|
11
|
+
from soia._impl.service import RawServiceResponse, Service, ServiceAsync
|
12
12
|
from soia._impl.service_client import ServiceClient
|
13
13
|
from soia._impl.timestamp import Timestamp
|
14
14
|
|
@@ -18,10 +18,10 @@ __all__ = [
|
|
18
18
|
"_",
|
19
19
|
"KeyedItems",
|
20
20
|
"Method",
|
21
|
-
"
|
22
|
-
"ResponseHeaders",
|
21
|
+
"RawServiceResponse",
|
23
22
|
"Serializer",
|
24
23
|
"Service",
|
24
|
+
"ServiceAsync",
|
25
25
|
"ServiceClient",
|
26
26
|
"Timestamp",
|
27
27
|
"array_serializer",
|
@@ -0,0 +1,339 @@
|
|
1
|
+
import inspect
|
2
|
+
import json
|
3
|
+
from collections.abc import Awaitable, Callable
|
4
|
+
from dataclasses import dataclass
|
5
|
+
from typing import Any, Generic, Literal, TypeVar, Union, cast
|
6
|
+
|
7
|
+
from soia._impl.method import Method, Request, Response
|
8
|
+
|
9
|
+
RequestHeaders = TypeVar("RequestHeaders")
|
10
|
+
|
11
|
+
ResponseHeaders = TypeVar("ResponseHeaders")
|
12
|
+
|
13
|
+
|
14
|
+
@dataclass(frozen=True)
|
15
|
+
class _MethodImpl(Generic[Request, Response, RequestHeaders, ResponseHeaders]):
|
16
|
+
method: Method[Request, Response]
|
17
|
+
impl: Callable[
|
18
|
+
# Parameters
|
19
|
+
[Request, RequestHeaders, ResponseHeaders],
|
20
|
+
# Return type
|
21
|
+
Union[Response, Awaitable[Response]],
|
22
|
+
]
|
23
|
+
|
24
|
+
|
25
|
+
@dataclass(frozen=True)
|
26
|
+
class RawServiceResponse:
|
27
|
+
data: str
|
28
|
+
type: Literal["ok-json", "bad-request", "server-error"]
|
29
|
+
|
30
|
+
@property
|
31
|
+
def status_code(self):
|
32
|
+
if self.type == "ok-json":
|
33
|
+
return 200
|
34
|
+
elif self.type == "bad-request":
|
35
|
+
return 400
|
36
|
+
elif self.type == "server-error":
|
37
|
+
return 500
|
38
|
+
else:
|
39
|
+
raise TypeError(f"Unknown response type: {self.type}")
|
40
|
+
|
41
|
+
@property
|
42
|
+
def content_type(self):
|
43
|
+
if self.type == "ok-json":
|
44
|
+
return "application/json"
|
45
|
+
elif self.type == "bad-request" or self.type == "server-error":
|
46
|
+
return "text/plain; charset=utf-8"
|
47
|
+
else:
|
48
|
+
raise TypeError(f"Unknown response type: {self.type}")
|
49
|
+
|
50
|
+
|
51
|
+
@dataclass()
|
52
|
+
class _HandleRequestFlow(Generic[Request, Response, RequestHeaders, ResponseHeaders]):
|
53
|
+
req_body: str
|
54
|
+
req_headers: RequestHeaders
|
55
|
+
res_headers: ResponseHeaders
|
56
|
+
number_to_method_impl: dict[
|
57
|
+
int, _MethodImpl[Any, Any, RequestHeaders, ResponseHeaders]
|
58
|
+
]
|
59
|
+
_format: str = ""
|
60
|
+
|
61
|
+
def run(self) -> RawServiceResponse:
|
62
|
+
req_impl_pair_or_raw_response = self._parse_request()
|
63
|
+
if isinstance(req_impl_pair_or_raw_response, RawServiceResponse):
|
64
|
+
return req_impl_pair_or_raw_response
|
65
|
+
req, method_impl = req_impl_pair_or_raw_response
|
66
|
+
try:
|
67
|
+
res = method_impl.impl(req, self.req_headers, self.res_headers)
|
68
|
+
except Exception as e:
|
69
|
+
return RawServiceResponse(f"server error: {e}", "server-error")
|
70
|
+
if inspect.isawaitable(res):
|
71
|
+
raise TypeError("Method implementation must be synchronous")
|
72
|
+
return self._response_to_json(res, method_impl)
|
73
|
+
|
74
|
+
async def run_async(self) -> RawServiceResponse:
|
75
|
+
req_impl_pair_or_raw_response = self._parse_request()
|
76
|
+
if isinstance(req_impl_pair_or_raw_response, RawServiceResponse):
|
77
|
+
return req_impl_pair_or_raw_response
|
78
|
+
req, method_impl = req_impl_pair_or_raw_response
|
79
|
+
try:
|
80
|
+
res: Any = method_impl.impl(req, self.req_headers, self.res_headers)
|
81
|
+
if inspect.isawaitable(res):
|
82
|
+
res = await res
|
83
|
+
except Exception as e:
|
84
|
+
return RawServiceResponse(f"server error: {e}", "server-error")
|
85
|
+
return self._response_to_json(res, method_impl)
|
86
|
+
|
87
|
+
def _parse_request(
|
88
|
+
self,
|
89
|
+
) -> Union[
|
90
|
+
tuple[Any, _MethodImpl[Request, Response, RequestHeaders, ResponseHeaders]],
|
91
|
+
RawServiceResponse,
|
92
|
+
]:
|
93
|
+
if self.req_body == "list":
|
94
|
+
|
95
|
+
def method_to_json(method: Method) -> Any:
|
96
|
+
return {
|
97
|
+
"method": method.name,
|
98
|
+
"number": method.number,
|
99
|
+
"request": method.request_serializer.type_descriptor.as_json(),
|
100
|
+
"response": method.response_serializer.type_descriptor.as_json(),
|
101
|
+
}
|
102
|
+
|
103
|
+
json_code = json.dumps(
|
104
|
+
{
|
105
|
+
"methods": [
|
106
|
+
method_to_json(method_impl.method)
|
107
|
+
for method_impl in self.number_to_method_impl.values()
|
108
|
+
]
|
109
|
+
},
|
110
|
+
indent=2,
|
111
|
+
)
|
112
|
+
return RawServiceResponse(json_code, "ok-json")
|
113
|
+
|
114
|
+
parts = self.req_body.split(":", 3)
|
115
|
+
if len(parts) != 4:
|
116
|
+
return RawServiceResponse(
|
117
|
+
"bad request: invalid request format", "bad-request"
|
118
|
+
)
|
119
|
+
method_name = parts[0]
|
120
|
+
method_number_str = parts[1]
|
121
|
+
self.format = parts[2]
|
122
|
+
request_data = parts[3]
|
123
|
+
try:
|
124
|
+
method_number = int(method_number_str)
|
125
|
+
except Exception:
|
126
|
+
return RawServiceResponse(
|
127
|
+
"bad request: can't parse method number", "bad-request"
|
128
|
+
)
|
129
|
+
method_impl = self.number_to_method_impl.get(method_number)
|
130
|
+
if not method_impl:
|
131
|
+
return RawServiceResponse(
|
132
|
+
f"bad request: method not found: {method_name}; number: {method_number}",
|
133
|
+
"bad-request",
|
134
|
+
)
|
135
|
+
try:
|
136
|
+
req: Any = method_impl.method.request_serializer.from_json_code(
|
137
|
+
request_data
|
138
|
+
)
|
139
|
+
except Exception as e:
|
140
|
+
return RawServiceResponse(
|
141
|
+
f"bad request: can't parse JSON: {e}", "bad-request"
|
142
|
+
)
|
143
|
+
return (req, method_impl)
|
144
|
+
|
145
|
+
def _response_to_json(
|
146
|
+
self,
|
147
|
+
res: Response,
|
148
|
+
method_impl: _MethodImpl[Request, Response, RequestHeaders, ResponseHeaders],
|
149
|
+
) -> RawServiceResponse:
|
150
|
+
try:
|
151
|
+
res_json = method_impl.method.response_serializer.to_json_code(
|
152
|
+
res, readable=(self.format == "readable")
|
153
|
+
)
|
154
|
+
except Exception as e:
|
155
|
+
return RawServiceResponse(
|
156
|
+
f"server error: can't serialize response to JSON: {e}", "server-error"
|
157
|
+
)
|
158
|
+
return RawServiceResponse(res_json, "ok-json")
|
159
|
+
|
160
|
+
|
161
|
+
class _ServiceImpl(Generic[RequestHeaders, ResponseHeaders]):
|
162
|
+
_number_to_method_impl: dict[
|
163
|
+
int, _MethodImpl[Any, Any, RequestHeaders, ResponseHeaders]
|
164
|
+
]
|
165
|
+
|
166
|
+
def __init__(self):
|
167
|
+
self._number_to_method_impl = {}
|
168
|
+
|
169
|
+
def add_method(
|
170
|
+
self,
|
171
|
+
method: Method[Request, Response],
|
172
|
+
impl: Union[
|
173
|
+
# Sync
|
174
|
+
Callable[[Request], Response],
|
175
|
+
Callable[[Request, RequestHeaders], Response],
|
176
|
+
Callable[[Request, RequestHeaders, ResponseHeaders], Response],
|
177
|
+
# Async
|
178
|
+
Callable[[Request], Awaitable[Response]],
|
179
|
+
Callable[[Request, RequestHeaders], Awaitable[Response]],
|
180
|
+
Callable[[Request, RequestHeaders, ResponseHeaders], Awaitable[Response]],
|
181
|
+
],
|
182
|
+
) -> None:
|
183
|
+
signature = inspect.Signature.from_callable(impl)
|
184
|
+
num_positional_params = 0
|
185
|
+
for param in signature.parameters.values():
|
186
|
+
if param.kind in (
|
187
|
+
inspect.Parameter.POSITIONAL_OR_KEYWORD,
|
188
|
+
inspect.Parameter.POSITIONAL_ONLY,
|
189
|
+
):
|
190
|
+
num_positional_params += 1
|
191
|
+
if param.kind == inspect.Parameter.VAR_POSITIONAL:
|
192
|
+
raise ValueError("Method implementation cannot accept *args")
|
193
|
+
if num_positional_params not in range(1, 4):
|
194
|
+
raise ValueError(
|
195
|
+
"Method implementation must accept 1 to 3 positional parameters"
|
196
|
+
)
|
197
|
+
|
198
|
+
def resolved_impl(
|
199
|
+
req: Request, req_headers: RequestHeaders, res_headers: ResponseHeaders
|
200
|
+
) -> Response:
|
201
|
+
if num_positional_params == 1:
|
202
|
+
return cast(Callable[[Request], Response], impl)(req)
|
203
|
+
elif num_positional_params == 2:
|
204
|
+
return cast(Callable[[Request, RequestHeaders], Response], impl)(
|
205
|
+
req, req_headers
|
206
|
+
)
|
207
|
+
else:
|
208
|
+
return cast(
|
209
|
+
Callable[[Request, RequestHeaders, ResponseHeaders], Response], impl
|
210
|
+
)(req, req_headers, res_headers)
|
211
|
+
|
212
|
+
number = method.number
|
213
|
+
if number in self._number_to_method_impl:
|
214
|
+
raise ValueError(
|
215
|
+
f"Method with the same number already registered ({number})"
|
216
|
+
)
|
217
|
+
self._number_to_method_impl[number] = _MethodImpl(
|
218
|
+
method=method,
|
219
|
+
impl=resolved_impl,
|
220
|
+
)
|
221
|
+
|
222
|
+
def handle_request(
|
223
|
+
self,
|
224
|
+
req_body: str,
|
225
|
+
req_headers: RequestHeaders,
|
226
|
+
res_headers: ResponseHeaders,
|
227
|
+
) -> RawServiceResponse:
|
228
|
+
flow = _HandleRequestFlow(
|
229
|
+
req_body=req_body,
|
230
|
+
req_headers=req_headers,
|
231
|
+
res_headers=res_headers,
|
232
|
+
number_to_method_impl=self._number_to_method_impl,
|
233
|
+
)
|
234
|
+
return flow.run()
|
235
|
+
|
236
|
+
async def handle_request_async(
|
237
|
+
self,
|
238
|
+
req_body: str,
|
239
|
+
req_headers: RequestHeaders,
|
240
|
+
res_headers: ResponseHeaders,
|
241
|
+
) -> RawServiceResponse:
|
242
|
+
flow = _HandleRequestFlow(
|
243
|
+
req_body=req_body,
|
244
|
+
req_headers=req_headers,
|
245
|
+
res_headers=res_headers,
|
246
|
+
number_to_method_impl=self._number_to_method_impl,
|
247
|
+
)
|
248
|
+
return await flow.run_async()
|
249
|
+
|
250
|
+
|
251
|
+
class Service(Generic[RequestHeaders, ResponseHeaders]):
|
252
|
+
"""Wraps around the implementation of a soia service on the server side.
|
253
|
+
|
254
|
+
Usage: call '.add_method()' to register method implementations, then call
|
255
|
+
'.handle_request()' from the function called by your web framework when an
|
256
|
+
HTTP request is received at your service's endpoint.
|
257
|
+
|
258
|
+
Example with Flask:
|
259
|
+
|
260
|
+
from flask import Response, request
|
261
|
+
from werkzeug.datastructures import Headers
|
262
|
+
|
263
|
+
|
264
|
+
s = soia.Service[Headers, Headers]()
|
265
|
+
s.add_method(...)
|
266
|
+
s.add_method(...)
|
267
|
+
|
268
|
+
@app.route("/myapi", methods=["GET", "POST"])
|
269
|
+
def myapi():
|
270
|
+
if request.method == "POST":
|
271
|
+
req_body = request.get_data(as_text=True)
|
272
|
+
else:
|
273
|
+
query_string = request.query_string.decode("utf-8")
|
274
|
+
req_body = urllib.parse.unquote(query_string)
|
275
|
+
req_headers = request.headers
|
276
|
+
res_headers = Headers()
|
277
|
+
raw_response = s.handle_request(req_body, req_headers, res_headers)
|
278
|
+
return Response(
|
279
|
+
raw_response.data,
|
280
|
+
status=raw_response.status_code,
|
281
|
+
content_type=raw_response.content_type,
|
282
|
+
headers=res_headers,
|
283
|
+
)
|
284
|
+
"""
|
285
|
+
|
286
|
+
_impl: _ServiceImpl[RequestHeaders, ResponseHeaders]
|
287
|
+
|
288
|
+
def __init__(self):
|
289
|
+
self._impl = _ServiceImpl[RequestHeaders, ResponseHeaders]()
|
290
|
+
|
291
|
+
def add_method(
|
292
|
+
self,
|
293
|
+
method: Method[Request, Response],
|
294
|
+
impl: Union[
|
295
|
+
Callable[[Request], Response],
|
296
|
+
Callable[[Request, RequestHeaders], Response],
|
297
|
+
Callable[[Request, RequestHeaders, ResponseHeaders], Response],
|
298
|
+
],
|
299
|
+
) -> None:
|
300
|
+
self._impl.add_method(method, impl)
|
301
|
+
|
302
|
+
def handle_request(
|
303
|
+
self,
|
304
|
+
req_body: str,
|
305
|
+
req_headers: RequestHeaders,
|
306
|
+
res_headers: ResponseHeaders,
|
307
|
+
) -> RawServiceResponse:
|
308
|
+
return self._impl.handle_request(req_body, req_headers, res_headers)
|
309
|
+
|
310
|
+
|
311
|
+
class ServiceAsync(Generic[RequestHeaders, ResponseHeaders]):
|
312
|
+
_impl: _ServiceImpl[RequestHeaders, ResponseHeaders]
|
313
|
+
|
314
|
+
def __init__(self):
|
315
|
+
self._impl = _ServiceImpl[RequestHeaders, ResponseHeaders]()
|
316
|
+
|
317
|
+
def add_method(
|
318
|
+
self,
|
319
|
+
method: Method[Request, Response],
|
320
|
+
impl: Union[
|
321
|
+
# Sync
|
322
|
+
Callable[[Request], Response],
|
323
|
+
Callable[[Request, RequestHeaders], Response],
|
324
|
+
Callable[[Request, RequestHeaders, ResponseHeaders], Response],
|
325
|
+
# Async
|
326
|
+
Callable[[Request], Awaitable[Response]],
|
327
|
+
Callable[[Request, RequestHeaders], Awaitable[Response]],
|
328
|
+
Callable[[Request, RequestHeaders, ResponseHeaders], Awaitable[Response]],
|
329
|
+
],
|
330
|
+
) -> None:
|
331
|
+
self._impl.add_method(method, impl)
|
332
|
+
|
333
|
+
async def handle_request(
|
334
|
+
self,
|
335
|
+
req_body: str,
|
336
|
+
req_headers: RequestHeaders,
|
337
|
+
res_headers: ResponseHeaders,
|
338
|
+
) -> RawServiceResponse:
|
339
|
+
return await self._impl.handle_request_async(req_body, req_headers, res_headers)
|
@@ -1,205 +0,0 @@
|
|
1
|
-
import inspect
|
2
|
-
import json
|
3
|
-
from dataclasses import dataclass
|
4
|
-
from typing import Any, Callable, Generic, Literal, TypeVar, Union, cast
|
5
|
-
|
6
|
-
from soia._impl.method import Method, Request, Response
|
7
|
-
|
8
|
-
RequestHeaders = TypeVar("RequestHeaders")
|
9
|
-
|
10
|
-
ResponseHeaders = TypeVar("ResponseHeaders")
|
11
|
-
|
12
|
-
|
13
|
-
class Service(Generic[RequestHeaders, ResponseHeaders]):
|
14
|
-
"""Wraps around the implementation of a soia service on the server side.
|
15
|
-
|
16
|
-
Usage: call '.add_method()' to register method implementations, then call
|
17
|
-
'.handle_request()' from the function called by your web framework when an
|
18
|
-
HTTP request is received at your service's endpoint.
|
19
|
-
|
20
|
-
Example with Flask:
|
21
|
-
|
22
|
-
from flask import Response, request
|
23
|
-
from werkzeug.datastructures import Headers
|
24
|
-
|
25
|
-
|
26
|
-
s = soia.Service[Headers, Headers]()
|
27
|
-
s.add_method(...)
|
28
|
-
s.add_method(...)
|
29
|
-
|
30
|
-
@app.route("/myapi", methods=["GET", "POST"])
|
31
|
-
def myapi():
|
32
|
-
if request.method == "POST":
|
33
|
-
req_body = request.get_data(as_text=True)
|
34
|
-
else:
|
35
|
-
query_string = request.query_string.decode("utf-8")
|
36
|
-
req_body = urllib.parse.unquote(query_string)
|
37
|
-
req_headers = request.headers
|
38
|
-
res_headers = Headers()
|
39
|
-
raw_response = s.handle_request(req_body, req_headers, res_headers)
|
40
|
-
return Response(
|
41
|
-
raw_response.data,
|
42
|
-
status=raw_response.status_code,
|
43
|
-
content_type=raw_response.content_type,
|
44
|
-
headers=res_headers,
|
45
|
-
)
|
46
|
-
"""
|
47
|
-
|
48
|
-
_number_to_method_impl: dict[int, "_MethodImpl"]
|
49
|
-
|
50
|
-
def __init__(self):
|
51
|
-
self._number_to_method_impl = {}
|
52
|
-
|
53
|
-
def add_method(
|
54
|
-
self,
|
55
|
-
method: Method[Request, Response],
|
56
|
-
impl: Union[
|
57
|
-
Callable[[Request], Response],
|
58
|
-
Callable[[Request, RequestHeaders], Response],
|
59
|
-
Callable[[Request, RequestHeaders, ResponseHeaders], Response],
|
60
|
-
],
|
61
|
-
) -> "Service":
|
62
|
-
signature = inspect.Signature.from_callable(impl)
|
63
|
-
num_positional_params = 0
|
64
|
-
for param in signature.parameters.values():
|
65
|
-
if param.kind in (
|
66
|
-
inspect.Parameter.POSITIONAL_OR_KEYWORD,
|
67
|
-
inspect.Parameter.POSITIONAL_ONLY,
|
68
|
-
):
|
69
|
-
num_positional_params += 1
|
70
|
-
if param.kind == inspect.Parameter.VAR_POSITIONAL:
|
71
|
-
raise ValueError("Method implementation cannot accept *args")
|
72
|
-
if num_positional_params not in range(1, 4):
|
73
|
-
raise ValueError(
|
74
|
-
"Method implementation must accept 1 to 3 positional parameters"
|
75
|
-
)
|
76
|
-
|
77
|
-
def resolved_impl(
|
78
|
-
req: Request, req_headers: RequestHeaders, res_headers: ResponseHeaders
|
79
|
-
) -> Response:
|
80
|
-
if num_positional_params == 1:
|
81
|
-
return cast(Callable[[Request], Response], impl)(req)
|
82
|
-
elif num_positional_params == 2:
|
83
|
-
return cast(Callable[[Request, RequestHeaders], Response], impl)(
|
84
|
-
req, req_headers
|
85
|
-
)
|
86
|
-
else:
|
87
|
-
return cast(
|
88
|
-
Callable[[Request, RequestHeaders, ResponseHeaders], Response], impl
|
89
|
-
)(req, req_headers, res_headers)
|
90
|
-
|
91
|
-
number = method.number
|
92
|
-
if number in self._number_to_method_impl:
|
93
|
-
raise ValueError(
|
94
|
-
f"Method with the same number already registered ({number})"
|
95
|
-
)
|
96
|
-
self._number_to_method_impl[number] = _MethodImpl(
|
97
|
-
method=method,
|
98
|
-
impl=resolved_impl,
|
99
|
-
)
|
100
|
-
return self
|
101
|
-
|
102
|
-
@dataclass(frozen=True)
|
103
|
-
class RawResponse:
|
104
|
-
data: str
|
105
|
-
type: Literal["ok-json", "bad-request", "server-error"]
|
106
|
-
|
107
|
-
@property
|
108
|
-
def status_code(self):
|
109
|
-
if self.type == "ok-json":
|
110
|
-
return 200
|
111
|
-
elif self.type == "bad-request":
|
112
|
-
return 400
|
113
|
-
elif self.type == "server-error":
|
114
|
-
return 500
|
115
|
-
else:
|
116
|
-
raise TypeError(f"Unknown response type: {self.type}")
|
117
|
-
|
118
|
-
@property
|
119
|
-
def content_type(self):
|
120
|
-
if self.type == "ok-json":
|
121
|
-
return "application/json"
|
122
|
-
elif self.type == "bad-request" or self.type == "server-error":
|
123
|
-
return "text/plain; charset=utf-8"
|
124
|
-
else:
|
125
|
-
raise TypeError(f"Unknown response type: {self.type}")
|
126
|
-
|
127
|
-
def handle_request(
|
128
|
-
self,
|
129
|
-
req_body: str,
|
130
|
-
req_headers: RequestHeaders,
|
131
|
-
res_headers: ResponseHeaders,
|
132
|
-
) -> RawResponse:
|
133
|
-
if req_body == "list":
|
134
|
-
|
135
|
-
def method_to_json(method: Method) -> Any:
|
136
|
-
return {
|
137
|
-
"method": method.name,
|
138
|
-
"number": method.number,
|
139
|
-
"request": method.request_serializer.type_descriptor.as_json(),
|
140
|
-
"response": method.response_serializer.type_descriptor.as_json(),
|
141
|
-
}
|
142
|
-
|
143
|
-
json_code = json.dumps(
|
144
|
-
{
|
145
|
-
"methods": [
|
146
|
-
method_to_json(method_impl.method)
|
147
|
-
for method_impl in self._number_to_method_impl.values()
|
148
|
-
]
|
149
|
-
},
|
150
|
-
indent=2,
|
151
|
-
)
|
152
|
-
return self.RawResponse(json_code, "ok-json")
|
153
|
-
|
154
|
-
parts = req_body.split(":", 3)
|
155
|
-
if len(parts) != 4:
|
156
|
-
return self.RawResponse(
|
157
|
-
"bad request: invalid request format", "bad-request"
|
158
|
-
)
|
159
|
-
method_name = parts[0]
|
160
|
-
method_number_str = parts[1]
|
161
|
-
format = parts[2]
|
162
|
-
request_data = parts[3]
|
163
|
-
try:
|
164
|
-
method_number = int(method_number_str)
|
165
|
-
except Exception:
|
166
|
-
return self.RawResponse(
|
167
|
-
"bad request: can't parse method number", "bad-request"
|
168
|
-
)
|
169
|
-
method_impl = self._number_to_method_impl.get(method_number)
|
170
|
-
if not method_impl:
|
171
|
-
return self.RawResponse(
|
172
|
-
f"bad request: method not found: {method_name}; number: {method_number}",
|
173
|
-
"bad-request",
|
174
|
-
)
|
175
|
-
|
176
|
-
try:
|
177
|
-
req: Any = method_impl.method.request_serializer.from_json_code(
|
178
|
-
request_data
|
179
|
-
)
|
180
|
-
except Exception as e:
|
181
|
-
return self.RawResponse(
|
182
|
-
f"bad request: can't parse JSON: {e}", "bad-request"
|
183
|
-
)
|
184
|
-
|
185
|
-
try:
|
186
|
-
res: Any = method_impl.impl(req, req_headers, res_headers)
|
187
|
-
except Exception as e:
|
188
|
-
return self.RawResponse(f"server error: {e}", "server-error")
|
189
|
-
|
190
|
-
try:
|
191
|
-
res_json = method_impl.method.response_serializer.to_json_code(
|
192
|
-
res, readable=(format == "readable")
|
193
|
-
)
|
194
|
-
except Exception as e:
|
195
|
-
return self.RawResponse(
|
196
|
-
f"server error: can't serialize response to JSON: {e}", "server-error"
|
197
|
-
)
|
198
|
-
|
199
|
-
return self.RawResponse(res_json, "ok-json")
|
200
|
-
|
201
|
-
|
202
|
-
@dataclass(frozen=True)
|
203
|
-
class _MethodImpl(Generic[Request, Response, RequestHeaders, ResponseHeaders]):
|
204
|
-
method: Method[Request, Response]
|
205
|
-
impl: Callable[[Request, RequestHeaders, ResponseHeaders], Response]
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|