wiederverwendbar 0.8.5__py3-none-any.whl → 0.9.0__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.
- wiederverwendbar/__init__.py +8 -6
- wiederverwendbar/branding/__init__.py +1 -0
- wiederverwendbar/branding/settings.py +85 -0
- wiederverwendbar/console/__init__.py +3 -0
- wiederverwendbar/console/console.py +199 -0
- wiederverwendbar/console/out_files.py +19 -0
- wiederverwendbar/console/settings.py +9 -0
- wiederverwendbar/default.py +4 -1
- wiederverwendbar/fastapi/__init__.py +3 -0
- wiederverwendbar/fastapi/app.py +385 -0
- wiederverwendbar/fastapi/dependencies.py +11 -0
- wiederverwendbar/fastapi/settings.py +39 -0
- wiederverwendbar/functions/is_coroutine_function.py +19 -0
- wiederverwendbar/inspect.py +26 -0
- wiederverwendbar/logger/__init__.py +0 -1
- wiederverwendbar/logger/handlers/rich_console_handler.py +15 -6
- wiederverwendbar/logger/handlers/stream_console_handler.py +3 -16
- wiederverwendbar/logger/log_levels.py +4 -0
- wiederverwendbar/logger/settings.py +15 -20
- wiederverwendbar/pydantic/file_config.py +20 -4
- wiederverwendbar/rich/__init__.py +2 -0
- wiederverwendbar/rich/console.py +215 -0
- wiederverwendbar/rich/settings.py +26 -0
- wiederverwendbar/sqlalchemy/base.py +4 -4
- wiederverwendbar/task_manger/task.py +1 -4
- wiederverwendbar/task_manger/task_manager.py +14 -19
- wiederverwendbar/typer/__init__.py +3 -1
- wiederverwendbar/typer/app.py +172 -0
- wiederverwendbar/typer/settings.py +14 -0
- wiederverwendbar/warnings.py +6 -0
- {wiederverwendbar-0.8.5.dist-info → wiederverwendbar-0.9.0.dist-info}/METADATA +9 -6
- {wiederverwendbar-0.8.5.dist-info → wiederverwendbar-0.9.0.dist-info}/RECORD +34 -49
- wiederverwendbar/examples/__init__.py +0 -0
- wiederverwendbar/examples/before_after_wrap.py +0 -74
- wiederverwendbar/examples/colors.py +0 -16
- wiederverwendbar/examples/extended_thread.py +0 -28
- wiederverwendbar/examples/file_config.py +0 -11
- wiederverwendbar/examples/indexable_model.py +0 -19
- wiederverwendbar/examples/logger.py +0 -31
- wiederverwendbar/examples/logger_context/__init__.py +0 -0
- wiederverwendbar/examples/logger_context/example.py +0 -58
- wiederverwendbar/examples/logger_context/example_module.py +0 -13
- wiederverwendbar/examples/mongoengine/__init__.py +0 -0
- wiederverwendbar/examples/mongoengine/automatic_reference.py +0 -25
- wiederverwendbar/examples/mongoengine/db.py +0 -7
- wiederverwendbar/examples/mongoengine/log_streamer.py +0 -9
- wiederverwendbar/examples/mongoengine/logger.py +0 -25
- wiederverwendbar/examples/post_init.py +0 -29
- wiederverwendbar/examples/route.py +0 -12
- wiederverwendbar/examples/singletons.py +0 -59
- wiederverwendbar/examples/sqlalchemy/__init__.py +0 -0
- wiederverwendbar/examples/sqlalchemy/db.py +0 -89
- wiederverwendbar/examples/starlette_admin/__init__.py +0 -0
- wiederverwendbar/examples/starlette_admin/action_log.py +0 -126
- wiederverwendbar/examples/starlette_admin/action_log_file_download.py +0 -99
- wiederverwendbar/examples/starlette_admin/action_log_form.py +0 -149
- wiederverwendbar/examples/starlette_admin/action_log_thread.py +0 -192
- wiederverwendbar/examples/starlette_admin/automatic_reference_admin.py +0 -47
- wiederverwendbar/examples/starlette_admin/generic_embedded_document_field.py +0 -74
- wiederverwendbar/examples/starlette_admin/multi_path_admin.py +0 -18
- wiederverwendbar/examples/task_manager.py +0 -55
- wiederverwendbar/examples/test_file.py +0 -14
- wiederverwendbar/examples/typer_resolve_defaults.py +0 -15
- wiederverwendbar/examples/uvicorn_server.py +0 -32
- wiederverwendbar/logger/terminal_out_files.py +0 -10
- {wiederverwendbar-0.8.5.dist-info → wiederverwendbar-0.9.0.dist-info}/WHEEL +0 -0
- {wiederverwendbar-0.8.5.dist-info → wiederverwendbar-0.9.0.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,385 @@
|
|
1
|
+
import inspect
|
2
|
+
import logging
|
3
|
+
from pathlib import Path
|
4
|
+
from typing import Optional, Union, Any, Callable, Awaitable, Annotated
|
5
|
+
|
6
|
+
from fastapi import FastAPI as _FastAPI, Request, Depends
|
7
|
+
from fastapi.openapi.docs import get_swagger_ui_html, get_redoc_html, get_swagger_ui_oauth2_redirect_html
|
8
|
+
from pydantic import BaseModel, Field, computed_field
|
9
|
+
from starlette.responses import FileResponse, HTMLResponse, JSONResponse, RedirectResponse, Response
|
10
|
+
|
11
|
+
from wiederverwendbar.default import Default
|
12
|
+
from wiederverwendbar.fastapi.settings import FastAPISettings
|
13
|
+
|
14
|
+
logger = logging.getLogger(__name__)
|
15
|
+
|
16
|
+
|
17
|
+
class InfoModel(BaseModel):
|
18
|
+
title: str = Field(..., title="Title", description="The title of the application.")
|
19
|
+
description: str = Field(..., title="Description", description="The description of the application.")
|
20
|
+
version: str = Field(..., title="Version", description="The version of the application.")
|
21
|
+
|
22
|
+
class Contact(BaseModel):
|
23
|
+
name: str = Field(..., title="Name", description="The name of the contact.")
|
24
|
+
email: str = Field(..., title="Email", description="The email of the contact.")
|
25
|
+
|
26
|
+
contact: Optional[Contact] = Field(None, title="Contact", description="The contact of the application.")
|
27
|
+
|
28
|
+
class LicenseInfo(BaseModel):
|
29
|
+
name: str = Field(..., title="Name", description="The name of the license.")
|
30
|
+
url: str = Field(..., title="URL", description="The URL of the license.")
|
31
|
+
|
32
|
+
license_info: Optional[LicenseInfo] = Field(None, title="License Info", description="The license info of the application.")
|
33
|
+
terms_of_service: Optional[str] = Field(None, title="Terms of Service", description="The terms of service of the application.")
|
34
|
+
|
35
|
+
|
36
|
+
class VersionModel(BaseModel):
|
37
|
+
version: Union[None, Default, str] = Field(default=Default(), title="Version", description="The version of the application.")
|
38
|
+
|
39
|
+
@computed_field(title="Version Major", description="The major version of the application.")
|
40
|
+
@property
|
41
|
+
def version_major(self) -> Optional[int]:
|
42
|
+
if self.version is None:
|
43
|
+
return None
|
44
|
+
return int(self.version.split(".")[0])
|
45
|
+
|
46
|
+
@computed_field(title="Version Minor", description="The minor version of the application.")
|
47
|
+
@property
|
48
|
+
def version_minor(self) -> Optional[int]:
|
49
|
+
if self.version is None:
|
50
|
+
return None
|
51
|
+
return int(self.version.split(".")[1])
|
52
|
+
|
53
|
+
@computed_field(title="Version Patch", description="The patch version of the application.")
|
54
|
+
@property
|
55
|
+
def version_patch(self) -> Optional[int]:
|
56
|
+
if self.version is None:
|
57
|
+
return None
|
58
|
+
return int(self.version.split(".")[2])
|
59
|
+
|
60
|
+
|
61
|
+
class FastAPI(_FastAPI):
|
62
|
+
def __init__(self,
|
63
|
+
debug: Union[None, Default, bool] = Default(),
|
64
|
+
title: Union[None, Default, str] = Default(),
|
65
|
+
summary: Union[None, Default, str] = Default(),
|
66
|
+
description: Union[None, Default, str] = Default(),
|
67
|
+
version: Union[None, Default, str] = Default(),
|
68
|
+
openapi_url: Union[None, Default, str] = Default(),
|
69
|
+
redirect_slashes: Union[None, Default, bool] = Default(),
|
70
|
+
favicon: Union[None, Default, Path] = Default(),
|
71
|
+
docs_url: Union[None, Default, str] = Default(),
|
72
|
+
docs_title: Union[None, Default, str] = Default(),
|
73
|
+
docs_favicon: Union[None, Default, Path] = Default(),
|
74
|
+
redoc_url: Union[None, Default, str] = Default(),
|
75
|
+
redoc_title: Union[None, Default, str] = Default(),
|
76
|
+
redoc_favicon: Union[None, Default, Path] = Default(),
|
77
|
+
terms_of_service: Union[None, Default, str] = Default(),
|
78
|
+
contact: Union[None, Default, dict[str, str]] = Default(),
|
79
|
+
license_info: Union[None, Default, dict[str, str]] = Default(),
|
80
|
+
root_path: Union[None, Default, str] = Default(),
|
81
|
+
root_path_in_servers: Union[None, Default, bool] = Default(),
|
82
|
+
deprecated: Union[None, Default, bool] = Default(),
|
83
|
+
info_url: Union[None, Default, str] = Default(),
|
84
|
+
info_response_model: Union[Default, type[InfoModel]] = Default(),
|
85
|
+
version_url: Union[None, Default, str] = Default(),
|
86
|
+
version_response_model: Union[Default, type[InfoModel]] = Default(),
|
87
|
+
root_redirect: Union[Default, None, FastAPISettings.RootRedirect, str] = Default(),
|
88
|
+
settings: Optional[FastAPISettings] = None,
|
89
|
+
**kwargs):
|
90
|
+
|
91
|
+
# set default
|
92
|
+
if settings is None:
|
93
|
+
settings = FastAPISettings()
|
94
|
+
if type(debug) is Default:
|
95
|
+
debug = settings.api_debug
|
96
|
+
if type(title) is Default:
|
97
|
+
title = settings.api_title
|
98
|
+
if type(title) is Default:
|
99
|
+
title = settings.branding_title
|
100
|
+
if title is None:
|
101
|
+
title = "FastAPI"
|
102
|
+
if type(summary) is Default:
|
103
|
+
summary = settings.api_summary
|
104
|
+
if type(description) is Default:
|
105
|
+
description = settings.api_description
|
106
|
+
if type(description) is Default:
|
107
|
+
description = settings.branding_description
|
108
|
+
if description is None:
|
109
|
+
description = ""
|
110
|
+
if type(version) is Default:
|
111
|
+
version = settings.api_version
|
112
|
+
if type(version) is Default:
|
113
|
+
version = settings.branding_version
|
114
|
+
if version is None:
|
115
|
+
version = "0.1.0"
|
116
|
+
if type(openapi_url) is Default:
|
117
|
+
openapi_url = settings.api_openapi_url
|
118
|
+
if type(redirect_slashes) is Default:
|
119
|
+
redirect_slashes = settings.api_redirect_slashes
|
120
|
+
if type(favicon) is Default:
|
121
|
+
favicon = settings.api_favicon
|
122
|
+
if type(docs_url) is Default:
|
123
|
+
docs_url = settings.api_docs_url
|
124
|
+
if type(docs_title) is Default:
|
125
|
+
docs_title = settings.api_docs_title
|
126
|
+
if type(docs_title) is Default:
|
127
|
+
docs_title = title
|
128
|
+
if type(docs_favicon) is Default:
|
129
|
+
docs_favicon = settings.api_docs_favicon
|
130
|
+
if type(docs_favicon) is Default:
|
131
|
+
docs_favicon = favicon
|
132
|
+
if type(redoc_url) is Default:
|
133
|
+
redoc_url = settings.api_redoc_url
|
134
|
+
if type(redoc_title) is Default:
|
135
|
+
redoc_title = settings.api_redoc_title
|
136
|
+
if type(redoc_title) is Default:
|
137
|
+
redoc_title = title
|
138
|
+
if type(redoc_favicon) is Default:
|
139
|
+
redoc_favicon = settings.api_redoc_favicon
|
140
|
+
if type(redoc_favicon) is Default:
|
141
|
+
redoc_favicon = favicon
|
142
|
+
if type(terms_of_service) is Default:
|
143
|
+
terms_of_service = settings.api_terms_of_service
|
144
|
+
if type(terms_of_service) is Default:
|
145
|
+
terms_of_service = settings.branding_terms_of_service
|
146
|
+
if type(contact) is Default:
|
147
|
+
contact = settings.api_contact
|
148
|
+
if type(contact) is Default:
|
149
|
+
if settings.branding_author is not None and settings.branding_author_email is not None:
|
150
|
+
contact = {"name": settings.branding_author,
|
151
|
+
"email": settings.branding_author_email}
|
152
|
+
if type(contact) is Default:
|
153
|
+
contact = None
|
154
|
+
if type(license_info) is Default:
|
155
|
+
license_info = settings.api_license_info
|
156
|
+
if type(license_info) is Default:
|
157
|
+
if settings.branding_license is not None and settings.branding_license_url is not None:
|
158
|
+
license_info = {"name": settings.branding_license,
|
159
|
+
"url": settings.branding_license_url}
|
160
|
+
if type(license_info) is Default:
|
161
|
+
license_info = None
|
162
|
+
if type(root_path) is Default:
|
163
|
+
root_path = settings.api_root_path
|
164
|
+
if root_path_in_servers is None:
|
165
|
+
root_path_in_servers = settings.api_root_path_in_servers
|
166
|
+
if type(deprecated) is Default:
|
167
|
+
deprecated = settings.api_deprecated
|
168
|
+
if type(info_url) is Default:
|
169
|
+
info_url = settings.api_info_url
|
170
|
+
if type(info_response_model) is Default:
|
171
|
+
info_response_model = InfoModel
|
172
|
+
if type(version_url) is Default:
|
173
|
+
version_url = settings.api_version_url
|
174
|
+
if type(version_response_model) is Default:
|
175
|
+
version_response_model = VersionModel
|
176
|
+
if type(root_redirect) is Default:
|
177
|
+
root_redirect = settings.api_root_redirect
|
178
|
+
if type(root_redirect) is Default:
|
179
|
+
if docs_url is not None:
|
180
|
+
root_redirect = FastAPISettings.RootRedirect.DOCS
|
181
|
+
elif redoc_url is not None:
|
182
|
+
root_redirect = FastAPISettings.RootRedirect.REDOC
|
183
|
+
else:
|
184
|
+
root_redirect = None
|
185
|
+
|
186
|
+
# set attrs
|
187
|
+
self.docs_title = docs_title
|
188
|
+
self.docs_favicon = docs_favicon
|
189
|
+
self.docs_favicon_url = "/swagger-favicon.ico"
|
190
|
+
self.redoc_title = redoc_title
|
191
|
+
self.redoc_favicon = redoc_favicon
|
192
|
+
self.redoc_favicon_url = "/redoc-favicon.ico"
|
193
|
+
self.info_url = info_url
|
194
|
+
self.info_response_model = info_response_model
|
195
|
+
self.version_url = version_url
|
196
|
+
self.version_response_model = version_response_model
|
197
|
+
self.root_redirect = root_redirect
|
198
|
+
|
199
|
+
# For storing the original "add_api_route" method from router.
|
200
|
+
# If None, the access to router will be blocked.
|
201
|
+
self._original_add_route: Union[None, bool, Callable, Any] = None
|
202
|
+
self._original_add_api_route: Union[None, bool, Callable, Any] = None
|
203
|
+
self._original_add_api_websocket_route: Union[None, bool, Callable, Any] = None
|
204
|
+
|
205
|
+
super().__init__(debug=debug,
|
206
|
+
title=title,
|
207
|
+
summary=summary,
|
208
|
+
description=description,
|
209
|
+
version=version,
|
210
|
+
openapi_url=openapi_url,
|
211
|
+
redirect_slashes=redirect_slashes,
|
212
|
+
docs_url=docs_url,
|
213
|
+
redoc_url=redoc_url,
|
214
|
+
terms_of_service=terms_of_service,
|
215
|
+
contact=contact,
|
216
|
+
license_info=license_info,
|
217
|
+
root_path=root_path,
|
218
|
+
root_path_in_servers=root_path_in_servers,
|
219
|
+
deprecated=deprecated,
|
220
|
+
**kwargs)
|
221
|
+
|
222
|
+
logger.info(f"Initialized FastAPI: {self}")
|
223
|
+
|
224
|
+
def __str__(self):
|
225
|
+
return f"{self.__class__.__name__}(title={self.title}, version={self.version})"
|
226
|
+
|
227
|
+
def __getattribute__(self, item):
|
228
|
+
# block router access if the init flag is not set
|
229
|
+
if item == "router":
|
230
|
+
if self._original_add_route is None or self._original_add_api_route is None or self._original_add_api_websocket_route is None:
|
231
|
+
raise RuntimeError("Class is not initialized!")
|
232
|
+
return super().__getattribute__(item)
|
233
|
+
|
234
|
+
def _add_route(self,
|
235
|
+
path: str,
|
236
|
+
endpoint: Callable[[Request], Union[Awaitable[Response], Response]],
|
237
|
+
methods: Optional[list[str]] = None,
|
238
|
+
name: Optional[str] = None,
|
239
|
+
include_in_schema: bool = True, *args, **kwargs) -> None:
|
240
|
+
if self._original_add_route is None or self._original_add_route is True:
|
241
|
+
raise RuntimeError("Original add_route method is not set!")
|
242
|
+
logger.debug(f"Adding route for {self} -> {path}")
|
243
|
+
return self._original_add_route(path, endpoint, methods, name, include_in_schema, *args, **kwargs)
|
244
|
+
|
245
|
+
def _add_api_route(self,
|
246
|
+
path: str,
|
247
|
+
endpoint: Callable[..., Any],
|
248
|
+
*args,
|
249
|
+
**kwargs) -> None:
|
250
|
+
if self._original_add_api_route is None or self._original_add_api_route is True:
|
251
|
+
raise RuntimeError("Original add_api_route method is not set!")
|
252
|
+
logger.debug(f"Adding API route for {self} -> {path}")
|
253
|
+
return self._original_add_api_route(path, endpoint, *args, **kwargs)
|
254
|
+
|
255
|
+
def _add_api_websocket_route(self,
|
256
|
+
path: str,
|
257
|
+
endpoint: Callable[..., Any],
|
258
|
+
name: Optional[str] = None,
|
259
|
+
*args,
|
260
|
+
**kwargs) -> None:
|
261
|
+
if self._original_add_api_websocket_route is None or self._original_add_api_websocket_route is True:
|
262
|
+
raise RuntimeError("Original add_api_websocket_route method is not set!")
|
263
|
+
logger.debug(f"Adding API websocket route for {self} -> {path}")
|
264
|
+
return self._original_add_api_websocket_route(path, endpoint, name, *args, **kwargs)
|
265
|
+
|
266
|
+
def setup(self) -> None:
|
267
|
+
# to unblock router access
|
268
|
+
self._original_add_route = True
|
269
|
+
self._original_add_api_route = True
|
270
|
+
self._original_add_api_websocket_route = True
|
271
|
+
|
272
|
+
# overwrite add_api_route for router
|
273
|
+
# noinspection PyTypeChecker
|
274
|
+
self._original_add_api_route = self.router.add_api_route
|
275
|
+
self.router.add_api_route = self._add_api_route
|
276
|
+
# noinspection PyTypeChecker
|
277
|
+
self._original_add_api_websocket_route = self.router.add_api_websocket_route
|
278
|
+
self.router.add_api_websocket_route = self._add_api_websocket_route
|
279
|
+
# noinspection PyTypeChecker
|
280
|
+
self._original_add_route = self.router.add_route
|
281
|
+
self.router.add_route = self._add_route
|
282
|
+
|
283
|
+
# create openapi route
|
284
|
+
if self.openapi_url:
|
285
|
+
self.add_route(path=self.openapi_url, route=self.get_openapi, include_in_schema=False)
|
286
|
+
|
287
|
+
# create docs routes
|
288
|
+
if self.openapi_url and self.docs_url:
|
289
|
+
if self.docs_favicon is not None:
|
290
|
+
self.add_route(path=self.docs_favicon_url, route=self.get_docs_favicon, include_in_schema=False)
|
291
|
+
self.add_route(path=self.docs_url, route=self.get_docs, include_in_schema=False)
|
292
|
+
if self.swagger_ui_oauth2_redirect_url:
|
293
|
+
self.add_route(path=self.swagger_ui_oauth2_redirect_url, route=self.get_docs_redirect, include_in_schema=False)
|
294
|
+
|
295
|
+
# create redoc routes
|
296
|
+
if self.openapi_url and self.redoc_url:
|
297
|
+
if self.redoc_favicon is not None:
|
298
|
+
self.add_route(path=self.docs_favicon_url, route=self.get_redoc_favicon, include_in_schema=False)
|
299
|
+
self.add_route(path=self.redoc_url, route=self.get_redoc, include_in_schema=False)
|
300
|
+
|
301
|
+
# create info route
|
302
|
+
if self.info_url:
|
303
|
+
self.add_api_route(path=self.info_url, endpoint=self.get_info, response_model=self.info_response_model)
|
304
|
+
|
305
|
+
# create version route
|
306
|
+
if self.version_url:
|
307
|
+
self.add_api_route(path=self.version_url, endpoint=self.get_version, response_model=self.version_response_model)
|
308
|
+
|
309
|
+
# create root redirect route
|
310
|
+
if self.root_redirect:
|
311
|
+
self.add_route(path="/", route=self.get_root_redirect, include_in_schema=False)
|
312
|
+
|
313
|
+
async def get_openapi(self, request: Request) -> JSONResponse:
|
314
|
+
root_path = request.scope.get("root_path", "").rstrip("/")
|
315
|
+
server_urls = {url for url in (server_data.get("url") for server_data in self.servers) if url}
|
316
|
+
if root_path not in server_urls:
|
317
|
+
if root_path and self.root_path_in_servers:
|
318
|
+
self.servers.insert(0, {"url": root_path})
|
319
|
+
server_urls.add(root_path)
|
320
|
+
return JSONResponse(self.openapi())
|
321
|
+
|
322
|
+
async def get_docs_favicon(self, request: Request) -> FileResponse:
|
323
|
+
return FileResponse(self.docs_favicon)
|
324
|
+
|
325
|
+
async def docs_parameters(self, request: Request) -> dict[str, Any]:
|
326
|
+
docs_kwargs = {"title": self.docs_title}
|
327
|
+
root_path = request.scope.get("root_path", "").rstrip("/")
|
328
|
+
if self.openapi_url is None:
|
329
|
+
raise RuntimeError("OpenAPI URL not set")
|
330
|
+
docs_kwargs["openapi_url"] = root_path + self.openapi_url
|
331
|
+
if self.swagger_ui_oauth2_redirect_url:
|
332
|
+
docs_kwargs["oauth2_redirect_url"] = root_path + self.swagger_ui_oauth2_redirect_url
|
333
|
+
docs_kwargs["init_oauth"] = self.swagger_ui_init_oauth
|
334
|
+
docs_kwargs["swagger_ui_parameters"] = self.swagger_ui_parameters
|
335
|
+
if self.docs_favicon is not None:
|
336
|
+
docs_kwargs["swagger_favicon_url"] = self.docs_favicon_url
|
337
|
+
return docs_kwargs
|
338
|
+
|
339
|
+
async def get_docs(self, request: Request) -> HTMLResponse:
|
340
|
+
return get_swagger_ui_html(**await self.docs_parameters(request))
|
341
|
+
|
342
|
+
async def get_docs_redirect(self, request: Request) -> HTMLResponse:
|
343
|
+
return get_swagger_ui_oauth2_redirect_html()
|
344
|
+
|
345
|
+
async def redoc_parameters(self, request: Request) -> dict[str, Any]:
|
346
|
+
redoc_kwargs = {"title": self.redoc_title}
|
347
|
+
root_path = request.scope.get("root_path", "").rstrip("/")
|
348
|
+
if self.openapi_url is None:
|
349
|
+
raise RuntimeError("OpenAPI URL not set")
|
350
|
+
redoc_kwargs["openapi_url"] = root_path + self.openapi_url
|
351
|
+
if self.redoc_favicon is not None:
|
352
|
+
redoc_kwargs["redoc_favicon_url"] = self.redoc_favicon_url
|
353
|
+
return redoc_kwargs
|
354
|
+
|
355
|
+
async def get_redoc_favicon(self, request: Request) -> FileResponse:
|
356
|
+
return FileResponse(self.redoc_favicon)
|
357
|
+
|
358
|
+
async def get_redoc(self, request: Request) -> HTMLResponse:
|
359
|
+
return get_redoc_html(**await self.redoc_parameters(request=request))
|
360
|
+
|
361
|
+
async def get_info(self, request: Request) -> dict[str, Any]:
|
362
|
+
return {"title": self.title,
|
363
|
+
"description": self.description,
|
364
|
+
"version": self.version,
|
365
|
+
"contact": self.contact,
|
366
|
+
"license_info": self.license_info,
|
367
|
+
"terms_of_service": self.terms_of_service}
|
368
|
+
|
369
|
+
async def get_version(self, request: Request) -> dict[str, Any]:
|
370
|
+
return {"version": self.version}
|
371
|
+
|
372
|
+
async def get_root_redirect(self, request: Request) -> RedirectResponse:
|
373
|
+
root_path = request.scope.get("root_path", "").rstrip("/")
|
374
|
+
if self.root_redirect is None:
|
375
|
+
raise RuntimeError("Root Redirect not set")
|
376
|
+
root_redirect = self.root_redirect
|
377
|
+
if root_redirect == FastAPISettings.RootRedirect.DOCS:
|
378
|
+
if self.docs_url is None:
|
379
|
+
raise RuntimeError("Docs URL not set")
|
380
|
+
root_redirect = root_path + self.docs_url
|
381
|
+
if root_redirect == FastAPISettings.RootRedirect.REDOC:
|
382
|
+
if self.redoc_url is None:
|
383
|
+
raise RuntimeError("Redoc URL not set")
|
384
|
+
root_redirect = root_path + self.redoc_url
|
385
|
+
return RedirectResponse(url=root_redirect)
|
@@ -0,0 +1,11 @@
|
|
1
|
+
from typing import Union, Any
|
2
|
+
|
3
|
+
from starlette.requests import Request
|
4
|
+
|
5
|
+
from wiederverwendbar.fastapi.app import FastAPI
|
6
|
+
|
7
|
+
|
8
|
+
async def get_app(request: Request) -> Union[FastAPI, Any]:
|
9
|
+
if not isinstance(request.app, FastAPI):
|
10
|
+
raise RuntimeError("The request app is not a FastAPI instance.")
|
11
|
+
return request.app
|
@@ -0,0 +1,39 @@
|
|
1
|
+
from enum import Enum
|
2
|
+
from pathlib import Path
|
3
|
+
from typing import Optional, Union
|
4
|
+
|
5
|
+
from pydantic import Field
|
6
|
+
|
7
|
+
from wiederverwendbar.branding import BrandingSettings
|
8
|
+
from wiederverwendbar.default import Default
|
9
|
+
|
10
|
+
|
11
|
+
class FastAPISettings(BrandingSettings):
|
12
|
+
api_debug: bool = Field(default=False, title="FastAPI Debug", description="Whether the FastAPI is in debug mode.")
|
13
|
+
api_title: Union[None, Default, str] = Field(default=Default(), title="FastAPI Title", description="The title of the FastAPI.")
|
14
|
+
api_summary: Optional[str] = Field(default=None, title="FastAPI Summary", description="The summary of the FastAPI.")
|
15
|
+
api_description: Union[None, Default, str] = Field(default=Default(), title="FastAPI Description", description="The description of the FastAPI.")
|
16
|
+
api_version: Union[None, Default, str] = Field(default=Default(), title="FastAPI Version", description="The version of the FastAPI.")
|
17
|
+
api_openapi_url: Optional[str] = Field(default="/openapi.json", title="FastAPI OpenAPI URL", description="The OpenAPI URL of the FastAPI.")
|
18
|
+
api_redirect_slashes: bool = Field(default=True, title="FastAPI Redirect Slashes", description="Whether the FastAPI redirects slashes.")
|
19
|
+
api_favicon: Optional[Path] = Field(default=None, title="FastAPI Favicon", description="The favicon of the FastAPI.")
|
20
|
+
api_docs_url: Optional[str] = Field(default="/docs", title="FastAPI Docs URL", description="The docs URL of the FastAPI.")
|
21
|
+
api_docs_title: Union[None, Default, str] = Field(default=Default(), title="FastAPI Docs Title", description="The title of the FastAPI docs.")
|
22
|
+
api_docs_favicon: Union[None, Default, Path] = Field(default=Default(), title="FastAPI Docs Favicon", description="The favicon of the FastAPI docs.")
|
23
|
+
api_redoc_url: Optional[str] = Field(default="/redoc", title="FastAPI Redoc URL", description="The Redoc URL of the FastAPI.")
|
24
|
+
api_redoc_title: Union[None, Default, str] = Field(default=Default(), title="FastAPI Redoc Title", description="The title of the FastAPI Redoc.")
|
25
|
+
api_redoc_favicon: Union[None, Default, Path] = Field(default=Default(), title="FastAPI Redoc Favicon", description="The favicon of the FastAPI Redoc.")
|
26
|
+
api_terms_of_service: Union[None, Default, str] = Field(default=Default(), title="FastAPI Terms of Service", description="The terms of service of the FastAPI.")
|
27
|
+
api_contact: Union[None, Default, dict[str, str]] = Field(default=Default(), title="FastAPI Contact", description="The contact of the FastAPI.")
|
28
|
+
api_license_info: Union[None, Default, dict[str, str]] = Field(default=Default(), title="FastAPI License Info", description="The license info of the FastAPI.")
|
29
|
+
api_root_path: str = Field(default="", title="FastAPI Root Path", description="The root path of the FastAPI.")
|
30
|
+
api_root_path_in_servers: bool = Field(default=True, title="FastAPI Root Path in Servers", description="Whether the root path of the FastAPI is in servers.")
|
31
|
+
api_deprecated: Optional[bool] = Field(default=None, title="FastAPI Deprecated", description="Whether the FastAPI is deprecated.")
|
32
|
+
api_info_url: Optional[str] = Field(default="/info", title="FastAPI Info URL", description="The info URL of the FastAPI.")
|
33
|
+
api_version_url: Optional[str] = Field(default="/version", title="FastAPI Version URL", description="The version URL of the FastAPI.")
|
34
|
+
|
35
|
+
class RootRedirect(str, Enum):
|
36
|
+
DOCS = "docs"
|
37
|
+
REDOC = "redoc"
|
38
|
+
|
39
|
+
api_root_redirect: Union[None, Default, RootRedirect, str] = Field(default=Default(), title="FastAPI Root Redirect", description="The root redirect of the FastAPI.")
|
@@ -0,0 +1,19 @@
|
|
1
|
+
import asyncio
|
2
|
+
import functools
|
3
|
+
|
4
|
+
from typing import Any
|
5
|
+
|
6
|
+
|
7
|
+
def is_coroutine_function(obj: Any) -> bool:
|
8
|
+
"""
|
9
|
+
Check if the object is a coroutine function.
|
10
|
+
|
11
|
+
This function is needed because functools.partial is not a coroutine function, but its func attribute is.
|
12
|
+
|
13
|
+
:param obj: The object to check.
|
14
|
+
:return: True if the object is a coroutine function, False otherwise.
|
15
|
+
"""
|
16
|
+
|
17
|
+
while isinstance(obj, functools.partial):
|
18
|
+
obj = obj.func
|
19
|
+
return asyncio.iscoroutinefunction(obj)
|
@@ -0,0 +1,26 @@
|
|
1
|
+
import inspect
|
2
|
+
import re
|
3
|
+
from pathlib import Path
|
4
|
+
from typing import Optional, Union
|
5
|
+
|
6
|
+
DEBUGGER_FILENAME_PARTS = [r".*/python-ce/helpers/pydev/.*"]
|
7
|
+
|
8
|
+
|
9
|
+
def get_enter_frame(stack: Optional[list[inspect.FrameInfo]] = None, deep: int = 1) -> inspect.FrameInfo:
|
10
|
+
if stack is None:
|
11
|
+
stack = inspect.stack()
|
12
|
+
|
13
|
+
last_frame_info = stack[0]
|
14
|
+
for frame_info in stack[deep:]:
|
15
|
+
if is_frame_in_debugger(frame_info=frame_info):
|
16
|
+
return last_frame_info
|
17
|
+
last_frame_info = frame_info
|
18
|
+
return last_frame_info
|
19
|
+
|
20
|
+
|
21
|
+
def is_frame_in_debugger(frame_info: inspect.FrameInfo) -> bool:
|
22
|
+
frame_filename = frame_info.filename.replace("\\", "/")
|
23
|
+
for debugger_filename_part in DEBUGGER_FILENAME_PARTS:
|
24
|
+
if re.match(debugger_filename_part, frame_filename):
|
25
|
+
return True
|
26
|
+
return False
|
@@ -9,4 +9,3 @@ from wiederverwendbar.logger.log_levels import (LogLevels)
|
|
9
9
|
from wiederverwendbar.logger.logger import (Logger)
|
10
10
|
from wiederverwendbar.logger.settings import (LoggerSettings)
|
11
11
|
from wiederverwendbar.logger.singleton import (LoggerSingleton)
|
12
|
-
from wiederverwendbar.logger.terminal_out_files import (TerminalOutFiles)
|
@@ -1,16 +1,25 @@
|
|
1
|
-
from
|
1
|
+
from typing import Optional
|
2
|
+
|
2
3
|
from rich.logging import RichHandler
|
3
4
|
|
4
|
-
from wiederverwendbar.
|
5
|
-
from wiederverwendbar.
|
5
|
+
from wiederverwendbar.console.out_files import OutFiles
|
6
|
+
from wiederverwendbar.rich.console import RichConsole
|
6
7
|
|
7
8
|
|
8
9
|
class RichConsoleHandler(RichHandler):
|
9
|
-
def __init__(self,
|
10
|
+
def __init__(self,
|
11
|
+
*args,
|
12
|
+
name: str,
|
13
|
+
console: Optional[RichConsole] = None,
|
14
|
+
console_outfile: Optional[OutFiles] = None,
|
15
|
+
console_width: Optional[int] = None,
|
16
|
+
**kwargs):
|
17
|
+
if console is None:
|
18
|
+
console = RichConsole(console_file=console_outfile,
|
19
|
+
console_width=console_width)
|
10
20
|
super().__init__(
|
11
21
|
*args,
|
12
|
-
console=
|
13
|
-
width=console_width),
|
22
|
+
console=console,
|
14
23
|
**kwargs
|
15
24
|
)
|
16
25
|
self.set_name(name)
|
@@ -1,22 +1,9 @@
|
|
1
1
|
import logging
|
2
|
-
import sys
|
3
2
|
|
4
|
-
from wiederverwendbar.
|
5
|
-
|
6
|
-
|
7
|
-
def _resolve_file(outfile: TerminalOutFiles):
|
8
|
-
# choose file
|
9
|
-
if outfile == TerminalOutFiles.STDOUT:
|
10
|
-
file = sys.stdout
|
11
|
-
elif outfile == TerminalOutFiles.STDERR:
|
12
|
-
file = sys.stderr
|
13
|
-
else:
|
14
|
-
file = sys.stderr
|
15
|
-
|
16
|
-
return file
|
3
|
+
from wiederverwendbar.console import OutFiles
|
17
4
|
|
18
5
|
|
19
6
|
class StreamConsoleHandler(logging.StreamHandler):
|
20
|
-
def __init__(self, name: str, console_outfile:
|
21
|
-
super().__init__(stream=
|
7
|
+
def __init__(self, name: str, console_outfile: OutFiles):
|
8
|
+
super().__init__(stream=console_outfile.get_file())
|
22
9
|
self.set_name(name)
|
@@ -5,9 +5,10 @@ from typing import Any, Optional, Union
|
|
5
5
|
|
6
6
|
from pydantic import BaseModel, Field, field_validator
|
7
7
|
|
8
|
+
from wiederverwendbar.console import OutFiles
|
9
|
+
from wiederverwendbar.default import Default
|
8
10
|
from wiederverwendbar.logger.file_modes import FileModes
|
9
11
|
from wiederverwendbar.logger.log_levels import LogLevels
|
10
|
-
from wiederverwendbar.logger.terminal_out_files import TerminalOutFiles
|
11
12
|
|
12
13
|
|
13
14
|
class LoggerSettings(BaseModel):
|
@@ -18,9 +19,9 @@ class LoggerSettings(BaseModel):
|
|
18
19
|
log_console: bool = Field(default=True,
|
19
20
|
title="Console Logging",
|
20
21
|
description="Whether to log to the console")
|
21
|
-
log_console_level:
|
22
|
-
|
23
|
-
|
22
|
+
log_console_level: Union[Default, LogLevels] = Field(default=Default(),
|
23
|
+
title="Console Log Level",
|
24
|
+
description="The log level for the console")
|
24
25
|
log_console_format: str = Field(default="%(name)s - %(message)s",
|
25
26
|
title="Console Log Format",
|
26
27
|
description="The log format for the console")
|
@@ -29,9 +30,9 @@ class LoggerSettings(BaseModel):
|
|
29
30
|
ge=0,
|
30
31
|
description="The width of the console")
|
31
32
|
|
32
|
-
log_console_outfile:
|
33
|
-
|
34
|
-
|
33
|
+
log_console_outfile: OutFiles = Field(default=OutFiles.STDOUT,
|
34
|
+
title="Console Outfile",
|
35
|
+
description="The console outfile")
|
35
36
|
log_console_rich_markup: bool = Field(default=True,
|
36
37
|
title="Rich Markup",
|
37
38
|
description="Whether to use rich markup in the console")
|
@@ -50,9 +51,9 @@ class LoggerSettings(BaseModel):
|
|
50
51
|
log_file_path: Optional[Path] = Field(default=None,
|
51
52
|
title="Log File Path",
|
52
53
|
description="The path of the log file")
|
53
|
-
log_file_level:
|
54
|
-
|
55
|
-
|
54
|
+
log_file_level: Union[Default, LogLevels] = Field(default=Default(),
|
55
|
+
title="File Log Level",
|
56
|
+
description="The log level for the file")
|
56
57
|
log_file_format: str = Field(default="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
|
57
58
|
title="File Log Format",
|
58
59
|
description="The log format for the file")
|
@@ -79,19 +80,13 @@ class LoggerSettings(BaseModel):
|
|
79
80
|
ge=0,
|
80
81
|
description="The number of backup log archives to keep")
|
81
82
|
|
82
|
-
def
|
83
|
-
|
84
|
-
|
85
|
-
if self.log_console_level is None:
|
83
|
+
def model_post_init(self, context: Any, /):
|
84
|
+
if type(self.log_console_level) is Default:
|
86
85
|
self.log_console_level = self.log_level
|
87
|
-
|
88
|
-
if self.log_file_level is None:
|
86
|
+
if type(self.log_file_level) is Default:
|
89
87
|
self.log_file_level = self.log_level
|
90
88
|
|
91
|
-
|
92
|
-
self.log_console_level = self.log_level
|
93
|
-
if self.log_file_level < self.log_level:
|
94
|
-
self.log_file_level = self.log_level
|
89
|
+
super().model_post_init(context)
|
95
90
|
|
96
91
|
@field_validator("log_level", "log_console_level", "log_file_level", mode="before")
|
97
92
|
def validate_log_level(cls, value: Union[int, str]) -> str:
|