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.
Files changed (67) hide show
  1. wiederverwendbar/__init__.py +8 -6
  2. wiederverwendbar/branding/__init__.py +1 -0
  3. wiederverwendbar/branding/settings.py +85 -0
  4. wiederverwendbar/console/__init__.py +3 -0
  5. wiederverwendbar/console/console.py +199 -0
  6. wiederverwendbar/console/out_files.py +19 -0
  7. wiederverwendbar/console/settings.py +9 -0
  8. wiederverwendbar/default.py +4 -1
  9. wiederverwendbar/fastapi/__init__.py +3 -0
  10. wiederverwendbar/fastapi/app.py +385 -0
  11. wiederverwendbar/fastapi/dependencies.py +11 -0
  12. wiederverwendbar/fastapi/settings.py +39 -0
  13. wiederverwendbar/functions/is_coroutine_function.py +19 -0
  14. wiederverwendbar/inspect.py +26 -0
  15. wiederverwendbar/logger/__init__.py +0 -1
  16. wiederverwendbar/logger/handlers/rich_console_handler.py +15 -6
  17. wiederverwendbar/logger/handlers/stream_console_handler.py +3 -16
  18. wiederverwendbar/logger/log_levels.py +4 -0
  19. wiederverwendbar/logger/settings.py +15 -20
  20. wiederverwendbar/pydantic/file_config.py +20 -4
  21. wiederverwendbar/rich/__init__.py +2 -0
  22. wiederverwendbar/rich/console.py +215 -0
  23. wiederverwendbar/rich/settings.py +26 -0
  24. wiederverwendbar/sqlalchemy/base.py +4 -4
  25. wiederverwendbar/task_manger/task.py +1 -4
  26. wiederverwendbar/task_manger/task_manager.py +14 -19
  27. wiederverwendbar/typer/__init__.py +3 -1
  28. wiederverwendbar/typer/app.py +172 -0
  29. wiederverwendbar/typer/settings.py +14 -0
  30. wiederverwendbar/warnings.py +6 -0
  31. {wiederverwendbar-0.8.5.dist-info → wiederverwendbar-0.9.0.dist-info}/METADATA +9 -6
  32. {wiederverwendbar-0.8.5.dist-info → wiederverwendbar-0.9.0.dist-info}/RECORD +34 -49
  33. wiederverwendbar/examples/__init__.py +0 -0
  34. wiederverwendbar/examples/before_after_wrap.py +0 -74
  35. wiederverwendbar/examples/colors.py +0 -16
  36. wiederverwendbar/examples/extended_thread.py +0 -28
  37. wiederverwendbar/examples/file_config.py +0 -11
  38. wiederverwendbar/examples/indexable_model.py +0 -19
  39. wiederverwendbar/examples/logger.py +0 -31
  40. wiederverwendbar/examples/logger_context/__init__.py +0 -0
  41. wiederverwendbar/examples/logger_context/example.py +0 -58
  42. wiederverwendbar/examples/logger_context/example_module.py +0 -13
  43. wiederverwendbar/examples/mongoengine/__init__.py +0 -0
  44. wiederverwendbar/examples/mongoengine/automatic_reference.py +0 -25
  45. wiederverwendbar/examples/mongoengine/db.py +0 -7
  46. wiederverwendbar/examples/mongoengine/log_streamer.py +0 -9
  47. wiederverwendbar/examples/mongoengine/logger.py +0 -25
  48. wiederverwendbar/examples/post_init.py +0 -29
  49. wiederverwendbar/examples/route.py +0 -12
  50. wiederverwendbar/examples/singletons.py +0 -59
  51. wiederverwendbar/examples/sqlalchemy/__init__.py +0 -0
  52. wiederverwendbar/examples/sqlalchemy/db.py +0 -89
  53. wiederverwendbar/examples/starlette_admin/__init__.py +0 -0
  54. wiederverwendbar/examples/starlette_admin/action_log.py +0 -126
  55. wiederverwendbar/examples/starlette_admin/action_log_file_download.py +0 -99
  56. wiederverwendbar/examples/starlette_admin/action_log_form.py +0 -149
  57. wiederverwendbar/examples/starlette_admin/action_log_thread.py +0 -192
  58. wiederverwendbar/examples/starlette_admin/automatic_reference_admin.py +0 -47
  59. wiederverwendbar/examples/starlette_admin/generic_embedded_document_field.py +0 -74
  60. wiederverwendbar/examples/starlette_admin/multi_path_admin.py +0 -18
  61. wiederverwendbar/examples/task_manager.py +0 -55
  62. wiederverwendbar/examples/test_file.py +0 -14
  63. wiederverwendbar/examples/typer_resolve_defaults.py +0 -15
  64. wiederverwendbar/examples/uvicorn_server.py +0 -32
  65. wiederverwendbar/logger/terminal_out_files.py +0 -10
  66. {wiederverwendbar-0.8.5.dist-info → wiederverwendbar-0.9.0.dist-info}/WHEEL +0 -0
  67. {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 rich.console import Console
1
+ from typing import Optional
2
+
2
3
  from rich.logging import RichHandler
3
4
 
4
- from wiederverwendbar.logger.handlers.stream_console_handler import _resolve_file
5
- from wiederverwendbar.logger.terminal_out_files import TerminalOutFiles
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, *args, name: str, console_outfile: TerminalOutFiles, console_width: int, **kwargs):
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=Console(file=_resolve_file(console_outfile),
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.logger.terminal_out_files import TerminalOutFiles
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: TerminalOutFiles):
21
- super().__init__(stream=_resolve_file(console_outfile))
7
+ def __init__(self, name: str, console_outfile: OutFiles):
8
+ super().__init__(stream=console_outfile.get_file())
22
9
  self.set_name(name)
@@ -1,3 +1,4 @@
1
+ import logging
1
2
  from enum import Enum
2
3
 
3
4
 
@@ -12,3 +13,6 @@ class LogLevels(str, Enum):
12
13
  WARNING = "WARNING"
13
14
  INFO = "INFO"
14
15
  DEBUG = "DEBUG"
16
+
17
+ def get_level_number(self) -> int:
18
+ return int(logging.getLevelName(self.value))
@@ -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: Optional[LogLevels] = Field(default=None,
22
- title="Console Log Level",
23
- description="The log level for the console")
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: TerminalOutFiles = Field(default=TerminalOutFiles.STDOUT,
33
- title="Console Outfile",
34
- description="The console outfile")
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: Optional[LogLevels] = Field(default=None,
54
- title="File Log Level",
55
- description="The log level for the file")
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 __init__(self, /, **data: Any):
83
- super().__init__(**data)
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
- if self.log_console_level < self.log_level:
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: