wellapi 0.2.1__py3-none-any.whl
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.
- wellapi/__init__.py +5 -0
- wellapi/__main__.py +3 -0
- wellapi/applications.py +389 -0
- wellapi/awsmodel.py +17 -0
- wellapi/build/__init__.py +0 -0
- wellapi/build/cdk.py +141 -0
- wellapi/build/packager.py +82 -0
- wellapi/build/sam_openapi.py +10 -0
- wellapi/cli/__init__.py +0 -0
- wellapi/cli/main.py +67 -0
- wellapi/convertors.py +89 -0
- wellapi/datastructures.py +383 -0
- wellapi/dependencies/__init__.py +0 -0
- wellapi/dependencies/models.py +138 -0
- wellapi/dependencies/utils.py +923 -0
- wellapi/exceptions.py +53 -0
- wellapi/local/__init__.py +0 -0
- wellapi/local/reloader.py +94 -0
- wellapi/local/router.py +116 -0
- wellapi/local/server.py +154 -0
- wellapi/middleware/__init__.py +0 -0
- wellapi/middleware/base.py +18 -0
- wellapi/middleware/error.py +239 -0
- wellapi/middleware/exceptions.py +74 -0
- wellapi/middleware/main.py +26 -0
- wellapi/models.py +150 -0
- wellapi/openapi/__init__.py +0 -0
- wellapi/openapi/docs.py +344 -0
- wellapi/openapi/models.py +404 -0
- wellapi/openapi/utils.py +535 -0
- wellapi/params.py +481 -0
- wellapi/routing.py +248 -0
- wellapi/security.py +82 -0
- wellapi/utils.py +37 -0
- wellapi-0.2.1.dist-info/METADATA +32 -0
- wellapi-0.2.1.dist-info/RECORD +38 -0
- wellapi-0.2.1.dist-info/WHEEL +4 -0
- wellapi-0.2.1.dist-info/entry_points.txt +2 -0
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import typing
|
|
4
|
+
|
|
5
|
+
from wellapi.exceptions import HTTPException
|
|
6
|
+
from wellapi.models import RequestAPIGateway, ResponseAPIGateway
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def _lookup_exception_handler(exc_handlers, exc: Exception):
|
|
10
|
+
for cls in type(exc).__mro__:
|
|
11
|
+
if cls in exc_handlers:
|
|
12
|
+
return exc_handlers[cls]
|
|
13
|
+
return None
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class ExceptionMiddleware:
|
|
17
|
+
def __init__(
|
|
18
|
+
self,
|
|
19
|
+
next_call: typing.Callable,
|
|
20
|
+
handlers: typing.Mapping[
|
|
21
|
+
typing.Any,
|
|
22
|
+
typing.Callable[[RequestAPIGateway, Exception], ResponseAPIGateway],
|
|
23
|
+
]
|
|
24
|
+
| None = None,
|
|
25
|
+
debug: bool = False,
|
|
26
|
+
) -> None:
|
|
27
|
+
self.next_call = next_call
|
|
28
|
+
self.debug = debug # TODO: We ought to handle 404 cases if debug is set.
|
|
29
|
+
self._status_handlers = {}
|
|
30
|
+
self._exception_handlers = {
|
|
31
|
+
HTTPException: self.http_exception,
|
|
32
|
+
}
|
|
33
|
+
if handlers is not None: # pragma: no branch
|
|
34
|
+
for key, value in handlers.items():
|
|
35
|
+
self.add_exception_handler(key, value)
|
|
36
|
+
|
|
37
|
+
def add_exception_handler(
|
|
38
|
+
self,
|
|
39
|
+
exc_class_or_status_code: int | type[Exception],
|
|
40
|
+
handler: typing.Callable[[RequestAPIGateway, Exception], ResponseAPIGateway],
|
|
41
|
+
) -> None:
|
|
42
|
+
if isinstance(exc_class_or_status_code, int):
|
|
43
|
+
self._status_handlers[exc_class_or_status_code] = handler
|
|
44
|
+
else:
|
|
45
|
+
assert issubclass(exc_class_or_status_code, Exception)
|
|
46
|
+
self._exception_handlers[exc_class_or_status_code] = handler
|
|
47
|
+
|
|
48
|
+
def __call__(self, request: RequestAPIGateway) -> ResponseAPIGateway:
|
|
49
|
+
try:
|
|
50
|
+
response = self.next_call(request)
|
|
51
|
+
except Exception as exc:
|
|
52
|
+
handler = None
|
|
53
|
+
|
|
54
|
+
if isinstance(exc, HTTPException):
|
|
55
|
+
handler = self._status_handlers.get(exc.status_code)
|
|
56
|
+
|
|
57
|
+
if handler is None:
|
|
58
|
+
handler = _lookup_exception_handler(self._exception_handlers, exc)
|
|
59
|
+
|
|
60
|
+
if handler is None:
|
|
61
|
+
raise exc
|
|
62
|
+
|
|
63
|
+
response = handler(request, exc)
|
|
64
|
+
|
|
65
|
+
return response
|
|
66
|
+
|
|
67
|
+
def http_exception(
|
|
68
|
+
self, _: RequestAPIGateway, exc: Exception
|
|
69
|
+
) -> ResponseAPIGateway:
|
|
70
|
+
assert isinstance(exc, HTTPException)
|
|
71
|
+
|
|
72
|
+
return ResponseAPIGateway(
|
|
73
|
+
exc.detail, status_code=exc.status_code, headers=exc.headers
|
|
74
|
+
)
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
from collections.abc import Iterator
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class Middleware:
|
|
6
|
+
def __init__(
|
|
7
|
+
self,
|
|
8
|
+
cls,
|
|
9
|
+
*args,
|
|
10
|
+
**kwargs,
|
|
11
|
+
) -> None:
|
|
12
|
+
self.cls = cls
|
|
13
|
+
self.args = args
|
|
14
|
+
self.kwargs = kwargs
|
|
15
|
+
|
|
16
|
+
def __iter__(self) -> Iterator[Any]:
|
|
17
|
+
as_tuple = (self.cls, self.args, self.kwargs)
|
|
18
|
+
return iter(as_tuple)
|
|
19
|
+
|
|
20
|
+
def __repr__(self) -> str:
|
|
21
|
+
class_name = self.__class__.__name__
|
|
22
|
+
args_strings = [f"{value!r}" for value in self.args]
|
|
23
|
+
option_strings = [f"{key}={value!r}" for key, value in self.kwargs.items()]
|
|
24
|
+
name = getattr(self.cls, "__name__", "")
|
|
25
|
+
args_repr = ", ".join([name] + args_strings + option_strings)
|
|
26
|
+
return f"{class_name}({args_repr})"
|
wellapi/models.py
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import typing
|
|
3
|
+
|
|
4
|
+
from wellapi.datastructures import Headers, MutableHeaders, QueryParams
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class RequestAPIGateway:
|
|
8
|
+
"""
|
|
9
|
+
https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html#http-api-develop-integrations-lambda.proxy-format
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
def __init__(self, path_params, query_params, headers, body, cookies):
|
|
13
|
+
self.path_params = path_params
|
|
14
|
+
self.query_params = QueryParams(query_params)
|
|
15
|
+
self.headers = Headers(raw=headers)
|
|
16
|
+
self.cookies = cookies
|
|
17
|
+
self._body = body
|
|
18
|
+
|
|
19
|
+
def json(self):
|
|
20
|
+
"""
|
|
21
|
+
Returns the request body as JSON.
|
|
22
|
+
"""
|
|
23
|
+
if not self._body:
|
|
24
|
+
return None
|
|
25
|
+
|
|
26
|
+
return json.loads(self._body)
|
|
27
|
+
|
|
28
|
+
@classmethod
|
|
29
|
+
def create_request_from_event(cls, event):
|
|
30
|
+
"""
|
|
31
|
+
Create a RequestAPIGateway object from the AWS API Gateway event.
|
|
32
|
+
"""
|
|
33
|
+
path_params = event.get("pathParameters", {})
|
|
34
|
+
multi_query_params = event.get("multiValueQueryStringParameters", {})
|
|
35
|
+
multi_headers = event.get("multiValueHeaders", {})
|
|
36
|
+
body = event.get("body", "")
|
|
37
|
+
cookies = event.get("cookies", {})
|
|
38
|
+
|
|
39
|
+
headers = []
|
|
40
|
+
for h_name, h_value in multi_headers.items():
|
|
41
|
+
if isinstance(h_value, list):
|
|
42
|
+
for h in h_value:
|
|
43
|
+
headers.append(
|
|
44
|
+
(h_name.lower().encode("latin-1"), h.encode("latin-1"))
|
|
45
|
+
)
|
|
46
|
+
else:
|
|
47
|
+
headers.append(
|
|
48
|
+
(h_name.lower().encode("latin-1"), h_value.encode("latin-1"))
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
query_params = []
|
|
52
|
+
for q_name, q_value in multi_query_params.items():
|
|
53
|
+
if isinstance(q_value, list):
|
|
54
|
+
for q in q_value:
|
|
55
|
+
query_params.append((q_name, q))
|
|
56
|
+
else:
|
|
57
|
+
query_params.append((q_name, q_value))
|
|
58
|
+
|
|
59
|
+
return cls(path_params, query_params, headers, body, cookies)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class ResponseAPIGateway:
|
|
63
|
+
isBase64Encoded: bool = False
|
|
64
|
+
|
|
65
|
+
def __init__(
|
|
66
|
+
self,
|
|
67
|
+
content: typing.Any = None,
|
|
68
|
+
status_code: int = 200,
|
|
69
|
+
headers: typing.Mapping[str, str] | None = None,
|
|
70
|
+
) -> None:
|
|
71
|
+
self.statusCode = status_code
|
|
72
|
+
self.body = content
|
|
73
|
+
self.raw_headers = headers
|
|
74
|
+
|
|
75
|
+
@property
|
|
76
|
+
def headers(self) -> MutableHeaders:
|
|
77
|
+
if not hasattr(self, "_headers"):
|
|
78
|
+
self._headers = MutableHeaders(headers=self.raw_headers)
|
|
79
|
+
return self._headers
|
|
80
|
+
|
|
81
|
+
def to_aws_response(self):
|
|
82
|
+
if isinstance(self.body, str):
|
|
83
|
+
body = self.body
|
|
84
|
+
else:
|
|
85
|
+
body = json.dumps(self.body)
|
|
86
|
+
|
|
87
|
+
return {
|
|
88
|
+
"statusCode": self.statusCode,
|
|
89
|
+
"headers": dict(self.headers),
|
|
90
|
+
"body": body,
|
|
91
|
+
"isBase64Encoded": self.isBase64Encoded,
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
class RequestSQS:
|
|
96
|
+
"""
|
|
97
|
+
https://docs.aws.amazon.com/lambda/latest/dg/with-sqs-example.html#with-sqs-create-test-function
|
|
98
|
+
"""
|
|
99
|
+
|
|
100
|
+
def __init__(self, records: list[dict[str, typing.Any]]):
|
|
101
|
+
self._records = records
|
|
102
|
+
self.path_params = None
|
|
103
|
+
self.query_params = None
|
|
104
|
+
self.headers = None
|
|
105
|
+
self.cookies = None
|
|
106
|
+
|
|
107
|
+
@classmethod
|
|
108
|
+
def create_request_from_event(cls, event):
|
|
109
|
+
"""
|
|
110
|
+
Create a RequestAPIGateway object from the AWS API Gateway event.
|
|
111
|
+
"""
|
|
112
|
+
|
|
113
|
+
return cls(event["Records"])
|
|
114
|
+
|
|
115
|
+
def json(self):
|
|
116
|
+
"""
|
|
117
|
+
Returns the request body as JSON.
|
|
118
|
+
"""
|
|
119
|
+
if not self._records:
|
|
120
|
+
return None
|
|
121
|
+
|
|
122
|
+
body = []
|
|
123
|
+
for record in self._records:
|
|
124
|
+
body.append(json.loads(record.get("body")))
|
|
125
|
+
|
|
126
|
+
return body
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
class RequestJob:
|
|
130
|
+
"""
|
|
131
|
+
https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-run-lambda-schedule.html#eb-schedule-create-rule
|
|
132
|
+
"""
|
|
133
|
+
|
|
134
|
+
def __init__(self, event: dict[str, typing.Any]):
|
|
135
|
+
self._event = event
|
|
136
|
+
self.path_params = None
|
|
137
|
+
self.query_params = None
|
|
138
|
+
self.headers = None
|
|
139
|
+
self.cookies = None
|
|
140
|
+
|
|
141
|
+
@classmethod
|
|
142
|
+
def create_request_from_event(cls, event):
|
|
143
|
+
"""
|
|
144
|
+
Create a RequestAPIGateway object from the AWS API Gateway event.
|
|
145
|
+
"""
|
|
146
|
+
|
|
147
|
+
return cls(event)
|
|
148
|
+
|
|
149
|
+
def json(self):
|
|
150
|
+
return None
|
|
File without changes
|
wellapi/openapi/docs.py
ADDED
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from typing import Annotated, Any
|
|
3
|
+
|
|
4
|
+
from fastapi.encoders import jsonable_encoder
|
|
5
|
+
from starlette.responses import HTMLResponse
|
|
6
|
+
from typing_extensions import Doc
|
|
7
|
+
|
|
8
|
+
swagger_ui_default_parameters: Annotated[
|
|
9
|
+
dict[str, Any],
|
|
10
|
+
Doc(
|
|
11
|
+
"""
|
|
12
|
+
Default configurations for Swagger UI.
|
|
13
|
+
|
|
14
|
+
You can use it as a template to add any other configurations needed.
|
|
15
|
+
"""
|
|
16
|
+
),
|
|
17
|
+
] = {
|
|
18
|
+
"dom_id": "#swagger-ui",
|
|
19
|
+
"layout": "BaseLayout",
|
|
20
|
+
"deepLinking": True,
|
|
21
|
+
"showExtensions": True,
|
|
22
|
+
"showCommonExtensions": True,
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def get_swagger_ui_html(
|
|
27
|
+
*,
|
|
28
|
+
openapi_url: Annotated[
|
|
29
|
+
str,
|
|
30
|
+
Doc(
|
|
31
|
+
"""
|
|
32
|
+
The OpenAPI URL that Swagger UI should load and use.
|
|
33
|
+
|
|
34
|
+
This is normally done automatically by FastAPI using the default URL
|
|
35
|
+
`/openapi.json`.
|
|
36
|
+
"""
|
|
37
|
+
),
|
|
38
|
+
],
|
|
39
|
+
title: Annotated[
|
|
40
|
+
str,
|
|
41
|
+
Doc(
|
|
42
|
+
"""
|
|
43
|
+
The HTML `<title>` content, normally shown in the browser tab.
|
|
44
|
+
"""
|
|
45
|
+
),
|
|
46
|
+
],
|
|
47
|
+
swagger_js_url: Annotated[
|
|
48
|
+
str,
|
|
49
|
+
Doc(
|
|
50
|
+
"""
|
|
51
|
+
The URL to use to load the Swagger UI JavaScript.
|
|
52
|
+
|
|
53
|
+
It is normally set to a CDN URL.
|
|
54
|
+
"""
|
|
55
|
+
),
|
|
56
|
+
] = "https://cdn.jsdelivr.net/npm/swagger-ui-dist@5/swagger-ui-bundle.js",
|
|
57
|
+
swagger_css_url: Annotated[
|
|
58
|
+
str,
|
|
59
|
+
Doc(
|
|
60
|
+
"""
|
|
61
|
+
The URL to use to load the Swagger UI CSS.
|
|
62
|
+
|
|
63
|
+
It is normally set to a CDN URL.
|
|
64
|
+
"""
|
|
65
|
+
),
|
|
66
|
+
] = "https://cdn.jsdelivr.net/npm/swagger-ui-dist@5/swagger-ui.css",
|
|
67
|
+
swagger_favicon_url: Annotated[
|
|
68
|
+
str,
|
|
69
|
+
Doc(
|
|
70
|
+
"""
|
|
71
|
+
The URL of the favicon to use. It is normally shown in the browser tab.
|
|
72
|
+
"""
|
|
73
|
+
),
|
|
74
|
+
] = "https://fastapi.tiangolo.com/img/favicon.png",
|
|
75
|
+
oauth2_redirect_url: Annotated[
|
|
76
|
+
str | None,
|
|
77
|
+
Doc(
|
|
78
|
+
"""
|
|
79
|
+
The OAuth2 redirect URL, it is normally automatically handled by FastAPI.
|
|
80
|
+
"""
|
|
81
|
+
),
|
|
82
|
+
] = None,
|
|
83
|
+
init_oauth: Annotated[
|
|
84
|
+
dict[str, Any] | None,
|
|
85
|
+
Doc(
|
|
86
|
+
"""
|
|
87
|
+
A dictionary with Swagger UI OAuth2 initialization configurations.
|
|
88
|
+
"""
|
|
89
|
+
),
|
|
90
|
+
] = None,
|
|
91
|
+
swagger_ui_parameters: Annotated[
|
|
92
|
+
dict[str, Any] | None,
|
|
93
|
+
Doc(
|
|
94
|
+
"""
|
|
95
|
+
Configuration parameters for Swagger UI.
|
|
96
|
+
|
|
97
|
+
It defaults to [swagger_ui_default_parameters][fastapi.openapi.docs.swagger_ui_default_parameters].
|
|
98
|
+
"""
|
|
99
|
+
),
|
|
100
|
+
] = None,
|
|
101
|
+
) -> HTMLResponse:
|
|
102
|
+
"""
|
|
103
|
+
Generate and return the HTML that loads Swagger UI for the interactive
|
|
104
|
+
API docs (normally served at `/docs`).
|
|
105
|
+
|
|
106
|
+
You would only call this function yourself if you needed to override some parts,
|
|
107
|
+
for example the URLs to use to load Swagger UI's JavaScript and CSS.
|
|
108
|
+
|
|
109
|
+
Read more about it in the
|
|
110
|
+
[FastAPI docs for Configure Swagger UI](https://fastapi.tiangolo.com/how-to/configure-swagger-ui/)
|
|
111
|
+
and the [FastAPI docs for Custom Docs UI Static Assets (Self-Hosting)](https://fastapi.tiangolo.com/how-to/custom-docs-ui-assets/).
|
|
112
|
+
"""
|
|
113
|
+
current_swagger_ui_parameters = swagger_ui_default_parameters.copy()
|
|
114
|
+
if swagger_ui_parameters:
|
|
115
|
+
current_swagger_ui_parameters.update(swagger_ui_parameters)
|
|
116
|
+
|
|
117
|
+
html = f"""
|
|
118
|
+
<!DOCTYPE html>
|
|
119
|
+
<html>
|
|
120
|
+
<head>
|
|
121
|
+
<link type="text/css" rel="stylesheet" href="{swagger_css_url}">
|
|
122
|
+
<link rel="shortcut icon" href="{swagger_favicon_url}">
|
|
123
|
+
<title>{title}</title>
|
|
124
|
+
</head>
|
|
125
|
+
<body>
|
|
126
|
+
<div id="swagger-ui">
|
|
127
|
+
</div>
|
|
128
|
+
<script src="{swagger_js_url}"></script>
|
|
129
|
+
<!-- `SwaggerUIBundle` is now available on the page -->
|
|
130
|
+
<script>
|
|
131
|
+
const ui = SwaggerUIBundle({{
|
|
132
|
+
url: '{openapi_url}',
|
|
133
|
+
"""
|
|
134
|
+
|
|
135
|
+
for key, value in current_swagger_ui_parameters.items():
|
|
136
|
+
html += f"{json.dumps(key)}: {json.dumps(jsonable_encoder(value))},\n"
|
|
137
|
+
|
|
138
|
+
if oauth2_redirect_url:
|
|
139
|
+
html += f"oauth2RedirectUrl: window.location.origin + '{oauth2_redirect_url}',"
|
|
140
|
+
|
|
141
|
+
html += """
|
|
142
|
+
presets: [
|
|
143
|
+
SwaggerUIBundle.presets.apis,
|
|
144
|
+
SwaggerUIBundle.SwaggerUIStandalonePreset
|
|
145
|
+
],
|
|
146
|
+
})"""
|
|
147
|
+
|
|
148
|
+
if init_oauth:
|
|
149
|
+
html += f"""
|
|
150
|
+
ui.initOAuth({json.dumps(jsonable_encoder(init_oauth))})
|
|
151
|
+
"""
|
|
152
|
+
|
|
153
|
+
html += """
|
|
154
|
+
</script>
|
|
155
|
+
</body>
|
|
156
|
+
</html>
|
|
157
|
+
"""
|
|
158
|
+
return HTMLResponse(html)
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def get_redoc_html(
|
|
162
|
+
*,
|
|
163
|
+
openapi_url: Annotated[
|
|
164
|
+
str,
|
|
165
|
+
Doc(
|
|
166
|
+
"""
|
|
167
|
+
The OpenAPI URL that ReDoc should load and use.
|
|
168
|
+
|
|
169
|
+
This is normally done automatically by FastAPI using the default URL
|
|
170
|
+
`/openapi.json`.
|
|
171
|
+
"""
|
|
172
|
+
),
|
|
173
|
+
],
|
|
174
|
+
title: Annotated[
|
|
175
|
+
str,
|
|
176
|
+
Doc(
|
|
177
|
+
"""
|
|
178
|
+
The HTML `<title>` content, normally shown in the browser tab.
|
|
179
|
+
"""
|
|
180
|
+
),
|
|
181
|
+
],
|
|
182
|
+
redoc_js_url: Annotated[
|
|
183
|
+
str,
|
|
184
|
+
Doc(
|
|
185
|
+
"""
|
|
186
|
+
The URL to use to load the ReDoc JavaScript.
|
|
187
|
+
|
|
188
|
+
It is normally set to a CDN URL.
|
|
189
|
+
"""
|
|
190
|
+
),
|
|
191
|
+
] = "https://cdn.jsdelivr.net/npm/redoc@next/bundles/redoc.standalone.js",
|
|
192
|
+
redoc_favicon_url: Annotated[
|
|
193
|
+
str,
|
|
194
|
+
Doc(
|
|
195
|
+
"""
|
|
196
|
+
The URL of the favicon to use. It is normally shown in the browser tab.
|
|
197
|
+
"""
|
|
198
|
+
),
|
|
199
|
+
] = "https://fastapi.tiangolo.com/img/favicon.png",
|
|
200
|
+
with_google_fonts: Annotated[
|
|
201
|
+
bool,
|
|
202
|
+
Doc(
|
|
203
|
+
"""
|
|
204
|
+
Load and use Google Fonts.
|
|
205
|
+
"""
|
|
206
|
+
),
|
|
207
|
+
] = True,
|
|
208
|
+
) -> HTMLResponse:
|
|
209
|
+
"""
|
|
210
|
+
Generate and return the HTML response that loads ReDoc for the alternative
|
|
211
|
+
API docs (normally served at `/redoc`).
|
|
212
|
+
|
|
213
|
+
You would only call this function yourself if you needed to override some parts,
|
|
214
|
+
for example the URLs to use to load ReDoc's JavaScript and CSS.
|
|
215
|
+
|
|
216
|
+
Read more about it in the
|
|
217
|
+
[FastAPI docs for Custom Docs UI Static Assets (Self-Hosting)](https://fastapi.tiangolo.com/how-to/custom-docs-ui-assets/).
|
|
218
|
+
"""
|
|
219
|
+
html = f"""
|
|
220
|
+
<!DOCTYPE html>
|
|
221
|
+
<html>
|
|
222
|
+
<head>
|
|
223
|
+
<title>{title}</title>
|
|
224
|
+
<!-- needed for adaptive design -->
|
|
225
|
+
<meta charset="utf-8"/>
|
|
226
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
227
|
+
"""
|
|
228
|
+
if with_google_fonts:
|
|
229
|
+
html += """
|
|
230
|
+
<link href="https://fonts.googleapis.com/css?family=Montserrat:300,400,700|Roboto:300,400,700" rel="stylesheet">
|
|
231
|
+
"""
|
|
232
|
+
html += f"""
|
|
233
|
+
<link rel="shortcut icon" href="{redoc_favicon_url}">
|
|
234
|
+
<!--
|
|
235
|
+
ReDoc doesn't change outer page styles
|
|
236
|
+
-->
|
|
237
|
+
<style>
|
|
238
|
+
body {{
|
|
239
|
+
margin: 0;
|
|
240
|
+
padding: 0;
|
|
241
|
+
}}
|
|
242
|
+
</style>
|
|
243
|
+
</head>
|
|
244
|
+
<body>
|
|
245
|
+
<noscript>
|
|
246
|
+
ReDoc requires Javascript to function. Please enable it to browse the documentation.
|
|
247
|
+
</noscript>
|
|
248
|
+
<redoc spec-url="{openapi_url}"></redoc>
|
|
249
|
+
<script src="{redoc_js_url}"> </script>
|
|
250
|
+
</body>
|
|
251
|
+
</html>
|
|
252
|
+
"""
|
|
253
|
+
return HTMLResponse(html)
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def get_swagger_ui_oauth2_redirect_html() -> HTMLResponse:
|
|
257
|
+
"""
|
|
258
|
+
Generate the HTML response with the OAuth2 redirection for Swagger UI.
|
|
259
|
+
|
|
260
|
+
You normally don't need to use or change this.
|
|
261
|
+
"""
|
|
262
|
+
# copied from https://github.com/swagger-api/swagger-ui/blob/v4.14.0/dist/oauth2-redirect.html
|
|
263
|
+
html = """
|
|
264
|
+
<!doctype html>
|
|
265
|
+
<html lang="en-US">
|
|
266
|
+
<head>
|
|
267
|
+
<title>Swagger UI: OAuth2 Redirect</title>
|
|
268
|
+
</head>
|
|
269
|
+
<body>
|
|
270
|
+
<script>
|
|
271
|
+
'use strict';
|
|
272
|
+
function run () {
|
|
273
|
+
var oauth2 = window.opener.swaggerUIRedirectOauth2;
|
|
274
|
+
var sentState = oauth2.state;
|
|
275
|
+
var redirectUrl = oauth2.redirectUrl;
|
|
276
|
+
var isValid, qp, arr;
|
|
277
|
+
|
|
278
|
+
if (/code|token|error/.test(window.location.hash)) {
|
|
279
|
+
qp = window.location.hash.substring(1).replace('?', '&');
|
|
280
|
+
} else {
|
|
281
|
+
qp = location.search.substring(1);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
arr = qp.split("&");
|
|
285
|
+
arr.forEach(function (v,i,_arr) { _arr[i] = '"' + v.replace('=', '":"') + '"';});
|
|
286
|
+
qp = qp ? JSON.parse('{' + arr.join() + '}',
|
|
287
|
+
function (key, value) {
|
|
288
|
+
return key === "" ? value : decodeURIComponent(value);
|
|
289
|
+
}
|
|
290
|
+
) : {};
|
|
291
|
+
|
|
292
|
+
isValid = qp.state === sentState;
|
|
293
|
+
|
|
294
|
+
if ((
|
|
295
|
+
oauth2.auth.schema.get("flow") === "accessCode" ||
|
|
296
|
+
oauth2.auth.schema.get("flow") === "authorizationCode" ||
|
|
297
|
+
oauth2.auth.schema.get("flow") === "authorization_code"
|
|
298
|
+
) && !oauth2.auth.code) {
|
|
299
|
+
if (!isValid) {
|
|
300
|
+
oauth2.errCb({
|
|
301
|
+
authId: oauth2.auth.name,
|
|
302
|
+
source: "auth",
|
|
303
|
+
level: "warning",
|
|
304
|
+
message: "Authorization may be unsafe, passed state was changed in server. The passed state wasn't returned from auth server."
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
if (qp.code) {
|
|
309
|
+
delete oauth2.state;
|
|
310
|
+
oauth2.auth.code = qp.code;
|
|
311
|
+
oauth2.callback({auth: oauth2.auth, redirectUrl: redirectUrl});
|
|
312
|
+
} else {
|
|
313
|
+
let oauthErrorMsg;
|
|
314
|
+
if (qp.error) {
|
|
315
|
+
oauthErrorMsg = "["+qp.error+"]: " +
|
|
316
|
+
(qp.error_description ? qp.error_description+ ". " : "no accessCode received from the server. ") +
|
|
317
|
+
(qp.error_uri ? "More info: "+qp.error_uri : "");
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
oauth2.errCb({
|
|
321
|
+
authId: oauth2.auth.name,
|
|
322
|
+
source: "auth",
|
|
323
|
+
level: "error",
|
|
324
|
+
message: oauthErrorMsg || "[Authorization failed]: no accessCode received from the server."
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
} else {
|
|
328
|
+
oauth2.callback({auth: oauth2.auth, token: qp, isValid: isValid, redirectUrl: redirectUrl});
|
|
329
|
+
}
|
|
330
|
+
window.close();
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
if (document.readyState !== 'loading') {
|
|
334
|
+
run();
|
|
335
|
+
} else {
|
|
336
|
+
document.addEventListener('DOMContentLoaded', function () {
|
|
337
|
+
run();
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
</script>
|
|
341
|
+
</body>
|
|
342
|
+
</html>
|
|
343
|
+
"""
|
|
344
|
+
return HTMLResponse(content=html)
|