Cat Feeder  1.0.0
The Cat feeder project
Loading...
Searching...
No Matches
swagger_class.py
Go to the documentation of this file.
1r"""
2# +==== BEGIN CatFeeder =================+
3# LOGO:
4# ..............(..../\
5# ...............)..(.')
6# ..............(../..)
7# ...............\‍(__)|
8# Inspired by Joan Stark
9# source https://www.asciiart.eu/
10# animals/cats
11# /STOP
12# PROJECT: CatFeeder
13# FILE: swagger_class.py
14# CREATION DATE: 26-11-2025
15# LAST Modified: 22:34:3 11-01-2026
16# DESCRIPTION:
17# This is the backend server in charge of making the actual website work.
18# /STOP
19# COPYRIGHT: (c) Cat Feeder
20# PURPOSE: The class in charge of handling the swagger instance.
21# // AR
22# +==== END CatFeeder =================+
23"""
24from typing import Optional, Dict, Any, TYPE_CHECKING
25from fastapi import FastAPI, Request, Response
26from fastapi.openapi.docs import get_swagger_ui_html, get_redoc_html
27from fastapi.openapi.utils import get_openapi
28from display_tty import Disp, initialise_logger
29from . import swagger_constants as SWAGGER_CONST
30from ...core import FinalClass, RuntimeControl, RuntimeManager, RI
31from ...http_codes import HCI, HTTP_DEFAULT_TYPE
32from ...server_header import ServerHeaders
33from ...boilerplates import BoilerplateResponses, BoilerplateIncoming
34
35if TYPE_CHECKING:
36 from ...path_manager import PathManager
37
38
39class SwaggerHandler(metaclass=FinalClass):
40 """Handler for Swagger/OpenAPI documentation integration.
41
42 This class manages the configuration and injection of Swagger UI
43 documentation interface into the FastAPI application. It provides endpoints
44 for accessing interactive API documentation and the OpenAPI schema.
45
46 Attributes:
47 disp (Disp): Logger instance for this class.
48 debug (bool): Debug mode flag.
49 success (int): Success return code.
50 error (int): Error return code.
51 runtime_manager (RuntimeManager): Shared runtime manager instance.
52 path_manager_initialised (PathManager): Path manager for registering endpoints.
53 runtime_control_initialised (RuntimeControl): Runtime control for accessing app instance.
54 server_headers_initialised (ServerHeaders): Server header configuration.
55 boilerplate_responses_initialised (BoilerplateResponses): Response templates.
56 boilerplate_incoming_initialised (BoilerplateIncoming): Request handling utilities.
57 """
58
59 disp: Disp = initialise_logger(__qualname__, False)
60
61 def __init__(self, success: int = 0, error: int = 84, debug: bool = False) -> None:
62 """Initialize the SwaggerHandler.
63
64 Args:
65 success (int, optional): Success return code. Defaults to 0.
66 error (int, optional): Error return code. Defaults to 84.
67 debug (bool, optional): Enable debug logging. Defaults to False.
68 """
69 # ------------------------ The logging function ------------------------
70 self.disp.update_disp_debug(debug)
71 self.disp.log_debug("Initialising...")
72 # -------------------------- Inherited values --------------------------
73 self.debug: bool = debug
74 self.success: int = success
75 self.error: int = error
76 self.runtime_manager: RuntimeManager = RI
77 # -------------------------- Shared instances --------------------------
78 self.path_manager_initialised: "PathManager" = self.runtime_manager.get(
79 "PathManager")
80 self.runtime_control_initialised: RuntimeControl = self.runtime_manager.get(
81 RuntimeControl)
82 self.server_headers_initialised: ServerHeaders = self.runtime_manager.get(
83 ServerHeaders)
84 self.boilerplate_responses_initialised: BoilerplateResponses = self.runtime_manager.get(
85 BoilerplateResponses)
86 self.boilerplate_incoming_initialised: BoilerplateIncoming = self.runtime_manager.get(
87 BoilerplateIncoming)
88 self.disp.log_debug("Initialised")
89
90 def _get_custom_openapi_schema(self, app: "FastAPI") -> Dict[str, Any]:
91 """Generate custom OpenAPI schema with metadata.
92
93 Args:
94 app (FastAPI): The FastAPI application instance.
95
96 Returns:
97 Dict[str, Any]: The OpenAPI schema dictionary.
98 """
99 func_title = "_get_custom_openapi_schema"
100 self.disp.log_debug("Generating custom OpenAPI schema", func_title)
101
102 if app.openapi_schema:
103 self.disp.log_debug("Returning cached OpenAPI schema", func_title)
104 return app.openapi_schema
105
106 openapi_schema = get_openapi(
107 title=SWAGGER_CONST.API_TITLE,
108 version=SWAGGER_CONST.API_VERSION,
109 description=SWAGGER_CONST.API_DESCRIPTION,
110 routes=app.routes,
111 tags=SWAGGER_CONST.TAGS_METADATA,
112 servers=SWAGGER_CONST.SERVERS,
113 )
114
115 openapi_schema["info"]["contact"] = SWAGGER_CONST.CONTACT_INFO
116 openapi_schema["info"]["license"] = SWAGGER_CONST.LICENSE_INFO
117
118 app.openapi_schema = openapi_schema
119 self.disp.log_debug("OpenAPI schema generated and cached", func_title)
120 return app.openapi_schema
121
122 def _custom_openapi_wrapper(self, app: "FastAPI") -> Dict[str, Any]:
123 """Wrapper method for app.openapi() that uses the custom schema generator.
124
125 This method serves as the openapi() callable for the FastAPI app instance.
126
127 Args:
128 app (FastAPI): The FastAPI application instance.
129
130 Returns:
131 Dict[str, Any]: The OpenAPI schema dictionary.
132 """
133 return self._get_custom_openapi_schema(app)
134
135 async def get_swagger_documentation(self, request: Request) -> Response:
136 """Endpoint to serve Swagger UI documentation.
137
138 Args:
139 request (Request): The incoming request object.
140
141 Returns:
142 Response: HTML response with Swagger UI interface.
143 """
144 func_title = "get_swagger_documentation"
145 self.disp.log_debug("Serving Swagger UI", func_title)
146
147 token = self.boilerplate_incoming_initialised.get_token_if_present(
148 request)
149 self.disp.log_debug(f"token = {token}", func_title)
150
151 if not self.runtime_control_initialised.app:
152 error_body = self.boilerplate_responses_initialised.build_response_body(
153 title="Swagger UI",
154 message="Application not initialized",
155 resp="App instance not found",
156 token=token,
157 error=True
158 )
159 return HCI.service_unavailable(
160 content=error_body,
161 content_type=HTTP_DEFAULT_TYPE,
162 headers=self.server_headers_initialised.for_json()
163 )
164
165 return get_swagger_ui_html(
166 openapi_url=SWAGGER_CONST.OPENAPI_URL,
167 title=f"{SWAGGER_CONST.API_TITLE} - Swagger UI",
168 oauth2_redirect_url=SWAGGER_CONST.SWAGGER_REDIRECT_URL,
169 swagger_ui_parameters=SWAGGER_CONST.SWAGGER_UI_PARAMETERS,
170 )
171
172 async def get_redoc_documentation(self, request: Request) -> Response:
173 """Endpoint to serve ReDoc documentation.
174
175 Args:
176 request (Request): The incoming request object.
177
178 Returns:
179 Response: HTML response with ReDoc interface.
180 """
181 func_title = "get_redoc_documentation"
182 self.disp.log_debug("Serving ReDoc", func_title)
183
184 token = self.boilerplate_incoming_initialised.get_token_if_present(
185 request)
186 self.disp.log_debug(f"token = {token}", func_title)
187
188 if not self.runtime_control_initialised.app:
189 error_body = self.boilerplate_responses_initialised.build_response_body(
190 title="ReDoc",
191 message="Application not initialized",
192 resp="App instance not found",
193 token=token,
194 error=True
195 )
196 return HCI.service_unavailable(
197 content=error_body,
198 content_type=HTTP_DEFAULT_TYPE,
199 headers=self.server_headers_initialised.for_json()
200 )
201
202 return get_redoc_html(
203 openapi_url=SWAGGER_CONST.OPENAPI_URL,
204 title=f"{SWAGGER_CONST.API_TITLE} - ReDoc",
205 )
206
207 async def get_openapi_schema(self, request: Request) -> Response:
208 """Endpoint to serve the OpenAPI JSON schema.
209
210 Args:
211 request (Request): The incoming request object.
212
213 Returns:
214 Response: JSON response with OpenAPI schema.
215 """
216 func_title = "get_openapi_schema"
217 self.disp.log_debug("Serving OpenAPI schema", func_title)
218
219 token = self.boilerplate_incoming_initialised.get_token_if_present(
220 request)
221 self.disp.log_debug(f"token = {token}", func_title)
222
223 if not self.runtime_control_initialised.app:
224 error_body = self.boilerplate_responses_initialised.build_response_body(
225 title="OpenAPI Schema",
226 message="Application not initialized",
227 resp="App instance not found",
228 token=token,
229 error=True
230 )
231 return HCI.service_unavailable(
232 content=error_body,
233 content_type=HTTP_DEFAULT_TYPE,
234 headers=self.server_headers_initialised.for_json()
235 )
236
237 openapi_schema = self._get_custom_openapi_schema(
239 )
240
241 return HCI.success(
242 content=openapi_schema,
243 content_type=HTTP_DEFAULT_TYPE,
244 headers=self.server_headers_initialised.for_json()
245 )
246
247 def inject(self, app: Optional["FastAPI"] = None) -> int:
248 """Inject Swagger/OpenAPI configuration into the FastAPI application.
249
250 This method configures the FastAPI application with custom OpenAPI documentation
251 settings and registers the documentation endpoints (Swagger UI, OpenAPI schema).
252
253 Args:
254 app (Optional[FastAPI], optional): The FastAPI application instance.
255 If None, uses the instance from RuntimeControl. Defaults to None.
256
257 Returns:
258 int: success if injection succeeded, error if there was an error.
259
260 Raises:
261 RuntimeError: If no FastAPI application instance is available.
262 """
263 func_title = "inject"
264 self.disp.log_debug("Starting Swagger injection", func_title)
265
266 if app is None:
267 app = self.runtime_control_initialised.app
268
269 if not app:
270 self.disp.log_error(
271 "No FastAPI app instance available", func_title)
272 raise RuntimeError("FastAPI application instance not found")
273
274 if not isinstance(app, FastAPI):
275 self.disp.log_error(
276 f"Invalid app type: {type(app)}, expected FastAPI",
277 func_title
278 )
279 return self.error
280
281 self.disp.log_debug("Configuring FastAPI OpenAPI settings", func_title)
282
283 app.docs_url = None
284 app.redoc_url = None
285 app.openapi_url = None
286
287 app.title = SWAGGER_CONST.API_TITLE
288 app.version = SWAGGER_CONST.API_VERSION
289 app.description = SWAGGER_CONST.API_DESCRIPTION
290 app.openapi_tags = SWAGGER_CONST.TAGS_METADATA
291 app.contact = SWAGGER_CONST.CONTACT_INFO
292 app.license_info = SWAGGER_CONST.LICENSE_INFO
293 app.servers = SWAGGER_CONST.SERVERS
294
295 self.disp.log_debug("Registering documentation endpoints", func_title)
296
297 result = self.path_manager_initialised.add_path(
298 path=SWAGGER_CONST.SWAGGER_URL,
299 endpoint=self.get_swagger_documentation,
300 method="GET"
301 )
302 if result != self.success:
303 self.disp.log_error(
304 f"Failed to register Swagger UI endpoint at {SWAGGER_CONST.SWAGGER_URL}",
305 func_title
306 )
307 return self.error
308
309 def custom_openapi() -> Dict[str, Any]:
310 return self._custom_openapi_wrapper(app)
311
312 app.openapi = custom_openapi
313
314 self.disp.log_info(
315 "Swagger/OpenAPI injection completed successfully", func_title)
316 self.disp.log_info(
317 f"Swagger UI available at: {SWAGGER_CONST.SWAGGER_URL}", func_title)
318 self.disp.log_info(
319 f"OpenAPI schema available at: {SWAGGER_CONST.OPENAPI_URL}", func_title)
320
321 return self.success
Dict[str, Any] _custom_openapi_wrapper(self, "FastAPI" app)
int inject(self, Optional["FastAPI"] app=None)
None __init__(self, int success=0, int error=84, bool debug=False)
Dict[str, Any] _get_custom_openapi_schema(self, "FastAPI" app)