Cat Feeder  1.0.0
The Cat feeder project
Loading...
Searching...
No Matches
server_management.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: server_management.py
14# CREATION DATE: 11-10-2025
15# LAST Modified: 11:29:40 18-02-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: This is the file in charge of containing the functions that will manage the server run status.
21# // AR
22# +==== END CatFeeder =================+
23"""
24
25
26from typing import Optional, TYPE_CHECKING
27import uvicorn
28from fastapi import FastAPI, Response, BackgroundTasks as FastAPIBackgroundTasks
29from fastapi.middleware.cors import CORSMiddleware
30from fastapi.middleware.gzip import GZipMiddleware
31from fastapi.middleware.trustedhost import TrustedHostMiddleware
32from fastapi.middleware.httpsredirect import HTTPSRedirectMiddleware
33from fastapi.middleware.asyncexitstack import AsyncExitStackMiddleware
34from display_tty import Disp, initialise_logger
35
36from .runtime_manager import RuntimeManager, RI
37from .final_class import FinalClass
38from . import core_const as CORE_CONST
39
40from ..utils import constants as CONST
41from ..http_codes import HCI, HTTP_DEFAULT_TYPE, HttpDataTypes
42
43if TYPE_CHECKING:
44 from ..sql import SQL
45 from ..crons import BackgroundTasks
46 from ..docs import DocumentationHandler
47 from ..server_header import ServerHeaders
48 from .runtime_controls import RuntimeControl
49 from ..boilerplates import BoilerplateResponses
50
51
52class ServerManagement(metaclass=FinalClass):
53 """_summary_
54 """
55
56 disp: Disp = initialise_logger(__qualname__, False)
57
58 def __init__(self, error: int = 84, success: int = 0, debug: bool = False) -> None:
59 """_summary_
60 """
61 # ------------------------ The logging function ------------------------
62 self.disp.update_disp_debug(debug)
63 self.disp.log_debug("Initialising...")
64 # -------------------------- Inherited values --------------------------
65 self.error: int = error
66 self.success: int = success
67 self.debug: bool = debug
68 # ----------------------- Shared instance handler ----------------------
69 self.runtime_manager: RuntimeManager = RI
70 # -------------------------- Shared instances --------------------------
71 self.runtime_control: "RuntimeControl" = self.runtime_manager.get(
72 "RuntimeControl")
73 self.database_link: "SQL" = self.runtime_manager.get("SQL")
74 self.background_tasks_initialised: Optional["BackgroundTasks"] = self.runtime_manager.get(
75 "BackgroundTasks")
76 self.boilerplate_responses_initialised: Optional["BoilerplateResponses"] = self.runtime_manager.get_if_exists(
77 "BoilerplateResponses",
78 None
79 )
80 self.server_headers: "ServerHeaders" = self.runtime_manager.get(
81 "ServerHeaders")
82 self.documentation_handler_initialised: Optional["DocumentationHandler"] = self.runtime_manager.get_if_exists(
83 "DocumentationHandler",
84 None
85 )
86 self.disp.log_debug("Initialised")
87
88 def __del__(self) -> None:
89 """_summary_
90 The destructor of the class
91 """
92 self.disp.log_info(
93 "Server sub processes are shutting down.",
94 "__del__"
95 )
96 if self.is_server_alive() is True:
97 del self.database_link
98 self.runtime_control.continue_running = False
99 if self.runtime_control.server is not None:
100 self.runtime_control.graceful_shutdown()
101 self.runtime_control.server = None
102 if self.background_tasks_initialised is not None:
105
106 def is_server_alive(self) -> bool:
107 """
108 Check if the server is still running.
109 Returns:
110 bool: Returns True if it is running.
111 """
112 return self.runtime_control.continue_running
113
114 def is_server_running(self) -> bool:
115 """
116 Check if the server is still running.
117 Returns:
118 bool: Returns True if it is running.
119 """
120 return self.is_server_alive()
121
122 def _perform_shutdown(self) -> None:
123 """Internal method to perform actual shutdown after response is sent"""
124 if self.database_link.is_connected() is True:
125 self.database_link.close()
126 self.runtime_control.graceful_shutdown()
127
128 async def shutdown(self, background_tasks: FastAPIBackgroundTasks) -> Response:
129 """
130 The function to shutdown the server
131 Args:
132 background_tasks: FastAPI's background tasks handler
133 Returns:
134 Response: Return the shutdown server message
135 """
136 self.boilerplate_responses_initialised = self.runtime_manager.get_if_exists(
137 "BoilerplateResponses",
139 )
140 # Schedule shutdown to happen after response is sent
141 background_tasks.add_task(self._perform_shutdown_perform_shutdown)
142
144 self.disp.log_error(
145 "Boilerplate Responses class not present during shutdown request."
146 )
147 return HCI.service_unavailable(
148 "Service component unavailable. Server is shutting down.",
149 content_type=HttpDataTypes.TEXT,
150 headers=self.server_headers.for_json()
151 )
152 body = self.boilerplate_responses_initialised.build_response_body(
153 title="Shutdown",
154 message="The server is shutting down.",
155 resp="Shutdown",
156 token="",
157 error=False
158 )
159 return HCI.success(body, content_type=HTTP_DEFAULT_TYPE, headers=self.server_headers.for_json())
160
161 def _handle_documentation(self) -> None:
162 self.documentation_handler_initialised: Optional["DocumentationHandler"] = self.runtime_manager.get_if_exists(
163 "DocumentationHandler",
165 )
167 self.disp.log_error(
168 "No documentation handler during injection time, skipping injection"
169 )
170 return None
172 return None
173
174 # -------------------Initialisation-----------------------
175
176 def initialise_classes(self) -> None:
177 """
178 The function to initialise the server classes
179 """
180
181 # ========= FastAPI app registration ==========
182 self.runtime_control.app = FastAPI(
183 # This allows Fastapi to send trace logs to the endpoint when it breaks.
184 debug=self.debug,
185 docs_url=None,
186 redoc_url=None,
187 openapi_url=None,
188 # Disabled for security - OAuth2 handled separately
189 swagger_ui_oauth2_redirect_url=None,
190 )
191 # ========= Documentation handling ==========
193 # ========== Middleware registration ==========
194 # ......... GZip Middelware .........
195 self.runtime_control.app.add_middleware(
196 GZipMiddleware,
197 minimum_size=CORE_CONST.GZIP_MINIMUM_SIZE,
198 compresslevel=CORE_CONST.GZIP_COMPRESSION_LEVEL
199 )
200 # ......... Force HTTPS Middelware .........
201 if CORE_CONST.SERVER_PROD_FORCE_HTTPS is True:
202 self.runtime_control.app.add_middleware(HTTPSRedirectMiddleware)
203 # ......... Middelware in charge of origin checking .........
204 self.runtime_control.app.add_middleware(
205 CORSMiddleware,
206 allow_origins=CORE_CONST.CORS_ALLOW_ORIGINS,
207 allow_credentials=CORE_CONST.CORS_ALLOW_CREDENTIALS,
208 allow_methods=CORE_CONST.CORS_ALLOW_METHODS,
209 allow_headers=CORE_CONST.CORS_ALLOW_HEADERS,
210 )
211 # ......... Trusted Host Middelware ..........
212 # self.runtime_control.app.add_middleware(
213 # TrustedHostMiddleware,
214 # allowed_hosts=CORE_CONST.TRUSTED_HOSTS_LIST
215 # )
216 # ......... Async Exit Stack Middelware .........
217 self.runtime_control.app.add_middleware(AsyncExitStackMiddleware)
218 # ========= Uvicorn server configuration ==========
219 msg = "uvicorn.Config(\n"
220 msg += f"app='{self.runtime_control.app}',\n"
221 msg += f"host='{self.server_headers.host}',\n"
222 msg += f"port='{self.server_headers.port}',\n"
223 msg += f"lifespan='{CONST.SERVER_LIFESPAN}',\n"
224 msg += f"timeout_keep_alive='{CONST.SERVER_TIMEOUT_KEEP_ALIVE}',\n"
225 msg += f"workers='{CONST.SERVER_WORKERS}',\n"
226 msg += f"reload='{CONST.SERVER_DEV_RELOAD}',\n"
227 msg += f"reload_dirs='{CONST.SERVER_DEV_RELOAD_DIRS}',\n"
228 msg += f"log_level='{CONST.SERVER_DEV_LOG_LEVEL}',\n"
229 msg += f"use_colors='{CONST.SERVER_DEV_USE_COLOURS}',\n"
230 msg += f"proxy_headers='{CONST.SERVER_PROD_PROXY_HEADERS}',\n"
231 msg += f"forwarded_allow_ips='{CONST.SERVER_PROD_FORWARDED_ALLOW_IPS}'"
232 msg += "\n\n)"
233 self.disp.log_debug(msg, "initialise_classes")
234 self.runtime_control.config = uvicorn.Config(
235 app=self.runtime_control.app,
236 host=self.server_headers.host,
237 port=self.server_headers.port,
238 lifespan=CONST.SERVER_LIFESPAN,
239 timeout_keep_alive=CONST.SERVER_TIMEOUT_KEEP_ALIVE,
240 workers=CONST.SERVER_WORKERS,
241 reload=CONST.SERVER_DEV_RELOAD,
242 reload_dirs=CONST.SERVER_DEV_RELOAD_DIRS,
243 log_level=CONST.SERVER_DEV_LOG_LEVEL,
244 use_colors=CONST.SERVER_DEV_USE_COLOURS,
245 proxy_headers=CONST.SERVER_PROD_PROXY_HEADERS,
246 forwarded_allow_ips=CONST.SERVER_PROD_FORWARDED_ALLOW_IPS
247 )
248 # ========= Uvicorn server instance ==========
249 self.runtime_control.server = uvicorn.Server(
250 self.runtime_control.config)
251 # ======== Set running flag ==========
252 self.runtime_control.continue_running = True
Response shutdown(self, FastAPIBackgroundTasks background_tasks)
None __init__(self, int error=84, int success=0, bool debug=False)