4729698049
git-subtree-dir: paste-framework git-subtree-split: 34e8684c4bc3cebbe177509f42ab4ef5b5425a7a
630 lines
23 KiB
Python
630 lines
23 KiB
Python
"""
|
||
数据模型基础类,继承于数据表。集成了模型的基础功能,如数据验证、错误消息、数据影射、对象比较等功能。
|
||
"""
|
||
import datetime
|
||
from decimal import Decimal, ROUND_HALF_UP
|
||
from typing import Union, Any, Optional, Callable
|
||
|
||
import pandas as pd
|
||
from sqlalchemy import Column, text, desc
|
||
|
||
from paste.db import baseadapter
|
||
from paste.db.basetable import BaseTable
|
||
from paste.util import udict, ustr
|
||
from paste.util.pagination import Pagination
|
||
from paste.util.snow_id import IdWorker
|
||
|
||
LOCAL_DATE_FORMAT = baseadapter.LOCAL_DATE_FORMAT
|
||
LOCAL_TIME_FORMAT = baseadapter.LOCAL_TIME_FORMAT
|
||
LOCAL_DATETIME_FORMAT = baseadapter.LOCAL_DATETIME_FORMAT
|
||
|
||
|
||
class BaseModel(BaseTable):
|
||
"""
|
||
数据模型基类。集成了验证辅助功能。
|
||
"""
|
||
|
||
__abstract__ = True
|
||
|
||
@classmethod
|
||
def new_id(cls, datacenter_id: int = 1, worker_id: int = 1, sequence: int = 0) -> int:
|
||
"""
|
||
生成新的 Snow ID 对象,并生成 ID 值。
|
||
|
||
:param datacenter_id: 数据中心(机器区域)ID
|
||
:param worker_id: 机器ID
|
||
:param sequence: 起始序号
|
||
:return: 新的 Snow ID 值
|
||
"""
|
||
return IdWorker.get_id_worker(datacenter_id, worker_id, sequence).get_id()
|
||
|
||
@classmethod
|
||
def now(cls):
|
||
"""
|
||
取得当前时间的格式化字符串。
|
||
|
||
:return: 当前时间格式化字符串
|
||
"""
|
||
return datetime.datetime.now().strftime(LOCAL_DATETIME_FORMAT)
|
||
|
||
@classmethod
|
||
def is_len(cls, v: str, length: int):
|
||
"""
|
||
检测字符串长度的函数,例如检测是否是18位。主要用于数据校验。
|
||
|
||
:param v: 待检测的值
|
||
:param length: 目标长度
|
||
:return: 相同返回 True,否则返回 False
|
||
"""
|
||
v = f"{v}"
|
||
return not cls.is_empty_or_none(v) and len(v) == length
|
||
|
||
@classmethod
|
||
def is_in_range(cls, v: Union[int, float], v_min: Union[int, float], v_max: Union[int, float]):
|
||
"""
|
||
返回检测数值范围的函数,检测是处于最大最小值范围内。主要用于数据校验。
|
||
|
||
:param v: 待检测的值
|
||
:param v_min: 最小值(包含)
|
||
:param v_max: 最大值(包含)
|
||
:return: 在范围内返回 True,否则返回 False
|
||
"""
|
||
return not cls.is_empty_or_none(v) and v_min <= v <= v_max
|
||
|
||
@classmethod
|
||
def is_in_items(cls, v: Union[int, float], items: list = None):
|
||
"""
|
||
返回检测数值是否在列表中。主要用于数据校验。
|
||
|
||
:param v: 待检测的值
|
||
:param items: 所有项目
|
||
:return: 在列表内返回 True,否则返回 False
|
||
"""
|
||
return items is not None and v in items
|
||
|
||
@classmethod
|
||
def is_empty_or_none(cls, v: Any):
|
||
"""
|
||
检查是 None 或 空字符串。
|
||
|
||
:param v: 待检查的内容
|
||
:return: 为 None 或 Nan 或 '' 时返回 True,否则返回 False
|
||
"""
|
||
return v is None or pd.isna(v) or f"{v}" == ''
|
||
|
||
@classmethod
|
||
def not_empty_or_none(cls, v: Any):
|
||
"""
|
||
与 isEmptyOrNone 函数功能相反。
|
||
"""
|
||
return not cls.is_empty_or_none(v)
|
||
|
||
@classmethod
|
||
def is_digit(cls, v: str):
|
||
"""
|
||
检查字符串是否是整数。
|
||
|
||
:param v: 带检查内容
|
||
:return: 是整数返回 True,否则返回 False
|
||
"""
|
||
v = f"{v}"
|
||
return v.isdigit()
|
||
|
||
@classmethod
|
||
def is_decimal(cls, v: str):
|
||
"""
|
||
检查是否是浮点数,若为整数,也返回 True。
|
||
:param v: 待检查内容
|
||
:return: 浮点数或整数返回 True,否则返回 False
|
||
"""
|
||
v = f"{v}"
|
||
is_decimal = True
|
||
vs = v.replace(',', '').split('.')
|
||
for _v in vs:
|
||
is_decimal = is_decimal and cls.is_digit(_v)
|
||
return is_decimal
|
||
|
||
@classmethod
|
||
def is_datetime(cls, v: str):
|
||
"""
|
||
检查是否是日期时间格式。
|
||
:param v: 待检查内容
|
||
:return: 日期时间返回 True,否则返回 False
|
||
"""
|
||
try:
|
||
datetime.datetime.strptime(v, LOCAL_DATETIME_FORMAT)
|
||
except (ValueError, Exception):
|
||
return False
|
||
return True
|
||
|
||
@classmethod
|
||
def is_date(cls, v: str):
|
||
"""
|
||
检查是否是日期格式。
|
||
|
||
:param v: 待检查内容
|
||
:return: 日期返回 True,否则返回 False
|
||
"""
|
||
try:
|
||
datetime.datetime.strptime(v, LOCAL_DATE_FORMAT)
|
||
except (ValueError, Exception):
|
||
return False
|
||
return True
|
||
|
||
@classmethod
|
||
def is_time(cls, v: str):
|
||
"""
|
||
检查是否是时间格式。
|
||
|
||
:param v: 待检查内容
|
||
:return: 时间返回 True,否则返回 False
|
||
"""
|
||
try:
|
||
datetime.datetime.strptime(v, LOCAL_TIME_FORMAT)
|
||
except (ValueError, Exception):
|
||
return False
|
||
return True
|
||
|
||
@classmethod
|
||
def error_empty_msg(cls, column: Union[Column, Any]):
|
||
"""
|
||
空字符串错误。主要用于数据校验错误。
|
||
|
||
:return: 以字段备注为主的错误消息
|
||
"""
|
||
return '%s必须包含内容.' % cls.label(column=column)
|
||
|
||
@classmethod
|
||
def error_date_msg(cls, column: Union[Column, Any]):
|
||
"""
|
||
日期格式错误。主要用于数据校验错误。
|
||
|
||
:return: 以字段备注为主的错误消息
|
||
"""
|
||
return '%s必须是日期.' % cls.label(column=column)
|
||
|
||
@classmethod
|
||
def error_datetime_msg(cls, column: Union[Column, Any]):
|
||
"""
|
||
日期时间格式错误。主要用于数据校验错误。
|
||
|
||
:return: 以字段备注为主的错误消息
|
||
"""
|
||
return '%s必须是日期时间.' % cls.label(column=column)
|
||
|
||
@classmethod
|
||
def error_time_msg(cls, column: Union[Column, Any]):
|
||
"""
|
||
时间格式错误。主要用于数据校验错误。
|
||
|
||
:return: 以字段备注为主的错误消息
|
||
"""
|
||
return '%s必须是时间.' % cls.label(column=column)
|
||
|
||
@classmethod
|
||
def error_decimal_msg(cls, column: Union[Column, Any]):
|
||
"""
|
||
非浮点或双精度类型错误。主要用于数据校验错误。
|
||
|
||
:return: 以字段备注为主的错误消息
|
||
"""
|
||
return '%s必须是浮点或双进度类型.' % cls.label(column=column)
|
||
|
||
@classmethod
|
||
def error_format_msg(cls, column: Union[Column, Any]):
|
||
"""
|
||
格式错误。主要用于数据校验错误。
|
||
|
||
:return: 以字段备注为主的错误消息
|
||
"""
|
||
return '%s格式错误.' % cls.label(column=column)
|
||
|
||
@classmethod
|
||
def error_int_msg(cls, column: Union[Column, Any]):
|
||
"""
|
||
非整数类型错误。主要用于数据校验错误。
|
||
|
||
:return: 以字段备注为主的错误消息
|
||
"""
|
||
return '%s必须是整数.' % cls.label(column=column)
|
||
|
||
@classmethod
|
||
def error_len_msg(cls, column: Union[Column, Any], length: int):
|
||
"""
|
||
长度错误消息。主要用于数据校验错误。
|
||
|
||
:return: 以字段备注为主的错误消息
|
||
"""
|
||
return '%s必须是%d位.' % (cls.label(column=column), length)
|
||
|
||
@classmethod
|
||
def error_in_range_msg(cls, column: Union[Column, Any],
|
||
v_min: Union[int, float], v_max: Union[int, float]):
|
||
"""
|
||
范围错误。主要用于数据值校验错误。
|
||
|
||
:return: 以字段备注为主的错误消息
|
||
"""
|
||
return '%s必须在:[%s,%s] 范围内.' % (cls.label(column=column), f"{v_min}", f"{v_max}")
|
||
|
||
@classmethod
|
||
def error_in_items_msg(cls, column: Union[Column, Any], items: list = None):
|
||
"""
|
||
范围错误。主要用于数据项校验错误。
|
||
|
||
:return: 以字段备注为主的错误消息
|
||
"""
|
||
if items is None:
|
||
return '%s超出范围.' % cls.label(column=column)
|
||
else:
|
||
return '%s必须在:[%s] 范围内.' % (cls.label(column=column), ','.join(items))
|
||
|
||
@classmethod
|
||
def error_str_msg(cls, column: Union[Column, Any]):
|
||
"""
|
||
非字符串类型错误。主要用于数据校验错误。
|
||
|
||
:return: 以字段备注为主的错误消息
|
||
"""
|
||
return '%s必须是字符串' % cls.label(column=column)
|
||
|
||
field_validators: dict[Column, tuple] = {}
|
||
"""
|
||
字段验证器配置。
|
||
规则为:字段名 -> 验证配置
|
||
验证配置为一个 tuple 数据,各元素说明如下::
|
||
|
||
第 0 项:验证方法与消息方法,类型为 method 或 tuple,若仅有验证方法,则直接是方法名即可,若两者皆有,则为 tuple。
|
||
第 1 项:是否跳过 None 值,类型为 bool。
|
||
第 2~n 项,验证方法或消息方法的参数,注意验证方法与消息方法除第一项参数外的其他参数必须一致。
|
||
"""
|
||
|
||
@classmethod
|
||
def validate_fields(cls, row: dict) -> list[dict[str, str]]:
|
||
"""
|
||
结合 field_validators 的配置,对字段执行验证。
|
||
若发现错误,则记录在 _errors 中并返回。
|
||
|
||
:param row: 待验证数据。
|
||
:return: 验证得到的错误描述。
|
||
"""
|
||
_errors: list[dict[str: str]] = []
|
||
for _column, _validator in cls.field_validators.items():
|
||
# 消息函数
|
||
_message_func = None
|
||
# 验证函数,是否跳过空值
|
||
_verify_func, _skip_null = _validator[:2]
|
||
|
||
if isinstance(_verify_func, tuple):
|
||
_verify_func, _message_func = _verify_func
|
||
|
||
_value = udict.get_with_default(row, _column.key, None)
|
||
if _value is None and _skip_null:
|
||
continue
|
||
|
||
_args = _validator[2:]
|
||
_vfy_args = (_value,) + _args
|
||
_err_args = (_column,) + _args
|
||
|
||
assert isinstance(_verify_func, Callable), '验证器配置错误.'
|
||
if not _verify_func(*_vfy_args):
|
||
if isinstance(_message_func, Callable):
|
||
_errors.append({_column.key: _message_func(*_err_args)})
|
||
else:
|
||
_errors.append({_column.key: f'{_column.key} 字段数据错误.'})
|
||
return _errors
|
||
|
||
@classmethod
|
||
def validate_dict(cls, row: dict, row_list: list[dict], err_list: list[dict]):
|
||
"""
|
||
验证字典数据。仅将结果加入对应的列表,不改变原有数据。
|
||
|
||
:param row: 待验证的行数据对象
|
||
:param row_list: 用于存放验证成功模型的列表
|
||
:param err_list: 用于存放错误消息的列表
|
||
"""
|
||
try:
|
||
row_list.append(row)
|
||
except TypeError:
|
||
err_list.append(row)
|
||
|
||
@classmethod
|
||
def validate_dict_list(cls, row_list: list[dict]) -> tuple[list[dict], list[dict]]:
|
||
"""
|
||
验证字典列表数据,首先清除历史模型列表和错误消息。
|
||
|
||
:param row_list: 待验证的字典数组
|
||
:return: 数据模型列表和错误消息列表
|
||
"""
|
||
_row_list: list[dict] = []
|
||
_err_list: list[dict] = []
|
||
|
||
for row in row_list:
|
||
cls.validate_dict(row=row, row_list=_row_list, err_list=_err_list)
|
||
return _row_list, _err_list
|
||
|
||
@classmethod
|
||
def mapping_data_struct(cls, source: Optional[dict], mapping: Optional[dict]):
|
||
"""
|
||
将源数据字典中的数据,按照映射关系字典的方式转换为新的字典对象。
|
||
|
||
下面是一个递归映射关系字典的样本::
|
||
|
||
dict_key_mapping = {
|
||
'devUseNo': 'dev_use_no',
|
||
'mainId': 'id',
|
||
'mainCycle': lambda dict_obj: MAIN_CYCLE_LABELS.get(dict_obj['main_cycle'], ''),
|
||
'mainCycleCode': 'main_cycle',
|
||
'fileList': {
|
||
'__name__': 'main_files',
|
||
'__mapping__': {
|
||
'fileName': 'file_name',
|
||
'filePath': 'file_url',
|
||
},
|
||
},
|
||
'mainDetailList': {
|
||
'__name__': 'main_items',
|
||
'__mapping__': {
|
||
'id': 'id',
|
||
'itemId': 'item_id',
|
||
'itemName': 'item_name',
|
||
'itemRequest': 'item_request',
|
||
'itemResult': 'item_result',
|
||
'remarks': 'remarks',
|
||
'itemFileList': {
|
||
'__name__': 'main_item_files',
|
||
'__mapping__': {
|
||
'fileName': 'file_name',
|
||
'filePath': 'file_url',
|
||
},
|
||
},
|
||
},
|
||
},
|
||
}
|
||
|
||
映射关系字典遵循:{`目标属性`: `源属性`} 的结构,对`源属性`,允许有以下几种类型::
|
||
|
||
1、为字符串时,表示从源数据字典中直接读取。
|
||
2、为函数或 lambda 表达式时,执行函数,并将源数据字典以参数形式传给该函数。
|
||
3、为字典时,表示有子对象数据,此时需要配置 __name__ 属性和 __mapping__ 属性。
|
||
4、非以上情况的,直接使用该内容作为目标字典属性的数据。
|
||
|
||
:param source: 源数据字典
|
||
:param mapping: 映射关系字典
|
||
|
||
:return: 转换后的字典
|
||
"""
|
||
if source is None or mapping is None:
|
||
return None
|
||
|
||
target = {}
|
||
for _tar_attr, _src_attr in mapping.items():
|
||
if isinstance(_src_attr, str):
|
||
#
|
||
# 直接处理 key 映射关系
|
||
# 注意,对于需要强制设置为字符串的,不能直接使用字符串,会被误认为是 key 映射关系,应当使用无参数 lambda 表达式。
|
||
#
|
||
target[_tar_attr] = source.get(_src_attr, None)
|
||
elif isinstance(_src_attr, Callable):
|
||
#
|
||
# 处理函数或 lambda 表达式
|
||
#
|
||
target[_tar_attr] = _src_attr(source)
|
||
elif isinstance(_src_attr, dict):
|
||
if '__name__' in _src_attr and '__mapping__' in _src_attr:
|
||
#
|
||
# 包含名称映射的,表示新的映射关系,递归处理
|
||
# 这里仅处理类型为 dict 和 list 的数据
|
||
#
|
||
|
||
# 取出内部源数据字典和映射关系
|
||
_sd = source.get(_src_attr.get('__name__'), None)
|
||
_mp = _src_attr.get('__mapping__', None)
|
||
|
||
if isinstance(_sd, dict):
|
||
#
|
||
# 直接递归映射
|
||
#
|
||
target[_tar_attr] = cls.mapping_data_struct(_sd, _mp)
|
||
elif isinstance(_sd, list):
|
||
#
|
||
# 遍历后递归映射
|
||
#
|
||
_t_list = []
|
||
for _sd_item in _sd:
|
||
_t_list.append(cls.mapping_data_struct(_sd_item, _mp))
|
||
target[_tar_attr] = _t_list
|
||
else:
|
||
#
|
||
# 非 dict,list 的,直接设置
|
||
#
|
||
target[_tar_attr] = _sd
|
||
else:
|
||
#
|
||
# 无映射关系的,直接设置
|
||
#
|
||
target[_tar_attr] = _src_attr
|
||
else:
|
||
#
|
||
# 非 str,function,dict 的,直接设置
|
||
#
|
||
target[_tar_attr] = _src_attr
|
||
|
||
return target
|
||
|
||
@classmethod
|
||
def transform(cls, rows: list[dict], mapping: dict):
|
||
"""
|
||
将源数据 rows 字典中的数据,按照映射关系字典 mapping 的方式转换为新的字典对象。
|
||
|
||
下面是一个递归映射关系字典的样本::
|
||
|
||
dict_key_mapping = {
|
||
'devUseNo': 'dev_use_no',
|
||
'mainId': 'id',
|
||
'mainCycle': lambda dict_obj: MAIN_CYCLE_LABELS.get(dict_obj['main_cycle'], ''),
|
||
'mainCycleCode': 'main_cycle',
|
||
'fileList': {
|
||
'__name__': 'main_files',
|
||
'__mapping__': {
|
||
'fileName': 'file_name',
|
||
'filePath': 'file_url',
|
||
},
|
||
},
|
||
'mainDetailList': {
|
||
'__name__': 'main_items',
|
||
'__mapping__': {
|
||
'id': 'id',
|
||
'itemId': 'item_id',
|
||
'itemName': 'item_name',
|
||
'itemRequest': 'item_request',
|
||
'itemResult': 'item_result',
|
||
'remarks': 'remarks',
|
||
'itemFileList': {
|
||
'__name__': 'main_item_files',
|
||
'__mapping__': {
|
||
'fileName': 'file_name',
|
||
'filePath': 'file_url',
|
||
},
|
||
},
|
||
},
|
||
},
|
||
}
|
||
|
||
映射关系字典遵循:{`目标属性`: `源属性`} 的结构,对`源属性`,允许有以下几种类型::
|
||
|
||
1、为字符串时,表示从源数据字典中直接读取。
|
||
2、为函数或 lambda 表达式时,执行函数,并将源数据字典以参数形式传给该函数。
|
||
3、为字典时,表示有子对象数据,此时需要配置 __name__ 属性和 __mapping__ 属性。
|
||
4、非以上情况的,直接使用该内容作为目标字典属性的数据。
|
||
:param rows: 源数据字典列表
|
||
:param mapping: 映射关系字典
|
||
:return: 转换结果
|
||
"""
|
||
_dict_list: list[dict] = []
|
||
for _r in rows:
|
||
_tar_dict = cls.mapping_data_struct(_r, mapping)
|
||
_dict_list.append(_tar_dict)
|
||
return _dict_list
|
||
|
||
@classmethod
|
||
def convert(cls, dataframe: pd.DataFrame, mapping: dict):
|
||
"""
|
||
将源数据框架 dataframe 中的数据,按照映射关系字典 mapping 的方式转换为新的 dataframe 对象。
|
||
|
||
下面是一个递归映关系射字典的样本::
|
||
|
||
dict_key_mapping = {
|
||
'devUseNo': 'dev_use_no',
|
||
'mainId': 'id',
|
||
'mainCycle': lambda dict_obj: MAIN_CYCLE_LABELS.get(dict_obj['main_cycle'], ''),
|
||
'mainCycleCode': 'main_cycle',
|
||
}
|
||
|
||
映射关系字典遵循:{`目标属性`: `源属性`} 的结构,对`源属性`,允许有以下几种类型::
|
||
|
||
1、为字符串时,表示从源数据字典中直接读取。
|
||
2、为函数或 lambda 表达式时,执行函数,并将源数据字典以参数形式传给该函数。
|
||
3、非以上情况的,直接使用该内容作为目标字典属性的数据。
|
||
|
||
注意:与字典映射转换不同,:class:`pd.DataFrame` 映射转换不支持多层递归转换。
|
||
|
||
:param dataframe: 源数据 dataframe
|
||
:param mapping: 映射关系字典
|
||
:return: 转换结果
|
||
"""
|
||
_tar_df = pd.DataFrame()
|
||
for _tar_attr, _src_attr in mapping.items():
|
||
if isinstance(_src_attr, str):
|
||
_tar_df[_tar_attr] = dataframe[_src_attr]
|
||
elif isinstance(_src_attr, Callable):
|
||
_tar_df[_tar_attr] = dataframe.apply(_src_attr, axis=1)
|
||
else:
|
||
_tar_df[_tar_attr] = _tar_attr
|
||
return _tar_df
|
||
|
||
@classmethod
|
||
def is_equal(cls, data_dict: dict, data_model: 'BaseModel', skip_kes: list[str] = None, decimals: str = '0.00'):
|
||
"""
|
||
判断 data_dict 中的值是否都与 equ_model 中的对应值相等。一般而言若相等,则表明无需更新数据模型,否则就需要更新。
|
||
|
||
:param data_dict: 数据字典,用于遍历比对的数据,也是用于更新的数据
|
||
:param data_model: 数据模型
|
||
:param skip_kes: 允许跳过,不做比较的字段
|
||
:param decimals: 浮点数保留的小数位,默认 2 位
|
||
:return: 是否相等,各字段是否相等的对应关系字典
|
||
"""
|
||
is_equal = True
|
||
equal_dict: dict = {}
|
||
if skip_kes is None:
|
||
skip_kes = []
|
||
|
||
for _key, _new_val in data_dict.items():
|
||
if _key in skip_kes:
|
||
continue
|
||
|
||
if _new_val is None:
|
||
# 跳过新值中的 None
|
||
continue
|
||
|
||
if _key not in data_model.__dict__:
|
||
# 跳过不存在的属性
|
||
continue
|
||
|
||
_old_val = data_model.__dict__.get(_key, None)
|
||
if isinstance(_old_val, (Decimal, float)):
|
||
_old_val = Decimal(f"{_old_val}").quantize(Decimal(decimals), rounding=ROUND_HALF_UP)
|
||
_new_val = Decimal(f"{_new_val}").quantize(Decimal(decimals), rounding=ROUND_HALF_UP)
|
||
elif isinstance(_old_val, int):
|
||
_new_val = int(_new_val)
|
||
elif isinstance(_old_val, datetime.datetime):
|
||
_old_val = _old_val.strftime(LOCAL_DATETIME_FORMAT)
|
||
_datetime = ustr.to_datetime(_new_val, [LOCAL_DATETIME_FORMAT, LOCAL_DATE_FORMAT])
|
||
_new_val = _datetime.strftime(LOCAL_DATETIME_FORMAT) if _datetime is not None else f"{_new_val}"
|
||
elif isinstance(_old_val, datetime.date):
|
||
_old_val = _old_val.strftime(LOCAL_DATE_FORMAT)
|
||
_date = ustr.to_datetime(_new_val, [LOCAL_DATE_FORMAT, LOCAL_DATETIME_FORMAT])
|
||
_new_val = _date.strftime(LOCAL_DATE_FORMAT) if _date is not None else f"{_new_val}"
|
||
else:
|
||
_old_val = f"{_old_val}" if _old_val is not None else ''
|
||
if isinstance(_new_val, float):
|
||
_new_val = int(_new_val)
|
||
_new_val = f"{_new_val}"
|
||
|
||
_isFieldEqual = _new_val == _old_val
|
||
is_equal = is_equal and _isFieldEqual
|
||
equal_dict[_key] = _isFieldEqual
|
||
|
||
return is_equal, equal_dict
|
||
|
||
@classmethod
|
||
async def page_info(cls, *where_clause, page_size: int = 20):
|
||
"""
|
||
分页参数。
|
||
|
||
:return: 页数, 数据行数
|
||
"""
|
||
_row_count = await cls.async_row_count(*where_clause)
|
||
_pagination = Pagination(row_count=_row_count)
|
||
return _pagination.pages(page_size=page_size), _row_count
|
||
|
||
@classmethod
|
||
def sort_clauses(cls, sort_d: dict):
|
||
"""
|
||
按照参数 sort_d 中的定义,组织排序表达式。参数 sortd_d 应该具有如下结构::
|
||
|
||
{
|
||
'field_name1': 'asc',
|
||
'field_name2': 'desc',
|
||
}
|
||
|
||
:param sort_d 排序参数
|
||
"""
|
||
_sort_clause = []
|
||
for _fn, _st in sort_d.items():
|
||
if _st in ('', 'asc', 'ascend'):
|
||
_sort_clause.append(text(_fn))
|
||
if _st in ('desc', 'descend'):
|
||
_sort_clause.append(desc(text(_fn)))
|
||
return _sort_clause
|