Skip to content

Commit 8abce23

Browse files
committed
Reworked and Added tests. Fixed TornadoRouter and changed status code for response with error 422 -> 500
1 parent 3f42459 commit 8abce23

40 files changed

+2857
-1361
lines changed

examples/sanic/run.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import uvicorn
21
from app.api.routes import api_router
32
from sanic import Sanic
43

@@ -18,4 +17,4 @@
1817

1918

2019
if __name__ == "__main__":
21-
uvicorn.run(app, host="127.0.0.1", port=8000)
20+
app.run(host="0.0.0.0", port=8000)

fastopenapi/base_router.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -132,9 +132,8 @@ def generate_openapi(self) -> dict:
132132
info = {
133133
"title": self.title,
134134
"version": self.version,
135+
"description": self.description,
135136
}
136-
if self.description:
137-
info["description"] = self.description
138137

139138
schema = {
140139
"openapi": self.openapi_version,
@@ -183,9 +182,6 @@ def _build_parameters_and_body(
183182
path_params = {match.group(1) for match in re.finditer(r"{(\w+)}", route_path)}
184183

185184
for param_name, param in sig.parameters.items():
186-
if param.annotation is inspect.Parameter.empty:
187-
continue
188-
189185
if isinstance(param.annotation, type) and issubclass(
190186
param.annotation, BaseModel
191187
):
@@ -243,6 +239,8 @@ def _build_responses(self, meta: dict, definitions: dict, status_code: str) -> d
243239
responses[status_code]["content"] = {
244240
"application/json": {"schema": resp_model_schema}
245241
}
242+
else:
243+
raise Exception("Incorrect response_model")
246244
return responses
247245

248246
def _register_docs_endpoints(self):

fastopenapi/routers/falcon.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ async def _handle_request(self, endpoint, req, resp, **path_params):
7676
try:
7777
kwargs = self.resolve_endpoint_params(endpoint, all_params, body)
7878
except Exception as e:
79-
return self._handle_error(resp, str(e))
79+
return self._handle_request_error(resp, str(e))
8080
try:
8181
if inspect.iscoroutinefunction(endpoint):
8282
result = await endpoint(**kwargs)
@@ -85,7 +85,7 @@ async def _handle_request(self, endpoint, req, resp, **path_params):
8585
except Exception as e:
8686
if isinstance(e, falcon.HTTPError):
8787
raise
88-
return self._handle_error(resp, str(e))
88+
return self._handle_response_error(resp, str(e))
8989
resp.status = get_falcon_status(status_code)
9090
result = self._serialize_response(result)
9191
resp.media = result
@@ -99,10 +99,14 @@ async def _read_body(self, req):
9999
pass
100100
return {}
101101

102-
def _handle_error(self, resp, error_message: str):
102+
def _handle_request_error(self, resp, error_message: str):
103103
resp.status = falcon.HTTP_422
104104
resp.media = {"detail": error_message}
105105

106+
def _handle_response_error(self, resp, error_message: str):
107+
resp.status = falcon.HTTP_500
108+
resp.media = {"detail": error_message}
109+
106110
def _register_docs_endpoints(self):
107111
outer = self
108112

fastopenapi/routers/flask.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ def view_func(**path_params):
3636
except Exception as e:
3737
if isinstance(e, HTTPException):
3838
return self.handle_http_exception(e)
39-
return jsonify({"detail": str(e)}), 422
39+
return jsonify({"detail": str(e)}), 500
4040

4141
meta = getattr(endpoint, "__route_meta__", {})
4242
status_code = meta.get("status_code", 200)

fastopenapi/routers/quart.py

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import inspect
21
import re
32
from collections.abc import Callable
43

@@ -33,14 +32,11 @@ async def view_func(**path_params):
3332
except Exception as e:
3433
return jsonify({"detail": str(e)}), 422
3534
try:
36-
if inspect.iscoroutinefunction(endpoint):
37-
result = await endpoint(**kwargs)
38-
else:
39-
result = endpoint(**kwargs)
35+
result = await endpoint(**kwargs)
4036
except Exception as e:
4137
if isinstance(e, HTTPException):
4238
return await self.handle_http_exception(e)
43-
return jsonify({"detail": str(e)}), 422
39+
return jsonify({"detail": str(e)}), 500
4440

4541
meta = getattr(endpoint, "__route_meta__", {})
4642
status_code = meta.get("status_code", 200)

fastopenapi/routers/sanic.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ async def view_func(request, **path_params):
4848
except Exception as e:
4949
if isinstance(e, HTTPException):
5050
return await self.handle_exceptions(request, e)
51-
return response.json({"detail": str(e)}, status=422)
51+
return response.json({"detail": str(e)}, status=500)
5252

5353
meta = getattr(endpoint, "__route_meta__", {})
5454
status_code = meta.get("status_code", 200)

fastopenapi/routers/starlette.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ async def _starlette_view(cls, request, router, endpoint):
5050
except Exception as e:
5151
if isinstance(e, HTTPException):
5252
return await cls.handle_exceptions(request, e)
53-
return JSONResponse({"detail": str(e)}, status_code=422)
53+
return JSONResponse({"detail": str(e)}, status_code=500)
5454

5555
meta = getattr(endpoint, "__route_meta__", {})
5656
status_code = meta.get("status_code", 200)

fastopenapi/routers/tornado.py

Lines changed: 43 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ class TornadoDynamicHandler(RequestHandler):
2020
"""
2121

2222
def initialize(self, **kwargs):
23-
self.endpoint = kwargs.get("endpoint")
23+
self.endpoints = kwargs.get("endpoints", {})
2424
self.router = kwargs.get("router")
2525

2626
async def prepare(self):
@@ -31,18 +31,23 @@ async def prepare(self):
3131
self.json_body = {}
3232
else:
3333
self.json_body = {}
34+
self.endpoint = self.endpoints.get(self.request.method.upper())
3435

3536
async def handle_http_exception(self, e):
3637
self.set_status(e.status_code)
3738
await self.finish(json_encode({"detail": str(e.log_message)}))
3839

3940
async def handle_request(self):
41+
if not hasattr(self, "endpoint") or not self.endpoint:
42+
self.send_error(405)
43+
return
44+
4045
query_params = {
4146
k: self.get_query_argument(k) for k in self.request.query_arguments
4247
}
4348

4449
all_params = {**self.path_kwargs, **query_params}
45-
body = self.json_body
50+
body = getattr(self, "json_body", {})
4651
try:
4752
resolved_kwargs = self.router.resolve_endpoint_params(
4853
self.endpoint, all_params, body
@@ -60,15 +65,18 @@ async def handle_request(self):
6065
if isinstance(e, HTTPError):
6166
await self.handle_http_exception(e)
6267
return
63-
self.set_status(422)
68+
self.set_status(500)
6469
await self.finish(json_encode({"detail": str(e)}))
6570
return
6671
meta = getattr(self.endpoint, "__route_meta__", {})
6772
status_code = meta.get("status_code", 200)
6873
result = self.router._serialize_response(result)
6974
self.set_status(status_code)
7075
self.set_header("Content-Type", "application/json")
71-
await self.finish(json_encode(result))
76+
if status_code == 204:
77+
await self.finish()
78+
else:
79+
await self.finish(json_encode(result))
7280

7381
async def get(self, *args, **kwargs):
7482
await self.handle_request()
@@ -89,6 +97,8 @@ async def delete(self, *args, **kwargs):
8997
class TornadoRouter(BaseRouter):
9098
def __init__(self, app: Application = None, **kwargs):
9199
self.routes = []
100+
self._endpoint_map: dict[str, dict[str, Callable]] = {}
101+
self._registered_paths: set[str] = set()
92102
super().__init__(app, **kwargs)
93103
if self.app is not None and (self.add_docs_route or self.add_openapi_route):
94104
self._register_docs_endpoints()
@@ -98,57 +108,57 @@ def add_route(self, path: str, method: str, endpoint: Callable):
98108

99109
tornado_path = re.sub(r"{(\w+)}", r"(?P<\1>[^/]+)", path)
100110

101-
spec = url(
102-
tornado_path,
103-
TornadoDynamicHandler,
104-
name=endpoint.__name__,
105-
kwargs={"endpoint": endpoint, "router": self},
106-
)
107-
self.routes.append(spec)
108-
if self.app is not None:
109-
self.app.add_handlers(r".*", [spec])
111+
if tornado_path not in self._endpoint_map:
112+
self._endpoint_map[tornado_path] = {}
113+
self._endpoint_map[tornado_path][method.upper()] = endpoint
114+
115+
if tornado_path not in self._registered_paths:
116+
self._registered_paths.add(tornado_path)
117+
spec = url(
118+
tornado_path,
119+
TornadoDynamicHandler,
120+
name=f"route_{len(self._registered_paths)}",
121+
kwargs={"endpoints": self._endpoint_map[tornado_path], "router": self},
122+
)
123+
self.routes.append(spec)
124+
if self.app is not None:
125+
self.app.add_handlers(r".*", [spec])
126+
else:
127+
for rule in self.routes:
128+
if rule.matcher.regex.pattern == f"{tornado_path}$":
129+
rule.target_kwargs["endpoints"] = self._endpoint_map[tornado_path]
130+
break
110131

111132
def _register_docs_endpoints(self):
112133
router = self
113134

114-
class OpenAPIHandler(TornadoDynamicHandler):
135+
class OpenAPIHandler(RequestHandler):
115136
async def get(self):
116137
self.set_header("Content-Type", "application/json")
117-
self.write(self.router.openapi)
138+
self.write(json_encode(router.openapi))
118139
await self.finish()
119140

120-
class SwaggerUIHandler(TornadoDynamicHandler):
141+
class SwaggerUIHandler(RequestHandler):
121142
async def get(self):
122-
html = self.router.render_swagger_ui(self.router.openapi_url)
143+
html = router.render_swagger_ui(router.openapi_url)
123144
self.set_header("Content-Type", "text/html")
124145
self.write(html)
125146
await self.finish()
126147

127-
class RedocUIHandler(TornadoDynamicHandler):
148+
class RedocUIHandler(RequestHandler):
128149
async def get(self):
129-
html = self.router.render_redoc_ui(self.router.openapi_url)
150+
html = router.render_redoc_ui(router.openapi_url)
130151
self.set_header("Content-Type", "text/html")
131152
self.write(html)
132153
await self.finish()
133154

134155
spec_openapi = url(
135-
self.openapi_url,
136-
OpenAPIHandler,
137-
name="openapi-schema",
138-
kwargs={"router": router},
156+
self.openapi_url, OpenAPIHandler, name="openapi-schema", kwargs={}
139157
)
140158
spec_swagger = url(
141-
self.docs_url,
142-
SwaggerUIHandler,
143-
name="swagger-ui",
144-
kwargs={"router": router},
145-
)
146-
spec_redoc = url(
147-
self.redoc_url,
148-
RedocUIHandler,
149-
name="redoc-ui",
150-
kwargs={"router": router},
159+
self.docs_url, SwaggerUIHandler, name="swagger-ui", kwargs={}
151160
)
161+
spec_redoc = url(self.redoc_url, RedocUIHandler, name="redoc-ui", kwargs={})
152162
self.routes.extend([spec_openapi, spec_swagger, spec_redoc])
153163
if self.app is not None:
154164
self.app.add_handlers(r".*", [spec_openapi, spec_swagger, spec_redoc])

tests/conftest.py

Lines changed: 0 additions & 44 deletions
This file was deleted.

tests/falcon/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)