lush-sqlalchemyx 0.4.2__tar.gz → 0.6.0__tar.gz

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.
Files changed (29) hide show
  1. {lush_sqlalchemyx-0.4.2 → lush_sqlalchemyx-0.6.0}/PKG-INFO +1 -1
  2. {lush_sqlalchemyx-0.4.2 → lush_sqlalchemyx-0.6.0}/pyproject.toml +7 -1
  3. {lush_sqlalchemyx-0.4.2 → lush_sqlalchemyx-0.6.0}/src/lush_sqlalchemyx/__init__.py +14 -0
  4. {lush_sqlalchemyx-0.4.2 → lush_sqlalchemyx-0.6.0}/src/lush_sqlalchemyx/base/dal/__init__.py +24 -12
  5. {lush_sqlalchemyx-0.4.2 → lush_sqlalchemyx-0.6.0}/src/lush_sqlalchemyx/base/dal/_async.py +2 -106
  6. {lush_sqlalchemyx-0.4.2 → lush_sqlalchemyx-0.6.0}/src/lush_sqlalchemyx/base/dal/_common.py +5 -46
  7. lush_sqlalchemyx-0.6.0/src/lush_sqlalchemyx/base/dal/_dynamic.py +857 -0
  8. {lush_sqlalchemyx-0.4.2 → lush_sqlalchemyx-0.6.0}/src/lush_sqlalchemyx/base/dal/_sync.py +1 -106
  9. {lush_sqlalchemyx-0.4.2 → lush_sqlalchemyx-0.6.0}/src/lush_sqlalchemyx/mgrs/mysql/__init__.py +4 -0
  10. lush_sqlalchemyx-0.6.0/src/lush_sqlalchemyx/mgrs/mysql/_pool_config.py +49 -0
  11. {lush_sqlalchemyx-0.4.2 → lush_sqlalchemyx-0.6.0}/src/lush_sqlalchemyx/mgrs/mysql/manager.py +14 -14
  12. {lush_sqlalchemyx-0.4.2 → lush_sqlalchemyx-0.6.0}/src/lush_sqlalchemyx/mgrs/mysql/sync_manager.py +13 -12
  13. {lush_sqlalchemyx-0.4.2 → lush_sqlalchemyx-0.6.0}/src/lush_sqlalchemyx/shortcuts/meta.py +7 -27
  14. {lush_sqlalchemyx-0.4.2 → lush_sqlalchemyx-0.6.0}/README.md +0 -0
  15. {lush_sqlalchemyx-0.4.2 → lush_sqlalchemyx-0.6.0}/src/lush_sqlalchemyx/_compat.py +0 -0
  16. {lush_sqlalchemyx-0.4.2 → lush_sqlalchemyx-0.6.0}/src/lush_sqlalchemyx/base/__init__.py +0 -0
  17. {lush_sqlalchemyx-0.4.2 → lush_sqlalchemyx-0.6.0}/src/lush_sqlalchemyx/base/dal/_pagination.py +0 -0
  18. {lush_sqlalchemyx-0.4.2 → lush_sqlalchemyx-0.6.0}/src/lush_sqlalchemyx/base/dal/_repository.py +0 -0
  19. {lush_sqlalchemyx-0.4.2 → lush_sqlalchemyx-0.6.0}/src/lush_sqlalchemyx/integrations/__init__.py +0 -0
  20. {lush_sqlalchemyx-0.4.2 → lush_sqlalchemyx-0.6.0}/src/lush_sqlalchemyx/integrations/fastapi/__init__.py +0 -0
  21. {lush_sqlalchemyx-0.4.2 → lush_sqlalchemyx-0.6.0}/src/lush_sqlalchemyx/integrations/fastapi/depends/__init__.py +0 -0
  22. {lush_sqlalchemyx-0.4.2 → lush_sqlalchemyx-0.6.0}/src/lush_sqlalchemyx/integrations/flask/__init__.py +0 -0
  23. {lush_sqlalchemyx-0.4.2 → lush_sqlalchemyx-0.6.0}/src/lush_sqlalchemyx/integrations/flask/ext.py +0 -0
  24. {lush_sqlalchemyx-0.4.2 → lush_sqlalchemyx-0.6.0}/src/lush_sqlalchemyx/mgrs/__init__.py +0 -0
  25. {lush_sqlalchemyx-0.4.2 → lush_sqlalchemyx-0.6.0}/src/lush_sqlalchemyx/mgrs/mysql/mapper.py +0 -0
  26. {lush_sqlalchemyx-0.4.2 → lush_sqlalchemyx-0.6.0}/src/lush_sqlalchemyx/mgrs/mysql/sync_mapper.py +0 -0
  27. {lush_sqlalchemyx-0.4.2 → lush_sqlalchemyx-0.6.0}/src/lush_sqlalchemyx/py.typed +0 -0
  28. {lush_sqlalchemyx-0.4.2 → lush_sqlalchemyx-0.6.0}/src/lush_sqlalchemyx/same_impl_just_warn_wrapper.py +0 -0
  29. {lush_sqlalchemyx-0.4.2 → lush_sqlalchemyx-0.6.0}/src/lush_sqlalchemyx/shortcuts/__init__.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: lush-sqlalchemyx
3
- Version: 0.4.2
3
+ Version: 0.6.0
4
4
  Summary: SQLAlchemy helpers (DAL) and async MySQL managers, with some web frameworks integrations
5
5
  Author: straydragon
6
6
  Author-email: straydragon <straydragonl@foxmail.com>
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "lush-sqlalchemyx"
3
- version = "0.4.2"
3
+ version = "0.6.0"
4
4
  description = "SQLAlchemy helpers (DAL) and async MySQL managers, with some web frameworks integrations"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10"
@@ -40,6 +40,7 @@ dev = [
40
40
  "jinja2sql>=0.8.0",
41
41
  "pytest>=8.4.1",
42
42
  "pytest-asyncio>=1.1.0",
43
+ "pytest-bdd>=8.1.0",
43
44
  "pytest-cov>=6.2.1",
44
45
  "aiosqlite>=0.20.0",
45
46
  "aiomysql>=0.2.0",
@@ -57,11 +58,15 @@ testpaths = ["tests"]
57
58
  asyncio_mode = "auto"
58
59
  asyncio_default_fixture_loop_scope = "function"
59
60
  asyncio_default_test_loop_scope = "function"
61
+ bdd_features_base_dir = "tests/features"
60
62
 
61
63
  [tool.coverage.run]
62
64
  branch = true
63
65
 
64
66
  [tool.coverage.report]
67
+ precision = 2
68
+ show_missing = true
69
+ skip_covered = true
65
70
  exclude_also = [
66
71
  "if TYPE_CHECKING:",
67
72
  "if __name__ == .__main__.:",
@@ -169,6 +174,7 @@ ignore = [
169
174
  "S101", # assert语句(测试中使用)
170
175
  "S105", # 硬编码密码字符串
171
176
  "S201", # Flask debug=True
177
+ "S608", # SQL注入(测试辅助函数的硬编码表名)
172
178
  "S301", # 可疑的pickle使用
173
179
  "S311", # 非加密安全的随机数
174
180
 
@@ -10,6 +10,13 @@
10
10
  """
11
11
 
12
12
  from lush_sqlalchemyx.base.dal import (
13
+ DynamicAsyncDAL,
14
+ DynamicSyncDAL,
15
+ DynamicTableConfig,
16
+ PrimaryKeyT,
17
+ TableRef,
18
+ derive_columns_from_dto,
19
+ derive_pk_from_dto,
13
20
  is_soft_delete_hooks_registered,
14
21
  register_soft_delete_hooks,
15
22
  setup_dal_hooks,
@@ -17,6 +24,13 @@ from lush_sqlalchemyx.base.dal import (
17
24
  )
18
25
 
19
26
  __all__ = (
27
+ "DynamicAsyncDAL",
28
+ "DynamicSyncDAL",
29
+ "DynamicTableConfig",
30
+ "PrimaryKeyT",
31
+ "TableRef",
32
+ "derive_columns_from_dto",
33
+ "derive_pk_from_dto",
20
34
  "is_soft_delete_hooks_registered",
21
35
  "register_soft_delete_hooks",
22
36
  "setup_dal_hooks",
@@ -28,8 +28,7 @@ from ._async import (
28
28
  BasicAsyncBaseTable,
29
29
  ReadOnlyAsyncBaseDAL,
30
30
  ReadOnlyBasicAsyncBaseTable,
31
- StdAsyncBaseTable,
32
- StdReadOnlyBasicAsyncBaseTable,
31
+
33
32
  async_temp_set_lock_wait_timeout,
34
33
  async_with_retry,
35
34
  )
@@ -49,8 +48,7 @@ from ._common import (
49
48
  RetryConfig,
50
49
  SoftDeleteTableMixin,
51
50
  SQLATableT,
52
- StdBaseCU,
53
- StdBaseDTO,
51
+
54
52
  escape_like,
55
53
  filtered_in_sql_values,
56
54
  is_soft_delete_hooks_registered,
@@ -62,6 +60,17 @@ from ._common import (
62
60
  # Event listener references — accessed by tests via getattr(module, name).
63
61
  from ._common import __prevent_readonly_write as __prevent_readonly_write # pyright: ignore[reportPrivateUsage]
64
62
  from ._common import __receive_before_flush as __receive_before_flush # pyright: ignore[reportPrivateUsage]
63
+
64
+ # --- dynamic (no ORM table class) ---
65
+ from ._dynamic import (
66
+ DynamicAsyncDAL,
67
+ DynamicSyncDAL,
68
+ DynamicTableConfig,
69
+ PrimaryKeyT,
70
+ TableRef,
71
+ derive_columns_from_dto,
72
+ derive_pk_from_dto,
73
+ )
65
74
  from ._pagination import (
66
75
  build_cursor_stmt,
67
76
  build_offset_stmt,
@@ -77,8 +86,7 @@ from ._sync import (
77
86
  BasicSyncBaseTable,
78
87
  ReadOnlySyncBaseDAL,
79
88
  ReadOnlySyncBaseTable,
80
- StdReadOnlySyncBaseTable,
81
- StdSyncBaseTable,
89
+
82
90
  SyncBaseDAL,
83
91
  SyncRawDAL,
84
92
  SyncRawReadDAL,
@@ -108,14 +116,20 @@ __all__ = (
108
116
  "SQLATableT",
109
117
  "SoftDeleteTableMixin",
110
118
  "FieldIsDeleteSoftDeleteTableMixin",
111
- "StdBaseCU",
112
- "StdBaseDTO",
113
119
  "escape_like",
114
120
  "filtered_in_sql_values",
115
121
  "is_soft_delete_hooks_registered",
116
122
  "register_soft_delete_hooks",
117
123
  "setup_dal_hooks",
118
124
  "unregister_soft_delete_hooks",
125
+ # dynamic
126
+ "DynamicAsyncDAL",
127
+ "DynamicSyncDAL",
128
+ "DynamicTableConfig",
129
+ "PrimaryKeyT",
130
+ "TableRef",
131
+ "derive_columns_from_dto",
132
+ "derive_pk_from_dto",
119
133
  # async
120
134
  "AsyncBaseDAL",
121
135
  "AsyncRawDAL",
@@ -128,16 +142,14 @@ __all__ = (
128
142
  "BasicAsyncBaseTable",
129
143
  "ReadOnlyAsyncBaseDAL",
130
144
  "ReadOnlyBasicAsyncBaseTable",
131
- "StdAsyncBaseTable",
132
- "StdReadOnlyBasicAsyncBaseTable",
145
+
133
146
  "async_temp_set_lock_wait_timeout",
134
147
  "async_with_retry",
135
148
  # sync
136
149
  "BasicSyncBaseTable",
137
150
  "ReadOnlySyncBaseDAL",
138
151
  "ReadOnlySyncBaseTable",
139
- "StdReadOnlySyncBaseTable",
140
- "StdSyncBaseTable",
152
+
141
153
  "SyncBaseDAL",
142
154
  "SyncRawDAL",
143
155
  "SyncRawReadDAL",
@@ -7,9 +7,7 @@
7
7
  from __future__ import annotations
8
8
 
9
9
  import asyncio
10
- import datetime
11
10
  import logging
12
- import warnings
13
11
  from collections.abc import AsyncGenerator, Awaitable, Callable, Iterable
14
12
  from contextlib import asynccontextmanager, suppress
15
13
  from typing import TYPE_CHECKING, Any, ClassVar, Generic, Literal, ParamSpec, TypeVar, cast
@@ -19,7 +17,7 @@ from lush_dal_protocol.abc import AbstractAsyncReadDAL, AbstractAsyncWriteDAL
19
17
  from pydantic import BaseModel
20
18
  from sqlalchemy import ColumnExpressionArgument
21
19
  from sqlalchemy.ext.asyncio import AsyncAttrs, AsyncSession
22
- from sqlalchemy.orm import DeclarativeBase, InstrumentedAttribute, Mapped, mapped_column
20
+ from sqlalchemy.orm import DeclarativeBase, InstrumentedAttribute
23
21
 
24
22
  from lush_sqlalchemyx._compat import require_async
25
23
 
@@ -31,7 +29,6 @@ from ._common import (
31
29
  CUModelT,
32
30
  DBRetryableError,
33
31
  DTOModelT,
34
- FieldIsDeleteSoftDeleteTableMixin,
35
32
  ReadOnlyMixin,
36
33
  RetryConfig,
37
34
  SoftDeleteTableMixin,
@@ -143,106 +140,6 @@ class ReadOnlyBasicAsyncBaseTable(AsyncSqlATableBase, ReadOnlyMixin):
143
140
  __abstract__ = True
144
141
 
145
142
 
146
- class StdAsyncBaseTable(BasicAsyncBaseTable, FieldIsDeleteSoftDeleteTableMixin):
147
- """标准异步表类: 包含 id/时间戳/操作人/软删除等标准字段.
148
-
149
- .. deprecated::
150
- 此类预设了特定业务字段, 下游应自行继承 ``BasicAsyncBaseTable`` 定义所需字段.
151
- """
152
-
153
- __abstract__ = True
154
-
155
- def __init_subclass__(cls, **kwargs: Any) -> None:
156
- super().__init_subclass__(**kwargs)
157
- if not cls.__dict__.get("__abstract__", False):
158
- warnings.warn(
159
- f"{cls.__name__} 继承了已废弃的 StdAsyncBaseTable, 请改为直接继承 BasicAsyncBaseTable 并自行定义所需字段",
160
- DeprecationWarning,
161
- stacklevel=2,
162
- )
163
-
164
- id: Mapped[int] = mapped_column(sa.Integer, primary_key=True, autoincrement=True)
165
-
166
- create_datetime: Mapped[datetime.datetime] = mapped_column(
167
- sa.DateTime,
168
- nullable=False,
169
- comment="创建时间",
170
- server_default=sa.sql.func.now(),
171
- server_onupdate=sa.FetchedValue(),
172
- )
173
-
174
- create_operator_id: Mapped[int] = mapped_column(
175
- sa.Integer,
176
- nullable=False,
177
- comment="创建人",
178
- default=0,
179
- )
180
-
181
- update_datetime: Mapped[datetime.datetime] = mapped_column(
182
- sa.DateTime,
183
- nullable=True,
184
- comment="修改时间",
185
- server_default=sa.sql.func.now(),
186
- onupdate=sa.sql.func.now(),
187
- server_onupdate=sa.FetchedValue(),
188
- )
189
- update_operator_id: Mapped[int | None] = mapped_column(
190
- sa.Integer,
191
- nullable=True,
192
- comment="修改人",
193
- )
194
-
195
-
196
- class StdReadOnlyBasicAsyncBaseTable(ReadOnlyBasicAsyncBaseTable):
197
- """标准只读异步表基类: 包含 id/时间戳/操作人等标准字段.
198
-
199
- .. deprecated::
200
- 此类预设了特定业务字段, 下游应自行继承 ``ReadOnlyBasicAsyncBaseTable`` 定义所需字段.
201
- """
202
-
203
- __abstract__ = True
204
-
205
- def __init_subclass__(cls, **kwargs: Any) -> None:
206
- super().__init_subclass__(**kwargs)
207
- if not cls.__dict__.get("__abstract__", False):
208
- warnings.warn(
209
- f"{cls.__name__} 继承了已废弃的 StdReadOnlyBasicAsyncBaseTable, 请改为直接继承 ReadOnlyBasicAsyncBaseTable 并自行定义所需字段",
210
- DeprecationWarning,
211
- stacklevel=2,
212
- )
213
-
214
- id: Mapped[int] = mapped_column(sa.Integer, primary_key=True, autoincrement=True)
215
-
216
- create_datetime: Mapped[datetime.datetime] = mapped_column(
217
- sa.DateTime,
218
- nullable=False,
219
- comment="创建时间",
220
- server_default=sa.sql.func.now(),
221
- server_onupdate=sa.FetchedValue(),
222
- )
223
-
224
- create_operator_id: Mapped[int] = mapped_column(
225
- sa.Integer,
226
- nullable=False,
227
- comment="创建人",
228
- default=0,
229
- )
230
-
231
- update_datetime: Mapped[datetime.datetime] = mapped_column(
232
- sa.DateTime,
233
- nullable=True,
234
- comment="修改时间",
235
- server_default=sa.sql.func.now(),
236
- onupdate=sa.sql.func.now(),
237
- server_onupdate=sa.FetchedValue(),
238
- )
239
- update_operator_id: Mapped[int | None] = mapped_column(
240
- sa.Integer,
241
- nullable=True,
242
- comment="修改人",
243
- )
244
-
245
-
246
143
  # ---------------------------------------------------------------------------
247
144
  # Async DAL hierarchy
248
145
  # ---------------------------------------------------------------------------
@@ -905,8 +802,7 @@ __all__ = (
905
802
  "BasicAsyncBaseTable",
906
803
  "ReadOnlyAsyncBaseDAL",
907
804
  "ReadOnlyBasicAsyncBaseTable",
908
- "StdAsyncBaseTable",
909
- "StdReadOnlyBasicAsyncBaseTable",
805
+
910
806
  "async_temp_set_lock_wait_timeout",
911
807
  "async_with_retry",
912
808
  )
@@ -5,10 +5,8 @@
5
5
 
6
6
  from __future__ import annotations
7
7
 
8
- import datetime
9
8
  import logging
10
9
  import random
11
- import warnings
12
10
  from collections.abc import Callable, Iterable
13
11
  from dataclasses import dataclass
14
12
  from typing import Any, ClassVar, Final, Generic, TypeVar, cast
@@ -18,7 +16,7 @@ from lush_dal_protocol.dto import BaseCU as _ProtocolBaseCU
18
16
  from lush_dal_protocol.dto import BaseDTO as _ProtocolBaseDTO
19
17
  from lush_dal_protocol.dto import CUModelT as CUModelT # noqa: PLC0414
20
18
  from lush_dal_protocol.dto import DTOModelT as DTOModelT # noqa: PLC0414
21
- from pydantic import BaseModel, ConfigDict, Field
19
+ from pydantic import BaseModel, ConfigDict
22
20
  from sqlalchemy import event as sa_event
23
21
  from sqlalchemy.exc import OperationalError as SQLAlchemyOperationalError
24
22
  from sqlalchemy.orm import Mapped, ORMExecuteState, mapped_column, with_loader_criteria
@@ -163,47 +161,7 @@ class BaseDTO(_ProtocolBaseDTO[CUModelT]):
163
161
  model_config = ConfigDict(from_attributes=True)
164
162
 
165
163
 
166
- class StdBaseCU(BaseCU[SQLATableT]):
167
- """标准 CU 基类: 包含创建人/修改人等标准字段.
168
164
 
169
- .. deprecated::
170
- 此类预设了特定业务字段, 下游应自行继承 ``BaseCU`` 定义所需字段.
171
- """
172
-
173
- def __init_subclass__(cls, **kwargs: Any) -> None:
174
- super().__init_subclass__(**kwargs)
175
- warnings.warn(
176
- f"{cls.__name__} 继承了已废弃的 StdBaseCU, 请改为直接继承 BaseCU 并自行定义所需字段",
177
- DeprecationWarning,
178
- stacklevel=2,
179
- )
180
-
181
- create_operator_id: int = 0
182
- update_operator_id: int | None = None
183
-
184
-
185
- class StdBaseDTO(BaseDTO[CUModelT]):
186
- """标准 DTO 基类: 包含 id/时间戳/操作人等标准字段.
187
-
188
- .. deprecated::
189
- 此类预设了特定业务字段, 下游应自行继承 ``BaseDTO`` 定义所需字段.
190
- """
191
-
192
- def __init_subclass__(cls, **kwargs: Any) -> None:
193
- super().__init_subclass__(**kwargs)
194
- warnings.warn(
195
- f"{cls.__name__} 继承了已废弃的 StdBaseDTO, 请改为直接继承 BaseDTO 并自行定义所需字段",
196
- DeprecationWarning,
197
- stacklevel=2,
198
- )
199
-
200
- id: int = Field(..., description="ID")
201
- create_datetime: datetime.datetime = Field(..., description="创建时间")
202
- create_operator_id: int = Field(..., description="创建人")
203
- update_datetime: datetime.datetime | None = Field(None, description="修改时间")
204
- update_operator_id: int | None = Field(None, description="修改人")
205
-
206
- model_config = ConfigDict(from_attributes=True)
207
165
 
208
166
 
209
167
  # ---------------------------------------------------------------------------
@@ -256,7 +214,9 @@ class FieldIsDeleteSoftDeleteTableMixin(SoftDeleteTableMixin):
256
214
  等价旧版 ``SoftDeleteTableMixin``,仅需 rename.
257
215
  """
258
216
 
259
- is_delete: Mapped[int] = mapped_column(sa.Integer, default=0, comment="逻辑删除")
217
+ is_delete: Mapped[int] = mapped_column(
218
+ sa.SmallInteger, default=0, comment="逻辑删除"
219
+ ) # NOTE(@l8ng): 可以优化, 比如给个配置配置mysql可以用tinyint, 现在用 smallint 是因为基本主流数据库都支持
260
220
 
261
221
 
262
222
  class ReadOnlyMixin:
@@ -452,8 +412,7 @@ __all__ = (
452
412
  "SQLATableT",
453
413
  "SQLAlchemyOperationalError",
454
414
  "SoftDeleteTableMixin",
455
- "StdBaseCU",
456
- "StdBaseDTO",
415
+
457
416
  "T",
458
417
  "V",
459
418
  "_ensure_strict_fields",