-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy pathmodule.py
More file actions
170 lines (142 loc) · 5.44 KB
/
Copy pathmodule.py
File metadata and controls
170 lines (142 loc) · 5.44 KB
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
160
161
162
163
164
165
166
167
168
169
170
import typing as t
from functools import lru_cache
from pathlib import Path
from ellar.app import App
from ellar.common import (
GuardCanActivate,
IExecutionContext,
Module,
ModuleRouter,
render,
render_template_string,
set_metadata,
)
from ellar.core import ModuleSetup
from ellar.di import injectable
from ellar.openapi.builder import OpenAPIDocumentBuilder
from ellar.openapi.constants import OPENAPI_OPERATION_KEY
from ellar.openapi.docs_ui import IDocumentationUI
from ellar.openapi.openapi_v3 import OpenAPI
from ellar.utils import get_unique_type
from starlette.responses import HTMLResponse
__all__ = ["OpenAPIDocumentModule"]
__BASE_DIR__ = Path(__file__).parent
ICON_SVG_PATH = "https://python-ellar.github.io/ellar/img/Icon.svg"
@injectable
class AllowAnyGuard(GuardCanActivate):
async def can_activate(self, context: "IExecutionContext") -> bool:
return True
class OpenAPIDocumentModule:
@classmethod
def setup(
cls,
app: App,
docs_ui: t.Union[t.Sequence[IDocumentationUI], IDocumentationUI],
document: t.Optional[
t.Union[OpenAPI, t.Callable, OpenAPIDocumentBuilder]
] = None,
router_prefix: str = "",
openapi_url: t.Optional[str] = None,
allow_any: bool = True,
guards: t.Optional[
t.List[t.Union[t.Type[GuardCanActivate], GuardCanActivate]]
] = None,
) -> t.Type[t.Any]:
"""
Sets up OpenAPIDocumentModule
@param app: Application instance
@param docs_ui: Type of DocumentationRenderer
@param document: Document Pydantic Model
@param router_prefix: OPENAPI route prefix
@param openapi_url: OPENAPI route url
@param allow_any: Allow AllowAnyGuard on openapi routes
@param guards: Guards that should be applied to openapi routes
@return:
"""
_guards = list(guards) if guards else []
if allow_any:
_guards = [AllowAnyGuard] + _guards
_document_renderer: t.List[IDocumentationUI] = []
router = ModuleRouter(
router_prefix,
guards=_guards,
name="openapi",
include_in_schema=False,
)
if isinstance(docs_ui, (list, tuple, set)):
_document_renderer = list(docs_ui)
else:
_document_renderer = [docs_ui] # type: ignore[list-item]
if not openapi_url and document:
openapi_url = "/openapi.json"
if isinstance(document, OpenAPIDocumentBuilder):
document_build_document = document.build_document
@lru_cache(1200)
def _get_document() -> OpenAPI:
"""Build OPENAPI Schema on `openapi.json` request"""
return document_build_document(app)
document = _get_document
@router.get(
openapi_url,
include_in_schema=False,
response=OpenAPI,
name="openapi_schema",
)
@set_metadata(OPENAPI_OPERATION_KEY, True)
def openapi_schema() -> t.Any:
_docs = document
if not isinstance(_docs, OpenAPI):
_docs = document() # type: ignore[operator]
return _docs
for docs_ui in _document_renderer:
if not isinstance(docs_ui, IDocumentationUI):
raise Exception(
f"{docs_ui.__class__.__name__ if not isinstance(docs_ui, type) else docs_ui.__name__} "
f"must be of type `IDocumentationUIContext`"
)
docs_ui.template_context.setdefault("favicon_url", ICON_SVG_PATH)
docs_ui.template_context.setdefault("openapi_url", openapi_url)
cls._setup_document_manager(router=router, docs_ui=docs_ui)
module: t.Type = Module(
template_folder="templates",
providers=[],
routers=(router,),
base_directory=str(__BASE_DIR__),
)(get_unique_type())
routes = ModuleSetup(module).build_and_get_routes(
injector=app.injector, config=app.config
)
app.router.extend(routes)
return module
@classmethod
def _setup_document_manager(
cls,
*,
router: ModuleRouter,
docs_ui: IDocumentationUI,
) -> None:
_path = docs_ui.path.lstrip("/").rstrip("/")
if not docs_ui.template_name:
assert docs_ui.template_string, (
f"`{docs_ui.__class__.__name__}` class requires the `template_string` attribute to be provided."
)
@t.no_type_check
async def _doc(ctx: IExecutionContext) -> HTMLResponse:
ctx.switch_to_http_connection().get_request()
html_str = render_template_string(
docs_ui.template_string, **docs_ui.template_context
)
return HTMLResponse(html_str)
else:
assert docs_ui.template_name, (
f"`{docs_ui.__class__.__name__}` class requires the `template_name` attribute to be provided."
)
@render(docs_ui.template_name)
async def _doc() -> t.Any:
return docs_ui.template_context
_doc = router.get(
f"/{_path}",
include_in_schema=False,
name=docs_ui.name,
)(_doc)
set_metadata(OPENAPI_OPERATION_KEY, True)(_doc)