Skip to content

Commit 444a86e

Browse files
committed
Added dynamic annotation for API parameters
1 parent 5adc69c commit 444a86e

File tree

5 files changed

+92
-60
lines changed

5 files changed

+92
-60
lines changed

examples/api_for_sqlalchemy/api/user.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
from sqlalchemy.ext.asyncio import AsyncSession
1010
from sqlalchemy.sql import Select
1111
from tortoise.exceptions import DoesNotExist
12-
from tortoise.queryset import QuerySet
1312

1413
from examples.api_for_sqlalchemy.extensions.sqlalchemy import Connector
1514
from examples.api_for_sqlalchemy.helpers.factories.meta_base import FactoryUseMode
@@ -20,7 +19,6 @@
2019
from examples.api_for_sqlalchemy.models.pydantic.user import UserInSchema
2120
from examples.api_for_sqlalchemy.models.sqlalchemy import User
2221
from fastapi_rest_jsonapi import SqlalchemyEngine
23-
2422
from fastapi_rest_jsonapi.exceptions import (
2523
BadRequest,
2624
HTTPException,

fastapi_rest_jsonapi/api.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,14 @@
66
List,
77
Optional,
88
Type,
9-
Union,
9+
Union, TypeVar,
1010
)
1111

1212
import pydantic
1313
from fastapi import APIRouter
1414
from pydantic import BaseModel, Field
1515

16-
from fastapi_rest_jsonapi.data_layers.data_typing import TypeModel, TypeSchema
16+
from fastapi_rest_jsonapi.data_layers.data_typing import TypeModel
1717
from fastapi_rest_jsonapi.data_layers.orm import DBORMType
1818
from fastapi_rest_jsonapi.exceptions import ExceptionResponseSchema
1919
from fastapi_rest_jsonapi.methods import (
@@ -28,13 +28,16 @@
2828

2929
JSON_API_RESPONSE_TYPE = Optional[Dict[Union[int, str], Dict[str, Any]]]
3030

31+
TypeAPIRouter = TypeVar("TypeAPIRouter", bound=APIRouter)
32+
TypeSchema = TypeVar("TypeSchema", bound=BaseModel)
33+
3134

3235
class RoutersJSONAPI:
3336
"""API Router interface for JSON API endpoints in web-services."""
3437

3538
def __init__( # noqa: WPS211
3639
self,
37-
routers: APIRouter,
40+
routers: TypeAPIRouter,
3841
path: Union[str, List[str]],
3942
tags: List[str],
4043
class_detail: Any,

fastapi_rest_jsonapi/methods.py

Lines changed: 63 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,7 @@
2323
from fastapi_rest_jsonapi.data_layers.tortoise_orm_engine import TortoiseORMEngine
2424
from fastapi_rest_jsonapi.exceptions.json_api import UnsupportedFeatureORM
2525
from fastapi_rest_jsonapi.querystring import QueryStringManager
26-
from fastapi_rest_jsonapi.signature import (
27-
is_necessary_request,
28-
update_signature,
29-
)
26+
from fastapi_rest_jsonapi.signature import update_signature
3027

3128

3229
def get_detail_jsonapi(
@@ -41,13 +38,16 @@ def get_detail_jsonapi(
4138
def inner(func: Callable) -> Callable:
4239
async def wrapper(request: Request, obj_id: int, **kwargs):
4340
query_params = QueryStringManager(request=request, schema=schema)
44-
data_dict: dict = dict(query_params=query_params, obj_id=obj_id)
45-
if is_necessary_request(func):
46-
data_dict["request"] = request
47-
48-
params_function = OrderedDict(signature(func).parameters)
49-
data_dict.update({i_k: i_v for i_k, i_v in kwargs.items() if i_k in params_function})
50-
data_dict = {i_k: i_v for i_k, i_v in data_dict.items() if i_k in params_function}
41+
data_dict = {"obj_id": obj_id}
42+
func_signature = signature(func).parameters
43+
for i_name, i_type in OrderedDict(func_signature).items():
44+
if i_type.annotation is Request:
45+
data_dict[i_name] = request
46+
elif i_type.annotation is QueryStringManager:
47+
data_dict[i_name] = query_params
48+
49+
data_dict.update({i_k: i_v for i_k, i_v in kwargs.items() if i_k in func_signature})
50+
data_dict = {i_k: i_v for i_k, i_v in data_dict.items() if i_k in func_signature}
5151
data_schema: Any = await func(**data_dict)
5252
return schema_resp(
5353
data={
@@ -86,13 +86,18 @@ def patch_detail_jsonapi(
8686
def inner(func: Callable) -> Callable:
8787
async def wrapper(request: Request, obj_id: int, data: schema_in, **kwargs): # type: ignore
8888
query_params = QueryStringManager(request=request, schema=schema)
89-
data_dict: dict = dict(query_params=query_params, obj_id=obj_id, data=getattr(data, "attributes", data))
90-
if is_necessary_request(func):
91-
data_dict["request"] = request
92-
93-
params_function = OrderedDict(signature(func).parameters)
94-
data_dict.update({i_k: i_v for i_k, i_v in kwargs.items() if i_k in params_function})
95-
data_dict = {i_k: i_v for i_k, i_v in data_dict.items() if i_k in params_function}
89+
data_dict = {"obj_id": obj_id}
90+
func_signature = signature(func).parameters
91+
for i_name, i_type in OrderedDict(func_signature).items():
92+
if i_type.annotation is schema_in.__fields__["attributes"].type_:
93+
data_dict[i_name] = getattr(data, 'attributes', data)
94+
elif i_type.annotation is Request:
95+
data_dict[i_name] = request
96+
elif i_type.annotation is QueryStringManager:
97+
data_dict[i_name] = query_params
98+
99+
data_dict.update({i_k: i_v for i_k, i_v in kwargs.items() if i_k in func_signature})
100+
data_dict = {i_k: i_v for i_k, i_v in data_dict.items() if i_k in func_signature}
96101
data_schema: Any = await func(**data_dict)
97102
return schema_resp(
98103
data={
@@ -122,13 +127,16 @@ def delete_detail_jsonapi(
122127
def inner(func: Callable) -> Callable:
123128
async def wrapper(request: Request, obj_id: int, **kwargs): # type: ignore
124129
query_params = QueryStringManager(request=request, schema=schema)
125-
data_dict: dict = dict(query_params=query_params, obj_id=obj_id)
126-
if is_necessary_request(func):
127-
data_dict["request"] = request
128-
129-
params_function = OrderedDict(signature(func).parameters)
130-
data_dict.update({i_k: i_v for i_k, i_v in kwargs.items() if i_k in params_function})
131-
data_dict = {i_k: i_v for i_k, i_v in data_dict.items() if i_k in params_function}
130+
data_dict = {"obj_id": obj_id}
131+
func_signature = signature(func).parameters
132+
for i_name, i_type in OrderedDict(func_signature).items():
133+
if i_type.annotation is Request:
134+
data_dict[i_name] = request
135+
elif i_type.annotation is QueryStringManager:
136+
data_dict[i_name] = query_params
137+
138+
data_dict.update({i_k: i_v for i_k, i_v in kwargs.items() if i_k in func_signature})
139+
data_dict = {i_k: i_v for i_k, i_v in data_dict.items() if i_k in func_signature}
132140
await func(**data_dict)
133141
return Response(status_code=status.HTTP_204_NO_CONTENT)
134142

@@ -163,9 +171,13 @@ async def wrapper(
163171
**kwargs,
164172
):
165173
query_params = QueryStringManager(request=request, schema=schema)
166-
data_dict: dict = dict(query_params=query_params)
167-
if is_necessary_request(func):
168-
data_dict["request"] = request
174+
data_dict = {}
175+
func_signature = signature(func).parameters
176+
for i_name, i_type in OrderedDict(func_signature).items():
177+
if i_type.annotation is Request:
178+
data_dict[i_name] = request
179+
elif i_type.annotation is QueryStringManager:
180+
data_dict[i_name] = query_params
169181

170182
params_function = OrderedDict(signature(func).parameters)
171183
data_dict.update({i_k: i_v for i_k, i_v in kwargs.items() if i_k in params_function})
@@ -238,22 +250,21 @@ async def wrapper(
238250
**kwargs,
239251
):
240252
query_params = QueryStringManager(request=request, schema=schema)
241-
data = {
242-
i_name: query_params
243-
for i_name, i_param in OrderedDict(signature(func).parameters).items()
244-
if i_param.annotation is QueryStringManager
245-
}
246-
if is_necessary_request(func):
247-
data["request"] = request
248-
249-
params_function = OrderedDict(signature(func).parameters)
250-
data.update({i_k: i_v for i_k, i_v in kwargs.items() if i_k in params_function})
251-
data = {i_k: i_v for i_k, i_v in data.items() if i_k in params_function}
252-
query = await func(**data)
253+
data_dict = {}
254+
func_signature = signature(func).parameters
255+
for i_name, i_type in OrderedDict(func_signature).items():
256+
if i_type.annotation is Request:
257+
data_dict[i_name] = request
258+
elif i_type.annotation is QueryStringManager:
259+
data_dict[i_name] = query_params
260+
261+
data_dict.update({i_k: i_v for i_k, i_v in kwargs.items() if i_k in func_signature})
262+
data_dict = {i_k: i_v for i_k, i_v in data_dict.items() if i_k in func_signature}
263+
query = await func(**data_dict)
253264

254265
if engine is DBORMType.sqlalchemy:
255266
# Для SQLAlchemy нужно указывать session, для Tortoise достаточно модели
256-
session_list = [i_v for i_k, i_v in params_function.items() if isinstance(i_v, AsyncSession)]
267+
session_list = [i_v for i_k, i_v in func_signature.items() if isinstance(i_v, AsyncSession)]
257268
session: Optional[AsyncSession] = session_list and session_list[0] or None
258269
else:
259270
session = None
@@ -303,11 +314,17 @@ def post_list_jsonapi(
303314
def inner(func: Callable) -> Callable:
304315
async def wrapper(request: Request, data: schema_in, **kwargs): # type: ignore
305316
query_params = QueryStringManager(request=request, schema=schema)
306-
data_dict: dict = dict(query_params=query_params, data=getattr(data, 'attributes', data))
307-
if is_necessary_request(func):
308-
data_dict["request"] = request
309-
310-
params_function = OrderedDict(signature(func).parameters)
317+
data_dict = {}
318+
func_signature = signature(func).parameters
319+
for i_name, i_type in OrderedDict(func_signature).items():
320+
if i_type.annotation is schema_in.__fields__["attributes"].type_:
321+
data_dict[i_name] = getattr(data, 'attributes', data)
322+
elif i_type.annotation is Request:
323+
data_dict[i_name] = request
324+
elif i_type.annotation is QueryStringManager:
325+
data_dict[i_name] = query_params
326+
327+
params_function = OrderedDict(func_signature)
311328
data_dict.update({i_k: i_v for i_k, i_v in kwargs.items() if i_k in params_function})
312329
data_dict = {i_k: i_v for i_k, i_v in data_dict.items() if i_k in params_function}
313330
data_pydantic: Any = await func(**data_dict)

fastapi_rest_jsonapi/schema.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ class BasePostJSONAPISchema(BaseJSONAPIItemSchema):
3434
class BaseJSONAPIObjectSchema(BaseJSONAPIItemSchema):
3535
"""Base JSON:API object schema."""
3636

37-
id: Union[uuid.UUID, int] = Field(description="ID объекта")
37+
id: Union[int, uuid.UUID, str] = Field(description="ID объекта")
3838

3939

4040
class BasePatchJSONAPISchema(BaseJSONAPIObjectSchema):

fastapi_rest_jsonapi/signature.py

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,11 @@
1616

1717
from fastapi import Query
1818
from pydantic import BaseModel
19+
from starlette.requests import Request
1920

2021
from fastapi_rest_jsonapi.querystring import QueryStringManager
2122

2223

23-
def is_necessary_request(func: Callable) -> bool:
24-
"""Check request in parameters."""
25-
sig = signature(func)
26-
params_dict = OrderedDict(sig.parameters)
27-
return "request" in params_dict
28-
29-
3024
def update_signature(
3125
sig: Signature,
3226
schema: Optional[Type[BaseModel]] = None,
@@ -39,8 +33,28 @@ def update_signature(
3933
:params schema: которую нужно вставить в сигнатуру.
4034
:params other: список параметров из начальной функции.
4135
"""
42-
other: OrderedDict[str, Parameter] = other or {}
4336
params_dict = OrderedDict(sig.parameters)
37+
38+
other_: OrderedDict[str, Parameter] = (other or {}).copy()
39+
for i_k, i_v in (other or {}).items():
40+
for j_k, j_v in params_dict.items():
41+
try:
42+
if i_v.annotation is j_v.annotation.__fields__["attributes"].type_:
43+
other_.pop(i_k)
44+
except Exception:
45+
pass
46+
try:
47+
if i_v.annotation is Request:
48+
other_.pop(i_k)
49+
except Exception:
50+
pass
51+
try:
52+
if i_v.annotation is QueryStringManager:
53+
other_.pop(i_k)
54+
except Exception:
55+
pass
56+
other = other_
57+
4458
params_dict.pop("kwargs", None)
4559
params_dict.pop("cls", None)
4660
params_no_default = [

0 commit comments

Comments
 (0)