mirror of
https://github.com/fastapiadmin/FastapiAdmin.git
synced 2026-09-21 20:55:14 +00:00
本次提交包含多项优化: 1. 移除大量冗余的文件头注释与过时的from __future__导入 2. 将CRUD的list方法统一重命名为get_list保持接口一致 3. 修复前后端状态字段类型不匹配问题,将string类型status改为number 4. 修正前端文案错别字,将"代办事项"修正为标准写法 5. 更新sqlalchemy版本并调整依赖配置 6. 新增缓存工具类替代fastapi-cache2,重构缓存调用逻辑 7. 新增开源授权函生成相关工具与数据库字段支持 8. 为多个业务模块添加防重复提交loading状态 9. 修复邮件模型的外键关联缺失问题 10. 优化pdf生成工具的导入时机与文档注释
623 lines
23 KiB
Python
623 lines
23 KiB
Python
""""统一 CRUD 基类 — 同时支持有认证和无认证场景
|
||
|
||
- 当 `auth` 不为 None 时(Controller 注入):自动处理租户隔离、数据权限过滤、审计字段填充
|
||
- 当 `auth` 为 None 时(后台任务/脚本):纯数据操作,无权限/租户过滤
|
||
|
||
用法:
|
||
# 带认证(Controller)
|
||
class UserCRUD(CRUDBase[UserModel, UserCreateSchema, UserUpdateSchema]):
|
||
def __init__(self, auth: AuthSchema) -> None:
|
||
super().__init__(model=UserModel, auth=auth)
|
||
|
||
# 无认证(后台任务)
|
||
class OrderCRUD(CRUDBase[OrderModel, Any, Any]):
|
||
def __init__(self, session: AsyncSession) -> None:
|
||
super().__init__(model=OrderModel, session=session)
|
||
"""
|
||
|
||
from collections.abc import Sequence
|
||
from datetime import datetime, timedelta
|
||
from typing import TYPE_CHECKING, Any, TypeVar
|
||
|
||
from pydantic import BaseModel
|
||
from sqlalchemy import Select, asc, delete, desc, false, func, literal_column, select, update
|
||
from sqlalchemy import inspect as sa_inspect
|
||
from sqlalchemy.ext.asyncio import AsyncSession
|
||
from sqlalchemy.orm import selectinload
|
||
from sqlalchemy.sql.elements import ColumnElement
|
||
|
||
from app.core.base_model import MappedBase
|
||
from app.core.base_schema import AuthSchema, PageResultSchema
|
||
from app.core.exceptions import CustomException
|
||
from app.core.permission import Permission
|
||
|
||
if TYPE_CHECKING:
|
||
from sqlalchemy.engine import Result
|
||
|
||
OutSchemaType = TypeVar("OutSchemaType", bound=BaseModel)
|
||
|
||
|
||
class CRUDBase[ModelType: MappedBase, CreateSchemaType: BaseModel, UpdateSchemaType: BaseModel]:
|
||
"""统一数据层基类 — 有认证时自动处理权限,无认证时纯数据操作"""
|
||
|
||
def __init__(
|
||
self,
|
||
model: type[ModelType],
|
||
auth: AuthSchema | None = None,
|
||
session: AsyncSession | None = None,
|
||
) -> None:
|
||
"""
|
||
初始化 CRUDBase。
|
||
|
||
参数:
|
||
- model: 数据模型类
|
||
- auth: 认证信息(Controller 场景),为 None 时跳过所有权限/租户/审计逻辑
|
||
- session: 数据库会话(后台任务场景),仅在 auth=None 时使用
|
||
"""
|
||
self.model = model
|
||
self.auth = auth
|
||
self.db = session if session is not None else (auth.db if auth else None)
|
||
if self.db is None:
|
||
raise RuntimeError(
|
||
"CRUDBase 未初始化数据库会话,请传入 auth 或 session 参数"
|
||
)
|
||
|
||
def _get_pk_col(self) -> ColumnElement:
|
||
"""获取模型主键列"""
|
||
mapper = sa_inspect(self.model)
|
||
pk_cols = list(getattr(mapper, "primary_key", []))
|
||
if not pk_cols:
|
||
raise CustomException(msg="模型缺少主键")
|
||
if len(pk_cols) > 1:
|
||
raise CustomException(msg="暂不支持复合主键操作")
|
||
return pk_cols[0]
|
||
|
||
@property
|
||
def _supports_soft_delete(self) -> bool:
|
||
"""模型是否支持软删除"""
|
||
return all(
|
||
hasattr(self.model, attr) for attr in ("is_deleted", "deleted_time", "deleted_id")
|
||
)
|
||
|
||
def _soft_delete_values(self) -> dict[str, Any]:
|
||
"""软删除时需要更新的字段值"""
|
||
data: dict[str, Any] = {"is_deleted": True, "deleted_time": datetime.now()}
|
||
if self.auth and self.auth.user:
|
||
data["deleted_id"] = self.auth.user.id
|
||
return data
|
||
|
||
async def _get_one(
|
||
self, preload: list[str | Any] | None = None, **kwargs
|
||
) -> ModelType | None:
|
||
"""内部方法:在当前实例会话上执行单条查询(get / update 共用)
|
||
|
||
参数:
|
||
- preload: 预加载关系
|
||
- **kwargs: 查询条件
|
||
|
||
返回:
|
||
- 对象实例或 None
|
||
"""
|
||
conditions = await self.__build_conditions(**kwargs)
|
||
sql = select(self.model).where(*conditions)
|
||
for opt in self.__loader_options(preload):
|
||
sql = sql.options(opt)
|
||
sql = await self.__filter_permissions(sql)
|
||
result: Result = await self.db.execute(sql)
|
||
return result.scalars().first()
|
||
|
||
async def get(self, preload: list[str | Any] | None = None, **kwargs) -> ModelType | None:
|
||
"""
|
||
根据条件获取单个对象(复用请求级事务会话,保证读已写一致性)
|
||
|
||
参数:
|
||
- preload: 预加载关系
|
||
- **kwargs: 查询条件
|
||
|
||
返回:
|
||
- 对象实例或 None
|
||
"""
|
||
try:
|
||
return await self._get_one(preload=preload, **kwargs)
|
||
except CustomException:
|
||
raise
|
||
except Exception as e:
|
||
raise CustomException(msg=f"获取查询失败: {e!s}")
|
||
|
||
async def get_by_id(self, model_id: int) -> ModelType | None:
|
||
"""按主键查询"""
|
||
return await self.get(id=model_id)
|
||
|
||
async def get_or_404(
|
||
self,
|
||
id: int | None = None,
|
||
msg: str = "该数据不存在",
|
||
preload: list[str | Any] | None = None,
|
||
out_schema: type[OutSchemaType] | None = None,
|
||
**kwargs,
|
||
) -> ModelType | OutSchemaType:
|
||
"""
|
||
按条件查询单条记录,不存在时抛出 404。
|
||
|
||
参数:
|
||
- id: 主键 ID(快捷方式,等价于 kwargs={"id": id})。
|
||
- msg: 不存在时的错误消息。
|
||
- preload: 预加载关系列表。
|
||
- out_schema: 输出 Schema,为 None 时返回 ORM 对象。
|
||
- **kwargs: 其他查询条件(与 id 互斥)。
|
||
|
||
返回:
|
||
- ORM 对象或 Pydantic Schema 实例。
|
||
|
||
异常:
|
||
- CustomException: 记录不存在。
|
||
"""
|
||
if id is not None:
|
||
kwargs["id"] = id
|
||
obj = await self.get(preload=preload, **kwargs)
|
||
if not obj:
|
||
raise CustomException(msg=msg)
|
||
return out_schema.model_validate(obj) if out_schema else obj
|
||
|
||
async def exists(self, **kwargs) -> bool:
|
||
"""
|
||
检查是否存在符合条件的记录
|
||
|
||
参数:
|
||
- **kwargs: 查询条件
|
||
|
||
返回:
|
||
- 是否存在
|
||
"""
|
||
return await self.get(**kwargs) is not None
|
||
|
||
async def count(self, **kwargs) -> int:
|
||
"""
|
||
统计符合条件的记录数(复用请求级事务会话)
|
||
|
||
参数:
|
||
- **kwargs: 查询条件,支持元组语法
|
||
|
||
返回:
|
||
- 记录数
|
||
"""
|
||
try:
|
||
conditions = await self.__build_conditions(**kwargs)
|
||
count_sql = select(func.count()).select_from(self.model).where(*conditions)
|
||
count_sql = await self.__filter_permissions(count_sql)
|
||
result: Result = await self.db.execute(count_sql)
|
||
return result.scalar() or 0
|
||
except CustomException:
|
||
raise
|
||
except Exception as e:
|
||
raise CustomException(msg=f"统计失败: {e!s}")
|
||
|
||
async def get_list(
|
||
self,
|
||
search: dict | None = None,
|
||
order_by: list[dict[str, str]] | None = None,
|
||
preload: list[str | Any] | None = None,
|
||
) -> Sequence[ModelType]:
|
||
"""
|
||
根据条件获取对象列表(复用请求级事务会话)
|
||
|
||
参数:
|
||
- search: 查询条件
|
||
- order_by: 排序字段, 格式为 [{'id': 'asc'}, {'name': 'desc'}]
|
||
- preload: 预加载关系
|
||
|
||
返回:
|
||
- 对象列表
|
||
"""
|
||
try:
|
||
conditions = await self.__build_conditions(**(search or {}))
|
||
order = order_by or [{"id": "asc"}]
|
||
sql = select(self.model).where(*conditions).order_by(*self._parse_order(order))
|
||
for opt in self.__loader_options(preload):
|
||
sql = sql.options(opt)
|
||
sql = await self.__filter_permissions(sql)
|
||
result: Result = await self.db.execute(sql)
|
||
return result.scalars().all()
|
||
except CustomException:
|
||
raise
|
||
except Exception as e:
|
||
raise CustomException(msg=f"列表查询失败: {e!s}")
|
||
|
||
async def tree_list(
|
||
self,
|
||
search: dict | None = None,
|
||
order_by: list[dict[str, str]] | None = None,
|
||
children_attr: str | None = None,
|
||
preload: list[str | Any] | None = None,
|
||
) -> Sequence[ModelType]:
|
||
"""
|
||
获取树形结构数据列表(复用请求级事务会话)
|
||
|
||
参数:
|
||
- search: 查询条件
|
||
- order_by: 排序字段
|
||
- children_attr: 子节点属性名(None 时自动从模型 __tree_children_attr__ 推断)
|
||
- preload: 额外预加载关系
|
||
|
||
返回:
|
||
- 树形结构数据列表
|
||
"""
|
||
# 自动从模型推断 children_attr
|
||
if children_attr is None:
|
||
children_attr = getattr(self.model, "__tree_children_attr__", "children")
|
||
try:
|
||
conditions = await self.__build_conditions(**(search or {}))
|
||
order = order_by or [{"id": "asc"}]
|
||
sql = select(self.model).where(*conditions).order_by(*self._parse_order(order))
|
||
|
||
final_preload = preload
|
||
if preload is None and children_attr and hasattr(self.model, children_attr):
|
||
model_defaults = getattr(self.model, "__loader_options__", [])
|
||
final_preload = [*list(model_defaults), children_attr]
|
||
|
||
for opt in self.__loader_options(final_preload):
|
||
sql = sql.options(opt)
|
||
|
||
sql = await self.__filter_permissions(sql)
|
||
result: Result = await self.db.execute(sql)
|
||
return result.scalars().all()
|
||
except CustomException:
|
||
raise
|
||
except Exception as e:
|
||
raise CustomException(msg=f"树形列表查询失败: {e!s}")
|
||
|
||
async def page(
|
||
self,
|
||
offset: int,
|
||
limit: int,
|
||
order_by: list[dict[str, str]],
|
||
search: dict,
|
||
out_schema: type[OutSchemaType] | None = None,
|
||
preload: list[str | Any] | None = None,
|
||
) -> PageResultSchema:
|
||
"""
|
||
获取分页数据(复用请求级事务会话;count 与 data 共享同一会话)
|
||
|
||
参数:
|
||
- offset: 偏移量
|
||
- limit: 每页数量
|
||
- order_by: 排序字段
|
||
- search: 查询条件
|
||
- out_schema: 输出数据模型(None 时返回原始 ORM 对象)
|
||
- preload: 预加载关系
|
||
|
||
返回:
|
||
- PageResultSchema: 分页结果
|
||
"""
|
||
try:
|
||
conditions = await self.__build_conditions(**(search or {}))
|
||
order = order_by or [{"id": "asc"}]
|
||
|
||
mapper = sa_inspect(self.model)
|
||
pk_cols = list(getattr(mapper, "primary_key", []))
|
||
pk = pk_cols[0] if pk_cols else literal_column("1")
|
||
|
||
data_sql = select(self.model).where(*conditions)
|
||
for opt in self.__loader_options(preload):
|
||
data_sql = data_sql.options(opt)
|
||
data_sql = await self.__filter_permissions(data_sql)
|
||
|
||
count_sql = select(func.count(pk)).select_from(self.model)
|
||
where_clause = data_sql.whereclause
|
||
if where_clause is not None:
|
||
count_sql = count_sql.where(where_clause)
|
||
|
||
total_result = await self.db.execute(count_sql)
|
||
total = total_result.scalar() or 0
|
||
|
||
result: Result = await self.db.execute(
|
||
data_sql.order_by(*self._parse_order(order)).offset(offset).limit(limit)
|
||
)
|
||
objs = result.scalars().all()
|
||
|
||
items = (
|
||
[out_schema.model_validate(obj).model_dump() for obj in objs]
|
||
if out_schema
|
||
else list(objs)
|
||
)
|
||
|
||
return PageResultSchema(
|
||
page_no=offset // limit + 1 if limit else 1,
|
||
page_size=limit or 10,
|
||
total=total,
|
||
has_next=offset + limit < total,
|
||
items=items,
|
||
)
|
||
except CustomException:
|
||
raise
|
||
except Exception as e:
|
||
raise CustomException(msg=f"分页查询失败: {e!s}")
|
||
|
||
async def create(self, data: CreateSchemaType) -> ModelType:
|
||
"""创建新对象(有认证时自动填充租户与审计字段)
|
||
|
||
事务由 request 级 db_getter 统一管理,本方法不开启独立事务。
|
||
|
||
参数:
|
||
- data: 对象属性
|
||
|
||
返回:
|
||
- 新创建的对象实例
|
||
"""
|
||
try:
|
||
obj_dict = data.model_dump()
|
||
obj = self.model(**obj_dict)
|
||
|
||
if self.auth and self.auth.user:
|
||
if hasattr(obj, "tenant_id"):
|
||
# 非超管始终使用当前租户;超管仅当未显式指定时自动填充
|
||
if not self.auth.user.is_superuser or getattr(obj, "tenant_id", None) is None:
|
||
setattr(obj, "tenant_id", self.auth.tenant_id or self.auth.user.tenant_id)
|
||
if hasattr(obj, "created_id"):
|
||
setattr(obj, "created_id", self.auth.user.id)
|
||
if hasattr(obj, "updated_id"):
|
||
setattr(obj, "updated_id", self.auth.user.id)
|
||
|
||
self.db.add(obj)
|
||
await self.db.flush()
|
||
await self.db.refresh(obj)
|
||
return obj
|
||
except CustomException:
|
||
raise
|
||
except Exception as e:
|
||
raise CustomException(msg=f"创建失败: {e!s}")
|
||
|
||
async def update(self, id: int, data: UpdateSchemaType) -> ModelType:
|
||
"""更新对象(有认证时检查租户归属 + 填充审计字段)
|
||
|
||
事务由 request 级 db_getter 统一管理,本方法不开启独立事务。
|
||
|
||
参数:
|
||
- id: 对象 ID
|
||
- data: 更新属性
|
||
|
||
返回:
|
||
- 更新后的对象实例
|
||
"""
|
||
try:
|
||
obj_dict = data.model_dump(exclude_unset=True, exclude={"id"})
|
||
model_defaults = getattr(self.model, "__loader_options__", [])
|
||
obj = await self._get_one(id=id, preload=model_defaults)
|
||
if not obj:
|
||
raise CustomException(msg="更新对象不存在")
|
||
|
||
# 租户权限检查(仅在有认证且非超管时)
|
||
if self.auth and self.auth.user and not self.auth.user.is_superuser:
|
||
if hasattr(obj, "tenant_id"):
|
||
obj_tid = getattr(obj, "tenant_id", None)
|
||
if obj_tid is not None and obj_tid != self.auth.user.tenant_id:
|
||
is_platform = getattr(self.model, "__platform_data_shared__", False)
|
||
if is_platform and obj_tid == 1:
|
||
raise CustomException(msg="平台数据仅管理员可修改")
|
||
raise CustomException(msg="无权修改其他租户的数据")
|
||
|
||
# 审计字段
|
||
if self.auth and self.auth.user and hasattr(obj, "updated_id"):
|
||
setattr(obj, "updated_id", self.auth.user.id)
|
||
|
||
for key, value in obj_dict.items():
|
||
if hasattr(obj, key):
|
||
setattr(obj, key, value)
|
||
|
||
await self.db.flush()
|
||
await self.db.refresh(obj)
|
||
return obj
|
||
except CustomException:
|
||
raise
|
||
except Exception as e:
|
||
raise CustomException(msg=f"更新失败: {e!s}")
|
||
|
||
async def delete(self, ids: list[int]) -> None:
|
||
"""软删除对象(有认证时填充删除人 + 租户隔离)"""
|
||
try:
|
||
pk = self._get_pk_col()
|
||
|
||
if self._supports_soft_delete:
|
||
sql = self._tenant_dml_where(
|
||
update(self.model).where(pk.in_(ids))
|
||
).values(**self._soft_delete_values())
|
||
await self.db.execute(sql)
|
||
else:
|
||
sql = self._tenant_dml_where(
|
||
delete(self.model).where(pk.in_(ids))
|
||
)
|
||
await self.db.execute(sql)
|
||
await self.db.flush()
|
||
except CustomException:
|
||
raise
|
||
except Exception as e:
|
||
raise CustomException(msg=f"删除失败: {e!s}")
|
||
|
||
async def clear(self) -> None:
|
||
"""软清空对象表(有认证时填充删除人 + 租户隔离)"""
|
||
try:
|
||
if self._supports_soft_delete:
|
||
sql = self._tenant_dml_where(update(self.model)).values(**self._soft_delete_values())
|
||
await self.db.execute(sql)
|
||
else:
|
||
sql = self._tenant_dml_where(delete(self.model))
|
||
await self.db.execute(sql)
|
||
await self.db.flush()
|
||
except CustomException:
|
||
raise
|
||
except Exception as e:
|
||
raise CustomException(msg=f"清空失败: {e!s}")
|
||
|
||
async def set(self, ids: list[int], **kwargs) -> None:
|
||
"""批量更新字段(带租户隔离)"""
|
||
try:
|
||
pk = self._get_pk_col()
|
||
sql = self._tenant_dml_where(update(self.model)).where(pk.in_(ids)).values(**kwargs)
|
||
await self.db.execute(sql)
|
||
await self.db.flush()
|
||
except CustomException:
|
||
raise
|
||
except Exception as e:
|
||
raise CustomException(msg=f"批量更新失败: {e!s}")
|
||
|
||
async def restore(self, ids: list[int]) -> None:
|
||
"""恢复软删除对象(带租户隔离)"""
|
||
try:
|
||
if not self._supports_soft_delete:
|
||
raise CustomException(msg="该模型不支持软删除,无法恢复")
|
||
pk = self._get_pk_col()
|
||
sql = self._tenant_dml_where(
|
||
update(self.model).where(pk.in_(ids))
|
||
).values(is_deleted=False, deleted_time=None, deleted_id=None)
|
||
await self.db.execute(sql)
|
||
await self.db.flush()
|
||
except CustomException:
|
||
raise
|
||
except Exception as e:
|
||
raise CustomException(msg=f"恢复失败: {e!s}")
|
||
|
||
async def __filter_permissions(self, sql: Select) -> Select:
|
||
"""过滤数据权限(仅用于 Select)"""
|
||
if not self.auth:
|
||
return sql
|
||
if getattr(self.model, "__platform_data_shared__", False):
|
||
for condition in self._platform_shared_conditions():
|
||
sql = sql.where(condition)
|
||
filter_obj = Permission(model=self.model, auth=self.auth)
|
||
return await filter_obj.filter_query(sql)
|
||
|
||
def _platform_shared_conditions(self) -> list[ColumnElement]:
|
||
if not self.auth or not self.auth.user:
|
||
return []
|
||
tid = self.auth.user.tenant_id
|
||
if tid is not None and tid != 1:
|
||
return [
|
||
(getattr(self.model, "tenant_id") == tid)
|
||
| (getattr(self.model, "tenant_id") == 1)
|
||
]
|
||
return []
|
||
|
||
def _tenant_dml_where(self, sql):
|
||
"""为 DML 语句注入 tenant_id 条件(不读平台数据)"""
|
||
if self.auth and hasattr(self.model, "tenant_id") \
|
||
and self.auth.user and not self.auth.user.is_superuser:
|
||
tid = self.auth.tenant_id
|
||
if tid is not None:
|
||
return sql.where(getattr(self.model, "tenant_id") == tid)
|
||
return sql
|
||
|
||
async def __build_conditions(self, **kwargs) -> list[ColumnElement]:
|
||
conditions: list[ColumnElement] = []
|
||
|
||
if hasattr(self.model, "is_deleted"):
|
||
conditions.append(getattr(self.model, "is_deleted") == False) # noqa: E712
|
||
|
||
if self.auth and hasattr(self.model, "tenant_id") \
|
||
and not getattr(self.model, "__platform_data_shared__", False):
|
||
if self.auth.user and not self.auth.user.is_superuser:
|
||
tid = self.auth.tenant_id
|
||
if tid is not None:
|
||
conditions.append(getattr(self.model, "tenant_id") == tid)
|
||
|
||
for key, value in kwargs.items():
|
||
if value is None or value == "":
|
||
continue
|
||
|
||
attr = getattr(self.model, key)
|
||
if isinstance(value, tuple):
|
||
seq, val = value
|
||
if seq == "None":
|
||
conditions.append(attr.is_(None))
|
||
elif seq == "not None":
|
||
conditions.append(attr.isnot(None))
|
||
elif seq == "date" and val:
|
||
dt = datetime.strptime(val, "%Y-%m-%d")
|
||
conditions.append(attr >= dt)
|
||
conditions.append(attr < dt + timedelta(days=1))
|
||
elif seq == "month" and val:
|
||
dt = datetime.strptime(val, "%Y-%m")
|
||
next_month = (
|
||
dt.replace(year=dt.year + 1, month=1)
|
||
if dt.month == 12
|
||
else dt.replace(month=dt.month + 1)
|
||
)
|
||
conditions.append(attr >= dt)
|
||
conditions.append(attr < next_month)
|
||
elif seq == "like" and val:
|
||
conditions.append(attr.like(f"%{val}%"))
|
||
elif seq == "in":
|
||
if val is None:
|
||
continue
|
||
if isinstance(val, (list, tuple, set)) and len(val) == 0:
|
||
conditions.append(false())
|
||
else:
|
||
conditions.append(attr.in_(val))
|
||
elif seq == "between" and isinstance(val, (list, tuple)) and len(val) == 2:
|
||
conditions.append(attr.between(val[0], val[1]))
|
||
elif seq in ("!=", "ne") and val is not None:
|
||
conditions.append(attr != val)
|
||
elif seq in (">", "gt") and val is not None:
|
||
conditions.append(attr > val)
|
||
elif seq in (">=", "ge") and val is not None:
|
||
conditions.append(attr >= val)
|
||
elif seq in ("<", "lt") and val is not None:
|
||
conditions.append(attr < val)
|
||
elif seq in ("<=", "le") and val is not None:
|
||
conditions.append(attr <= val)
|
||
elif seq in ("eq", "==") and val is not None:
|
||
conditions.append(attr == val)
|
||
else:
|
||
conditions.append(attr == value)
|
||
return conditions
|
||
|
||
def _parse_order(
|
||
self, order: list[dict[str, str]]
|
||
) -> list[ColumnElement]:
|
||
"""
|
||
解析排序参数
|
||
|
||
参数:
|
||
- order: 排序字段列表, 格式为 [{'id': 'asc'}, {'name': 'desc'}]
|
||
|
||
返回:
|
||
- 排序表达式列表
|
||
"""
|
||
columns: list[ColumnElement] = []
|
||
for item in order:
|
||
for field, direction in item.items():
|
||
column = getattr(self.model, field)
|
||
columns.append(desc(column) if direction.lower() == "desc" else asc(column))
|
||
return columns
|
||
|
||
def __loader_options(
|
||
self, preload: list[str | Any] | None = None
|
||
) -> list[Any]:
|
||
"""
|
||
构建预加载选项
|
||
|
||
参数:
|
||
- preload: 预加载关系,支持关系名字符串或 SQLAlchemy loader option
|
||
|
||
返回:
|
||
- 预加载选项列表
|
||
"""
|
||
options: list[Any] = []
|
||
model_loader_options = getattr(self.model, "__loader_options__", [])
|
||
|
||
all_preloads: set[str | Any] = set(model_loader_options)
|
||
if preload:
|
||
for opt in preload:
|
||
if isinstance(opt, str):
|
||
all_preloads.add(opt)
|
||
elif preload == []:
|
||
all_preloads = set()
|
||
|
||
for opt in all_preloads:
|
||
if isinstance(opt, str):
|
||
if hasattr(self.model, opt):
|
||
options.append(selectinload(getattr(self.model, opt)))
|
||
else:
|
||
options.append(opt)
|
||
|
||
return options
|