hframe 0.1.0__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.
- hframe/__init__.py +108 -0
- hframe/config.py +107 -0
- hframe/exceptions.py +126 -0
- hframe/handler/__init__.py +13 -0
- hframe/handler/config.py +108 -0
- hframe/handler/generic.py +501 -0
- hframe/handler/hooks.py +64 -0
- hframe/handler/request.py +77 -0
- hframe/infra/__init__.py +32 -0
- hframe/infra/cache.py +108 -0
- hframe/infra/db_util.py +134 -0
- hframe/infra/mysql.py +1231 -0
- hframe/infra/redis.py +929 -0
- hframe/infra/wechat.py +327 -0
- hframe/logging.py +95 -0
- hframe/registry.py +111 -0
- hframe/repository/__init__.py +9 -0
- hframe/repository/base.py +212 -0
- hframe/repository/version.py +85 -0
- hframe/service/__init__.py +12 -0
- hframe/service/config.py +140 -0
- hframe/service/data.py +346 -0
- hframe/service/generic.py +482 -0
- hframe/util/__init__.py +14 -0
- hframe/util/auth.py +154 -0
- hframe/util/crypto.py +33 -0
- hframe/util/data.py +162 -0
- hframe/util/format.py +92 -0
- hframe/util/transform.py +43 -0
- hframe/util/verify.py +40 -0
- hframe/util/yaml_util.py +26 -0
- hframe/view/__init__.py +12 -0
- hframe/view/base.py +395 -0
- hframe/view/h5.py +42 -0
- hframe-0.1.0.dist-info/METADATA +1347 -0
- hframe-0.1.0.dist-info/RECORD +38 -0
- hframe-0.1.0.dist-info/WHEEL +5 -0
- hframe-0.1.0.dist-info/top_level.txt +1 -0
hframe/__init__.py
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"""
|
|
2
|
+
hframe — 分层架构后端框架
|
|
3
|
+
|
|
4
|
+
架构链路:View(可选) → Handler → Service → Repository → DB
|
|
5
|
+
|
|
6
|
+
核心暴露:
|
|
7
|
+
- Config: 全局配置
|
|
8
|
+
- GenericHandler: HTTP 无关的业务编排层(级联/事务/权限)
|
|
9
|
+
- GenericService: 单实体生命周期(before/do/after 钩子)
|
|
10
|
+
- BaseRepository: 数据访问唯一出口
|
|
11
|
+
|
|
12
|
+
工具暴露:
|
|
13
|
+
- util.crypto: md5, crypt, make_id
|
|
14
|
+
- util.data: build_tree, copy_dict, check_data_type
|
|
15
|
+
- util.format: to_snake_case, to_camel_case, json_dumps, Serialize
|
|
16
|
+
- util.transform: is_integer, set_default_int, trans_str_to_arr
|
|
17
|
+
- util.yaml_util: read_yaml_file, write_yaml_file
|
|
18
|
+
- util.auth: UserService(登录/注册/身份)
|
|
19
|
+
- util.verify: CodeService(验证码)
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
__version__ = "0.1.0"
|
|
23
|
+
|
|
24
|
+
# ========== 核心配置 ==========
|
|
25
|
+
from .config import Config
|
|
26
|
+
|
|
27
|
+
# ========== 类型化异常 ==========
|
|
28
|
+
from .exceptions import (
|
|
29
|
+
HFrameException,
|
|
30
|
+
ValidationException,
|
|
31
|
+
UniqueValidationException,
|
|
32
|
+
NotFoundException,
|
|
33
|
+
UnauthorizedException,
|
|
34
|
+
PermissionException,
|
|
35
|
+
ServiceException,
|
|
36
|
+
ConflictException,
|
|
37
|
+
ForbiddenOperationException,
|
|
38
|
+
RepositoryException,
|
|
39
|
+
InfrastructureException,
|
|
40
|
+
is_exception,
|
|
41
|
+
get_exception_code,
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
# ========== 日志 ==========
|
|
45
|
+
from .logging import LogUtil
|
|
46
|
+
|
|
47
|
+
# ========== Repository 层 ==========
|
|
48
|
+
from .repository import BaseRepository, VersionRepository, ListFilters, Filter, FilterOp
|
|
49
|
+
|
|
50
|
+
# ========== Service 层 ==========
|
|
51
|
+
from .service import GenericService, ServiceConfig, ServiceHooks, CrudRequest, VersionFieldMapping
|
|
52
|
+
|
|
53
|
+
# ========== Handler 层(核心暴露) ==========
|
|
54
|
+
from .handler import (
|
|
55
|
+
GenericHandler,
|
|
56
|
+
HandlerConfig,
|
|
57
|
+
HandlerHooks,
|
|
58
|
+
CascadeRelation,
|
|
59
|
+
ReferenceRelation,
|
|
60
|
+
ChildRefRelation,
|
|
61
|
+
GetRequest,
|
|
62
|
+
MapRequest,
|
|
63
|
+
RequestFactory,
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
# ========== 工具层 ==========
|
|
67
|
+
from .util import UserService, CodeService
|
|
68
|
+
from .util import crypto, data, format, transform, yaml_util
|
|
69
|
+
|
|
70
|
+
# ========== 注册表 ==========
|
|
71
|
+
from .registry import Registry, EntityConfig
|
|
72
|
+
|
|
73
|
+
# ========== Django View 层(可选) ==========
|
|
74
|
+
try:
|
|
75
|
+
from .view import (
|
|
76
|
+
BaseView, ModuleView, ListView, DetailView, EditView, DeleteView,
|
|
77
|
+
H5BaseView, H5ModuleView, H5ListView, H5DetailView, H5EditView, H5DeleteView, H5Mixin,
|
|
78
|
+
)
|
|
79
|
+
except ImportError:
|
|
80
|
+
pass
|
|
81
|
+
|
|
82
|
+
__all__ = [
|
|
83
|
+
# 版本
|
|
84
|
+
'__version__',
|
|
85
|
+
# 配置
|
|
86
|
+
'Config',
|
|
87
|
+
# 异常
|
|
88
|
+
'HFrameException', 'ValidationException', 'UniqueValidationException',
|
|
89
|
+
'NotFoundException', 'UnauthorizedException', 'PermissionException',
|
|
90
|
+
'ServiceException', 'ConflictException', 'ForbiddenOperationException',
|
|
91
|
+
'RepositoryException', 'InfrastructureException',
|
|
92
|
+
'is_exception', 'get_exception_code',
|
|
93
|
+
# 日志
|
|
94
|
+
'LogUtil',
|
|
95
|
+
# Repository
|
|
96
|
+
'BaseRepository', 'VersionRepository', 'ListFilters', 'Filter', 'FilterOp',
|
|
97
|
+
# Service
|
|
98
|
+
'GenericService', 'ServiceConfig', 'ServiceHooks', 'CrudRequest', 'VersionFieldMapping',
|
|
99
|
+
# Handler(核心)
|
|
100
|
+
'GenericHandler', 'HandlerConfig', 'HandlerHooks',
|
|
101
|
+
'CascadeRelation', 'ReferenceRelation', 'ChildRefRelation',
|
|
102
|
+
'GetRequest', 'MapRequest', 'RequestFactory',
|
|
103
|
+
# 注册表
|
|
104
|
+
'Registry', 'EntityConfig',
|
|
105
|
+
# 工具
|
|
106
|
+
'UserService', 'CodeService',
|
|
107
|
+
'crypto', 'data', 'format', 'transform', 'yaml_util',
|
|
108
|
+
]
|
hframe/config.py
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"""
|
|
2
|
+
hframe 配置模块
|
|
3
|
+
|
|
4
|
+
集中管理所有配置:数据库、缓存、应用基础信息等。
|
|
5
|
+
支持从 YAML 文件加载并深度合并默认配置。
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import os
|
|
9
|
+
import yaml
|
|
10
|
+
from typing import Any, Dict
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
# 默认配置(可作为开发环境的兜底)
|
|
14
|
+
_DEFAULTS: Dict[str, Any] = {
|
|
15
|
+
"BASE_INFO": {
|
|
16
|
+
"secret_key": "py_crux_default_secret",
|
|
17
|
+
"datetime_format": "%Y-%m-%d %H:%M:%S",
|
|
18
|
+
"auth_token_expire_time": 3600,
|
|
19
|
+
"admin_role_id": -1,
|
|
20
|
+
"check_page_permission": 0,
|
|
21
|
+
"user_tables": {},
|
|
22
|
+
},
|
|
23
|
+
"MYSQL_INFO": {
|
|
24
|
+
"host": "localhost",
|
|
25
|
+
"port": 3306,
|
|
26
|
+
"user": "root",
|
|
27
|
+
"password": "",
|
|
28
|
+
"database": "test",
|
|
29
|
+
"charset": "utf8mb4",
|
|
30
|
+
},
|
|
31
|
+
"REDIS_INFO": {
|
|
32
|
+
"host": "localhost",
|
|
33
|
+
"port": 6379,
|
|
34
|
+
"password": "",
|
|
35
|
+
"db": 0,
|
|
36
|
+
},
|
|
37
|
+
"SSH_INFO": {},
|
|
38
|
+
"WECHAT_INFO": {},
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _deep_merge(source: Dict, dest: Dict) -> Dict:
|
|
43
|
+
"""深度合并字典,source 覆盖 dest"""
|
|
44
|
+
for key, value in source.items():
|
|
45
|
+
if key in dest and isinstance(value, dict) and isinstance(dest[key], dict):
|
|
46
|
+
_deep_merge(value, dest[key])
|
|
47
|
+
else:
|
|
48
|
+
dest[key] = value
|
|
49
|
+
return dest
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class Config:
|
|
53
|
+
"""
|
|
54
|
+
全局配置容器
|
|
55
|
+
|
|
56
|
+
使用方式:
|
|
57
|
+
Config.BASE_INFO['secret_key']
|
|
58
|
+
Config.MYSQL_INFO['host']
|
|
59
|
+
|
|
60
|
+
初始化:
|
|
61
|
+
Config.load('config.yaml') # 从 YAML 加载并深度合并默认值
|
|
62
|
+
"""
|
|
63
|
+
|
|
64
|
+
BASE_INFO: Dict[str, Any] = dict(_DEFAULTS["BASE_INFO"])
|
|
65
|
+
MYSQL_INFO: Dict[str, Any] = dict(_DEFAULTS["MYSQL_INFO"])
|
|
66
|
+
REDIS_INFO: Dict[str, Any] = dict(_DEFAULTS["REDIS_INFO"])
|
|
67
|
+
SSH_INFO: Dict[str, Any] = dict(_DEFAULTS["SSH_INFO"])
|
|
68
|
+
WECHAT_INFO: Dict[str, Any] = dict(_DEFAULTS["WECHAT_INFO"])
|
|
69
|
+
|
|
70
|
+
@classmethod
|
|
71
|
+
def load(cls, file_path: str = "config.yaml"):
|
|
72
|
+
"""
|
|
73
|
+
从 YAML 文件加载配置,与默认值深度合并
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
file_path: YAML 配置文件路径,支持相对路径和绝对路径
|
|
77
|
+
"""
|
|
78
|
+
if not os.path.exists(file_path):
|
|
79
|
+
return
|
|
80
|
+
|
|
81
|
+
with open(file_path, 'r', encoding='utf-8') as f:
|
|
82
|
+
user_config = yaml.safe_load(f) or {}
|
|
83
|
+
|
|
84
|
+
# 深度合并:用户配置覆盖默认值
|
|
85
|
+
for section in ("BASE_INFO", "MYSQL_INFO", "REDIS_INFO", "SSH_INFO", "WECHAT_INFO"):
|
|
86
|
+
if section in user_config:
|
|
87
|
+
_deep_merge(user_config[section], cls.__dict__[section])
|
|
88
|
+
|
|
89
|
+
@classmethod
|
|
90
|
+
def reset(cls):
|
|
91
|
+
"""重置为默认配置"""
|
|
92
|
+
cls.BASE_INFO = dict(_DEFAULTS["BASE_INFO"])
|
|
93
|
+
cls.MYSQL_INFO = dict(_DEFAULTS["MYSQL_INFO"])
|
|
94
|
+
cls.REDIS_INFO = dict(_DEFAULTS["REDIS_INFO"])
|
|
95
|
+
cls.SSH_INFO = dict(_DEFAULTS["SSH_INFO"])
|
|
96
|
+
cls.WECHAT_INFO = dict(_DEFAULTS["WECHAT_INFO"])
|
|
97
|
+
|
|
98
|
+
@classmethod
|
|
99
|
+
def get(cls, key: str, default: Any = None) -> Any:
|
|
100
|
+
"""快捷取值:Config.get('BASE_INFO.secret_key')"""
|
|
101
|
+
parts = key.split('.')
|
|
102
|
+
obj = cls
|
|
103
|
+
for part in parts:
|
|
104
|
+
obj = getattr(obj, part, None) if hasattr(obj, part) else (obj.get(part) if isinstance(obj, dict) else None)
|
|
105
|
+
if obj is None:
|
|
106
|
+
return default
|
|
107
|
+
return obj
|
hframe/exceptions.py
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
"""
|
|
2
|
+
hframe 类型化异常体系
|
|
3
|
+
|
|
4
|
+
借鉴 Go 项目哨兵错误模式,用 Python 的异常类继承体系实现。
|
|
5
|
+
每个异常类型携带业务码和消息,支持精确匹配(类比 errors.Is())。
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class HFrameException(Exception):
|
|
10
|
+
"""框架基础异常"""
|
|
11
|
+
|
|
12
|
+
def __init__(self, message: str = "", code: int = 500):
|
|
13
|
+
self.message = message
|
|
14
|
+
self.code = code
|
|
15
|
+
super().__init__(message)
|
|
16
|
+
|
|
17
|
+
def __repr__(self):
|
|
18
|
+
return f"{self.__class__.__name__}(code={self.code}, message={self.message})"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
# ============================================================
|
|
22
|
+
# 校验类异常(客户端可修正)
|
|
23
|
+
# ============================================================
|
|
24
|
+
|
|
25
|
+
class ValidationException(HFrameException):
|
|
26
|
+
"""数据校验失败"""
|
|
27
|
+
|
|
28
|
+
def __init__(self, message: str = "数据校验失败"):
|
|
29
|
+
super().__init__(message=message, code=400)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class UniqueValidationException(HFrameException):
|
|
33
|
+
"""唯一性校验失败"""
|
|
34
|
+
|
|
35
|
+
def __init__(self, message: str = "唯一性校验失败"):
|
|
36
|
+
super().__init__(message=message, code=409)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class NotFoundException(HFrameException):
|
|
40
|
+
"""资源不存在"""
|
|
41
|
+
|
|
42
|
+
def __init__(self, message: str = "资源不存在"):
|
|
43
|
+
super().__init__(message=message, code=404)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
# ============================================================
|
|
47
|
+
# 权限/认证类异常
|
|
48
|
+
# ============================================================
|
|
49
|
+
|
|
50
|
+
class UnauthorizedException(HFrameException):
|
|
51
|
+
"""未认证(需要登录)"""
|
|
52
|
+
|
|
53
|
+
def __init__(self, message: str = "未认证"):
|
|
54
|
+
super().__init__(message=message, code=401)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class PermissionException(HFrameException):
|
|
58
|
+
"""无权限(认证了但权限不足)"""
|
|
59
|
+
|
|
60
|
+
def __init__(self, message: str = "权限不足"):
|
|
61
|
+
super().__init__(message=message, code=403)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
# ============================================================
|
|
65
|
+
# 业务类异常
|
|
66
|
+
# ============================================================
|
|
67
|
+
|
|
68
|
+
class ServiceException(HFrameException):
|
|
69
|
+
"""Service 层业务异常(通用)"""
|
|
70
|
+
|
|
71
|
+
def __init__(self, message: str = "服务异常"):
|
|
72
|
+
super().__init__(message=message, code=500)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class ConflictException(HFrameException):
|
|
76
|
+
"""状态冲突(如版本已发布)"""
|
|
77
|
+
|
|
78
|
+
def __init__(self, message: str = "状态冲突"):
|
|
79
|
+
super().__init__(message=message, code=409)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class ForbiddenOperationException(HFrameException):
|
|
83
|
+
"""操作被禁止(业务规则限制)"""
|
|
84
|
+
|
|
85
|
+
def __init__(self, message: str = "操作被禁止"):
|
|
86
|
+
super().__init__(message=message, code=403)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
# ============================================================
|
|
90
|
+
# 基础设施类异常
|
|
91
|
+
# ============================================================
|
|
92
|
+
|
|
93
|
+
class RepositoryException(HFrameException):
|
|
94
|
+
"""Repository 层数据访问异常"""
|
|
95
|
+
|
|
96
|
+
def __init__(self, message: str = "数据访问异常"):
|
|
97
|
+
super().__init__(message=message, code=500)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
class InfrastructureException(HFrameException):
|
|
101
|
+
"""基础设施异常(DB连接、Redis连接等)"""
|
|
102
|
+
|
|
103
|
+
def __init__(self, message: str = "基础设施异常"):
|
|
104
|
+
super().__init__(message=message, code=500)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
# ============================================================
|
|
108
|
+
# 辅助:异常匹配函数(类比 Go 的 errors.Is)
|
|
109
|
+
# ============================================================
|
|
110
|
+
|
|
111
|
+
def is_exception(exc: Exception, target_type: type) -> bool:
|
|
112
|
+
"""
|
|
113
|
+
判断异常是否为指定类型(含子类)
|
|
114
|
+
|
|
115
|
+
用法:
|
|
116
|
+
if is_exception(e, NotFoundException):
|
|
117
|
+
# 处理未找到的情况
|
|
118
|
+
"""
|
|
119
|
+
return isinstance(exc, target_type)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def get_exception_code(exc: Exception) -> int:
|
|
123
|
+
"""获取异常的业务码(非 HFrameException 返回 500)"""
|
|
124
|
+
if isinstance(exc, HFrameException):
|
|
125
|
+
return exc.code
|
|
126
|
+
return 500
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""Handler 层 — HTTP 无关的业务编排层"""
|
|
2
|
+
|
|
3
|
+
from .config import HandlerConfig, CascadeRelation, ReferenceRelation, ChildRefRelation
|
|
4
|
+
from .generic import GenericHandler
|
|
5
|
+
from .hooks import HandlerHooks
|
|
6
|
+
from .request import GetRequest, MapRequest, RequestFactory
|
|
7
|
+
|
|
8
|
+
__all__ = [
|
|
9
|
+
'HandlerConfig', 'CascadeRelation', 'ReferenceRelation', 'ChildRefRelation',
|
|
10
|
+
'GenericHandler',
|
|
11
|
+
'HandlerHooks',
|
|
12
|
+
'GetRequest', 'MapRequest', 'RequestFactory',
|
|
13
|
+
]
|
hframe/handler/config.py
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Handler 配置
|
|
3
|
+
|
|
4
|
+
定义 Handler 层的配置项:级联关系、请求工厂、认证/权限钩子等。
|
|
5
|
+
借鉴 Go 项目 HandlerConfig[M] 的设计,去掉泛型和 gin 绑定。
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from typing import Any, Callable, Dict, List, Optional, Tuple
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class CascadeRelation:
|
|
12
|
+
"""
|
|
13
|
+
级联关系声明(向下:父→子)
|
|
14
|
+
|
|
15
|
+
配置后,Handler 在 Create/Delete/Get/List 时自动处理子表联动。
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
def __init__(self, *,
|
|
19
|
+
child_table: str,
|
|
20
|
+
parent_fk_field: str,
|
|
21
|
+
child_fk_field: str,
|
|
22
|
+
child_columns: List[str] = None,
|
|
23
|
+
show_name: str = "",
|
|
24
|
+
on_delete: str = "cascade"): # cascade | soft_delete | restrict
|
|
25
|
+
self.child_table = child_table
|
|
26
|
+
self.parent_fk_field = parent_fk_field # 父表中指向子表的FK字段名
|
|
27
|
+
self.child_fk_field = child_fk_field # 子表中指向父表的FK字段名
|
|
28
|
+
self.child_columns = child_columns or ["id", "name"]
|
|
29
|
+
self.show_name = show_name or child_table
|
|
30
|
+
self.on_delete = on_delete
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class ReferenceRelation:
|
|
34
|
+
"""
|
|
35
|
+
向上级联声明(子→父)
|
|
36
|
+
|
|
37
|
+
配置后,Get/List 查询时自动解析逻辑外键字段(如 site_ulid→site),
|
|
38
|
+
并将解析结果挂到返回 dict 的对应键下。
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
def __init__(self, *,
|
|
42
|
+
parent_table: str,
|
|
43
|
+
fk_field: str, # 本实体中的外键字段名
|
|
44
|
+
parent_pk_field: str = "id",
|
|
45
|
+
parent_columns: List[str] = None,
|
|
46
|
+
show_name: str = "",
|
|
47
|
+
parent_service_name: str = ""):
|
|
48
|
+
self.parent_table = parent_table
|
|
49
|
+
self.fk_field = fk_field
|
|
50
|
+
self.parent_pk_field = parent_pk_field
|
|
51
|
+
self.parent_columns = parent_columns or ["id", "name"]
|
|
52
|
+
self.show_name = show_name or parent_table
|
|
53
|
+
self.parent_service_name = parent_service_name
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class ChildRefRelation:
|
|
57
|
+
"""
|
|
58
|
+
向下引用声明(父→子 FK 列表)
|
|
59
|
+
|
|
60
|
+
配置后,Get/List 查询时自动将 FK 列表(如 tag_ulids: [1,2,3])
|
|
61
|
+
批量解析为完整对象列表。
|
|
62
|
+
与 CascadeRelation 不同:仅关联已有实体,不参与级联创建/删除/更新。
|
|
63
|
+
"""
|
|
64
|
+
|
|
65
|
+
def __init__(self, *,
|
|
66
|
+
child_table: str,
|
|
67
|
+
fk_list_field: str, # 父实体中存放子ID列表的字段名
|
|
68
|
+
child_pk_field: str = "id",
|
|
69
|
+
child_columns: List[str] = None,
|
|
70
|
+
show_name: str = "",
|
|
71
|
+
child_service_name: str = ""):
|
|
72
|
+
self.child_table = child_table
|
|
73
|
+
self.fk_list_field = fk_list_field
|
|
74
|
+
self.child_pk_field = child_pk_field
|
|
75
|
+
self.child_columns = child_columns or ["id", "name"]
|
|
76
|
+
self.show_name = show_name or child_table
|
|
77
|
+
self.child_service_name = child_service_name
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class HandlerConfig:
|
|
81
|
+
"""
|
|
82
|
+
Handler 配置
|
|
83
|
+
|
|
84
|
+
每个实体类型创建一个 Handler 实例时附带一份配置。
|
|
85
|
+
"""
|
|
86
|
+
|
|
87
|
+
def __init__(self, *,
|
|
88
|
+
entity_name: str = "",
|
|
89
|
+
cascades: List[CascadeRelation] = None,
|
|
90
|
+
references: List[ReferenceRelation] = None,
|
|
91
|
+
child_refs: List[ChildRefRelation] = None,
|
|
92
|
+
auth: Callable = None,
|
|
93
|
+
perm: Callable = None):
|
|
94
|
+
"""
|
|
95
|
+
Args:
|
|
96
|
+
entity_name: 实体名(用于日志和错误信息)
|
|
97
|
+
cascades: 级联关系列表
|
|
98
|
+
references: 向上级联引用列表
|
|
99
|
+
child_refs: 向下子引用列表
|
|
100
|
+
auth: 认证钩子 Callable,接收请求上下文返回 user_info
|
|
101
|
+
perm: 权限校验钩子 Callable,接收 (user_info, resource, action) 返回 bool
|
|
102
|
+
"""
|
|
103
|
+
self.entity_name = entity_name
|
|
104
|
+
self.cascades = cascades or []
|
|
105
|
+
self.references = references or []
|
|
106
|
+
self.child_refs = child_refs or []
|
|
107
|
+
self.auth = auth
|
|
108
|
+
self.perm = perm
|