Cat Feeder  1.0.0
The Cat feeder project
Loading...
Searching...
No Matches
openapi_builder.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: openapi_builder.py
14# CREATION DATE: 23-01-2026
15# LAST Modified: 3:12:29 24-01-2026
16# DESCRIPTION:
17# OpenAPI schema builder for FastAPI route documentation
18# /STOP
19# COPYRIGHT: (c) Cat Feeder
20# PURPOSE: Handles all OpenAPI/documentation related functionality for PathManager
21# /STOP
22# // AR
23# +==== END CatFeeder =================+
24"""
25
26import inspect
27import json
28import uuid
29from typing import Dict, Any, List, Callable, Optional
30from display_tty import Disp, initialise_logger
31
32
34 """Handles OpenAPI schema generation and metadata extraction for endpoints."""
35
36 disp: Disp = initialise_logger(__qualname__, False)
37
38 def __init__(self, debug: bool = False):
39 self.disp.update_disp_debug(debug)
40 self.disp.log_debug("OpenAPIBuilder initialized")
41
42 def extract_endpoint_metadata(self, endpoint: Callable) -> Dict[str, Any]:
43 """Extract metadata from endpoint function for FastAPI documentation."""
44 endpoint_name = getattr(endpoint, '__name__', 'unknown_endpoint')
45 self.disp.log_debug(
46 f"Extracting metadata for endpoint: {endpoint_name}")
47
48 metadata = {}
49
50 try:
51 decorator_metadata = self._extract_decorator_metadata(endpoint)
52 if decorator_metadata:
53 self.disp.log_debug(
54 f"Found decorator metadata for {endpoint_name}: {list(decorator_metadata.keys())}")
55 metadata.update(decorator_metadata)
56 except Exception as e:
57 self.disp.log_warning(
58 f"Failed to extract decorator metadata for {endpoint_name}: {e}")
59
60 # Handle description: prefer function docstring, then decorator description
61 self._extract_description_metadata(endpoint, endpoint_name, metadata)
62
63 # DON'T extract annotations - this causes FastAPI to misinterpret function signatures
64 # self._extract_annotation_metadata(endpoint, endpoint_name, metadata)
65
66 self.disp.log_debug(
67 f"Completed metadata extraction for {endpoint_name}")
68 return metadata
69
70 def build_openapi_parameters(self, metadata: Dict[str, Any]) -> Dict[str, Any]:
71 """Build OpenAPI parameter information from metadata."""
72 openapi_info = {}
73
74 # Add request body information
75 self._build_request_body_info(metadata, openapi_info)
76
77 # Add parameter information to description
78 param_descriptions = []
79 self._build_header_descriptions(metadata, param_descriptions)
80 self._build_query_param_descriptions(metadata, param_descriptions)
81 self._build_path_param_descriptions(metadata, param_descriptions)
82
83 if param_descriptions:
84 openapi_info['parameter_description'] = "\n\n".join(
85 param_descriptions)
86
87 return openapi_info
88
89 def build_security_description(self, metadata: Dict[str, Any]) -> str:
90 """Build security description from metadata."""
91 security_parts = []
92
93 if metadata.get('requires_auth'):
94 security_parts.append("Authentication required")
95 if metadata.get('requires_admin'):
96 security_parts.append("Admin privileges required")
97 if metadata.get('public'):
98 security_parts.append("Public access")
99 if metadata.get('testing_only'):
100 security_parts.append("Testing only")
101
102 security_level = metadata.get('security_level')
103 if security_level:
104 security_parts.append(f"Security level: {security_level}")
105
106 environment = metadata.get('environment')
107 if environment:
108 security_parts.append(f"Environment: {environment}")
109
110 return "\n".join(security_parts)
111
112 # Private methods for metadata extraction
113 def _extract_decorator_metadata(self, endpoint: Callable) -> Dict[str, Any]:
114 """Extract metadata from decorator attributes."""
115 endpoint_name = getattr(endpoint, '__name__', 'unknown_endpoint')
116 metadata = {}
117
118 # Extract security metadata
119 self._extract_security_metadata(endpoint, endpoint_name, metadata)
120 # Extract documentation metadata
121 self._extract_documentation_metadata(endpoint, endpoint_name, metadata)
122 # Extract parameter metadata
123 self._extract_parameter_metadata(endpoint, endpoint_name, metadata)
124
125 return metadata
126
127 def _extract_description_metadata(self, endpoint: Callable, endpoint_name: str, metadata: Dict[str, Any]) -> None:
128 """Extract and combine description metadata."""
129 function_description = ""
130 if hasattr(endpoint, '__doc__') and endpoint.__doc__:
131 function_description = endpoint.__doc__.strip()
132 self.disp.log_debug(
133 f"Found function docstring for {endpoint_name}")
134
135 decorator_description = metadata.get('description', "")
136 if decorator_description:
137 self.disp.log_debug(
138 f"Found decorator description for {endpoint_name}")
139
140 if function_description and decorator_description:
141 if function_description != decorator_description:
142 metadata['description'] = f"{function_description}\n\n{decorator_description}"
143 self.disp.log_debug(
144 f"Combined function and decorator descriptions for {endpoint_name}")
145 else:
146 metadata['description'] = function_description
147 self.disp.log_debug(
148 f"Using identical description for {endpoint_name}")
149 elif function_description:
150 metadata['description'] = function_description
151 self.disp.log_debug(
152 f"Using function description for {endpoint_name}")
153 elif decorator_description:
154 metadata['description'] = decorator_description
155 self.disp.log_debug(
156 f"Using decorator description for {endpoint_name}")
157
158 def _extract_annotation_metadata(self, endpoint: Callable, endpoint_name: str, metadata: Dict[str, Any]) -> None:
159 """Extract type annotation metadata safely."""
160 if hasattr(endpoint, '__annotations__'):
161 try:
162 annotations = endpoint.__annotations__
163 safe_annotations = {}
164 for key, value in annotations.items():
165 if not callable(value) and not inspect.isclass(value):
166 safe_annotations[key] = str(value)
167
168 if safe_annotations:
169 metadata['annotations'] = safe_annotations
170 self.disp.log_debug(
171 f"Found {len(safe_annotations)} safe annotations for {endpoint_name}")
172 except Exception as e:
173 self.disp.log_warning(
174 f"Failed to process annotations for {endpoint_name}: {e}")
175
176 def _extract_security_metadata(self, endpoint: Callable, endpoint_name: str, metadata: Dict[str, Any]) -> None:
177 """Extract security-related metadata from endpoint."""
178 if hasattr(endpoint, '_requires_auth') and getattr(endpoint, "_requires_auth", None):
179 metadata['requires_auth'] = True
180 self.disp.log_debug(
181 f"Endpoint {endpoint_name} requires authentication")
182
183 if hasattr(endpoint, '_requires_admin') and getattr(endpoint, "_requires_admin", None):
184 metadata['requires_admin'] = True
185 self.disp.log_debug(
186 f"Endpoint {endpoint_name} requires admin privileges")
187
188 if hasattr(endpoint, '_public') and getattr(endpoint, "_public", None):
189 metadata['public'] = True
190 self.disp.log_debug(f"Endpoint {endpoint_name} is public")
191
192 if hasattr(endpoint, '_testing_only') and getattr(endpoint, "_testing_only", None):
193 metadata['testing_only'] = True
194 self.disp.log_debug(f"Endpoint {endpoint_name} is testing-only")
195
196 if hasattr(endpoint, '_security_level'):
197 metadata['security_level'] = getattr(
198 endpoint, "_security_level", None)
199 self.disp.log_debug(
200 f"Endpoint {endpoint_name} has security level: {getattr(endpoint, '_security_level', 'unknown')}")
201
202 if hasattr(endpoint, '_environment'):
203 metadata['environment'] = getattr(endpoint, "_environment", None)
204 self.disp.log_debug(
205 f"Endpoint {endpoint_name} has environment: {getattr(endpoint, '_environment', 'unknown')}")
206
207 def _extract_documentation_metadata(self, endpoint: Callable, endpoint_name: str, metadata: Dict[str, Any]) -> None:
208 """Extract documentation-related metadata from endpoint."""
209 if hasattr(endpoint, '_tags'):
210 metadata['tags'] = getattr(endpoint, "_tags", None)
211 self.disp.log_debug(
212 f"Endpoint {endpoint_name} has tags: {getattr(endpoint, '_tags', False)}")
213
214 if hasattr(endpoint, '_description'):
215 metadata['description'] = getattr(endpoint, "_description", None)
216 self.disp.log_debug(
217 f"Endpoint {endpoint_name} has decorator description")
218
219 if hasattr(endpoint, '_summary'):
220 metadata['summary'] = getattr(endpoint, "_summary", None)
221 self.disp.log_debug(
222 f"Endpoint {endpoint_name} has summary: {getattr(endpoint, '_summary', 'None')}")
223
224 if hasattr(endpoint, '_response_model'):
225 metadata['response_model'] = getattr(
226 endpoint, "_response_model", None)
227 self.disp.log_debug(
228 f"Endpoint {endpoint_name} has decorator response_model")
229
230 def _extract_parameter_metadata(self, endpoint: Callable, endpoint_name: str, metadata: Dict[str, Any]) -> None:
231 """Extract parameter-related metadata from endpoint."""
232 # Body metadata
233 if hasattr(endpoint, '_requires_body') and getattr(endpoint, "_requires_body", None):
234 metadata['requires_body'] = True
235 metadata['body_model'] = getattr(endpoint, "_body_model", None)
236 metadata['body_description'] = getattr(
237 endpoint, "_body_description", "Request body")
238 self.disp.log_debug(
239 f"Endpoint {endpoint_name} requires request body")
240
241 # Header metadata
242 if hasattr(endpoint, '_requires_headers') and getattr(endpoint, "_requires_headers", None):
243 metadata['requires_headers'] = True
244 metadata['header_names'] = getattr(endpoint, "_header_names", [])
245 metadata['headers_description'] = getattr(
246 endpoint, "_headers_description", "Required headers")
247 self.disp.log_debug(
248 f"Endpoint {endpoint_name} requires headers: {metadata['header_names']}")
249
250 if hasattr(endpoint, '_requires_auth_header') and getattr(endpoint, "_requires_auth_header", None):
251 metadata['requires_auth_header'] = True
252 metadata['auth_header_name'] = getattr(
253 endpoint, "_auth_header_name", "Authorization")
254 metadata['auth_scheme'] = getattr(endpoint, "_auth_scheme", None)
255 self.disp.log_debug(
256 f"Endpoint {endpoint_name} requires auth header")
257
258 if hasattr(endpoint, '_requires_bearer_auth') and getattr(endpoint, "_requires_bearer_auth", None):
259 metadata['requires_bearer_auth'] = True
260 metadata['auth_scheme'] = "Bearer"
261 metadata['auth_header_name'] = "Authorization"
262 self.disp.log_debug(
263 f"Endpoint {endpoint_name} requires Bearer token")
264
265 if hasattr(endpoint, '_requires_basic_auth') and getattr(endpoint, "_requires_basic_auth", None):
266 metadata['requires_basic_auth'] = True
267 metadata['auth_scheme'] = "Basic"
268 metadata['auth_header_name'] = "Authorization"
269 self.disp.log_debug(
270 f"Endpoint {endpoint_name} requires Basic auth")
271
272 if hasattr(endpoint, '_requires_api_key') and getattr(endpoint, "_requires_api_key", None):
273 metadata['requires_api_key'] = True
274 metadata['auth_scheme'] = "API-Key"
275 metadata['auth_header_name'] = getattr(
276 endpoint, "_auth_header_name", "X-API-Key")
277 self.disp.log_debug(f"Endpoint {endpoint_name} requires API key")
278
279 # Query parameter metadata
280 if hasattr(endpoint, '_requires_query_params') and getattr(endpoint, "_requires_query_params", None):
281 metadata['requires_query_params'] = True
282 metadata['query_params'] = getattr(endpoint, "_query_params", {})
283 self.disp.log_debug(
284 f"Endpoint {endpoint_name} requires query params: {list(metadata['query_params'].keys())}")
285
286 # Path parameter metadata
287 if hasattr(endpoint, '_requires_path_params') and getattr(endpoint, "_requires_path_params", None):
288 metadata['requires_path_params'] = True
289 metadata['path_params'] = getattr(endpoint, "_path_params", {})
290 self.disp.log_debug(
291 f"Endpoint {endpoint_name} requires path params: {list(metadata['path_params'].keys())}")
292
293 # Content type metadata
294 self._extract_content_type_metadata(endpoint, endpoint_name, metadata)
295
296 def _extract_content_type_metadata(self, endpoint: Callable, endpoint_name: str, metadata: Dict[str, Any]) -> None:
297 """Extract content type related metadata from endpoint."""
298 if hasattr(endpoint, '_accepts_json_body') and getattr(endpoint, "_accepts_json_body", None):
299 metadata['accepts_json_body'] = True
300 metadata['json_body_description'] = getattr(
301 endpoint, "_json_body_description", "JSON request body")
302 # Extract JSON body example if available - but ensure it's serializable
303 if hasattr(endpoint, '_json_body_example'):
304 json_example = getattr(endpoint, "_json_body_example")
305 # Only store the example if it's already a dict or can be safely converted
306 if isinstance(json_example, (dict, list, str, int, float, bool, type(None))):
307 metadata['json_body_example'] = json_example
308 else:
309 # Convert to string if it's not a simple type
310 metadata['json_body_example'] = str(json_example)
311 self.disp.log_debug(f"Endpoint {endpoint_name} accepts JSON body")
312
313 if hasattr(endpoint, '_accepts_form_data') and getattr(endpoint, "_accepts_form_data", None):
314 metadata['accepts_form_data'] = True
315 metadata['form_data_description'] = getattr(
316 endpoint, "_form_data_description", "Form data")
317 self.disp.log_debug(f"Endpoint {endpoint_name} accepts form data")
318
319 if hasattr(endpoint, '_accepts_file_upload') and getattr(endpoint, "_accepts_file_upload", None):
320 metadata['accepts_file_upload'] = True
321 metadata['file_upload_description'] = getattr(
322 endpoint, "_file_upload_description", "File upload")
323 self.disp.log_debug(
324 f"Endpoint {endpoint_name} accepts file upload")
325
326 def _build_request_body_info(self, metadata: Dict[str, Any], openapi_info: Dict[str, Any]) -> None:
327 """Build request body information for OpenAPI."""
328 if metadata.get('requires_body') and metadata.get('body_model'):
329 openapi_info['request_body'] = {
330 "content": {
331 "application/json": {
332 "schema": {"type": "object"} # Simple fallback
333 }
334 },
335 "description": metadata.get('body_description', 'Request body'),
336 "required": True
337 }
338 elif metadata.get('accepts_json_body'):
339 json_schema = {"type": "object"}
340
341 json_example = metadata.get('json_body_example')
342 if json_example is not None:
343 try:
344 if isinstance(json_example, str):
345 parsed_example = json.loads(json_example)
346 json_schema["example"] = parsed_example
347 else:
348 json_schema["example"] = json_example
349 except (json.JSONDecodeError, TypeError) as e:
350 self.disp.log_warning(
351 f"Invalid JSON example provided: {json_example}, error: {e}")
352 json_schema["description"] = f"Example: {json_example}"
353
354 openapi_info['request_body'] = {
355 "content": {
356 "application/json": {
357 "schema": json_schema
358 }
359 },
360 "description": metadata.get('json_body_description', 'JSON request body'),
361 "required": True
362 }
363 elif metadata.get('accepts_form_data'):
364 openapi_info['request_body'] = {
365 "content": {
366 "application/x-www-form-urlencoded": {
367 "schema": {"type": "object"}
368 }
369 },
370 "description": metadata.get('form_data_description', 'Form data'),
371 "required": True
372 }
373 elif metadata.get('accepts_file_upload'):
374 openapi_info['request_body'] = {
375 "content": {
376 "multipart/form-data": {
377 "schema": {
378 "type": "object",
379 "properties": {
380 "file": {
381 "type": "string",
382 "format": "binary"
383 }
384 }
385 }
386 }
387 },
388 "description": metadata.get('file_upload_description', 'File upload'),
389 "required": True
390 }
391
392 def _build_header_descriptions(self, metadata: Dict[str, Any], param_descriptions: List[str]) -> None:
393 """Build header parameter descriptions."""
394 if metadata.get('requires_bearer_auth'):
395 param_descriptions.append(
396 "**Header:** `Authorization: Bearer <token>` - Bearer token required")
397 elif metadata.get('requires_basic_auth'):
398 param_descriptions.append(
399 "**Header:** `Authorization: Basic <credentials>` - Basic authentication required")
400 elif metadata.get('requires_api_key'):
401 header_name = metadata.get('auth_header_name', 'X-API-Key')
402 param_descriptions.append(
403 f"**Header:** `{header_name}: <api-key>` - API key required")
404 elif metadata.get('requires_auth_header'):
405 header_name = metadata.get('auth_header_name', 'Authorization')
406 scheme = metadata.get('auth_scheme')
407 if scheme:
408 param_descriptions.append(
409 f"**Header:** `{header_name}: {scheme} <credentials>` - Authentication required")
410 else:
411 param_descriptions.append(
412 f"**Header:** `{header_name}` - Authentication token required")
413
414 if metadata.get('requires_headers'):
415 headers = metadata.get('header_names', [])
416 if headers:
417 header_list = ", ".join(f"`{h}`" for h in headers)
418 param_descriptions.append(
419 f"**Headers:** {header_list} - {metadata.get('headers_description', 'Required headers')}")
420
421 def _build_query_param_descriptions(self, metadata: Dict[str, Any], param_descriptions: List[str]) -> None:
422 """Build query parameter descriptions."""
423 if metadata.get('requires_query_params'):
424 query_params = metadata.get('query_params', {})
425 if query_params:
426 param_list = []
427 for param, desc in query_params.items():
428 param_list.append(f"`{param}` - {desc}")
429 param_descriptions.append(
430 f"**Query Parameters:** {'; '.join(param_list)}")
431
432 def _build_path_param_descriptions(self, metadata: Dict[str, Any], param_descriptions: List[str]) -> None:
433 """Build path parameter descriptions."""
434 if metadata.get('requires_path_params'):
435 path_params = metadata.get('path_params', {})
436 if path_params:
437 param_list = []
438 for param, desc in path_params.items():
439 param_list.append(f"`{param}` - {desc}")
440 param_descriptions.append(
441 f"**Path Parameters:** {'; '.join(param_list)}")
442
443 def extract_route_metadata(self, endpoint: Callable) -> Dict[str, Any]:
444 """Extract route metadata from decorated endpoint for FastAPI route configuration."""
445 metadata = {}
446
447 # Extract all possible metadata attributes
448 metadata_attrs = {
449 'tags': '_tags',
450 'summary': '_summary',
451 'description': '_description',
452 'response_model': '_response_model',
453 'responses': '_responses',
454 'dependencies': '_dependencies',
455 'status_code': '_status_code',
456 'deprecated': '_deprecated',
457 'include_in_schema': '_include_in_schema'
458 }
459
460 # Handle operation_id specially - DON'T set it for multi-method endpoints
461 # FastAPI will auto-generate unique operation IDs per method
462 if hasattr(endpoint, '_operation_id_base'):
463 # This is a multi-method endpoint - let FastAPI auto-generate operation_id
464 self.disp.log_debug(
465 "Multi-method endpoint detected - skipping operation_id to avoid duplicates")
466 elif hasattr(endpoint, '_operation_id'):
467 # Single-method endpoint - use custom operation_id
468 metadata['operation_id'] = getattr(endpoint, '_operation_id')
469 self.disp.log_debug(
470 f"Found operation_id: {metadata['operation_id']}")
471 # Don't set operation_id at all if neither condition is met - let FastAPI auto-generate
472
473 for fastapi_param, attr_name in metadata_attrs.items():
474 if hasattr(endpoint, attr_name):
475 value = getattr(endpoint, attr_name)
476 if value is not None:
477 metadata[fastapi_param] = value
478 self.disp.log_debug(f"Found {fastapi_param}: {value}")
479
480 # Handle request body metadata for proper schema generation
481 if hasattr(endpoint, '_accepts_json_body') and getattr(endpoint, '_accepts_json_body'):
482 metadata['include_in_schema'] = True
483
484 # Add request body info to metadata for better documentation
485 if hasattr(endpoint, '_json_body_description'):
486 body_desc = getattr(endpoint, '_json_body_description')
487 if 'description' not in metadata:
488 metadata['description'] = body_desc
489 else:
490 metadata['description'] += f"\n\nRequest Body: {body_desc}"
491
492 # Special handling for authentication metadata
493 if hasattr(endpoint, '_requires_auth') and getattr(endpoint, '_requires_auth'):
494 metadata['dependencies'] = metadata.get('dependencies', [])
495
496 if hasattr(endpoint, '_requires_admin') and getattr(endpoint, '_requires_admin'):
497 metadata['dependencies'] = metadata.get('dependencies', [])
498
499 # Handle bearer auth metadata
500 if hasattr(endpoint, '_requires_bearer_auth') and getattr(endpoint, '_requires_bearer_auth'):
501 metadata['dependencies'] = metadata.get('dependencies', [])
502
503 # Ensure include_in_schema defaults to True
504 if 'include_in_schema' not in metadata:
505 metadata['include_in_schema'] = True
506
507 return metadata
508
509 def create_uuid_wrapper(self, original_func: Callable, endpoint_name: str) -> Callable:
510 """Create a wrapper with unique UUID-based name for multi-method endpoints.
511
512 Args:
513 original_func: The original endpoint function.
514 endpoint_name: Base name for the endpoint.
515
516 Returns:
517 Wrapper function with unique UUID-based name.
518 """
519 # Generate unique ID first
520 unique_id = str(uuid.uuid4()).replace('-', '')[:8] # Short UUID
521 unique_name = f"{endpoint_name}_{unique_id}"
522
523 # Create a proper wrapper function that copies all metadata
524 if hasattr(original_func, '__await__'):
525 # Async function
526 async def uuid_wrapper(*args, **kwargs):
527 return await original_func(*args, **kwargs)
528 else:
529 # Sync function
530 def uuid_wrapper(*args, **kwargs):
531 return original_func(*args, **kwargs)
532
533 # Copy ALL function metadata to make it appear as a completely different function
534 uuid_wrapper.__name__ = unique_name
535 uuid_wrapper.__qualname__ = unique_name
536 uuid_wrapper.__doc__ = getattr(original_func, '__doc__', None)
537 uuid_wrapper.__module__ = getattr(original_func, '__module__', None)
538 uuid_wrapper.__annotations__ = getattr(
539 original_func, '__annotations__', {})
540
541 # Copy any decorator metadata
542 self._copy_decorator_metadata(original_func, uuid_wrapper)
543
544 # Make sure FastAPI sees this as a completely different function
545 try:
546 uuid_wrapper.__code__ = original_func.__code__
547 except AttributeError:
548 # Some functions might not have __code__, that's fine
549 pass
550
551 return uuid_wrapper
552
553 def _copy_decorator_metadata(self, original_func: Callable, wrapper_func: Callable) -> None:
554 """Copy decorator metadata from original function to wrapper.
555
556 Args:
557 original_func: The original endpoint function.
558 wrapper_func: The wrapper function to copy metadata to.
559 """
560 metadata_attrs = [
561 '_tags', '_summary', '_description', '_response_model',
562 '_requires_auth', '_requires_admin', '_public', '_testing_only',
563 '_security_level', '_environment', '_operation_id', '_accepts_json_body',
564 '_json_body_description', '_json_body_example', '_requires_bearer_auth'
565 ]
566
567 for attr in metadata_attrs:
568 if hasattr(original_func, attr):
569 setattr(wrapper_func, attr, getattr(original_func, attr))
570
571 def prepare_endpoint_for_multi_method(self, endpoint: Callable, methods: List[str]) -> Callable:
572 """Prepare endpoint for multi-method registration to avoid Operation ID conflicts.
573
574 Args:
575 endpoint: The original endpoint function.
576 methods: List of HTTP methods for this endpoint.
577
578 Returns:
579 Original endpoint if single method, UUID wrapper if multi-method.
580 """
581 if len(methods) <= 1:
582 return endpoint
583
584 endpoint_name = getattr(endpoint, '__name__', 'unknown_endpoint')
585 return self.create_uuid_wrapper(endpoint, endpoint_name)
None _extract_security_metadata(self, Callable endpoint, str endpoint_name, Dict[str, Any] metadata)
Dict[str, Any] build_openapi_parameters(self, Dict[str, Any] metadata)
Dict[str, Any] _extract_decorator_metadata(self, Callable endpoint)
Callable prepare_endpoint_for_multi_method(self, Callable endpoint, List[str] methods)
Dict[str, Any] extract_endpoint_metadata(self, Callable endpoint)
Dict[str, Any] extract_route_metadata(self, Callable endpoint)
Callable create_uuid_wrapper(self, Callable original_func, str endpoint_name)
None _build_request_body_info(self, Dict[str, Any] metadata, Dict[str, Any] openapi_info)
None _build_header_descriptions(self, Dict[str, Any] metadata, List[str] param_descriptions)
None _extract_content_type_metadata(self, Callable endpoint, str endpoint_name, Dict[str, Any] metadata)
str build_security_description(self, Dict[str, Any] metadata)
None _build_path_param_descriptions(self, Dict[str, Any] metadata, List[str] param_descriptions)
None _extract_documentation_metadata(self, Callable endpoint, str endpoint_name, Dict[str, Any] metadata)
None _copy_decorator_metadata(self, Callable original_func, Callable wrapper_func)
None _extract_description_metadata(self, Callable endpoint, str endpoint_name, Dict[str, Any] metadata)
None _build_query_param_descriptions(self, Dict[str, Any] metadata, List[str] param_descriptions)
None _extract_annotation_metadata(self, Callable endpoint, str endpoint_name, Dict[str, Any] metadata)
None _extract_parameter_metadata(self, Callable endpoint, str endpoint_name, Dict[str, Any] metadata)