toms-fast 0.2.1__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.
- toms_fast-0.2.1.dist-info/METADATA +467 -0
- toms_fast-0.2.1.dist-info/RECORD +60 -0
- toms_fast-0.2.1.dist-info/WHEEL +4 -0
- toms_fast-0.2.1.dist-info/entry_points.txt +2 -0
- tomskit/__init__.py +0 -0
- tomskit/celery/README.md +693 -0
- tomskit/celery/__init__.py +4 -0
- tomskit/celery/celery.py +306 -0
- tomskit/celery/config.py +377 -0
- tomskit/cli/__init__.py +207 -0
- tomskit/cli/__main__.py +8 -0
- tomskit/cli/scaffold.py +123 -0
- tomskit/cli/templates/__init__.py +42 -0
- tomskit/cli/templates/base.py +348 -0
- tomskit/cli/templates/celery.py +101 -0
- tomskit/cli/templates/extensions.py +213 -0
- tomskit/cli/templates/fastapi.py +400 -0
- tomskit/cli/templates/migrations.py +281 -0
- tomskit/cli/templates_config.py +122 -0
- tomskit/logger/README.md +466 -0
- tomskit/logger/__init__.py +4 -0
- tomskit/logger/config.py +106 -0
- tomskit/logger/logger.py +290 -0
- tomskit/py.typed +0 -0
- tomskit/redis/README.md +462 -0
- tomskit/redis/__init__.py +6 -0
- tomskit/redis/config.py +85 -0
- tomskit/redis/redis_pool.py +87 -0
- tomskit/redis/redis_sync.py +66 -0
- tomskit/server/__init__.py +47 -0
- tomskit/server/config.py +117 -0
- tomskit/server/exceptions.py +412 -0
- tomskit/server/middleware.py +371 -0
- tomskit/server/parser.py +312 -0
- tomskit/server/resource.py +464 -0
- tomskit/server/server.py +276 -0
- tomskit/server/type.py +263 -0
- tomskit/sqlalchemy/README.md +590 -0
- tomskit/sqlalchemy/__init__.py +20 -0
- tomskit/sqlalchemy/config.py +125 -0
- tomskit/sqlalchemy/database.py +125 -0
- tomskit/sqlalchemy/pagination.py +359 -0
- tomskit/sqlalchemy/property.py +19 -0
- tomskit/sqlalchemy/sqlalchemy.py +131 -0
- tomskit/sqlalchemy/types.py +32 -0
- tomskit/task/README.md +67 -0
- tomskit/task/__init__.py +4 -0
- tomskit/task/task_manager.py +124 -0
- tomskit/tools/README.md +63 -0
- tomskit/tools/__init__.py +18 -0
- tomskit/tools/config.py +70 -0
- tomskit/tools/warnings.py +37 -0
- tomskit/tools/woker.py +81 -0
- tomskit/utils/README.md +666 -0
- tomskit/utils/README_SERIALIZER.md +644 -0
- tomskit/utils/__init__.py +35 -0
- tomskit/utils/fields.py +434 -0
- tomskit/utils/marshal_utils.py +137 -0
- tomskit/utils/response_utils.py +13 -0
- tomskit/utils/serializers.py +447 -0
|
@@ -0,0 +1,464 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import Any, Callable
|
|
5
|
+
|
|
6
|
+
from fastapi import APIRouter
|
|
7
|
+
|
|
8
|
+
from tomskit.server.server import FastModule
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass
|
|
12
|
+
class ResourceInfo:
|
|
13
|
+
"""Resource registration information"""
|
|
14
|
+
resource_cls: type
|
|
15
|
+
path: str
|
|
16
|
+
tags: list[str]
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class ResourceRegistry:
|
|
20
|
+
"""
|
|
21
|
+
Global resource registry, organized by module name.
|
|
22
|
+
|
|
23
|
+
Used to store all Resources registered via the @register_resource decorator,
|
|
24
|
+
supporting deferred registration, where resources are registered to their
|
|
25
|
+
corresponding FastModule instances at application startup.
|
|
26
|
+
"""
|
|
27
|
+
_modules: dict[str, list[ResourceInfo]] = {}
|
|
28
|
+
|
|
29
|
+
@classmethod
|
|
30
|
+
def register(
|
|
31
|
+
cls,
|
|
32
|
+
resource_cls: type,
|
|
33
|
+
module_name: str,
|
|
34
|
+
path: str,
|
|
35
|
+
tags: list[str] | None = None,
|
|
36
|
+
):
|
|
37
|
+
"""
|
|
38
|
+
Register a Resource to the specified module.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
resource_cls: Resource class
|
|
42
|
+
module_name: Module name, must match FastModule.name
|
|
43
|
+
path: Default path for the resource
|
|
44
|
+
tags: Default tags list for the resource
|
|
45
|
+
"""
|
|
46
|
+
if module_name not in cls._modules:
|
|
47
|
+
cls._modules[module_name] = []
|
|
48
|
+
cls._modules[module_name].append(
|
|
49
|
+
ResourceInfo(
|
|
50
|
+
resource_cls=resource_cls,
|
|
51
|
+
path=path,
|
|
52
|
+
tags=tags or []
|
|
53
|
+
)
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
@classmethod
|
|
57
|
+
def get_module_resources(cls, module_name: str) -> list[ResourceInfo]:
|
|
58
|
+
"""
|
|
59
|
+
Get all Resources for the specified module.
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
module_name: Module name
|
|
63
|
+
|
|
64
|
+
Returns:
|
|
65
|
+
List of ResourceInfo for the module
|
|
66
|
+
"""
|
|
67
|
+
return cls._modules.get(module_name, [])
|
|
68
|
+
|
|
69
|
+
@classmethod
|
|
70
|
+
def clear_module(cls, module_name: str):
|
|
71
|
+
"""
|
|
72
|
+
Clear registrations for the specified module (mainly for testing).
|
|
73
|
+
|
|
74
|
+
Args:
|
|
75
|
+
module_name: Module name
|
|
76
|
+
"""
|
|
77
|
+
if module_name in cls._modules:
|
|
78
|
+
del cls._modules[module_name]
|
|
79
|
+
|
|
80
|
+
@classmethod
|
|
81
|
+
def clear_all(cls):
|
|
82
|
+
"""Clear all registrations (mainly for testing)"""
|
|
83
|
+
cls._modules.clear()
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def register_resource(
|
|
87
|
+
module: str,
|
|
88
|
+
path: str,
|
|
89
|
+
tags: list[str] | None = None,
|
|
90
|
+
):
|
|
91
|
+
"""
|
|
92
|
+
Decorator: Register a Resource to the specified module.
|
|
93
|
+
|
|
94
|
+
Resources marked with this decorator will be automatically registered
|
|
95
|
+
when FastModule.auto_register_resources() is called.
|
|
96
|
+
|
|
97
|
+
Args:
|
|
98
|
+
module: Module name, must match FastModule.name
|
|
99
|
+
path: Default path for the resource
|
|
100
|
+
tags: Default tags list for the resource
|
|
101
|
+
|
|
102
|
+
Example:
|
|
103
|
+
@register_resource(module="files", path="/files", tags=["File Management"])
|
|
104
|
+
class FileResource(Resource):
|
|
105
|
+
@api_doc(summary="Upload file", response_model=FileResponse)
|
|
106
|
+
async def post(self, request: Request):
|
|
107
|
+
...
|
|
108
|
+
"""
|
|
109
|
+
def decorator(cls):
|
|
110
|
+
ResourceRegistry.register(cls, module, path, tags=tags)
|
|
111
|
+
return cls
|
|
112
|
+
return decorator
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def _normalize_responses(responses: dict[int | str, str | dict[str, Any]]) -> dict[int | str, dict[str, Any]]:
|
|
116
|
+
"""
|
|
117
|
+
Normalize response format, supporting simplified string format.
|
|
118
|
+
|
|
119
|
+
Converts simplified format:
|
|
120
|
+
{200: "Success", 400: "Bad Request"}
|
|
121
|
+
to FastAPI expected format:
|
|
122
|
+
{200: {"description": "Success"}, 400: {"description": "Bad Request"}}
|
|
123
|
+
"""
|
|
124
|
+
normalized: dict[int | str, dict[str, Any]] = {}
|
|
125
|
+
for status_code, value in responses.items():
|
|
126
|
+
if isinstance(value, str):
|
|
127
|
+
normalized[status_code] = {"description": value}
|
|
128
|
+
elif isinstance(value, dict):
|
|
129
|
+
normalized[status_code] = value
|
|
130
|
+
else:
|
|
131
|
+
normalized[status_code] = {"description": str(value)}
|
|
132
|
+
return normalized
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def api_doc(
|
|
136
|
+
summary: str | None = None,
|
|
137
|
+
description: str | None = None,
|
|
138
|
+
response_description: str | None = None,
|
|
139
|
+
deprecated: bool = False,
|
|
140
|
+
operation_id: str | None = None,
|
|
141
|
+
response_model: Any = None,
|
|
142
|
+
status_code: int | None = None,
|
|
143
|
+
responses: dict[int | str, str | dict[str, Any]] | None = None,
|
|
144
|
+
path: str | None = None,
|
|
145
|
+
):
|
|
146
|
+
"""
|
|
147
|
+
Decorator for setting API documentation information and path for Resource class methods.
|
|
148
|
+
|
|
149
|
+
Important rules:
|
|
150
|
+
- The same parameter cannot be used repeatedly in multiple @api_doc decorators
|
|
151
|
+
(except responses which can be merged)
|
|
152
|
+
- response_model: Used to specify the BaseModel/Pydantic model for FastAPI API responses
|
|
153
|
+
- path: Method-level path, if provided will override the default path from registration
|
|
154
|
+
- responses can be merged across multiple decorators
|
|
155
|
+
- Other parameters (summary, description, response_model, operation_id, path, etc.)
|
|
156
|
+
will raise an exception if used repeatedly
|
|
157
|
+
- tags should be set in @register_resource, not in @api_doc
|
|
158
|
+
|
|
159
|
+
Example:
|
|
160
|
+
from pydantic import BaseModel
|
|
161
|
+
|
|
162
|
+
class UserResponse(BaseModel):
|
|
163
|
+
id: int
|
|
164
|
+
name: str
|
|
165
|
+
|
|
166
|
+
@register_resource(module="users", path="/users", tags=["User Management"])
|
|
167
|
+
class UserResource(Resource):
|
|
168
|
+
# Method 1: Using simplified string format
|
|
169
|
+
@api_doc(
|
|
170
|
+
summary="Get user list",
|
|
171
|
+
description="Get all users with pagination",
|
|
172
|
+
response_model=list[UserResponse],
|
|
173
|
+
responses={
|
|
174
|
+
200: "Success",
|
|
175
|
+
400: "Bad request",
|
|
176
|
+
404: "Not found"
|
|
177
|
+
}
|
|
178
|
+
)
|
|
179
|
+
async def get(self, request: Request):
|
|
180
|
+
...
|
|
181
|
+
|
|
182
|
+
# Method 2: Using full dictionary format
|
|
183
|
+
@api_doc(
|
|
184
|
+
summary="Create user",
|
|
185
|
+
response_model=UserResponse,
|
|
186
|
+
responses={
|
|
187
|
+
201: {"description": "User created successfully"},
|
|
188
|
+
400: {"description": "Invalid input data"},
|
|
189
|
+
409: {"description": "User already exists", "content": {...}}
|
|
190
|
+
}
|
|
191
|
+
)
|
|
192
|
+
async def post(self, request: Request):
|
|
193
|
+
...
|
|
194
|
+
|
|
195
|
+
# Method 3: Setting multiple times, responses will be automatically merged
|
|
196
|
+
@api_doc(responses={200: "Success"})
|
|
197
|
+
@api_doc(responses={404: "Not found"})
|
|
198
|
+
@api_doc(responses={500: "Internal server error"})
|
|
199
|
+
@api_doc(
|
|
200
|
+
path="/users/{user_id}", # Override default path
|
|
201
|
+
summary="Get user details",
|
|
202
|
+
response_model=UserResponse
|
|
203
|
+
)
|
|
204
|
+
async def get(self, request: Request):
|
|
205
|
+
...
|
|
206
|
+
"""
|
|
207
|
+
def decorator(func: Callable) -> Callable:
|
|
208
|
+
# Check for non-mergeable parameters that cannot be repeated
|
|
209
|
+
non_mergeable_params = {
|
|
210
|
+
'summary': summary,
|
|
211
|
+
'description': description,
|
|
212
|
+
'response_description': response_description,
|
|
213
|
+
'response_model': response_model,
|
|
214
|
+
'status_code': status_code,
|
|
215
|
+
'operation_id': operation_id,
|
|
216
|
+
'path': path,
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
for param_name, param_value in non_mergeable_params.items():
|
|
220
|
+
if param_value is not None:
|
|
221
|
+
attr_name = f'_api_{param_name}'
|
|
222
|
+
if hasattr(func, attr_name):
|
|
223
|
+
raise ValueError(
|
|
224
|
+
f"Parameter '{param_name}' has already been set in a previous @api_doc decorator. "
|
|
225
|
+
f"It cannot be used repeatedly in multiple decorators. Please merge into a single @api_doc."
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
# Set parameters
|
|
229
|
+
if summary is not None:
|
|
230
|
+
setattr(func, '_api_summary', summary)
|
|
231
|
+
if description is not None:
|
|
232
|
+
setattr(func, '_api_description', description)
|
|
233
|
+
if response_description is not None:
|
|
234
|
+
setattr(func, '_api_response_description', response_description)
|
|
235
|
+
if response_model is not None:
|
|
236
|
+
setattr(func, '_api_response_model', response_model)
|
|
237
|
+
if status_code is not None:
|
|
238
|
+
setattr(func, '_api_status_code', status_code)
|
|
239
|
+
if operation_id is not None:
|
|
240
|
+
setattr(func, '_api_operation_id', operation_id)
|
|
241
|
+
if path is not None:
|
|
242
|
+
setattr(func, '_api_path', path)
|
|
243
|
+
|
|
244
|
+
# Merge responses: merge dictionaries instead of overwriting
|
|
245
|
+
if responses is not None:
|
|
246
|
+
existing_responses = getattr(func, '_api_responses', None)
|
|
247
|
+
if existing_responses:
|
|
248
|
+
merged_responses = dict(existing_responses)
|
|
249
|
+
normalized_new = _normalize_responses(responses)
|
|
250
|
+
merged_responses.update(normalized_new)
|
|
251
|
+
setattr(func, '_api_responses', merged_responses)
|
|
252
|
+
else:
|
|
253
|
+
normalized_responses = _normalize_responses(responses)
|
|
254
|
+
setattr(func, '_api_responses', normalized_responses)
|
|
255
|
+
|
|
256
|
+
if deprecated:
|
|
257
|
+
setattr(func, '_api_deprecated', True)
|
|
258
|
+
|
|
259
|
+
# Mark method as decorated by api_doc
|
|
260
|
+
setattr(func, '_api_doc_decorated', True)
|
|
261
|
+
|
|
262
|
+
return func
|
|
263
|
+
return decorator
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
class Resource:
|
|
267
|
+
"""
|
|
268
|
+
RESTful API resource base class.
|
|
269
|
+
|
|
270
|
+
A Resource represents a resource entity, containing CRUD operations for that resource.
|
|
271
|
+
All methods share the same path (specified during registration), but can be overridden
|
|
272
|
+
via @api_doc(path=...).
|
|
273
|
+
|
|
274
|
+
Attributes:
|
|
275
|
+
decorators (List[Callable]): Class-level decorator list, applied to all methods.
|
|
276
|
+
methods (List[str]): List of supported HTTP methods.
|
|
277
|
+
"""
|
|
278
|
+
decorators: list[Callable] | None = None
|
|
279
|
+
methods: list[str] = ["get", "post", "put", "delete", "patch"] # Only standard RESTful methods
|
|
280
|
+
|
|
281
|
+
def __init__(self, router: ResourceRouter):
|
|
282
|
+
self.router = router
|
|
283
|
+
self.app: FastModule = self.router.router_app
|
|
284
|
+
|
|
285
|
+
def __init_subclass__(cls):
|
|
286
|
+
"""
|
|
287
|
+
Called when a subclass is initialized, ensuring methods are properly decorated.
|
|
288
|
+
"""
|
|
289
|
+
# If subclass hasn't set decorators, initialize as empty list
|
|
290
|
+
if not hasattr(cls, 'decorators') or cls.decorators is None:
|
|
291
|
+
cls.decorators = []
|
|
292
|
+
super().__init_subclass__()
|
|
293
|
+
cls._decorate_methods()
|
|
294
|
+
|
|
295
|
+
@classmethod
|
|
296
|
+
def _decorate_methods(cls):
|
|
297
|
+
"""
|
|
298
|
+
Apply decorators to methods in the subclass.
|
|
299
|
+
"""
|
|
300
|
+
for method_name in cls.methods:
|
|
301
|
+
method = getattr(cls, method_name, None)
|
|
302
|
+
if method:
|
|
303
|
+
decorated = cls.apply_decorators(method)
|
|
304
|
+
setattr(cls, method_name, decorated)
|
|
305
|
+
|
|
306
|
+
@classmethod
|
|
307
|
+
def apply_decorators(cls, func: Callable) -> Callable:
|
|
308
|
+
"""
|
|
309
|
+
Apply decorators to the specified method.
|
|
310
|
+
|
|
311
|
+
Args:
|
|
312
|
+
func (Callable): Method to be decorated.
|
|
313
|
+
|
|
314
|
+
Returns:
|
|
315
|
+
Callable: Decorated method.
|
|
316
|
+
"""
|
|
317
|
+
if cls.decorators:
|
|
318
|
+
for decorator in reversed(cls.decorators):
|
|
319
|
+
func = decorator(func)
|
|
320
|
+
return func
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
class ResourceRouter(APIRouter):
|
|
324
|
+
"""
|
|
325
|
+
RESTful resource router for registering and managing Resources.
|
|
326
|
+
|
|
327
|
+
Inherits from FastAPI's APIRouter, specifically designed for handling Resource class registration.
|
|
328
|
+
"""
|
|
329
|
+
|
|
330
|
+
def __init__(self, app: FastModule, *args, **kwargs):
|
|
331
|
+
"""
|
|
332
|
+
Initialize ResourceRouter.
|
|
333
|
+
|
|
334
|
+
Args:
|
|
335
|
+
app (FastModule): FastModule instance, must be provided.
|
|
336
|
+
*args, **kwargs: Other parameters passed to APIRouter.
|
|
337
|
+
"""
|
|
338
|
+
self.router_app = app
|
|
339
|
+
if self.router_app is None:
|
|
340
|
+
raise ValueError("The 'app' parameter must be provided.")
|
|
341
|
+
|
|
342
|
+
super().__init__(*args, **kwargs)
|
|
343
|
+
self._default_dependencies = kwargs.get('dependencies', [])
|
|
344
|
+
# Cache route names for efficient duplicate checking
|
|
345
|
+
self._route_names: set[str] = set()
|
|
346
|
+
|
|
347
|
+
def check_name_duplicate(self, name: str) -> bool:
|
|
348
|
+
"""Check if route name already exists"""
|
|
349
|
+
return name in self._route_names
|
|
350
|
+
|
|
351
|
+
def add_resource(
|
|
352
|
+
self,
|
|
353
|
+
resource_cls: type[Resource],
|
|
354
|
+
path: str,
|
|
355
|
+
tags: list[str] | None = None,
|
|
356
|
+
):
|
|
357
|
+
"""
|
|
358
|
+
Add Resource to the router.
|
|
359
|
+
|
|
360
|
+
Args:
|
|
361
|
+
resource_cls (Type[Resource]): Resource class, must inherit from Resource.
|
|
362
|
+
path (str): Default path for the resource. All methods use this path by default,
|
|
363
|
+
unless a method specifies a different path via @api_doc(path=...).
|
|
364
|
+
tags (List[str] | None): Default tags list. Applied to all methods,
|
|
365
|
+
unless a method specifies different tags via @api_doc(tags=...).
|
|
366
|
+
"""
|
|
367
|
+
resource_instance = resource_cls(router=self)
|
|
368
|
+
|
|
369
|
+
# Use tags passed during registration (Resource class no longer has tags attribute)
|
|
370
|
+
class_tags = tags or []
|
|
371
|
+
|
|
372
|
+
# Standard HTTP methods set for efficient validation
|
|
373
|
+
VALID_HTTP_METHODS = {'get', 'post', 'put', 'delete', 'patch'}
|
|
374
|
+
|
|
375
|
+
# Get all methods decorated with @api_doc
|
|
376
|
+
# Check class methods directly instead of using dir() for better performance
|
|
377
|
+
methods_to_register = []
|
|
378
|
+
for attr_name in resource_cls.methods:
|
|
379
|
+
# Validate method name early
|
|
380
|
+
method_name_lower = attr_name.lower()
|
|
381
|
+
if method_name_lower not in VALID_HTTP_METHODS:
|
|
382
|
+
raise ValueError(
|
|
383
|
+
f"Method name '{attr_name}' is not a standard HTTP method. "
|
|
384
|
+
f"Only 'get', 'post', 'put', 'delete', 'patch' are supported. "
|
|
385
|
+
f"Non-standard method names like 'get_detail' are not allowed."
|
|
386
|
+
)
|
|
387
|
+
|
|
388
|
+
class_method = getattr(resource_cls, attr_name, None)
|
|
389
|
+
if class_method and callable(class_method) and hasattr(class_method, '_api_doc_decorated'):
|
|
390
|
+
# Get the method from instance to ensure it has the correct bound state
|
|
391
|
+
instance_method = getattr(resource_instance, attr_name, None)
|
|
392
|
+
if instance_method:
|
|
393
|
+
# Store original method name, lowercase method name, class method, and instance method
|
|
394
|
+
methods_to_register.append((attr_name, method_name_lower, class_method, instance_method))
|
|
395
|
+
|
|
396
|
+
if not methods_to_register:
|
|
397
|
+
raise ValueError(
|
|
398
|
+
f"Resource {resource_cls.__name__} has no methods decorated with @api_doc. "
|
|
399
|
+
f"Please add @api_doc decorator to at least one method."
|
|
400
|
+
)
|
|
401
|
+
|
|
402
|
+
# Register each method
|
|
403
|
+
for original_method_name, method_name_lower, class_method, instance_method in methods_to_register:
|
|
404
|
+
# Apply class-level decorators to instance method
|
|
405
|
+
decorated_method = resource_cls.apply_decorators(instance_method)
|
|
406
|
+
|
|
407
|
+
# Batch get all method-level documentation attributes from class method
|
|
408
|
+
# (attributes are set on the class method by @api_doc decorator)
|
|
409
|
+
method_path = getattr(class_method, '_api_path', None) or path
|
|
410
|
+
method_summary = getattr(class_method, '_api_summary', None)
|
|
411
|
+
method_description = getattr(class_method, '_api_description', None)
|
|
412
|
+
method_response_model = getattr(class_method, '_api_response_model', None)
|
|
413
|
+
method_status_code = getattr(class_method, '_api_status_code', None)
|
|
414
|
+
method_responses = getattr(class_method, '_api_responses', None)
|
|
415
|
+
method_response_description = getattr(class_method, '_api_response_description', None)
|
|
416
|
+
method_deprecated = getattr(class_method, '_api_deprecated', False)
|
|
417
|
+
method_operation_id = getattr(class_method, '_api_operation_id', None)
|
|
418
|
+
|
|
419
|
+
# Use tags from register_resource only, not from api_doc
|
|
420
|
+
method_tags = class_tags
|
|
421
|
+
|
|
422
|
+
# Determine HTTP method (already validated and lowercased)
|
|
423
|
+
http_method = method_name_lower.upper()
|
|
424
|
+
|
|
425
|
+
# Generate route name using class name and original method name
|
|
426
|
+
route_name = f"{resource_cls.__name__}_{original_method_name}"
|
|
427
|
+
if self.check_name_duplicate(route_name):
|
|
428
|
+
raise ValueError(f"Resource route name {route_name} already exists")
|
|
429
|
+
# Add to cache
|
|
430
|
+
self._route_names.add(route_name)
|
|
431
|
+
|
|
432
|
+
# Build route parameters
|
|
433
|
+
# Note: FastAPI's APIRouter.add_api_route will automatically prepend
|
|
434
|
+
# the router's prefix to the path. So we pass the path without prefix here.
|
|
435
|
+
route_kwargs: dict[str, Any] = {
|
|
436
|
+
"path": method_path,
|
|
437
|
+
"endpoint": decorated_method,
|
|
438
|
+
"methods": [http_method],
|
|
439
|
+
"name": route_name,
|
|
440
|
+
"dependencies": self._default_dependencies,
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
# Add documentation-related parameters (only if they have values)
|
|
444
|
+
if method_tags:
|
|
445
|
+
route_kwargs["tags"] = method_tags
|
|
446
|
+
if method_summary:
|
|
447
|
+
route_kwargs["summary"] = method_summary
|
|
448
|
+
if method_description:
|
|
449
|
+
route_kwargs["description"] = method_description
|
|
450
|
+
if method_response_model:
|
|
451
|
+
route_kwargs["response_model"] = method_response_model
|
|
452
|
+
if method_status_code:
|
|
453
|
+
route_kwargs["status_code"] = method_status_code
|
|
454
|
+
if method_responses:
|
|
455
|
+
# method_responses is already normalized in api_doc decorator
|
|
456
|
+
route_kwargs["responses"] = method_responses
|
|
457
|
+
if method_response_description:
|
|
458
|
+
route_kwargs["response_description"] = method_response_description
|
|
459
|
+
if method_deprecated:
|
|
460
|
+
route_kwargs["deprecated"] = True
|
|
461
|
+
if method_operation_id:
|
|
462
|
+
route_kwargs["operation_id"] = method_operation_id
|
|
463
|
+
|
|
464
|
+
self.add_api_route(**route_kwargs)
|