springbootAI 1.8.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.
- spring/__init__.py +66 -0
- spring/ai/__init__.py +78 -0
- spring/ai/advisors.py +139 -0
- spring/ai/annotations.py +74 -0
- spring/ai/autoconfig.py +481 -0
- spring/ai/core.py +391 -0
- spring/ai/etl.py +188 -0
- spring/ai/memory.py +109 -0
- spring/ai/observability.py +129 -0
- spring/ai/providers.py +789 -0
- spring/ai/resilience.py +258 -0
- spring/ai/tools.py +106 -0
- spring/ai/vectorstore.py +303 -0
- spring/annotations/__init__.py +188 -0
- spring/annotations/cache.py +126 -0
- spring/annotations/cloud.py +207 -0
- spring/annotations/conditional.py +272 -0
- spring/annotations/core.py +864 -0
- spring/annotations/messaging.py +107 -0
- spring/aop/__init__.py +4 -0
- spring/aop/cloud_aop.py +404 -0
- spring/aop/comprehensive_aop.py +1015 -0
- spring/aop/method_interceptor.py +19 -0
- spring/aop/proxy_factory.py +55 -0
- spring/cloud/__init__.py +76 -0
- spring/cloud/discovery.py +364 -0
- spring/cloud/feign.py +469 -0
- spring/cloud/gateway.py +452 -0
- spring/cloud/load_balancer.py +149 -0
- spring/cloud/seata.py +557 -0
- spring/cloud/sentinel.py +525 -0
- spring/cloud/tracer.py +337 -0
- spring/config/__init__.py +21 -0
- spring/config/binding.py +206 -0
- spring/config/config_loader.py +405 -0
- spring/context/__init__.py +13 -0
- spring/context/application_context.py +589 -0
- spring/context/bean_definition.py +70 -0
- spring/context/bean_factory.py +1052 -0
- spring/context/registry.py +58 -0
- spring/context/scanner.py +106 -0
- spring/core/__init__.py +3 -0
- spring/core/graceful_shutdown.py +196 -0
- spring/core/typing_utils.py +50 -0
- spring/csv/__init__.py +52 -0
- spring/csv/annotations.py +402 -0
- spring/csv/converters.py +69 -0
- spring/csv/easy_csv.py +95 -0
- spring/csv/exceptions.py +27 -0
- spring/csv/reader.py +195 -0
- spring/csv/writer.py +155 -0
- spring/data/__init__.py +54 -0
- spring/data/page.py +181 -0
- spring/data/repository.py +274 -0
- spring/data/specification.py +228 -0
- spring/datasource/__init__.py +66 -0
- spring/datasource/annotations.py +133 -0
- spring/datasource/context.py +69 -0
- spring/datasource/dynamic.py +148 -0
- spring/event/__init__.py +7 -0
- spring/event/publisher.py +69 -0
- spring/excel/__init__.py +51 -0
- spring/excel/annotations.py +405 -0
- spring/excel/converters.py +231 -0
- spring/excel/easy_excel.py +94 -0
- spring/excel/exceptions.py +31 -0
- spring/excel/reader.py +254 -0
- spring/excel/style.py +95 -0
- spring/excel/writer.py +197 -0
- spring/i18n/__init__.py +97 -0
- spring/i18n/accessor.py +94 -0
- spring/i18n/auto_config.py +177 -0
- spring/i18n/holder.py +106 -0
- spring/i18n/locale.py +152 -0
- spring/i18n/locale_resolver.py +367 -0
- spring/i18n/message_source.py +250 -0
- spring/i18n/middleware.py +79 -0
- spring/i18n/properties.py +168 -0
- spring/i18n/sources.py +255 -0
- spring/logging/__init__.py +1 -0
- spring/logging/loguru_logger.py +228 -0
- spring/main.py +378 -0
- spring/messaging/__init__.py +1 -0
- spring/messaging/rabbitmq.py +302 -0
- spring/monitoring/__init__.py +1 -0
- spring/monitoring/prometheus.py +199 -0
- spring/orm/__init__.py +258 -0
- spring/orm/database.py +222 -0
- spring/orm/ddl_auto.py +1217 -0
- spring/orm/migration.py +419 -0
- spring/orm/mybatis_integration.py +400 -0
- spring/orm/pymybatis/__init__.py +86 -0
- spring/orm/pymybatis/annotations/__init__.py +30 -0
- spring/orm/pymybatis/annotations/annotations.py +332 -0
- spring/orm/pymybatis/cache/__init__.py +47 -0
- spring/orm/pymybatis/cache/cache.py +371 -0
- spring/orm/pymybatis/cache/redis_cache.py +434 -0
- spring/orm/pymybatis/circuit_breaker/__init__.py +21 -0
- spring/orm/pymybatis/circuit_breaker/circuit_breaker.py +424 -0
- spring/orm/pymybatis/configuration.py +525 -0
- spring/orm/pymybatis/core/__init__.py +10 -0
- spring/orm/pymybatis/core/sql_session.py +1382 -0
- spring/orm/pymybatis/core/sql_session_factory.py +76 -0
- spring/orm/pymybatis/dialect/__init__.py +9 -0
- spring/orm/pymybatis/dialect/dialect.py +445 -0
- spring/orm/pymybatis/dynamic_sql/__init__.py +9 -0
- spring/orm/pymybatis/dynamic_sql/dynamic_sql.py +900 -0
- spring/orm/pymybatis/interceptor/__init__.py +31 -0
- spring/orm/pymybatis/interceptor/interceptor.py +427 -0
- spring/orm/pymybatis/mapper/__init__.py +9 -0
- spring/orm/pymybatis/mapper/mapper.py +540 -0
- spring/orm/pymybatis/metrics/__init__.py +41 -0
- spring/orm/pymybatis/metrics/metrics.py +595 -0
- spring/orm/pymybatis/pool/__init__.py +9 -0
- spring/orm/pymybatis/pool/connection_pool.py +711 -0
- spring/orm/pymybatis/security/__init__.py +19 -0
- spring/orm/pymybatis/security/access_control.py +415 -0
- spring/orm/pymybatis/security/password_encoder.py +293 -0
- spring/orm/pymybatis/security/sensitive_data_masker.py +326 -0
- spring/orm/pymybatis/security/sql_injection_detector.py +675 -0
- spring/orm/pymybatis/transaction/__init__.py +9 -0
- spring/orm/pymybatis/transaction/transaction.py +288 -0
- spring/orm/pymybatis/type_handler/__init__.py +37 -0
- spring/orm/pymybatis/type_handler/type_handler.py +473 -0
- spring/orm/pymybatis/version.py +9 -0
- spring/orm/pymybatis/xml_parser/__init__.py +9 -0
- spring/orm/pymybatis/xml_parser/xml_parser.py +761 -0
- spring/retry/__init__.py +12 -0
- spring/retry/retry_annotations.py +71 -0
- spring/retry/retry_decorator.py +155 -0
- spring/scheduling/__init__.py +3 -0
- spring/scheduling/scheduler.py +389 -0
- spring/security/__init__.py +39 -0
- spring/security/jwt_utils.py +281 -0
- spring/security/replay_protection.py +206 -0
- spring/security/secret_manager.py +226 -0
- spring/security/security_aop.py +248 -0
- spring/security/security_context.py +172 -0
- spring/test/__init__.py +45 -0
- spring/test/slicing.py +341 -0
- spring/tracing/__init__.py +11 -0
- spring/tracing/skywalking.py +229 -0
- spring/tx/__init__.py +52 -0
- spring/tx/events.py +172 -0
- spring/tx/synchronization.py +143 -0
- spring/utils/__init__.py +5 -0
- spring/utils/banner.py +32 -0
- spring/utils/logger.py +73 -0
- spring/utils/redis_client.py +526 -0
- spring/validation/__init__.py +55 -0
- spring/validation/aop.py +141 -0
- spring/validation/constraints.py +357 -0
- spring/validation/exceptions.py +55 -0
- spring/validation/validator.py +139 -0
- spring/web/__init__.py +12 -0
- spring/web/actuator.py +319 -0
- spring/web/exception_handler.py +61 -0
- spring/web/health.py +399 -0
- spring/web/interceptor.py +91 -0
- spring/web/result.py +44 -0
- spring/web/swagger.py +601 -0
- spring/web/web_context.py +755 -0
- spring/websocket/__init__.py +86 -0
- spring/websocket/annotations.py +169 -0
- spring/websocket/broker.py +238 -0
- spring/websocket/exceptions.py +26 -0
- spring/websocket/handler.py +243 -0
- spring/websocket/router.py +526 -0
- spring/websocket/session.py +216 -0
- springbootai-1.8.0.dist-info/METADATA +2796 -0
- springbootai-1.8.0.dist-info/RECORD +175 -0
- springbootai-1.8.0.dist-info/WHEEL +5 -0
- springbootai-1.8.0.dist-info/entry_points.txt +2 -0
- springbootai-1.8.0.dist-info/licenses/LICENSE +7 -0
- springbootai-1.8.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,405 @@
|
|
|
1
|
+
"""SpringBootAI Excel 注解 —— 字段/类级映射元数据。
|
|
2
|
+
|
|
3
|
+
设计原则:**复用项目既有范式,不重复造轮子**。本模块的字段级注解完全镜像 ORM 层
|
|
4
|
+
``spring/orm/ddl_auto.py`` 的 ``Column`` / ``Id`` / ``@entity`` 元数据描述符范式:
|
|
5
|
+
|
|
6
|
+
- 字段级:``ExcelProperty`` / ``ExcelIgnore`` 作为类属性标记(描述符实例)或函数装饰器,
|
|
7
|
+
元数据通过 ``cls.__dict__`` + MRO 反射读取(与 ``Column``/``__column__`` 一致)。
|
|
8
|
+
- 类级:``@excel_sheet`` 装饰器在类上设置 ``__excel_sheet__``(与 ``@entity`` 设置
|
|
9
|
+
``__entity__``/``__table__`` 一致)。
|
|
10
|
+
|
|
11
|
+
注解本身**不依赖 openpyxl**,可独立声明;仅 read/write 引擎实际需要 openpyxl。
|
|
12
|
+
|
|
13
|
+
对齐 alibaba EasyExcel 的核心注解:``@ExcelProperty`` / ``@ExcelIgnore`` / ``@ExcelSheet``。
|
|
14
|
+
"""
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
from typing import Any, Callable, List, Optional, Type, Union
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
# ==================== 字段级注解 ====================
|
|
21
|
+
|
|
22
|
+
class ExcelProperty:
|
|
23
|
+
"""字段级注解:声明实体字段与 Excel 列的映射关系(镜像 ORM ``Column``)。
|
|
24
|
+
|
|
25
|
+
两种使用方式(与 ``Column`` 一致):
|
|
26
|
+
|
|
27
|
+
1. 类属性描述符(推荐)::
|
|
28
|
+
|
|
29
|
+
@excel_sheet("用户列表")
|
|
30
|
+
class DemoData:
|
|
31
|
+
id = ExcelProperty("ID", order=1)
|
|
32
|
+
name = ExcelProperty("姓名", order=2)
|
|
33
|
+
age = ExcelProperty("年龄", order=3, converter=IntConverter)
|
|
34
|
+
|
|
35
|
+
def __init__(self, id=None, name=None, age=None):
|
|
36
|
+
self.id = id; self.name = name; self.age = age
|
|
37
|
+
|
|
38
|
+
2. 函数装饰器(镜像 ``@column``)::
|
|
39
|
+
|
|
40
|
+
@ExcelProperty("姓名", order=2)
|
|
41
|
+
def name(self): ...
|
|
42
|
+
|
|
43
|
+
属性说明(对齐 EasyExcel ``@ExcelProperty``):
|
|
44
|
+
value: 列标题(表头文案)。为空时用字段名转标题。
|
|
45
|
+
order: 列顺序,越小越靠前;同 order 按 MRO 声明顺序。默认 0。
|
|
46
|
+
index: 绝对列索引(从 0 起),设置后覆盖 order。默认 None。
|
|
47
|
+
converter: 自定义转换器(``Converter`` 子类或实例)。默认 None(按类型自动选)。
|
|
48
|
+
format: 通用格式占位(同时作 date_format/num_format 默认)。
|
|
49
|
+
date_format: 日期格式串,如 ``%Y-%m-%d``。读时按此解析,写时按此格式化。
|
|
50
|
+
num_format: Excel 数字格式串,如 ``#,##0.00``。写时应用到单元格。
|
|
51
|
+
width: 列宽(字符数)。0 表示自适应。
|
|
52
|
+
big_number: 是否按字符串写入以避免 Excel 精度丢失(长 ID/大数)。默认 False。
|
|
53
|
+
head_style: 自定义表头样式名(见 style 模块)。默认 None(用默认表头样式)。
|
|
54
|
+
content_style:自定义内容样式名。默认 None。
|
|
55
|
+
ignore: 内部等价 @ExcelIgnore 的快捷开关。默认 False。
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
def __init__(
|
|
59
|
+
self,
|
|
60
|
+
value: str = "",
|
|
61
|
+
order: int = 0,
|
|
62
|
+
index: Optional[int] = None,
|
|
63
|
+
converter: Optional[Union[Type, Any]] = None,
|
|
64
|
+
format: Optional[str] = None,
|
|
65
|
+
date_format: Optional[str] = None,
|
|
66
|
+
num_format: Optional[str] = None,
|
|
67
|
+
width: float = 0,
|
|
68
|
+
big_number: bool = False,
|
|
69
|
+
head_style: Optional[str] = None,
|
|
70
|
+
content_style: Optional[str] = None,
|
|
71
|
+
ignore: bool = False,
|
|
72
|
+
):
|
|
73
|
+
self.value = value
|
|
74
|
+
self.order = order
|
|
75
|
+
self.index = index
|
|
76
|
+
self.converter = converter
|
|
77
|
+
self.format = format
|
|
78
|
+
self.date_format = date_format or format
|
|
79
|
+
self.num_format = num_format
|
|
80
|
+
self.width = width
|
|
81
|
+
self.big_number = big_number
|
|
82
|
+
self.head_style = head_style
|
|
83
|
+
self.content_style = content_style
|
|
84
|
+
self.ignore = ignore
|
|
85
|
+
# 反射时回填
|
|
86
|
+
self.attr_name: str = ""
|
|
87
|
+
|
|
88
|
+
def __set_name__(self, owner: type, name: str) -> None:
|
|
89
|
+
"""类属性描述符形式时,Python 自动回填字段名。"""
|
|
90
|
+
self.attr_name = name
|
|
91
|
+
|
|
92
|
+
def __call__(self, target: Callable) -> Callable:
|
|
93
|
+
"""函数装饰器形式:``@ExcelProperty(...)``,把元数据挂到 ``__excel_property__``。
|
|
94
|
+
|
|
95
|
+
镜像 ORM ``column()`` 装饰器的 ``setattr(f, '__column__', col)`` 写法。
|
|
96
|
+
"""
|
|
97
|
+
setattr(target, "__excel_property__", self)
|
|
98
|
+
# 函数上的 attr_name 取函数名
|
|
99
|
+
self.attr_name = getattr(target, "__name__", "")
|
|
100
|
+
return target
|
|
101
|
+
|
|
102
|
+
def resolve_header(self, attr_name: str) -> str:
|
|
103
|
+
"""计算最终表头文案。"""
|
|
104
|
+
return self.value or _field_to_header(attr_name)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
class ExcelIgnore:
|
|
108
|
+
"""字段级注解:标记字段在读写时跳过(镜像 ORM 中跳过未标注字段的语义)。
|
|
109
|
+
|
|
110
|
+
用法与 ``ExcelProperty`` 一致,支持类属性描述符与函数装饰器两种形式::
|
|
111
|
+
|
|
112
|
+
remark = ExcelIgnore()
|
|
113
|
+
# 或
|
|
114
|
+
@ExcelIgnore()
|
|
115
|
+
def remark(self): ...
|
|
116
|
+
"""
|
|
117
|
+
|
|
118
|
+
def __init__(self):
|
|
119
|
+
self.attr_name: str = ""
|
|
120
|
+
|
|
121
|
+
def __set_name__(self, owner: type, name: str) -> None:
|
|
122
|
+
self.attr_name = name
|
|
123
|
+
|
|
124
|
+
def __call__(self, target: Callable) -> Callable:
|
|
125
|
+
setattr(target, "__excel_ignore__", True)
|
|
126
|
+
self.attr_name = getattr(target, "__name__", "")
|
|
127
|
+
return target
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
# ==================== 类级注解 ====================
|
|
131
|
+
|
|
132
|
+
class ExcelSheet:
|
|
133
|
+
"""类级注解元数据:Excel 工作表配置(镜像 ORM ``Table``)。
|
|
134
|
+
|
|
135
|
+
属性说明(对齐 EasyExcel ``@ExcelProperty`` + sheet 配置):
|
|
136
|
+
sheet_name: 工作表名称。为空时用 "Sheet1"(写)或按索引读(读)。
|
|
137
|
+
head_row_number: 表头所在行号(从 1 起)。默认 1。读时数据从该行之后开始。
|
|
138
|
+
freeze_head: 是否冻结表头行。默认 True(写时生效)。
|
|
139
|
+
auto_width: 是否自适应列宽。默认 True。字段 ``width>0`` 时以字段为准。
|
|
140
|
+
head_style: 默认表头样式名。
|
|
141
|
+
content_style: 默认内容样式名。
|
|
142
|
+
"""
|
|
143
|
+
|
|
144
|
+
def __init__(
|
|
145
|
+
self,
|
|
146
|
+
sheet_name: str = "",
|
|
147
|
+
head_row_number: int = 1,
|
|
148
|
+
freeze_head: bool = True,
|
|
149
|
+
auto_width: bool = True,
|
|
150
|
+
head_style: Optional[str] = None,
|
|
151
|
+
content_style: Optional[str] = None,
|
|
152
|
+
):
|
|
153
|
+
self.sheet_name = sheet_name
|
|
154
|
+
self.head_row_number = head_row_number
|
|
155
|
+
self.freeze_head = freeze_head
|
|
156
|
+
self.auto_width = auto_width
|
|
157
|
+
self.head_style = head_style
|
|
158
|
+
self.content_style = content_style
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def excel_sheet(
|
|
162
|
+
sheet_name: str = "",
|
|
163
|
+
head_row_number: int = 1,
|
|
164
|
+
freeze_head: bool = True,
|
|
165
|
+
auto_width: bool = True,
|
|
166
|
+
head_style: Optional[str] = None,
|
|
167
|
+
content_style: Optional[str] = None,
|
|
168
|
+
) -> Callable[[type], type]:
|
|
169
|
+
"""类级装饰器:标注实体类对应的 Excel 工作表配置(镜像 ORM ``@entity``)。
|
|
170
|
+
|
|
171
|
+
用法::
|
|
172
|
+
|
|
173
|
+
@excel_sheet("用户列表", head_row_number=1)
|
|
174
|
+
class DemoData:
|
|
175
|
+
id = ExcelProperty("ID", order=1)
|
|
176
|
+
...
|
|
177
|
+
|
|
178
|
+
不使用本装饰器时,读写引擎使用默认配置(sheet_name="Sheet1",head_row_number=1)。
|
|
179
|
+
"""
|
|
180
|
+
meta = ExcelSheet(
|
|
181
|
+
sheet_name=sheet_name,
|
|
182
|
+
head_row_number=head_row_number,
|
|
183
|
+
freeze_head=freeze_head,
|
|
184
|
+
auto_width=auto_width,
|
|
185
|
+
head_style=head_style,
|
|
186
|
+
content_style=content_style,
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
def decorator(cls: type) -> type:
|
|
190
|
+
setattr(cls, "__excel_sheet__", meta)
|
|
191
|
+
return cls
|
|
192
|
+
|
|
193
|
+
return decorator
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
# ==================== 元数据解析(复用 ORM 反射范式) ====================
|
|
197
|
+
|
|
198
|
+
class ExcelColumnModel:
|
|
199
|
+
"""解析后的列模型,供 reader/writer 统一消费。"""
|
|
200
|
+
|
|
201
|
+
__slots__ = (
|
|
202
|
+
"attr_name", "header", "order", "index", "converter", "date_format",
|
|
203
|
+
"num_format", "width", "big_number", "head_style", "content_style",
|
|
204
|
+
"py_type",
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
def __init__(
|
|
208
|
+
self,
|
|
209
|
+
attr_name: str,
|
|
210
|
+
header: str,
|
|
211
|
+
order: int,
|
|
212
|
+
index: Optional[int],
|
|
213
|
+
converter: Optional[Union[Type, Any]],
|
|
214
|
+
date_format: Optional[str],
|
|
215
|
+
num_format: Optional[str],
|
|
216
|
+
width: float,
|
|
217
|
+
big_number: bool,
|
|
218
|
+
head_style: Optional[str],
|
|
219
|
+
content_style: Optional[str],
|
|
220
|
+
py_type: Any,
|
|
221
|
+
):
|
|
222
|
+
self.attr_name = attr_name
|
|
223
|
+
self.header = header
|
|
224
|
+
self.order = order
|
|
225
|
+
self.index = index
|
|
226
|
+
self.converter = converter
|
|
227
|
+
self.date_format = date_format
|
|
228
|
+
self.num_format = num_format
|
|
229
|
+
self.width = width
|
|
230
|
+
self.big_number = big_number
|
|
231
|
+
self.head_style = head_style
|
|
232
|
+
self.content_style = content_style
|
|
233
|
+
self.py_type = py_type
|
|
234
|
+
|
|
235
|
+
@property
|
|
236
|
+
def sort_key(self):
|
|
237
|
+
"""排序键:index 优先(None 视作大值靠后),其次 order,最后 attr_name 稳定。"""
|
|
238
|
+
return (self.index if self.index is not None else float("inf"),
|
|
239
|
+
self.order, self.attr_name)
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def _field_to_header(name: str) -> str:
|
|
243
|
+
"""字段名转表头:snake_case/camelCase -> 友好标题。
|
|
244
|
+
|
|
245
|
+
例:``user_name`` -> ``User Name``;``userName`` -> ``User Name``;``id`` -> ``Id``。
|
|
246
|
+
"""
|
|
247
|
+
import re
|
|
248
|
+
s1 = re.sub(r"(.)([A-Z][a-z]+)", r"\1 \2", name)
|
|
249
|
+
s2 = re.sub(r"([a-z0-9])([A-Z])", r"\1 \2", s1)
|
|
250
|
+
return " ".join(part.capitalize() for part in s2.replace("_", " ").split())
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
def _get_class_sheet_meta(cls: type) -> ExcelSheet:
|
|
254
|
+
"""读取类上的 ``__excel_sheet__``,缺失则返回默认。镜像 ORM ``_parse_entity`` 读 ``__table__``。"""
|
|
255
|
+
meta = getattr(cls, "__excel_sheet__", None)
|
|
256
|
+
if isinstance(meta, ExcelSheet):
|
|
257
|
+
return meta
|
|
258
|
+
return ExcelSheet()
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
def _resolve_init_hints(cls: type) -> dict:
|
|
262
|
+
"""获取 ``__init__`` 参数的类型注解(用于无类属性注解时的类型推断)。"""
|
|
263
|
+
import inspect
|
|
264
|
+
try:
|
|
265
|
+
from typing import get_type_hints
|
|
266
|
+
return get_type_hints(cls.__init__)
|
|
267
|
+
except Exception:
|
|
268
|
+
try:
|
|
269
|
+
return dict(inspect.signature(cls).parameters)
|
|
270
|
+
except Exception:
|
|
271
|
+
return {}
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
def _get_init_param_names(cls: type) -> List[str]:
|
|
275
|
+
"""提取 ``__init__`` 中 ``self.xxx`` 以外的参数名,作为字段名回退顺序。"""
|
|
276
|
+
import inspect
|
|
277
|
+
try:
|
|
278
|
+
sig = inspect.signature(cls.__init__)
|
|
279
|
+
except (TypeError, ValueError):
|
|
280
|
+
return []
|
|
281
|
+
names = []
|
|
282
|
+
for pname, param in list(sig.parameters.items())[1:]: # 跳过 self
|
|
283
|
+
if param.kind in (inspect.Parameter.VAR_POSITIONAL, inspect.Parameter.VAR_KEYWORD):
|
|
284
|
+
continue
|
|
285
|
+
names.append(pname)
|
|
286
|
+
return names
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
def parse_excel_columns(cls: type) -> List[ExcelColumnModel]:
|
|
290
|
+
"""解析实体类的 Excel 列模型。
|
|
291
|
+
|
|
292
|
+
解析顺序(镜像 ORM ``_parse_entity`` 对 ``Column`` 的处理):
|
|
293
|
+
1. 遍历 ``cls.__mro__`` 的 ``__dict__``,收集 ``ExcelProperty`` 实例或带
|
|
294
|
+
``__excel_property__`` 的成员;遇到 ``ExcelIgnore`` / ``__excel_ignore__`` 则跳过。
|
|
295
|
+
2. 若类上没有任何 ``ExcelProperty`` 标记,回退到 ``__init__`` 参数列表,按字段名自动
|
|
296
|
+
生成表头(让未改造的纯 ``__init__`` 模型如 ``example_all/models/User.py`` 也能导出)。
|
|
297
|
+
3. 按 ``index`` -> ``order`` -> 声明顺序排序。
|
|
298
|
+
"""
|
|
299
|
+
from .exceptions import ExcelPropertyError
|
|
300
|
+
|
|
301
|
+
seen: dict = {} # attr_name -> ExcelProperty
|
|
302
|
+
ignored: set = set()
|
|
303
|
+
declaration_order: dict = {}
|
|
304
|
+
counter = 0
|
|
305
|
+
|
|
306
|
+
for base in reversed(cls.__mro__): # 自底向上,子类覆盖父类
|
|
307
|
+
for attr_name, value in vars(base).items():
|
|
308
|
+
if attr_name.startswith("__"):
|
|
309
|
+
continue
|
|
310
|
+
prop = None
|
|
311
|
+
if isinstance(value, ExcelProperty):
|
|
312
|
+
prop = value
|
|
313
|
+
if not prop.attr_name:
|
|
314
|
+
prop.attr_name = attr_name
|
|
315
|
+
elif hasattr(value, "__excel_property__"):
|
|
316
|
+
prop = getattr(value, "__excel_property__")
|
|
317
|
+
if not isinstance(prop, ExcelProperty):
|
|
318
|
+
continue
|
|
319
|
+
if not prop.attr_name:
|
|
320
|
+
prop.attr_name = attr_name
|
|
321
|
+
else:
|
|
322
|
+
if isinstance(value, ExcelIgnore):
|
|
323
|
+
ignored.add(attr_name)
|
|
324
|
+
continue
|
|
325
|
+
if getattr(value, "__excel_ignore__", False) is True:
|
|
326
|
+
ignored.add(attr_name)
|
|
327
|
+
continue
|
|
328
|
+
continue
|
|
329
|
+
if attr_name in ignored:
|
|
330
|
+
continue
|
|
331
|
+
if prop.ignore:
|
|
332
|
+
ignored.add(attr_name)
|
|
333
|
+
continue
|
|
334
|
+
if attr_name not in declaration_order:
|
|
335
|
+
declaration_order[attr_name] = counter
|
|
336
|
+
counter += 1
|
|
337
|
+
# 子类覆盖父类
|
|
338
|
+
seen[attr_name] = (prop, declaration_order[attr_name])
|
|
339
|
+
|
|
340
|
+
init_hints = _resolve_init_hints(cls)
|
|
341
|
+
|
|
342
|
+
columns = []
|
|
343
|
+
|
|
344
|
+
if seen:
|
|
345
|
+
# 有显式 ExcelProperty 标记
|
|
346
|
+
for attr_name, (prop, decl_order) in seen.items():
|
|
347
|
+
columns.append(_to_column_model(
|
|
348
|
+
attr_name=attr_name,
|
|
349
|
+
prop=prop,
|
|
350
|
+
decl_order=decl_order,
|
|
351
|
+
py_type=init_hints.get(attr_name, None),
|
|
352
|
+
))
|
|
353
|
+
# 排序:index 优先,其次 order,最后声明顺序
|
|
354
|
+
columns.sort(key=lambda c: (
|
|
355
|
+
c.index if c.index is not None else float("inf"),
|
|
356
|
+
c.order,
|
|
357
|
+
declaration_order.get(c.attr_name, 0),
|
|
358
|
+
))
|
|
359
|
+
else:
|
|
360
|
+
# 回退:没有任何 ExcelProperty 标记 -> 用 __init__ 参数自动建列
|
|
361
|
+
for pname in _get_init_param_names(cls):
|
|
362
|
+
if pname in ignored:
|
|
363
|
+
continue
|
|
364
|
+
columns.append(_to_column_model(
|
|
365
|
+
attr_name=pname,
|
|
366
|
+
prop=ExcelProperty(), # 默认元数据
|
|
367
|
+
decl_order=0,
|
|
368
|
+
py_type=init_hints.get(pname, None),
|
|
369
|
+
))
|
|
370
|
+
|
|
371
|
+
# 全部被 @ExcelIgnore 或无可导出字段时,统一抛错
|
|
372
|
+
if not columns:
|
|
373
|
+
raise ExcelPropertyError(
|
|
374
|
+
f"类 {cls.__name__} 没有可导出字段(全部被 @ExcelIgnore,或无 __init__ 字段?)"
|
|
375
|
+
)
|
|
376
|
+
return columns
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
def _to_column_model(attr_name: str, prop: ExcelProperty, decl_order: int, py_type: Any) -> ExcelColumnModel:
|
|
380
|
+
"""把 ExcelProperty + 类型注解组装为 ExcelColumnModel。"""
|
|
381
|
+
return ExcelColumnModel(
|
|
382
|
+
attr_name=attr_name,
|
|
383
|
+
header=prop.resolve_header(attr_name),
|
|
384
|
+
order=prop.order if prop.order else decl_order,
|
|
385
|
+
index=prop.index,
|
|
386
|
+
converter=prop.converter,
|
|
387
|
+
date_format=prop.date_format,
|
|
388
|
+
num_format=prop.num_format,
|
|
389
|
+
width=prop.width,
|
|
390
|
+
big_number=prop.big_number,
|
|
391
|
+
head_style=prop.head_style,
|
|
392
|
+
content_style=prop.content_style,
|
|
393
|
+
py_type=py_type,
|
|
394
|
+
)
|
|
395
|
+
|
|
396
|
+
|
|
397
|
+
__all__ = [
|
|
398
|
+
"ExcelProperty",
|
|
399
|
+
"ExcelIgnore",
|
|
400
|
+
"ExcelSheet",
|
|
401
|
+
"excel_sheet",
|
|
402
|
+
"ExcelColumnModel",
|
|
403
|
+
"parse_excel_columns",
|
|
404
|
+
"_get_class_sheet_meta",
|
|
405
|
+
]
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
"""SpringBootAI Excel 转换器 —— Python 值与 Excel 单元格值之间的双向转换。
|
|
2
|
+
|
|
3
|
+
对齐 EasyExcel 的 ``Converter`` 机制:用户可实现 ``Converter`` 接口自定义任意类型的读写转换;
|
|
4
|
+
内置常用类型(int/float/bool/str/datetime/date/Decimal)的转换器,并在未显式指定 converter 时
|
|
5
|
+
按字段类型注解自动选择。
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import datetime
|
|
10
|
+
from decimal import Decimal, InvalidOperation
|
|
11
|
+
from typing import Any, Optional, Type
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Converter:
|
|
15
|
+
"""转换器接口(对齐 EasyExcel ``Converter``)。
|
|
16
|
+
|
|
17
|
+
子类需实现:
|
|
18
|
+
- ``to_excel(value)`` :Python 值 -> Excel 单元格写入值
|
|
19
|
+
- ``from_excel(cell_value)``:Excel 单元格读出值 -> Python 值
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
def to_excel(self, value: Any) -> Any:
|
|
23
|
+
raise NotImplementedError
|
|
24
|
+
|
|
25
|
+
def from_excel(self, cell_value: Any) -> Any:
|
|
26
|
+
raise NotImplementedError
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
# ==================== 内置转换器 ====================
|
|
30
|
+
|
|
31
|
+
class StringConverter(Converter):
|
|
32
|
+
def to_excel(self, value: Any) -> Any:
|
|
33
|
+
return "" if value is None else str(value)
|
|
34
|
+
|
|
35
|
+
def from_excel(self, cell_value: Any) -> Optional[str]:
|
|
36
|
+
if cell_value is None:
|
|
37
|
+
return None
|
|
38
|
+
return str(cell_value).strip()
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class IntegerConverter(Converter):
|
|
42
|
+
def to_excel(self, value: Any) -> Any:
|
|
43
|
+
if value is None or value == "":
|
|
44
|
+
return None
|
|
45
|
+
return int(value)
|
|
46
|
+
|
|
47
|
+
def from_excel(self, cell_value: Any) -> Optional[int]:
|
|
48
|
+
if cell_value is None or cell_value == "":
|
|
49
|
+
return None
|
|
50
|
+
try:
|
|
51
|
+
# 容错:单元格可能是 "12.0" 或 12.9(截断为 int)
|
|
52
|
+
return int(float(str(cell_value).strip()))
|
|
53
|
+
except (TypeError, ValueError):
|
|
54
|
+
return None
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class FloatConverter(Converter):
|
|
58
|
+
def to_excel(self, value: Any) -> Any:
|
|
59
|
+
if value is None or value == "":
|
|
60
|
+
return None
|
|
61
|
+
return float(value)
|
|
62
|
+
|
|
63
|
+
def from_excel(self, cell_value: Any) -> Optional[float]:
|
|
64
|
+
if cell_value is None or cell_value == "":
|
|
65
|
+
return None
|
|
66
|
+
try:
|
|
67
|
+
return float(str(cell_value).strip())
|
|
68
|
+
except (TypeError, ValueError):
|
|
69
|
+
return None
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class BooleanConverter(Converter):
|
|
73
|
+
"""布尔转换器:写为 True/False,读时兼容 1/0、true/false、是/否、yes/no。"""
|
|
74
|
+
|
|
75
|
+
_TRUE_TOKENS = {"1", "true", "yes", "y", "t", "是", "✓"}
|
|
76
|
+
_FALSE_TOKENS = {"0", "false", "no", "n", "f", "否", ""}
|
|
77
|
+
|
|
78
|
+
def to_excel(self, value: Any) -> Any:
|
|
79
|
+
if value is None:
|
|
80
|
+
return None
|
|
81
|
+
return bool(value)
|
|
82
|
+
|
|
83
|
+
def from_excel(self, cell_value: Any) -> Optional[bool]:
|
|
84
|
+
if cell_value is None:
|
|
85
|
+
return None
|
|
86
|
+
if isinstance(cell_value, bool):
|
|
87
|
+
return cell_value
|
|
88
|
+
token = str(cell_value).strip().lower()
|
|
89
|
+
if token in self._TRUE_TOKENS:
|
|
90
|
+
return True
|
|
91
|
+
if token in self._FALSE_TOKENS:
|
|
92
|
+
return False
|
|
93
|
+
# 数值非零视为 True
|
|
94
|
+
try:
|
|
95
|
+
return float(token) != 0
|
|
96
|
+
except ValueError:
|
|
97
|
+
return None
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
class DateStringConverter(Converter):
|
|
101
|
+
"""日期/时间按格式串在 Python str 与 Excel 之间转换。
|
|
102
|
+
|
|
103
|
+
写入:datetime/date -> 按 ``fmt`` 格式化为字符串(避免 Excel 自动改写日期)。
|
|
104
|
+
读取:单元格值(str 或 datetime)-> 按 ``fmt`` 解析为 datetime。
|
|
105
|
+
"""
|
|
106
|
+
|
|
107
|
+
def __init__(self, fmt: str = "%Y-%m-%d %H:%M:%S"):
|
|
108
|
+
self.fmt = fmt
|
|
109
|
+
|
|
110
|
+
def to_excel(self, value: Any) -> Any:
|
|
111
|
+
if value is None:
|
|
112
|
+
return None
|
|
113
|
+
if isinstance(value, (datetime.datetime, datetime.date)):
|
|
114
|
+
return value.strftime(self.fmt)
|
|
115
|
+
return str(value)
|
|
116
|
+
|
|
117
|
+
def from_excel(self, cell_value: Any) -> Optional[datetime.datetime]:
|
|
118
|
+
if cell_value is None or cell_value == "":
|
|
119
|
+
return None
|
|
120
|
+
if isinstance(cell_value, datetime.datetime):
|
|
121
|
+
return cell_value
|
|
122
|
+
if isinstance(cell_value, datetime.date):
|
|
123
|
+
return datetime.datetime(cell_value.year, cell_value.month, cell_value.day)
|
|
124
|
+
text = str(cell_value).strip()
|
|
125
|
+
try:
|
|
126
|
+
return datetime.datetime.strptime(text, self.fmt)
|
|
127
|
+
except ValueError:
|
|
128
|
+
# 兜底:尝试 ISO 与常见格式
|
|
129
|
+
for fallback in ("%Y-%m-%d %H:%M:%S", "%Y-%m-%d", "%Y/%m/%d", "%d/%m/%Y"):
|
|
130
|
+
try:
|
|
131
|
+
return datetime.datetime.strptime(text, fallback)
|
|
132
|
+
except ValueError:
|
|
133
|
+
continue
|
|
134
|
+
return None
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
class BigDecimalConverter(Converter):
|
|
138
|
+
"""大数/金额转换器:以字符串读写,避免 Excel 浮点精度丢失(EasyExcel 经典特性)。
|
|
139
|
+
|
|
140
|
+
写入:Decimal/float/int/str -> 原样字符串。
|
|
141
|
+
读取:单元格值 -> Decimal。
|
|
142
|
+
"""
|
|
143
|
+
|
|
144
|
+
def to_excel(self, value: Any) -> Any:
|
|
145
|
+
if value is None:
|
|
146
|
+
return None
|
|
147
|
+
return str(value)
|
|
148
|
+
|
|
149
|
+
def from_excel(self, cell_value: Any) -> Optional[Decimal]:
|
|
150
|
+
if cell_value is None or cell_value == "":
|
|
151
|
+
return None
|
|
152
|
+
try:
|
|
153
|
+
return Decimal(str(cell_value).strip())
|
|
154
|
+
except (InvalidOperation, ValueError):
|
|
155
|
+
return None
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
# ==================== 自动选择 ====================
|
|
159
|
+
|
|
160
|
+
# Python 类型 -> 默认转换器类
|
|
161
|
+
_TYPE_CONVERTER_MAP: dict = {
|
|
162
|
+
int: IntegerConverter,
|
|
163
|
+
float: FloatConverter,
|
|
164
|
+
bool: BooleanConverter,
|
|
165
|
+
str: StringConverter,
|
|
166
|
+
Decimal: BigDecimalConverter,
|
|
167
|
+
datetime.datetime: DateStringConverter,
|
|
168
|
+
datetime.date: DateStringConverter,
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def resolve_converter(py_type: Any, declared: Any = None, date_format: Optional[str] = None) -> Optional[Converter]:
|
|
173
|
+
"""解析最终使用的转换器实例。
|
|
174
|
+
|
|
175
|
+
优先级:显式声明的 converter > 按类型注解自动选择 > None(由引擎按原值处理)。
|
|
176
|
+
|
|
177
|
+
Args:
|
|
178
|
+
py_type: 字段类型注解(可能为 None)。
|
|
179
|
+
declared: ``@ExcelProperty(converter=...)`` 显式声明的转换器类或实例。
|
|
180
|
+
date_format:日期格式串,仅在自动选择 DateStringConverter 时使用。
|
|
181
|
+
"""
|
|
182
|
+
if declared is not None:
|
|
183
|
+
if isinstance(declared, Converter):
|
|
184
|
+
return declared
|
|
185
|
+
if isinstance(declared, type) and issubclass(declared, Converter):
|
|
186
|
+
# DateStringConverter 支持注入 date_format
|
|
187
|
+
if issubclass(declared, DateStringConverter):
|
|
188
|
+
return declared(date_format or "%Y-%m-%d %H:%M:%S")
|
|
189
|
+
return declared()
|
|
190
|
+
# 容错:用户传了非 Converter 对象,直接返回(用户自负其责)
|
|
191
|
+
return declared
|
|
192
|
+
|
|
193
|
+
if py_type is None:
|
|
194
|
+
return None
|
|
195
|
+
# 解析 typing 可选类型如 Optional[int]
|
|
196
|
+
origin = getattr(py_type, "__origin__", None)
|
|
197
|
+
args = getattr(py_type, "__args__", ())
|
|
198
|
+
if origin is not None and args:
|
|
199
|
+
# 取非 None 的第一个参数
|
|
200
|
+
candidates = [a for a in args if a is not type(None)] # noqa: E721
|
|
201
|
+
if len(candidates) == 1:
|
|
202
|
+
py_type = candidates[0]
|
|
203
|
+
|
|
204
|
+
converter_cls = _TYPE_CONVERTER_MAP.get(py_type)
|
|
205
|
+
if converter_cls is None:
|
|
206
|
+
# datetime 子类 / Decimal 等
|
|
207
|
+
for mapped_type, cls in _TYPE_CONVERTER_MAP.items():
|
|
208
|
+
try:
|
|
209
|
+
if py_type is not None and isinstance(py_type, type) and issubclass(py_type, mapped_type):
|
|
210
|
+
converter_cls = cls
|
|
211
|
+
break
|
|
212
|
+
except TypeError:
|
|
213
|
+
continue
|
|
214
|
+
if converter_cls is None:
|
|
215
|
+
return None
|
|
216
|
+
if issubclass(converter_cls, DateStringConverter):
|
|
217
|
+
fmt = date_format or ("%Y-%m-%d" if py_type is datetime.date else "%Y-%m-%d %H:%M:%S")
|
|
218
|
+
return converter_cls(fmt)
|
|
219
|
+
return converter_cls()
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
__all__ = [
|
|
223
|
+
"Converter",
|
|
224
|
+
"StringConverter",
|
|
225
|
+
"IntegerConverter",
|
|
226
|
+
"FloatConverter",
|
|
227
|
+
"BooleanConverter",
|
|
228
|
+
"DateStringConverter",
|
|
229
|
+
"BigDecimalConverter",
|
|
230
|
+
"resolve_converter",
|
|
231
|
+
]
|