Skip to content

Commit 46d6fbc

Browse files
authored
Simplified query interface returns data serialization (#219)
* Simplified query interface returns data serialization * reply to the inquiry pydantic verification * restore import sorting
1 parent 4484469 commit 46d6fbc

File tree

10 files changed

+51
-64
lines changed

10 files changed

+51
-64
lines changed

backend/app/api/v1/dept.py

+3-4
Original file line numberDiff line numberDiff line change
@@ -7,23 +7,22 @@
77
from backend.app.common.rbac import DependsRBAC
88
from backend.app.common.jwt import DependsJwtAuth
99
from backend.app.common.response.response_schema import response_base
10-
from backend.app.schemas.dept import CreateDept, GetAllDept, UpdateDept
10+
from backend.app.schemas.dept import GetAllDept, CreateDept, UpdateDept
1111
from backend.app.services.dept_service import DeptService
12-
from backend.app.utils.serializers import select_to_json
12+
from backend.app.utils.serializers import select_as_dict
1313

1414
router = APIRouter()
1515

1616

1717
@router.get('/{pk}', summary='获取部门详情', dependencies=[DependsJwtAuth])
1818
async def get_dept(pk: int):
1919
dept = await DeptService.get(pk=pk)
20-
data = GetAllDept(**select_to_json(dept))
20+
data = GetAllDept(**await select_as_dict(dept))
2121
return await response_base.success(data=data)
2222

2323

2424
@router.get('', summary='获取所有部门展示树', dependencies=[DependsJwtAuth])
2525
async def get_all_depts(
26-
level: Annotated[int | None, Query()] = None,
2726
name: Annotated[str | None, Query()] = None,
2827
leader: Annotated[str | None, Query()] = None,
2928
phone: Annotated[str | None, Query()] = None,

backend/app/api/v1/dict_data.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,15 @@
1111
from backend.app.database.db_mysql import CurrentSession
1212
from backend.app.schemas.dict_data import GetAllDictData, CreateDictData, UpdateDictData
1313
from backend.app.services.dict_data_service import DictDataService
14-
from backend.app.utils.serializers import select_to_json
14+
from backend.app.utils.serializers import select_as_dict
1515

1616
router = APIRouter()
1717

1818

1919
@router.get('/{pk}', summary='获取字典详情', dependencies=[DependsJwtAuth])
2020
async def get_dict_data(pk: int):
2121
dict_data = await DictDataService.get(pk=pk)
22-
data = GetAllDictData(**select_to_json(dict_data))
22+
data = GetAllDictData(**await select_as_dict(dict_data))
2323
return await response_base.success(data=data)
2424

2525

backend/app/api/v1/menu.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from backend.app.common.response.response_schema import response_base
1010
from backend.app.schemas.menu import GetAllMenu, CreateMenu, UpdateMenu
1111
from backend.app.services.menu_service import MenuService
12-
from backend.app.utils.serializers import select_to_json
12+
from backend.app.utils.serializers import select_as_dict
1313

1414
router = APIRouter()
1515

@@ -23,7 +23,7 @@ async def get_user_menus(request: Request):
2323
@router.get('/{pk}', summary='获取菜单详情', dependencies=[DependsJwtAuth])
2424
async def get_menu(pk: int):
2525
menu = await MenuService.get(pk=pk)
26-
data = GetAllMenu(**select_to_json(menu))
26+
data = GetAllMenu(**await select_as_dict(menu))
2727
return await response_base.success(data=data)
2828

2929

backend/app/api/v1/role.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -12,22 +12,22 @@
1212
from backend.app.schemas.role import GetAllRole, CreateRole, UpdateRole, UpdateRoleMenu
1313
from backend.app.services.menu_service import MenuService
1414
from backend.app.services.role_service import RoleService
15-
from backend.app.utils.serializers import select_to_json, select_to_list
15+
from backend.app.utils.serializers import select_list_serialize, select_as_dict
1616

1717
router = APIRouter()
1818

1919

2020
@router.get('/all', summary='获取所有角色', dependencies=[DependsJwtAuth])
2121
async def get_all_roles():
2222
roles = await RoleService.get_all()
23-
data = select_to_list(roles)
23+
data = await select_list_serialize(roles)
2424
return await response_base.success(data=data)
2525

2626

2727
@router.get('/{pk}/all', summary='获取用户所有角色', dependencies=[DependsJwtAuth])
2828
async def get_user_all_roles(pk: int):
2929
roles = await RoleService.get_user_all(pk=pk)
30-
data = select_to_list(roles)
30+
data = await select_list_serialize(roles)
3131
return await response_base.success(data=data)
3232

3333

@@ -40,7 +40,7 @@ async def get_role_all_menus(pk: int):
4040
@router.get('/{pk}', summary='获取角色详情', dependencies=[DependsJwtAuth])
4141
async def get_role(pk: int):
4242
role = await RoleService.get(pk=pk)
43-
data = GetAllRole(**select_to_json(role))
43+
data = GetAllRole(**await select_as_dict(role))
4444
return await response_base.success(data=data)
4545

4646

backend/app/api/v1/user.py

+5-5
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,12 @@
1515
ResetPassword,
1616
UpdateUser,
1717
Avatar,
18-
GetCurrentUserInfo,
1918
UpdateUserRole,
2019
AddUser,
20+
GetCurrentUserInfo,
2121
)
2222
from backend.app.services.user_service import UserService
23-
from backend.app.utils.serializers import select_to_json
23+
from backend.app.utils.serializers import select_as_dict
2424

2525
router = APIRouter()
2626

@@ -35,7 +35,7 @@ async def user_register(obj: RegisterUser):
3535
async def add_user(obj: AddUser):
3636
await UserService.add(obj=obj)
3737
current_user = await UserService.get_userinfo(username=obj.username)
38-
data = GetAllUserInfo(**select_to_json(current_user))
38+
data = GetAllUserInfo(**await select_as_dict(current_user))
3939
return await response_base.success(data=data)
4040

4141

@@ -49,14 +49,14 @@ async def password_reset(request: Request, obj: ResetPassword):
4949

5050
@router.get('/me', summary='获取当前用户信息', dependencies=[DependsJwtAuth])
5151
async def get_current_userinfo(request: Request):
52-
data = GetCurrentUserInfo(**select_to_json(request.user))
52+
data = GetCurrentUserInfo(**await select_as_dict(request.user))
5353
return await response_base.success(data=data, exclude={'password'})
5454

5555

5656
@router.get('/{username}', summary='查看用户信息', dependencies=[DependsJwtAuth])
5757
async def get_user(username: str):
5858
current_user = await UserService.get_userinfo(username=username)
59-
data = GetAllUserInfo(**select_to_json(current_user))
59+
data = GetAllUserInfo(**await select_as_dict(current_user))
6060
return await response_base.success(data=data)
6161

6262

backend/app/common/response/response_schema.py

+20-32
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,7 @@
33
from datetime import datetime
44
from typing import Any
55

6-
from asgiref.sync import sync_to_async
7-
from pydantic import validate_arguments, BaseModel
6+
from pydantic import BaseModel
87

98
from backend.app.core.conf import settings
109
from backend.app.utils.encoders import jsonable_encoder
@@ -20,7 +19,8 @@ class ResponseModel(BaseModel):
2019
2120
.. tip::
2221
23-
如果你不想使用 ResponseBase 中的自定义编码器,可以使用此模型,返回数据将通过 fastapi 内部的编码器直接自动解析并返回
22+
如果你不想使用 ResponseBase 中的自定义编码器,可以使用此模型,返回数据将通过 fastapi 内部的编码器自动解析并返回;
23+
此返回模型会生成 openapi schema 文档
2424
2525
E.g. ::
2626
@@ -47,7 +47,8 @@ class ResponseBase:
4747
4848
.. tip::
4949
50-
此类中的返回方法将通过自定义编码器预解析,然后由 fastapi 内部的编码器再次处理并返回,可能存在性能损耗,取决于个人喜好
50+
此类中的返回方法将通过自定义编码器预解析,然后由 fastapi 内部的编码器再次处理并返回,可能存在性能损耗,取决于个人喜好;
51+
此返回模型不会生成 openapi schema 文档
5152
5253
E.g. ::
5354
@@ -57,47 +58,34 @@ def test():
5758
""" # noqa: E501
5859

5960
@staticmethod
60-
@sync_to_async
61-
def __json_encoder(data: Any, exclude: _ExcludeData | None = None, **kwargs):
62-
custom_encoder = {datetime: lambda x: x.strftime(settings.DATETIME_FORMAT)}
63-
kwargs.update({'custom_encoder': custom_encoder})
64-
result = jsonable_encoder(data, exclude=exclude, **kwargs)
65-
return result
66-
67-
@validate_arguments
68-
async def success(
69-
self,
70-
*,
71-
code: int = 200,
72-
msg: str = 'Success',
73-
data: Any | None = None,
74-
exclude: _ExcludeData | None = None,
75-
**kwargs
61+
async def __response(
62+
*, code: int = None, msg: str = None, data: Any | None = None, exclude: _ExcludeData | None = None, **kwargs
7663
) -> dict:
7764
"""
7865
请求成功返回通用方法
7966
8067
:param code: 返回状态码
8168
:param msg: 返回信息
8269
:param data: 返回数据
83-
:param exclude: 排除返回数据(data)字段
70+
:param exclude: 返回数据字段排除
71+
:param kwargs: jsonable_encoder 配置项
8472
:return:
8573
"""
86-
data = data if data is None else await self.__json_encoder(data, exclude, **kwargs)
74+
if data is not None:
75+
custom_encoder = {datetime: lambda x: x.strftime(settings.DATETIME_FORMAT)}
76+
kwargs.update({'custom_encoder': custom_encoder})
77+
data = jsonable_encoder(data, exclude=exclude, **kwargs)
8778
return {'code': code, 'msg': msg, 'data': data}
8879

89-
@validate_arguments
80+
async def success(
81+
self, *, code=200, msg='Success', data: Any | None = None, exclude: _ExcludeData | None = None, **kwargs
82+
) -> dict:
83+
return await self.__response(code=code, msg=msg, data=data, exclude=exclude, **kwargs)
84+
9085
async def fail(
91-
self,
92-
*,
93-
code: int = 400,
94-
msg: str = 'Bad Request',
95-
data: Any = None,
96-
exclude: _ExcludeData | None = None,
97-
**kwargs
86+
self, *, code=400, msg='Bad Request', data: Any = None, exclude: _ExcludeData | None = None, **kwargs
9887
) -> dict:
99-
data = data if data is None else await self.__json_encoder(data, exclude, **kwargs)
100-
return {'code': code, 'msg': msg, 'data': data}
88+
return await self.__response(code=code, msg=msg, data=data, exclude=exclude, **kwargs)
10189

10290

10391
response_base = ResponseBase()

backend/app/schemas/user.py

+1-4
Original file line numberDiff line numberDiff line change
@@ -106,10 +106,7 @@ class Config:
106106
orm_mode = True
107107

108108

109-
class GetCurrentUserInfo(GetUserInfoNoRelation):
110-
dept: GetAllDept | None = None
111-
roles: list[GetAllRole]
112-
109+
class GetCurrentUserInfo(GetAllUserInfo):
113110
@root_validator
114111
def handel(cls, values):
115112
"""处理部门和角色"""

backend/app/services/task_service.py

+1
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ def get_task(pk: str):
5050
'next_run_time': job.next_run_time,
5151
}
5252
)
53+
5354
return task
5455

5556
async def run(self, pk: str):

backend/app/utils/build_tree.py

+3-4
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,12 @@
55
from asgiref.sync import sync_to_async
66

77
from backend.app.common.enums import BuildTreeType
8-
from backend.app.utils.serializers import RowData, select_to_list
8+
from backend.app.utils.serializers import RowData, select_list_serialize
99

1010

11-
@sync_to_async
12-
def get_tree_nodes(row: Sequence[RowData]) -> list[dict[str, Any]]:
11+
async def get_tree_nodes(row: Sequence[RowData]) -> list[dict[str, Any]]:
1312
"""获取所有树形结构节点"""
14-
tree_nodes = select_to_list(row)
13+
tree_nodes = await select_list_serialize(row)
1514
tree_nodes.sort(key=lambda x: x['sort'])
1615
return tree_nodes
1716

backend/app/utils/serializers.py

+10-7
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,18 @@
33
from decimal import Decimal
44
from typing import Any, TypeVar, Sequence
55

6+
from asgiref.sync import sync_to_async
67
from sqlalchemy import Row, RowMapping
78

89
RowData = Row | RowMapping | Any
910

1011
R = TypeVar('R', bound=RowData)
1112

1213

13-
def select_to_dict(row: R) -> dict:
14+
@sync_to_async
15+
def select_columns_serialize(row: R) -> dict:
1416
"""
15-
Serialize SQLAlchemy Select to dict
17+
Serialize SQLAlchemy select table columns, does not contain relational columns
1618
1719
:param row:
1820
:return:
@@ -28,20 +30,21 @@ def select_to_dict(row: R) -> dict:
2830
return obj_dict
2931

3032

31-
def select_to_list(row: Sequence[R]) -> list:
33+
async def select_list_serialize(row: Sequence[R]) -> list:
3234
"""
33-
Serialize SQLAlchemy Select to list
35+
Serialize SQLAlchemy select list
3436
3537
:param row:
3638
:return:
3739
"""
38-
ret_list = [select_to_dict(_) for _ in row]
40+
ret_list = [await select_columns_serialize(_) for _ in row]
3941
return ret_list
4042

4143

42-
def select_to_json(row: R) -> dict:
44+
@sync_to_async
45+
def select_as_dict(row: R) -> dict:
4346
"""
44-
Serialize SQLAlchemy Select to json
47+
Converting select to dict, which can contain relational data, depends on the properties of the select object itself
4548
4649
:param row:
4750
:return:

0 commit comments

Comments
 (0)