2# +==== BEGIN CatFeeder =================+
5# ...............)..(.')
7# ...............\(__)|
8# Inspired by Joan Stark
9# source https://www.asciiart.eu/
14# CREATION DATE: 11-10-2025
15# LAST Modified: 3:39:15 24-01-2026
17# This is the backend server in charge of making the actual website work.
19# COPYRIGHT: (c) Cat Feeder
20# PURPOSE: File in charge of referencing all the paths_initialised supported by the server.
22# +==== END CatFeeder =================+
25from typing
import Union, List, Dict, Any, Optional, TYPE_CHECKING, Callable
28from display_tty
import Disp, initialise_logger
29from .path_constants
import PATH_KEY, ENDPOINT_KEY, METHOD_KEY, ALLOWED_METHODS
30from ..core.runtime_manager
import RuntimeManager, RI
31from ..core
import FinalClass, RuntimeControl
32from .openapi_builder
import OpenAPIBuilder
35 from ..endpoint_manager
import EndpointManager
39 """Manager for API route registration and validation.
41 Handles registration of API endpoints with their paths and HTTP methods,
42 validates route configurations, and manages route injection into FastAPI.
43 Automatically merges methods when the same path/endpoint combination is
44 registered multiple times.
47 disp: Disp = initialise_logger(__qualname__,
False)
49 def __init__(self, success: int = 0, error: int = 84, debug: bool =
False) ->
None:
50 """Initialize the path manager.
53 success: Success return code.
54 error: Error return code.
55 debug: Enable debug logging.
58 self.
disp.update_disp_debug(debug)
59 self.
disp.log_debug(
"Initialising...")
63 self.
routes: List[Dict[str, Any]] = []
74 self.
disp.log_debug(
"Initialised")
76 def endpoint_valid(self, path: str, endpoint: object, method: Union[str, List[str]]) -> bool:
77 """Validate endpoint configuration.
79 Checks that path is a string, endpoint is callable, and method(s)
80 are valid HTTP methods from the ALLOWED_METHODS list.
83 path: The endpoint path to validate.
84 endpoint: The endpoint function to validate.
85 method: HTTP method(s) to validate.
88 True if all validations pass, False otherwise.
90 if not isinstance(path, str)
or not isinstance(method, (str, list))
or not callable(endpoint):
92 f
"Failed to insert {path} with method {method}"
96 if isinstance(method, str):
97 methods_to_check = [method]
99 methods_to_check = method
101 for http_method
in methods_to_check:
102 if not isinstance(http_method, str)
or http_method.upper()
not in ALLOWED_METHODS:
104 f
"Failed to insert {path}, method {http_method} not allowed"
110 def _build_endpoint(self, path: str, endpoint: object, method: Union[str, List[str]]) -> Optional[Dict[str, Union[str, object, List[str]]]]:
111 """Build endpoint dictionary from components.
113 Validates the endpoint and constructs a standardized dictionary
114 with path, endpoint function, and method(s).
117 path: The endpoint path.
118 endpoint: The endpoint function.
119 method: HTTP method(s) - converted to list if string.
122 Dictionary with PATH_KEY, ENDPOINT_KEY, and METHOD_KEY if valid,
123 None if validation fails.
128 if isinstance(method, str):
133 return {PATH_KEY: path, ENDPOINT_KEY: endpoint, METHOD_KEY: methods}
135 def add_path(self, path: str, endpoint: object, method: Union[str, List[str]], *, decorators: Optional[List[Callable]] =
None) -> int:
136 """Add or update a path in the routes list."""
137 self.
disp.log_debug(f
"Adding path <{path}> with methods {method}")
142 f
"Applying {len(decorators)} decorator(s) to {path}")
146 orig_sig = inspect.signature(endpoint)
147 self.
disp.log_debug(f
"Original endpoint signature: {orig_sig}")
148 except Exception
as e:
149 self.
disp.log_warning(f
"Could not get original signature: {e}")
151 for i, decorator
in enumerate(decorators):
152 decorator_name = getattr(
153 decorator,
'__name__', f
'decorator_{i}')
155 f
"Applying decorator {decorator_name} to {path}")
158 if not callable(decorator):
160 f
"Decorator {decorator_name} is not callable for {path}")
164 decorated_endpoint = decorator(endpoint)
165 if not callable(decorated_endpoint):
167 f
"Decorator {decorator_name} did not return callable for {path}")
172 new_sig = inspect.signature(decorated_endpoint)
173 self.
disp.log_debug(f
"After {decorator_name}: {new_sig}")
174 except Exception
as e:
175 self.
disp.log_warning(
176 f
"Could not get signature after {decorator_name}: {e}")
178 endpoint = decorated_endpoint
180 self.
disp.log_debug(f
"No decorators provided for {path}")
185 f
"Endpoint validation failed for {path} with method {method}"
192 if existing_idx != -1:
194 existing_route = self.
routes[existing_idx]
196 existing_route[METHOD_KEY],
199 self.
routes[existing_idx][METHOD_KEY] = merged_methods
200 self.
disp.log_warning(
201 f
"Updated existing route <{path}> with methods: {merged_methods}"
207 self.
disp.log_error(f
"Failed to build endpoint for {path}")
209 self.
routes.append(new_endpoint)
211 f
"Added new route <{path}> with methods: {new_endpoint[METHOD_KEY]}"
216 def has_endpoint(self, path: str, endpoint: object, method: Union[str, List[str]]) -> bool:
217 """Check if an endpoint with exact path, endpoint function, and method exists.
220 path: The endpoint path.
221 endpoint: The endpoint function.
222 method: HTTP method(s) to check.
225 True if exact match exists, False otherwise.
228 if not endpoint_config:
231 return endpoint_config
in self.
routes
234 """Check if a path is already registered, optionally with specific method(s).
237 path: The endpoint path to check.
238 method: Optional HTTP method(s) to check. If None, checks if path exists with any method.
241 True if path is registered (with optional method match), False otherwise.
244 if route[PATH_KEY] == path:
250 if isinstance(method, str):
251 methods_to_check = [method.upper()]
253 methods_to_check = [m.upper()
for m
in method]
255 route_methods = [m.upper()
for m
in route[METHOD_KEY]]
258 for check_method
in methods_to_check:
259 if check_method
in route_methods:
264 def add_path_if_not_exists(self, path: str, endpoint: object, method: Union[str, List[str]], *, decorators: Optional[List[Callable]] =
None) -> int:
265 """Add path only if it doesn't already exist.
268 path: The path to call for the endpoint to be triggered.
269 endpoint: The function that represents the endpoint.
270 method: The HTTP method(s) used (GET, PUT, POST, etc.).
271 decorators: Optional list of decorators to apply.
274 success if it succeeded or already exists, error if there was an error.
278 f
"Path {path} with method(s) {method} already registered, skipping")
281 return self.
add_path(path, endpoint, method, decorators=decorators)
284 """Find the index of a route by path and endpoint function.
287 path: The endpoint path.
288 endpoint: The endpoint function.
291 Index of the route if found, -1 otherwise.
293 for idx, route
in enumerate(self.
routes):
294 if route[PATH_KEY] == path
and route[ENDPOINT_KEY] == endpoint:
298 def _merge_methods(self, existing_methods: List[str], new_methods: Union[str, List[str]]) -> List[str]:
299 """Merge new methods with existing methods, avoiding duplicates.
302 existing_methods: List of existing HTTP methods.
303 new_methods: New method(s) to add.
306 Merged list of unique methods in uppercase.
308 if isinstance(new_methods, str):
309 methods_to_add = [new_methods]
311 methods_to_add = new_methods
314 for method
in existing_methods:
315 all_methods.add(method.upper())
317 for method
in methods_to_add:
318 all_methods.add(method.upper())
320 return sorted(all_methods)
323 """Load default application paths from EndpointManager.
325 Retrieves the EndpointManager instance and calls its inject_routes()
326 method to register all default application endpoints.
329 RuntimeError: If EndpointManager instance cannot be found.
331 func_title =
"load_default_paths_initialised"
332 self.
disp.log_debug(
"Loading default paths_initialised", func_title)
340 error_message =
"EndpointManager could not be found"
341 self.
disp.log_critical(error_message)
342 raise RuntimeError(error_message)
347 """Inject all registered routes into the FastAPI application."""
348 self.
disp.log_info(
"Starting route injection process")
349 self.
disp.log_info(f
"Total routes to inject: {len(self.routes)}")
354 self.
disp.log_critical(
355 "No FastAPI app instance found in RuntimeControl")
357 "No instance was found in the app variable of the RuntimeControl instance")
359 if not hasattr(app,
"add_api_route"):
360 self.
disp.log_critical(
"FastAPI app missing add_api_route method")
362 "No add_api_route function was found in the app variable of the RuntimeControl instance")
364 successful_injections = 0
365 failed_injections = 0
367 for i, route
in enumerate(self.
routes, 1):
368 route_path = route[PATH_KEY]
369 route_methods = route[METHOD_KEY]
372 f
"Processing route {i}/{len(self.routes)}: {route_path} [{', '.join(route_methods)}]")
374 original_endpoint = route[ENDPOINT_KEY]
377 endpoint_to_register = self.
openapi_builder.prepare_endpoint_for_multi_method(
378 original_endpoint, route_methods)
380 if endpoint_to_register != original_endpoint:
382 f
"OpenAPIBuilder created wrapper: {endpoint_to_register.__name__}")
386 endpoint_to_register)
387 route_kwargs[
'methods'] = route_methods
390 f
"Route metadata for {route_path}: {route_kwargs}")
394 route_path, endpoint_to_register, **route_kwargs)
396 f
"Successfully injected route: {route_path} [{', '.join(route_methods)}]")
397 successful_injections += 1
399 except Exception
as e:
400 self.
disp.log_error(f
"Error adding route {route_path}: {e}")
404 route_path, original_endpoint, methods=route_methods)
405 self.
disp.log_warning(
406 f
"Fallback injection successful for {route_path}")
407 successful_injections += 1
408 except Exception
as fallback_error:
410 f
"Fallback injection failed for {route_path}: {fallback_error}")
411 failed_injections += 1
414 f
"Route injection completed: {successful_injections} successful, {failed_injections} failed")
RuntimeControl runtime_control_initialised
Optional[] endpoints_initialised
int add_path_if_not_exists(self, str path, object endpoint, Union[str, List[str]] method, *, Optional[List[Callable]] decorators=None)
None load_default_paths_initialised(self)
int _find_route_index(self, str path, object endpoint)
str endpoints_initialised
None __init__(self, int success=0, int error=84, bool debug=False)
bool endpoint_valid(self, str path, object endpoint, Union[str, List[str]] method)
RuntimeManager runtime_manager_initialised
int add_path(self, str path, object endpoint, Union[str, List[str]] method, *, Optional[List[Callable]] decorators=None)
bool has_endpoint(self, str path, object endpoint, Union[str, List[str]] method)
List[str] _merge_methods(self, List[str] existing_methods, Union[str, List[str]] new_methods)
bool is_path_registered(self, str path, Optional[Union[str, List[str]]] method=None)
Optional[Dict[str, Union[str, object, List[str]]]] _build_endpoint(self, str path, object endpoint, Union[str, List[str]] method)