lush-sqlalchemyx 0.3.1__tar.gz → 0.3.2__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.
- {lush_sqlalchemyx-0.3.1 → lush_sqlalchemyx-0.3.2}/PKG-INFO +1 -1
- {lush_sqlalchemyx-0.3.1 → lush_sqlalchemyx-0.3.2}/pyproject.toml +1 -4
- {lush_sqlalchemyx-0.3.1 → lush_sqlalchemyx-0.3.2}/src/lush_sqlalchemyx/base/dal/__init__.py +1 -33
- {lush_sqlalchemyx-0.3.1 → lush_sqlalchemyx-0.3.2}/src/lush_sqlalchemyx/base/dal/_async.py +30 -129
- {lush_sqlalchemyx-0.3.1 → lush_sqlalchemyx-0.3.2}/src/lush_sqlalchemyx/base/dal/_pagination.py +2 -2
- {lush_sqlalchemyx-0.3.1 → lush_sqlalchemyx-0.3.2}/src/lush_sqlalchemyx/base/dal/_repository.py +36 -24
- {lush_sqlalchemyx-0.3.1 → lush_sqlalchemyx-0.3.2}/src/lush_sqlalchemyx/base/dal/_sync.py +30 -129
- lush_sqlalchemyx-0.3.1/src/lush_sqlalchemyx/base/dal/_async_v2.py +0 -184
- lush_sqlalchemyx-0.3.1/src/lush_sqlalchemyx/base/dal/_params.py +0 -32
- lush_sqlalchemyx-0.3.1/src/lush_sqlalchemyx/base/dal/_sync_v2.py +0 -183
- {lush_sqlalchemyx-0.3.1 → lush_sqlalchemyx-0.3.2}/README.md +0 -0
- {lush_sqlalchemyx-0.3.1 → lush_sqlalchemyx-0.3.2}/src/lush_sqlalchemyx/__init__.py +0 -0
- {lush_sqlalchemyx-0.3.1 → lush_sqlalchemyx-0.3.2}/src/lush_sqlalchemyx/_compat.py +0 -0
- {lush_sqlalchemyx-0.3.1 → lush_sqlalchemyx-0.3.2}/src/lush_sqlalchemyx/base/__init__.py +0 -0
- {lush_sqlalchemyx-0.3.1 → lush_sqlalchemyx-0.3.2}/src/lush_sqlalchemyx/base/dal/_common.py +0 -0
- {lush_sqlalchemyx-0.3.1 → lush_sqlalchemyx-0.3.2}/src/lush_sqlalchemyx/integrations/__init__.py +0 -0
- {lush_sqlalchemyx-0.3.1 → lush_sqlalchemyx-0.3.2}/src/lush_sqlalchemyx/integrations/fastapi/__init__.py +0 -0
- {lush_sqlalchemyx-0.3.1 → lush_sqlalchemyx-0.3.2}/src/lush_sqlalchemyx/integrations/fastapi/depends/__init__.py +0 -0
- {lush_sqlalchemyx-0.3.1 → lush_sqlalchemyx-0.3.2}/src/lush_sqlalchemyx/integrations/flask/__init__.py +0 -0
- {lush_sqlalchemyx-0.3.1 → lush_sqlalchemyx-0.3.2}/src/lush_sqlalchemyx/integrations/flask/ext.py +0 -0
- {lush_sqlalchemyx-0.3.1 → lush_sqlalchemyx-0.3.2}/src/lush_sqlalchemyx/mgrs/__init__.py +0 -0
- {lush_sqlalchemyx-0.3.1 → lush_sqlalchemyx-0.3.2}/src/lush_sqlalchemyx/mgrs/mysql/__init__.py +0 -0
- {lush_sqlalchemyx-0.3.1 → lush_sqlalchemyx-0.3.2}/src/lush_sqlalchemyx/mgrs/mysql/manager.py +0 -0
- {lush_sqlalchemyx-0.3.1 → lush_sqlalchemyx-0.3.2}/src/lush_sqlalchemyx/mgrs/mysql/mapper.py +0 -0
- {lush_sqlalchemyx-0.3.1 → lush_sqlalchemyx-0.3.2}/src/lush_sqlalchemyx/mgrs/mysql/sync_manager.py +0 -0
- {lush_sqlalchemyx-0.3.1 → lush_sqlalchemyx-0.3.2}/src/lush_sqlalchemyx/mgrs/mysql/sync_mapper.py +0 -0
- {lush_sqlalchemyx-0.3.1 → lush_sqlalchemyx-0.3.2}/src/lush_sqlalchemyx/py.typed +0 -0
- {lush_sqlalchemyx-0.3.1 → lush_sqlalchemyx-0.3.2}/src/lush_sqlalchemyx/same_impl_just_warn_wrapper.py +0 -0
- {lush_sqlalchemyx-0.3.1 → lush_sqlalchemyx-0.3.2}/src/lush_sqlalchemyx/shortcuts/__init__.py +0 -0
- {lush_sqlalchemyx-0.3.1 → lush_sqlalchemyx-0.3.2}/src/lush_sqlalchemyx/shortcuts/meta.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "lush-sqlalchemyx"
|
|
3
|
-
version = "0.3.
|
|
3
|
+
version = "0.3.2"
|
|
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"
|
|
@@ -149,9 +149,6 @@ ignore = [
|
|
|
149
149
|
]
|
|
150
150
|
|
|
151
151
|
[tool.ruff.lint.per-file-ignores]
|
|
152
|
-
"**/dal/_*_v2.py" = [
|
|
153
|
-
"ARG003", # extra 参数是 ABC 合规所必需但在部分方法中未使用
|
|
154
|
-
]
|
|
155
152
|
"tests/**/*.py" = [
|
|
156
153
|
# === 测试代码质量 ===
|
|
157
154
|
"B011", # assert False应改为raise AssertionError
|
|
@@ -6,21 +6,14 @@
|
|
|
6
6
|
# --- shared (sync/async agnostic) ---
|
|
7
7
|
# --- lush-dal-protocol ABCs (ORM 无关的抽象层) ---
|
|
8
8
|
from lush_dal_protocol import (
|
|
9
|
-
AbstractAsyncAdvancedWriteDAL,
|
|
10
9
|
AbstractAsyncBaseDAL,
|
|
11
|
-
AbstractAsyncBatchFieldDAL,
|
|
12
|
-
AbstractAsyncLockDAL,
|
|
13
|
-
AbstractAsyncRawSQLDAL,
|
|
14
10
|
AbstractAsyncReadDAL,
|
|
15
11
|
AbstractAsyncWriteDAL,
|
|
16
|
-
AbstractSyncAdvancedWriteDAL,
|
|
17
12
|
AbstractSyncBaseDAL,
|
|
18
|
-
AbstractSyncBatchFieldDAL,
|
|
19
|
-
AbstractSyncLockDAL,
|
|
20
|
-
AbstractSyncRawSQLDAL,
|
|
21
13
|
AbstractSyncReadDAL,
|
|
22
14
|
AbstractSyncWriteDAL,
|
|
23
15
|
)
|
|
16
|
+
from lush_dal_protocol.params.pagination import CursorPagination, CursorResult, OffsetPagination, PageResult
|
|
24
17
|
|
|
25
18
|
# --- async (requires sqlalchemy[asyncio]) ---
|
|
26
19
|
from ._async import (
|
|
@@ -40,9 +33,6 @@ from ._async import (
|
|
|
40
33
|
async_temp_set_lock_wait_timeout,
|
|
41
34
|
async_with_retry,
|
|
42
35
|
)
|
|
43
|
-
|
|
44
|
-
# --- V2 (ABC-compliant, options-based) ---
|
|
45
|
-
from ._async_v2 import AsyncBaseDALV2, AsyncReadDALV2, AsyncWriteDALV2
|
|
46
36
|
from ._common import (
|
|
47
37
|
DEFAULT_RETRY_CONFIG,
|
|
48
38
|
OPTIMISTIC_LOCK_ERROR_MSG_TRAIT,
|
|
@@ -68,10 +58,6 @@ from ._common import (
|
|
|
68
58
|
from ._common import __prevent_readonly_write as __prevent_readonly_write # pyright: ignore[reportPrivateUsage]
|
|
69
59
|
from ._common import __receive_before_flush as __receive_before_flush # pyright: ignore[reportPrivateUsage]
|
|
70
60
|
from ._pagination import (
|
|
71
|
-
CursorPagination,
|
|
72
|
-
CursorResult,
|
|
73
|
-
OffsetPagination,
|
|
74
|
-
PageResult,
|
|
75
61
|
build_cursor_stmt,
|
|
76
62
|
build_offset_stmt,
|
|
77
63
|
decode_cursor,
|
|
@@ -79,7 +65,6 @@ from ._pagination import (
|
|
|
79
65
|
make_cursor_result,
|
|
80
66
|
make_page_result,
|
|
81
67
|
)
|
|
82
|
-
from ._params import SQLAExtra
|
|
83
68
|
from ._repository import AsyncSQLAlchemyRepository, SyncSQLAlchemyRepository
|
|
84
69
|
|
|
85
70
|
# --- sync ---
|
|
@@ -100,7 +85,6 @@ from ._sync import (
|
|
|
100
85
|
sync_temp_set_lock_wait_timeout,
|
|
101
86
|
sync_with_retry,
|
|
102
87
|
)
|
|
103
|
-
from ._sync_v2 import SyncBaseDALV2, SyncReadDALV2, SyncWriteDALV2
|
|
104
88
|
|
|
105
89
|
__all__ = (
|
|
106
90
|
# common
|
|
@@ -165,30 +149,14 @@ __all__ = (
|
|
|
165
149
|
"encode_cursor",
|
|
166
150
|
"make_cursor_result",
|
|
167
151
|
"make_page_result",
|
|
168
|
-
# V2
|
|
169
|
-
"AsyncBaseDALV2",
|
|
170
|
-
"AsyncReadDALV2",
|
|
171
|
-
"AsyncWriteDALV2",
|
|
172
|
-
"SQLAExtra",
|
|
173
|
-
"SyncBaseDALV2",
|
|
174
|
-
"SyncReadDALV2",
|
|
175
|
-
"SyncWriteDALV2",
|
|
176
152
|
# repository
|
|
177
153
|
"AsyncSQLAlchemyRepository",
|
|
178
154
|
"SyncSQLAlchemyRepository",
|
|
179
155
|
# lush-dal-protocol ABCs
|
|
180
|
-
"AbstractAsyncAdvancedWriteDAL",
|
|
181
156
|
"AbstractAsyncBaseDAL",
|
|
182
|
-
"AbstractAsyncBatchFieldDAL",
|
|
183
|
-
"AbstractAsyncLockDAL",
|
|
184
|
-
"AbstractAsyncRawSQLDAL",
|
|
185
157
|
"AbstractAsyncReadDAL",
|
|
186
158
|
"AbstractAsyncWriteDAL",
|
|
187
|
-
"AbstractSyncAdvancedWriteDAL",
|
|
188
159
|
"AbstractSyncBaseDAL",
|
|
189
|
-
"AbstractSyncBatchFieldDAL",
|
|
190
|
-
"AbstractSyncLockDAL",
|
|
191
|
-
"AbstractSyncRawSQLDAL",
|
|
192
160
|
"AbstractSyncReadDAL",
|
|
193
161
|
"AbstractSyncWriteDAL",
|
|
194
162
|
)
|
|
@@ -15,6 +15,7 @@ from contextlib import asynccontextmanager, suppress
|
|
|
15
15
|
from typing import TYPE_CHECKING, Any, ClassVar, Generic, Literal, ParamSpec, TypeVar, cast
|
|
16
16
|
|
|
17
17
|
import sqlalchemy as sa
|
|
18
|
+
from lush_dal_protocol.abc import AbstractAsyncReadDAL, AbstractAsyncWriteDAL
|
|
18
19
|
from pydantic import BaseModel
|
|
19
20
|
from sqlalchemy import ColumnExpressionArgument
|
|
20
21
|
from sqlalchemy.ext.asyncio import AsyncAttrs, AsyncSession
|
|
@@ -316,7 +317,11 @@ class AsyncRawReadDAL:
|
|
|
316
317
|
last_id = getattr(batch[-1], id_attr.key)
|
|
317
318
|
|
|
318
319
|
|
|
319
|
-
class AsyncReadDAL(
|
|
320
|
+
class AsyncReadDAL(
|
|
321
|
+
AsyncRawReadDAL,
|
|
322
|
+
AbstractAsyncReadDAL[AsyncSession, AsyncSQLATableT, DTOModelT, int],
|
|
323
|
+
Generic[AsyncSQLATableT, DTOModelT],
|
|
324
|
+
):
|
|
320
325
|
"""抽象只读数据访问层基类."""
|
|
321
326
|
|
|
322
327
|
_Table: ClassVar[type[AsyncSQLATableT]] # pyright: ignore[reportGeneralTypeIssues]
|
|
@@ -426,15 +431,15 @@ class AsyncReadDAL(AsyncRawReadDAL, Generic[AsyncSQLATableT, DTOModelT]):
|
|
|
426
431
|
return entity is not None
|
|
427
432
|
|
|
428
433
|
@classmethod
|
|
429
|
-
async def
|
|
434
|
+
async def get_by_id_for_update(
|
|
430
435
|
cls,
|
|
431
436
|
session: AsyncSession,
|
|
432
437
|
entity_id: int,
|
|
433
438
|
*,
|
|
434
|
-
|
|
439
|
+
lock_wait_timeout: int | None = None,
|
|
435
440
|
) -> AsyncSQLATableT | None:
|
|
436
441
|
try:
|
|
437
|
-
async with async_temp_set_lock_wait_timeout(session,
|
|
442
|
+
async with async_temp_set_lock_wait_timeout(session, lock_wait_timeout):
|
|
438
443
|
stmt = (
|
|
439
444
|
sa.select(cls._Table)
|
|
440
445
|
.where(cls._Table.id == entity_id) # pyright: ignore[reportUnknownMemberType, reportAttributeAccessIssue, reportUnknownArgumentType]
|
|
@@ -449,29 +454,19 @@ class AsyncReadDAL(AsyncRawReadDAL, Generic[AsyncSQLATableT, DTOModelT]):
|
|
|
449
454
|
raise
|
|
450
455
|
|
|
451
456
|
@classmethod
|
|
452
|
-
async def
|
|
453
|
-
cls,
|
|
454
|
-
session: AsyncSession,
|
|
455
|
-
entity_id: int,
|
|
456
|
-
*,
|
|
457
|
-
lock_wait_timeout: int | None = None,
|
|
458
|
-
) -> AsyncSQLATableT | None:
|
|
459
|
-
return await cls._get_by_id_for_update_core(session, entity_id, timeout=lock_wait_timeout)
|
|
460
|
-
|
|
461
|
-
@classmethod
|
|
462
|
-
async def _batch_get_for_update_core(
|
|
457
|
+
async def batch_get_for_update(
|
|
463
458
|
cls,
|
|
464
459
|
session: AsyncSession,
|
|
465
460
|
entity_ids: Iterable[int],
|
|
466
461
|
*,
|
|
467
|
-
|
|
462
|
+
lock_wait_timeout: int | None = None,
|
|
468
463
|
) -> list[AsyncSQLATableT]:
|
|
469
464
|
filtered_ids = filtered_in_sql_values(entity_ids, int)
|
|
470
465
|
if not filtered_ids:
|
|
471
466
|
return []
|
|
472
467
|
|
|
473
468
|
try:
|
|
474
|
-
async with async_temp_set_lock_wait_timeout(session,
|
|
469
|
+
async with async_temp_set_lock_wait_timeout(session, lock_wait_timeout):
|
|
475
470
|
stmt = (
|
|
476
471
|
sa.select(cls._Table)
|
|
477
472
|
.where(cls._Table.id.in_(filtered_ids)) # pyright: ignore[reportUnknownMemberType, reportAttributeAccessIssue, reportUnknownArgumentType]
|
|
@@ -486,25 +481,15 @@ class AsyncReadDAL(AsyncRawReadDAL, Generic[AsyncSQLATableT, DTOModelT]):
|
|
|
486
481
|
raise
|
|
487
482
|
|
|
488
483
|
@classmethod
|
|
489
|
-
async def
|
|
490
|
-
cls,
|
|
491
|
-
session: AsyncSession,
|
|
492
|
-
entity_ids: Iterable[int],
|
|
493
|
-
*,
|
|
494
|
-
lock_wait_timeout: int | None = None,
|
|
495
|
-
) -> list[AsyncSQLATableT]:
|
|
496
|
-
return await cls._batch_get_for_update_core(session, entity_ids, timeout=lock_wait_timeout)
|
|
497
|
-
|
|
498
|
-
@classmethod
|
|
499
|
-
async def _get_one_for_update_core(
|
|
484
|
+
async def get_one_for_update(
|
|
500
485
|
cls,
|
|
501
486
|
session: AsyncSession,
|
|
502
487
|
*,
|
|
503
488
|
where_clauses: list[ColumnExpressionArgument[bool]],
|
|
504
|
-
|
|
489
|
+
lock_wait_timeout: int | None = None,
|
|
505
490
|
) -> AsyncSQLATableT | None:
|
|
506
491
|
try:
|
|
507
|
-
async with async_temp_set_lock_wait_timeout(session,
|
|
492
|
+
async with async_temp_set_lock_wait_timeout(session, lock_wait_timeout):
|
|
508
493
|
stmt = sa.select(cls._Table).with_for_update()
|
|
509
494
|
|
|
510
495
|
for clause in where_clauses:
|
|
@@ -518,16 +503,6 @@ class AsyncReadDAL(AsyncRawReadDAL, Generic[AsyncSQLATableT, DTOModelT]):
|
|
|
518
503
|
raise DBRetryableError(f"{PESSIMISTIC_LOCK_ERROR_MSG_TRAIT}-条件锁等待超时: {error_msg}") from e
|
|
519
504
|
raise
|
|
520
505
|
|
|
521
|
-
@classmethod
|
|
522
|
-
async def get_one_for_update(
|
|
523
|
-
cls,
|
|
524
|
-
session: AsyncSession,
|
|
525
|
-
*,
|
|
526
|
-
where_clauses: list[ColumnExpressionArgument[bool]],
|
|
527
|
-
lock_wait_timeout: int | None = None,
|
|
528
|
-
) -> AsyncSQLATableT | None:
|
|
529
|
-
return await cls._get_one_for_update_core(session, where_clauses=where_clauses, timeout=lock_wait_timeout)
|
|
530
|
-
|
|
531
506
|
@classmethod
|
|
532
507
|
async def iter_record_dtos(
|
|
533
508
|
cls,
|
|
@@ -564,7 +539,12 @@ class AsyncRawDAL:
|
|
|
564
539
|
return await session.execute(stmt, params)
|
|
565
540
|
|
|
566
541
|
|
|
567
|
-
class AsyncWriteDAL(
|
|
542
|
+
class AsyncWriteDAL(
|
|
543
|
+
AsyncRawDAL,
|
|
544
|
+
AsyncRawReadDAL,
|
|
545
|
+
AbstractAsyncWriteDAL[AsyncSession, AsyncSQLATableT, DTOModelT, CUModelT, int],
|
|
546
|
+
Generic[AsyncSQLATableT, DTOModelT, CUModelT],
|
|
547
|
+
):
|
|
568
548
|
"""写入数据访问层基类."""
|
|
569
549
|
|
|
570
550
|
_Table: ClassVar[type[AsyncSQLATableT]] # pyright: ignore[reportGeneralTypeIssues]
|
|
@@ -644,7 +624,7 @@ class AsyncWriteDAL(AsyncRawDAL, AsyncRawReadDAL, Generic[AsyncSQLATableT, DTOMo
|
|
|
644
624
|
_ensure_strict_fields(provided_keys=provided_keys, allowed_names=allowed_names, strict=strict)
|
|
645
625
|
|
|
646
626
|
@classmethod
|
|
647
|
-
async def
|
|
627
|
+
async def update_full_by_id(
|
|
648
628
|
cls,
|
|
649
629
|
session: AsyncSession,
|
|
650
630
|
entity_id: int,
|
|
@@ -678,19 +658,7 @@ class AsyncWriteDAL(AsyncRawDAL, AsyncRawReadDAL, Generic[AsyncSQLATableT, DTOMo
|
|
|
678
658
|
return entity
|
|
679
659
|
|
|
680
660
|
@classmethod
|
|
681
|
-
async def
|
|
682
|
-
cls,
|
|
683
|
-
session: AsyncSession,
|
|
684
|
-
entity_id: int,
|
|
685
|
-
cu: CUModelT,
|
|
686
|
-
*,
|
|
687
|
-
need_refresh: bool = False,
|
|
688
|
-
strict_missing: bool = True,
|
|
689
|
-
) -> AsyncSQLATableT | None:
|
|
690
|
-
return await cls._update_full_by_id_core(session, entity_id, cu, need_refresh=need_refresh, strict_missing=strict_missing)
|
|
691
|
-
|
|
692
|
-
@classmethod
|
|
693
|
-
async def _update_partial_by_id_core(
|
|
661
|
+
async def update_partial_by_id(
|
|
694
662
|
cls,
|
|
695
663
|
session: AsyncSession,
|
|
696
664
|
entity_id: int,
|
|
@@ -757,30 +725,6 @@ class AsyncWriteDAL(AsyncRawDAL, AsyncRawReadDAL, Generic[AsyncSQLATableT, DTOMo
|
|
|
757
725
|
await session.refresh(entity)
|
|
758
726
|
return entity
|
|
759
727
|
|
|
760
|
-
@classmethod
|
|
761
|
-
async def update_partial_by_id(
|
|
762
|
-
cls,
|
|
763
|
-
session: AsyncSession,
|
|
764
|
-
entity_id: int,
|
|
765
|
-
cu: CUModelT,
|
|
766
|
-
*,
|
|
767
|
-
need_refresh: bool = False,
|
|
768
|
-
fields: set[InstrumentedAttribute[Any]] | set[sa.Column[Any]] | None = None,
|
|
769
|
-
none_policy: Literal["ignore", "allow", "forbid"] = "ignore",
|
|
770
|
-
none_policy_overrides: dict[InstrumentedAttribute[Any] | sa.Column[Any], Literal["ignore", "allow", "forbid"]] | None = None,
|
|
771
|
-
strict: bool = False,
|
|
772
|
-
) -> AsyncSQLATableT | None:
|
|
773
|
-
return await cls._update_partial_by_id_core(
|
|
774
|
-
session,
|
|
775
|
-
entity_id,
|
|
776
|
-
cu,
|
|
777
|
-
need_refresh=need_refresh,
|
|
778
|
-
fields=fields,
|
|
779
|
-
none_policy=none_policy,
|
|
780
|
-
none_policy_overrides=none_policy_overrides,
|
|
781
|
-
strict=strict,
|
|
782
|
-
)
|
|
783
|
-
|
|
784
728
|
@classmethod
|
|
785
729
|
async def delete_by_id(
|
|
786
730
|
cls,
|
|
@@ -816,11 +760,11 @@ class AsyncWriteDAL(AsyncRawDAL, AsyncRawReadDAL, Generic[AsyncSQLATableT, DTOMo
|
|
|
816
760
|
yield entity
|
|
817
761
|
|
|
818
762
|
@classmethod
|
|
819
|
-
async def
|
|
763
|
+
async def batch_update_by_conditions(
|
|
820
764
|
cls,
|
|
821
765
|
session: AsyncSession,
|
|
822
766
|
*,
|
|
823
|
-
|
|
767
|
+
whereclause: list[ColumnExpressionArgument[bool]],
|
|
824
768
|
update_data: dict[InstrumentedAttribute[Any], Any] | dict[sa.Column[Any], Any],
|
|
825
769
|
updater_id: int | None = None,
|
|
826
770
|
) -> int:
|
|
@@ -845,24 +789,13 @@ class AsyncWriteDAL(AsyncRawDAL, AsyncRawReadDAL, Generic[AsyncSQLATableT, DTOMo
|
|
|
845
789
|
if hasattr(cls._Table, "update_operator_id") and updater_id is not None:
|
|
846
790
|
final_update_data["update_operator_id"] = updater_id
|
|
847
791
|
|
|
848
|
-
stmt = sa.update(cls._Table).where(*
|
|
792
|
+
stmt = sa.update(cls._Table).where(*whereclause).values(**final_update_data)
|
|
849
793
|
|
|
850
794
|
result = await session.execute(stmt)
|
|
851
795
|
await session.flush()
|
|
852
796
|
|
|
853
797
|
return result.rowcount # pyright: ignore[reportAttributeAccessIssue, reportUnknownMemberType, reportUnknownVariableType]
|
|
854
798
|
|
|
855
|
-
@classmethod
|
|
856
|
-
async def batch_update_by_conditions(
|
|
857
|
-
cls,
|
|
858
|
-
session: AsyncSession,
|
|
859
|
-
*,
|
|
860
|
-
whereclause: list[ColumnExpressionArgument[bool]],
|
|
861
|
-
update_data: dict[InstrumentedAttribute[Any], Any] | dict[sa.Column[Any], Any],
|
|
862
|
-
updater_id: int | None = None,
|
|
863
|
-
) -> int:
|
|
864
|
-
return await cls._batch_update_by_conditions_core(session, conditions=whereclause, update_data=update_data, updater_id=updater_id)
|
|
865
|
-
|
|
866
799
|
@classmethod
|
|
867
800
|
async def batch_update_by_ids(
|
|
868
801
|
cls,
|
|
@@ -876,15 +809,15 @@ class AsyncWriteDAL(AsyncRawDAL, AsyncRawReadDAL, Generic[AsyncSQLATableT, DTOMo
|
|
|
876
809
|
if not filtered_ids:
|
|
877
810
|
return 0
|
|
878
811
|
_id_column = cls._Table.id # pyright: ignore[reportAttributeAccessIssue,reportUnknownVariableType, reportUnknownMemberType]
|
|
879
|
-
return await cls.
|
|
812
|
+
return await cls.batch_update_by_conditions(
|
|
880
813
|
session,
|
|
881
|
-
|
|
814
|
+
whereclause=[_id_column.in_(filtered_ids)], # pyright: ignore[reportUnknownMemberType]
|
|
882
815
|
update_data=update_data,
|
|
883
816
|
updater_id=updater_id,
|
|
884
817
|
)
|
|
885
818
|
|
|
886
819
|
@classmethod
|
|
887
|
-
async def
|
|
820
|
+
async def update_only_set_with_optimistic_lock(
|
|
888
821
|
cls,
|
|
889
822
|
session: AsyncSession,
|
|
890
823
|
entity_id: int,
|
|
@@ -935,45 +868,13 @@ class AsyncWriteDAL(AsyncRawDAL, AsyncRawReadDAL, Generic[AsyncSQLATableT, DTOMo
|
|
|
935
868
|
|
|
936
869
|
raise DBRetryableError(f"{OPTIMISTIC_LOCK_ERROR_MSG_TRAIT}-版本号不匹配({entity_id=}, {expected_version=})")
|
|
937
870
|
|
|
938
|
-
@classmethod
|
|
939
|
-
async def update_only_set_with_optimistic_lock(
|
|
940
|
-
cls,
|
|
941
|
-
session: AsyncSession,
|
|
942
|
-
entity_id: int,
|
|
943
|
-
cu: CUModelT,
|
|
944
|
-
*,
|
|
945
|
-
expected_version: int,
|
|
946
|
-
need_refresh: bool = False,
|
|
947
|
-
version_field: str = "version",
|
|
948
|
-
) -> AsyncSQLATableT | None:
|
|
949
|
-
return await cls._update_only_set_with_optimistic_lock_core(
|
|
950
|
-
session,
|
|
951
|
-
entity_id,
|
|
952
|
-
cu,
|
|
953
|
-
expected_version=expected_version,
|
|
954
|
-
need_refresh=need_refresh,
|
|
955
|
-
version_field=version_field,
|
|
956
|
-
)
|
|
957
|
-
|
|
958
871
|
|
|
959
872
|
class AsyncXDALOp(AsyncRawReadDAL, AsyncRawDAL):
|
|
960
873
|
"""扩展数据访问操作类."""
|
|
961
874
|
|
|
962
875
|
|
|
963
876
|
class AsyncBaseDAL(AsyncReadDAL[AsyncSQLATableT, DTOModelT], AsyncWriteDAL[AsyncSQLATableT, DTOModelT, CUModelT]):
|
|
964
|
-
"""基础数据访问层.
|
|
965
|
-
|
|
966
|
-
.. deprecated:: 0.3.0
|
|
967
|
-
V1 DAL 将在 1.0 移除, 请迁移至 ``AsyncBaseDALV2``.
|
|
968
|
-
"""
|
|
969
|
-
|
|
970
|
-
def __init_subclass__(cls, **kwargs: Any) -> None:
|
|
971
|
-
super().__init_subclass__(**kwargs)
|
|
972
|
-
warnings.warn(
|
|
973
|
-
f"{cls.__name__} 继承了 V1 AsyncBaseDAL, 建议迁移至 AsyncBaseDALV2",
|
|
974
|
-
DeprecationWarning,
|
|
975
|
-
stacklevel=2,
|
|
976
|
-
)
|
|
877
|
+
"""基础数据访问层."""
|
|
977
878
|
|
|
978
879
|
|
|
979
880
|
class ReadOnlyAsyncBaseDAL(AsyncReadDAL[AsyncSQLATableT, ReadOnlyDTOModelT]):
|
{lush_sqlalchemyx-0.3.1 → lush_sqlalchemyx-0.3.2}/src/lush_sqlalchemyx/base/dal/_pagination.py
RENAMED
|
@@ -42,7 +42,7 @@ def build_offset_stmt(
|
|
|
42
42
|
if order_by is not None:
|
|
43
43
|
stmt = stmt.order_by(order_by)
|
|
44
44
|
else:
|
|
45
|
-
stmt = stmt.order_by(table.id)
|
|
45
|
+
stmt = stmt.order_by(table.id)
|
|
46
46
|
return stmt.offset(p.skip).limit(p.limit)
|
|
47
47
|
|
|
48
48
|
|
|
@@ -55,7 +55,7 @@ def build_cursor_stmt(
|
|
|
55
55
|
使用 id > cursor_value 的 keyset 分页方式.
|
|
56
56
|
"""
|
|
57
57
|
p = pagination or CursorPagination()
|
|
58
|
-
id_col = table.id
|
|
58
|
+
id_col = table.id
|
|
59
59
|
stmt = sa.select(table).order_by(id_col)
|
|
60
60
|
|
|
61
61
|
if p.cursor is not None:
|
{lush_sqlalchemyx-0.3.1 → lush_sqlalchemyx-0.3.2}/src/lush_sqlalchemyx/base/dal/_repository.py
RENAMED
|
@@ -1,15 +1,21 @@
|
|
|
1
1
|
"""SQLAlchemy 具体 Repository 实现.
|
|
2
2
|
|
|
3
|
-
提供高层声明式 CRUD 接口, 内部委托给 DAL
|
|
3
|
+
提供高层声明式 CRUD 接口, 内部委托给 DAL.
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
6
|
from __future__ import annotations
|
|
7
7
|
|
|
8
|
-
from collections.abc import Callable, Iterable
|
|
8
|
+
from collections.abc import Callable, Generator, Iterable
|
|
9
9
|
from contextlib import contextmanager
|
|
10
|
-
from typing import Any, ClassVar, Generic, TypeVar
|
|
10
|
+
from typing import TYPE_CHECKING, Any, ClassVar, Generic, TypeVar, cast
|
|
11
11
|
|
|
12
12
|
import sqlalchemy as sa
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from contextlib import AbstractAsyncContextManager
|
|
16
|
+
|
|
17
|
+
from sqlalchemy.ext.asyncio import AsyncSession
|
|
18
|
+
from sqlalchemy.orm import Session
|
|
13
19
|
from lush_dal_protocol.params.pagination import CursorPagination, CursorResult, OffsetPagination, PageResult
|
|
14
20
|
from lush_dal_protocol.repository import AbstractAsyncRepository, AbstractSyncRepository
|
|
15
21
|
|
|
@@ -31,15 +37,21 @@ class SyncSQLAlchemyRepository(
|
|
|
31
37
|
_session_factory: 返回 Session 的工厂函数
|
|
32
38
|
"""
|
|
33
39
|
|
|
34
|
-
_Table: ClassVar[type]
|
|
35
|
-
_DTO: ClassVar[type]
|
|
36
|
-
_session_factory: ClassVar[Callable[[],
|
|
40
|
+
_Table: ClassVar[type[TableT]] # pyright: ignore[reportGeneralTypeIssues] — ClassVar + TypeVar 是 pyright 已知限制
|
|
41
|
+
_DTO: ClassVar[type[DTOModelT]] # pyright: ignore[reportGeneralTypeIssues]
|
|
42
|
+
_session_factory: ClassVar[Callable[[], Session]]
|
|
37
43
|
|
|
38
44
|
@classmethod
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
session = cls._session_factory() # pyright: ignore[reportAttributeAccessIssue]
|
|
45
|
+
def _make_session(cls) -> Session:
|
|
46
|
+
"""创建并配置一个新的 Session."""
|
|
47
|
+
session = cls._session_factory() # pyright: ignore[reportAttributeAccessIssue] — pyright generic classmethod limitation
|
|
42
48
|
session.expire_on_commit = False
|
|
49
|
+
return session
|
|
50
|
+
|
|
51
|
+
@classmethod
|
|
52
|
+
@contextmanager
|
|
53
|
+
def _get_session(cls) -> Generator[Session, None, None]:
|
|
54
|
+
session = cls._make_session()
|
|
43
55
|
try:
|
|
44
56
|
yield session
|
|
45
57
|
session.commit()
|
|
@@ -150,10 +162,10 @@ class SyncSQLAlchemyRepository(
|
|
|
150
162
|
if not pk_list:
|
|
151
163
|
return 0
|
|
152
164
|
with cls._get_session() as session:
|
|
153
|
-
id_col = cls._Table.id # pyright: ignore[reportAttributeAccessIssue]
|
|
165
|
+
id_col: sa.Column[int] = cast("sa.Column[int]", cls._Table.id) # pyright: ignore[reportAttributeAccessIssue] — SA 列描述符对 pyright 不可见 # SA 列描述符对 pyright 不可见
|
|
154
166
|
stmt = sa.update(cls._Table).where(id_col.in_(pk_list)).values(**data)
|
|
155
|
-
result = session.execute(stmt)
|
|
156
|
-
return result.rowcount
|
|
167
|
+
result = cast("sa.CursorResult[Any]", session.execute(stmt)) # SA stubs 返回 Result, 但运行时为 CursorResult
|
|
168
|
+
return result.rowcount
|
|
157
169
|
|
|
158
170
|
@classmethod
|
|
159
171
|
def bulk_delete(cls, pks: Iterable[int]) -> int:
|
|
@@ -162,10 +174,10 @@ class SyncSQLAlchemyRepository(
|
|
|
162
174
|
if not pk_list:
|
|
163
175
|
return 0
|
|
164
176
|
with cls._get_session() as session:
|
|
165
|
-
id_col = cls._Table.id # pyright: ignore[reportAttributeAccessIssue]
|
|
177
|
+
id_col: sa.Column[int] = cast("sa.Column[int]", cls._Table.id) # pyright: ignore[reportAttributeAccessIssue] — SA 列描述符对 pyright 不可见
|
|
166
178
|
stmt = sa.delete(cls._Table).where(id_col.in_(pk_list))
|
|
167
|
-
result = session.execute(stmt)
|
|
168
|
-
return result.rowcount
|
|
179
|
+
result = cast("sa.CursorResult[Any]", session.execute(stmt))
|
|
180
|
+
return result.rowcount
|
|
169
181
|
|
|
170
182
|
|
|
171
183
|
class AsyncSQLAlchemyRepository(
|
|
@@ -180,9 +192,9 @@ class AsyncSQLAlchemyRepository(
|
|
|
180
192
|
_session_factory: 返回 async context manager 的工厂
|
|
181
193
|
"""
|
|
182
194
|
|
|
183
|
-
_Table: ClassVar[type]
|
|
184
|
-
_DTO: ClassVar[type]
|
|
185
|
-
_session_factory: ClassVar[Callable[...,
|
|
195
|
+
_Table: ClassVar[type[TableT]] # pyright: ignore[reportGeneralTypeIssues]
|
|
196
|
+
_DTO: ClassVar[type[DTOModelT]] # pyright: ignore[reportGeneralTypeIssues]
|
|
197
|
+
_session_factory: ClassVar[Callable[..., AbstractAsyncContextManager[AsyncSession]]]
|
|
186
198
|
|
|
187
199
|
@classmethod
|
|
188
200
|
async def get(cls, pk: int) -> TableT | None:
|
|
@@ -291,11 +303,11 @@ class AsyncSQLAlchemyRepository(
|
|
|
291
303
|
if not pk_list:
|
|
292
304
|
return 0
|
|
293
305
|
async with cls._session_factory() as session:
|
|
294
|
-
id_col = cls._Table.id # pyright: ignore[reportAttributeAccessIssue]
|
|
306
|
+
id_col: sa.Column[int] = cast("sa.Column[int]", cls._Table.id) # pyright: ignore[reportAttributeAccessIssue] — SA 列描述符对 pyright 不可见
|
|
295
307
|
stmt = sa.update(cls._Table).where(id_col.in_(pk_list)).values(**data)
|
|
296
|
-
result = await session.execute(stmt)
|
|
308
|
+
result = cast("sa.CursorResult[Any]", await session.execute(stmt))
|
|
297
309
|
await session.commit()
|
|
298
|
-
return result.rowcount
|
|
310
|
+
return result.rowcount
|
|
299
311
|
|
|
300
312
|
@classmethod
|
|
301
313
|
async def bulk_delete(cls, pks: Iterable[int]) -> int:
|
|
@@ -304,8 +316,8 @@ class AsyncSQLAlchemyRepository(
|
|
|
304
316
|
if not pk_list:
|
|
305
317
|
return 0
|
|
306
318
|
async with cls._session_factory() as session:
|
|
307
|
-
id_col = cls._Table.id # pyright: ignore[reportAttributeAccessIssue]
|
|
319
|
+
id_col: sa.Column[int] = cast("sa.Column[int]", cls._Table.id) # pyright: ignore[reportAttributeAccessIssue] — SA 列描述符对 pyright 不可见
|
|
308
320
|
stmt = sa.delete(cls._Table).where(id_col.in_(pk_list))
|
|
309
|
-
result = await session.execute(stmt)
|
|
321
|
+
result = cast("sa.CursorResult[Any]", await session.execute(stmt))
|
|
310
322
|
await session.commit()
|
|
311
|
-
return result.rowcount
|
|
323
|
+
return result.rowcount
|
|
@@ -15,6 +15,7 @@ from contextlib import contextmanager, suppress
|
|
|
15
15
|
from typing import TYPE_CHECKING, Any, ClassVar, Generic, Literal, ParamSpec, TypeVar, cast
|
|
16
16
|
|
|
17
17
|
import sqlalchemy as sa
|
|
18
|
+
from lush_dal_protocol.abc import AbstractSyncReadDAL, AbstractSyncWriteDAL
|
|
18
19
|
from pydantic import BaseModel
|
|
19
20
|
from sqlalchemy import ColumnExpressionArgument
|
|
20
21
|
from sqlalchemy.orm import DeclarativeBase, InstrumentedAttribute, Mapped, Session, mapped_column
|
|
@@ -309,7 +310,11 @@ class SyncRawReadDAL:
|
|
|
309
310
|
last_id = getattr(batch[-1], id_attr.key)
|
|
310
311
|
|
|
311
312
|
|
|
312
|
-
class SyncReadDAL(
|
|
313
|
+
class SyncReadDAL(
|
|
314
|
+
SyncRawReadDAL,
|
|
315
|
+
AbstractSyncReadDAL[Session, SyncSQLATableT, DTOModelT, int],
|
|
316
|
+
Generic[SyncSQLATableT, DTOModelT],
|
|
317
|
+
):
|
|
313
318
|
"""抽象只读数据访问层基类 (同步版)."""
|
|
314
319
|
|
|
315
320
|
_Table: ClassVar[type[SyncSQLATableT]] # pyright: ignore[reportGeneralTypeIssues]
|
|
@@ -417,15 +422,15 @@ class SyncReadDAL(SyncRawReadDAL, Generic[SyncSQLATableT, DTOModelT]):
|
|
|
417
422
|
return entity is not None
|
|
418
423
|
|
|
419
424
|
@classmethod
|
|
420
|
-
def
|
|
425
|
+
def get_by_id_for_update(
|
|
421
426
|
cls,
|
|
422
427
|
session: Session,
|
|
423
428
|
entity_id: int,
|
|
424
429
|
*,
|
|
425
|
-
|
|
430
|
+
lock_wait_timeout: int | None = None,
|
|
426
431
|
) -> SyncSQLATableT | None:
|
|
427
432
|
try:
|
|
428
|
-
with sync_temp_set_lock_wait_timeout(session,
|
|
433
|
+
with sync_temp_set_lock_wait_timeout(session, lock_wait_timeout):
|
|
429
434
|
stmt = (
|
|
430
435
|
sa.select(cls._Table)
|
|
431
436
|
.where(cls._Table.id == entity_id) # pyright: ignore[reportUnknownMemberType, reportAttributeAccessIssue, reportUnknownArgumentType]
|
|
@@ -440,28 +445,18 @@ class SyncReadDAL(SyncRawReadDAL, Generic[SyncSQLATableT, DTOModelT]):
|
|
|
440
445
|
raise
|
|
441
446
|
|
|
442
447
|
@classmethod
|
|
443
|
-
def
|
|
444
|
-
cls,
|
|
445
|
-
session: Session,
|
|
446
|
-
entity_id: int,
|
|
447
|
-
*,
|
|
448
|
-
lock_wait_timeout: int | None = None,
|
|
449
|
-
) -> SyncSQLATableT | None:
|
|
450
|
-
return cls._get_by_id_for_update_core(session, entity_id, timeout=lock_wait_timeout)
|
|
451
|
-
|
|
452
|
-
@classmethod
|
|
453
|
-
def _batch_get_for_update_core(
|
|
448
|
+
def batch_get_for_update(
|
|
454
449
|
cls,
|
|
455
450
|
session: Session,
|
|
456
451
|
entity_ids: Iterable[int],
|
|
457
452
|
*,
|
|
458
|
-
|
|
453
|
+
lock_wait_timeout: int | None = None,
|
|
459
454
|
) -> list[SyncSQLATableT]:
|
|
460
455
|
filtered_ids = filtered_in_sql_values(entity_ids, int)
|
|
461
456
|
if not filtered_ids:
|
|
462
457
|
return []
|
|
463
458
|
try:
|
|
464
|
-
with sync_temp_set_lock_wait_timeout(session,
|
|
459
|
+
with sync_temp_set_lock_wait_timeout(session, lock_wait_timeout):
|
|
465
460
|
stmt = (
|
|
466
461
|
sa.select(cls._Table)
|
|
467
462
|
.where(cls._Table.id.in_(filtered_ids)) # pyright: ignore[reportUnknownMemberType, reportAttributeAccessIssue, reportUnknownArgumentType]
|
|
@@ -476,25 +471,15 @@ class SyncReadDAL(SyncRawReadDAL, Generic[SyncSQLATableT, DTOModelT]):
|
|
|
476
471
|
raise
|
|
477
472
|
|
|
478
473
|
@classmethod
|
|
479
|
-
def
|
|
480
|
-
cls,
|
|
481
|
-
session: Session,
|
|
482
|
-
entity_ids: Iterable[int],
|
|
483
|
-
*,
|
|
484
|
-
lock_wait_timeout: int | None = None,
|
|
485
|
-
) -> list[SyncSQLATableT]:
|
|
486
|
-
return cls._batch_get_for_update_core(session, entity_ids, timeout=lock_wait_timeout)
|
|
487
|
-
|
|
488
|
-
@classmethod
|
|
489
|
-
def _get_one_for_update_core(
|
|
474
|
+
def get_one_for_update(
|
|
490
475
|
cls,
|
|
491
476
|
session: Session,
|
|
492
477
|
*,
|
|
493
478
|
where_clauses: list[ColumnExpressionArgument[bool]],
|
|
494
|
-
|
|
479
|
+
lock_wait_timeout: int | None = None,
|
|
495
480
|
) -> SyncSQLATableT | None:
|
|
496
481
|
try:
|
|
497
|
-
with sync_temp_set_lock_wait_timeout(session,
|
|
482
|
+
with sync_temp_set_lock_wait_timeout(session, lock_wait_timeout):
|
|
498
483
|
stmt = sa.select(cls._Table).with_for_update()
|
|
499
484
|
for clause in where_clauses:
|
|
500
485
|
stmt = stmt.where(clause)
|
|
@@ -506,16 +491,6 @@ class SyncReadDAL(SyncRawReadDAL, Generic[SyncSQLATableT, DTOModelT]):
|
|
|
506
491
|
raise DBRetryableError(f"{PESSIMISTIC_LOCK_ERROR_MSG_TRAIT}-条件锁等待超时: {error_msg}") from e
|
|
507
492
|
raise
|
|
508
493
|
|
|
509
|
-
@classmethod
|
|
510
|
-
def get_one_for_update(
|
|
511
|
-
cls,
|
|
512
|
-
session: Session,
|
|
513
|
-
*,
|
|
514
|
-
where_clauses: list[ColumnExpressionArgument[bool]],
|
|
515
|
-
lock_wait_timeout: int | None = None,
|
|
516
|
-
) -> SyncSQLATableT | None:
|
|
517
|
-
return cls._get_one_for_update_core(session, where_clauses=where_clauses, timeout=lock_wait_timeout)
|
|
518
|
-
|
|
519
494
|
@classmethod
|
|
520
495
|
def iter_record_dtos(
|
|
521
496
|
cls,
|
|
@@ -552,7 +527,12 @@ class SyncRawDAL:
|
|
|
552
527
|
return session.execute(stmt, params)
|
|
553
528
|
|
|
554
529
|
|
|
555
|
-
class SyncWriteDAL(
|
|
530
|
+
class SyncWriteDAL(
|
|
531
|
+
SyncRawDAL,
|
|
532
|
+
SyncRawReadDAL,
|
|
533
|
+
AbstractSyncWriteDAL[Session, SyncSQLATableT, DTOModelT, CUModelT, int],
|
|
534
|
+
Generic[SyncSQLATableT, DTOModelT, CUModelT],
|
|
535
|
+
):
|
|
556
536
|
"""写入数据访问层基类 (同步版)."""
|
|
557
537
|
|
|
558
538
|
_Table: ClassVar[type[SyncSQLATableT]] # pyright: ignore[reportGeneralTypeIssues]
|
|
@@ -632,7 +612,7 @@ class SyncWriteDAL(SyncRawDAL, SyncRawReadDAL, Generic[SyncSQLATableT, DTOModelT
|
|
|
632
612
|
_ensure_strict_fields(provided_keys=provided_keys, allowed_names=allowed_names, strict=strict)
|
|
633
613
|
|
|
634
614
|
@classmethod
|
|
635
|
-
def
|
|
615
|
+
def update_full_by_id(
|
|
636
616
|
cls,
|
|
637
617
|
session: Session,
|
|
638
618
|
entity_id: int,
|
|
@@ -661,19 +641,7 @@ class SyncWriteDAL(SyncRawDAL, SyncRawReadDAL, Generic[SyncSQLATableT, DTOModelT
|
|
|
661
641
|
return entity
|
|
662
642
|
|
|
663
643
|
@classmethod
|
|
664
|
-
def
|
|
665
|
-
cls,
|
|
666
|
-
session: Session,
|
|
667
|
-
entity_id: int,
|
|
668
|
-
cu: CUModelT,
|
|
669
|
-
*,
|
|
670
|
-
need_refresh: bool = False,
|
|
671
|
-
strict_missing: bool = True,
|
|
672
|
-
) -> SyncSQLATableT | None:
|
|
673
|
-
return cls._update_full_by_id_core(session, entity_id, cu, need_refresh=need_refresh, strict_missing=strict_missing)
|
|
674
|
-
|
|
675
|
-
@classmethod
|
|
676
|
-
def _update_partial_by_id_core(
|
|
644
|
+
def update_partial_by_id(
|
|
677
645
|
cls,
|
|
678
646
|
session: Session,
|
|
679
647
|
entity_id: int,
|
|
@@ -731,30 +699,6 @@ class SyncWriteDAL(SyncRawDAL, SyncRawReadDAL, Generic[SyncSQLATableT, DTOModelT
|
|
|
731
699
|
session.refresh(entity)
|
|
732
700
|
return entity
|
|
733
701
|
|
|
734
|
-
@classmethod
|
|
735
|
-
def update_partial_by_id(
|
|
736
|
-
cls,
|
|
737
|
-
session: Session,
|
|
738
|
-
entity_id: int,
|
|
739
|
-
cu: CUModelT,
|
|
740
|
-
*,
|
|
741
|
-
need_refresh: bool = False,
|
|
742
|
-
fields: set[InstrumentedAttribute[Any]] | set[sa.Column[Any]] | None = None,
|
|
743
|
-
none_policy: Literal["ignore", "allow", "forbid"] = "ignore",
|
|
744
|
-
none_policy_overrides: dict[InstrumentedAttribute[Any] | sa.Column[Any], Literal["ignore", "allow", "forbid"]] | None = None,
|
|
745
|
-
strict: bool = False,
|
|
746
|
-
) -> SyncSQLATableT | None:
|
|
747
|
-
return cls._update_partial_by_id_core(
|
|
748
|
-
session,
|
|
749
|
-
entity_id,
|
|
750
|
-
cu,
|
|
751
|
-
need_refresh=need_refresh,
|
|
752
|
-
fields=fields,
|
|
753
|
-
none_policy=none_policy,
|
|
754
|
-
none_policy_overrides=none_policy_overrides,
|
|
755
|
-
strict=strict,
|
|
756
|
-
)
|
|
757
|
-
|
|
758
702
|
@classmethod
|
|
759
703
|
def delete_by_id(
|
|
760
704
|
cls,
|
|
@@ -789,11 +733,11 @@ class SyncWriteDAL(SyncRawDAL, SyncRawReadDAL, Generic[SyncSQLATableT, DTOModelT
|
|
|
789
733
|
)
|
|
790
734
|
|
|
791
735
|
@classmethod
|
|
792
|
-
def
|
|
736
|
+
def batch_update_by_conditions(
|
|
793
737
|
cls,
|
|
794
738
|
session: Session,
|
|
795
739
|
*,
|
|
796
|
-
|
|
740
|
+
whereclause: list[ColumnExpressionArgument[bool]],
|
|
797
741
|
update_data: dict[InstrumentedAttribute[Any], Any] | dict[sa.Column[Any], Any],
|
|
798
742
|
updater_id: int | None = None,
|
|
799
743
|
) -> int:
|
|
@@ -813,22 +757,11 @@ class SyncWriteDAL(SyncRawDAL, SyncRawReadDAL, Generic[SyncSQLATableT, DTOModelT
|
|
|
813
757
|
final_update_data["update_datetime"] = sa.sql.func.now()
|
|
814
758
|
if hasattr(cls._Table, "update_operator_id") and updater_id is not None:
|
|
815
759
|
final_update_data["update_operator_id"] = updater_id
|
|
816
|
-
stmt = sa.update(cls._Table).where(*
|
|
760
|
+
stmt = sa.update(cls._Table).where(*whereclause).values(**final_update_data)
|
|
817
761
|
result = session.execute(stmt)
|
|
818
762
|
session.flush()
|
|
819
763
|
return result.rowcount # pyright: ignore[reportAttributeAccessIssue, reportUnknownMemberType, reportUnknownVariableType]
|
|
820
764
|
|
|
821
|
-
@classmethod
|
|
822
|
-
def batch_update_by_conditions(
|
|
823
|
-
cls,
|
|
824
|
-
session: Session,
|
|
825
|
-
*,
|
|
826
|
-
whereclause: list[ColumnExpressionArgument[bool]],
|
|
827
|
-
update_data: dict[InstrumentedAttribute[Any], Any] | dict[sa.Column[Any], Any],
|
|
828
|
-
updater_id: int | None = None,
|
|
829
|
-
) -> int:
|
|
830
|
-
return cls._batch_update_by_conditions_core(session, conditions=whereclause, update_data=update_data, updater_id=updater_id)
|
|
831
|
-
|
|
832
765
|
@classmethod
|
|
833
766
|
def batch_update_by_ids(
|
|
834
767
|
cls,
|
|
@@ -842,15 +775,15 @@ class SyncWriteDAL(SyncRawDAL, SyncRawReadDAL, Generic[SyncSQLATableT, DTOModelT
|
|
|
842
775
|
if not filtered_ids:
|
|
843
776
|
return 0
|
|
844
777
|
_id_column = cls._Table.id # pyright: ignore[reportAttributeAccessIssue,reportUnknownVariableType, reportUnknownMemberType]
|
|
845
|
-
return cls.
|
|
778
|
+
return cls.batch_update_by_conditions(
|
|
846
779
|
session,
|
|
847
|
-
|
|
780
|
+
whereclause=[_id_column.in_(filtered_ids)], # pyright: ignore[reportUnknownMemberType]
|
|
848
781
|
update_data=update_data,
|
|
849
782
|
updater_id=updater_id,
|
|
850
783
|
)
|
|
851
784
|
|
|
852
785
|
@classmethod
|
|
853
|
-
def
|
|
786
|
+
def update_only_set_with_optimistic_lock(
|
|
854
787
|
cls,
|
|
855
788
|
session: Session,
|
|
856
789
|
entity_id: int,
|
|
@@ -890,45 +823,13 @@ class SyncWriteDAL(SyncRawDAL, SyncRawReadDAL, Generic[SyncSQLATableT, DTOModelT
|
|
|
890
823
|
return entity
|
|
891
824
|
raise DBRetryableError(f"{OPTIMISTIC_LOCK_ERROR_MSG_TRAIT}-版本号不匹配({entity_id=}, {expected_version=})")
|
|
892
825
|
|
|
893
|
-
@classmethod
|
|
894
|
-
def update_only_set_with_optimistic_lock(
|
|
895
|
-
cls,
|
|
896
|
-
session: Session,
|
|
897
|
-
entity_id: int,
|
|
898
|
-
cu: CUModelT,
|
|
899
|
-
*,
|
|
900
|
-
expected_version: int,
|
|
901
|
-
need_refresh: bool = False,
|
|
902
|
-
version_field: str = "version",
|
|
903
|
-
) -> SyncSQLATableT | None:
|
|
904
|
-
return cls._update_only_set_with_optimistic_lock_core(
|
|
905
|
-
session,
|
|
906
|
-
entity_id,
|
|
907
|
-
cu,
|
|
908
|
-
expected_version=expected_version,
|
|
909
|
-
need_refresh=need_refresh,
|
|
910
|
-
version_field=version_field,
|
|
911
|
-
)
|
|
912
|
-
|
|
913
826
|
|
|
914
827
|
class SyncXDALOp(SyncRawReadDAL, SyncRawDAL):
|
|
915
828
|
"""扩展数据访问操作类 (同步版)."""
|
|
916
829
|
|
|
917
830
|
|
|
918
831
|
class SyncBaseDAL(SyncReadDAL[SyncSQLATableT, DTOModelT], SyncWriteDAL[SyncSQLATableT, DTOModelT, CUModelT]):
|
|
919
|
-
"""基础数据访问层 (同步版).
|
|
920
|
-
|
|
921
|
-
.. deprecated:: 0.3.0
|
|
922
|
-
V1 DAL 将在 1.0 移除, 请迁移至 ``SyncBaseDALV2``.
|
|
923
|
-
"""
|
|
924
|
-
|
|
925
|
-
def __init_subclass__(cls, **kwargs: Any) -> None:
|
|
926
|
-
super().__init_subclass__(**kwargs)
|
|
927
|
-
warnings.warn(
|
|
928
|
-
f"{cls.__name__} 继承了 V1 SyncBaseDAL, 建议迁移至 SyncBaseDALV2",
|
|
929
|
-
DeprecationWarning,
|
|
930
|
-
stacklevel=2,
|
|
931
|
-
)
|
|
832
|
+
"""基础数据访问层 (同步版)."""
|
|
932
833
|
|
|
933
834
|
|
|
934
835
|
class ReadOnlySyncBaseDAL(SyncReadDAL[SyncSQLATableT, ReadOnlyDTOModelT]):
|
|
@@ -1,184 +0,0 @@
|
|
|
1
|
-
"""V2 异步 DAL — 继承 lush-dal-protocol ABC, 使用统一 extra 参数.
|
|
2
|
-
|
|
3
|
-
V2 类继承 V1 实现 + ABC 接口, 仅覆写签名变更的方法.
|
|
4
|
-
不变的方法 (get_by_id, create, count 等) 直接继承自 V1 而不需要覆写.
|
|
5
|
-
"""
|
|
6
|
-
|
|
7
|
-
from __future__ import annotations
|
|
8
|
-
|
|
9
|
-
from collections.abc import Iterable
|
|
10
|
-
from typing import Any, Generic
|
|
11
|
-
|
|
12
|
-
from lush_dal_protocol.abc import (
|
|
13
|
-
AbstractAsyncAdvancedWriteDAL,
|
|
14
|
-
AbstractAsyncBatchFieldDAL,
|
|
15
|
-
AbstractAsyncLockDAL,
|
|
16
|
-
AbstractAsyncRawSQLDAL,
|
|
17
|
-
AbstractAsyncReadDAL,
|
|
18
|
-
AbstractAsyncWriteDAL,
|
|
19
|
-
)
|
|
20
|
-
from sqlalchemy.ext.asyncio import AsyncSession
|
|
21
|
-
from sqlalchemy.orm import InstrumentedAttribute
|
|
22
|
-
|
|
23
|
-
from ._async import AsyncReadDAL, AsyncSQLATableT, AsyncWriteDAL
|
|
24
|
-
from ._common import CUModelT, DTOModelT, filtered_in_sql_values
|
|
25
|
-
from ._params import SQLAExtra
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
class AsyncReadDALV2( # pyright: ignore[reportIncompatibleMethodOverride]
|
|
29
|
-
AsyncReadDAL[AsyncSQLATableT, DTOModelT],
|
|
30
|
-
AbstractAsyncReadDAL[AsyncSession, AsyncSQLATableT, DTOModelT, int, SQLAExtra],
|
|
31
|
-
AbstractAsyncBatchFieldDAL[AsyncSession, AsyncSQLATableT, DTOModelT, int, SQLAExtra],
|
|
32
|
-
Generic[AsyncSQLATableT, DTOModelT],
|
|
33
|
-
):
|
|
34
|
-
"""V2 异步只读 DAL — ABC 合规接口.
|
|
35
|
-
|
|
36
|
-
与 V1 共享全部实现, 仅悲观锁相关方法使用 extra 参数.
|
|
37
|
-
"""
|
|
38
|
-
|
|
39
|
-
@classmethod
|
|
40
|
-
async def get_by_id_for_update( # pyright: ignore[reportIncompatibleMethodOverride]
|
|
41
|
-
cls,
|
|
42
|
-
session: AsyncSession,
|
|
43
|
-
entity_id: int,
|
|
44
|
-
extra: SQLAExtra | None = None,
|
|
45
|
-
) -> AsyncSQLATableT | None:
|
|
46
|
-
timeout = extra.lock_timeout if extra else None
|
|
47
|
-
return await cls._get_by_id_for_update_core(session, entity_id, timeout=timeout)
|
|
48
|
-
|
|
49
|
-
@classmethod
|
|
50
|
-
async def batch_get_for_update( # pyright: ignore[reportIncompatibleMethodOverride]
|
|
51
|
-
cls,
|
|
52
|
-
session: AsyncSession,
|
|
53
|
-
entity_ids: Iterable[int],
|
|
54
|
-
extra: SQLAExtra | None = None,
|
|
55
|
-
) -> list[AsyncSQLATableT]:
|
|
56
|
-
timeout = extra.lock_timeout if extra else None
|
|
57
|
-
return await cls._batch_get_for_update_core(session, entity_ids, timeout=timeout)
|
|
58
|
-
|
|
59
|
-
@classmethod
|
|
60
|
-
async def get_one_for_update( # pyright: ignore[reportIncompatibleMethodOverride]
|
|
61
|
-
cls,
|
|
62
|
-
session: AsyncSession,
|
|
63
|
-
extra: SQLAExtra | None = None,
|
|
64
|
-
*,
|
|
65
|
-
where_clauses: Any,
|
|
66
|
-
) -> AsyncSQLATableT | None:
|
|
67
|
-
timeout = extra.lock_timeout if extra else None
|
|
68
|
-
return await cls._get_one_for_update_core(session, where_clauses=where_clauses, timeout=timeout)
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
class AsyncWriteDALV2( # pyright: ignore[reportIncompatibleMethodOverride]
|
|
72
|
-
AsyncWriteDAL[AsyncSQLATableT, DTOModelT, CUModelT],
|
|
73
|
-
AbstractAsyncWriteDAL[AsyncSession, AsyncSQLATableT, DTOModelT, CUModelT, int, SQLAExtra],
|
|
74
|
-
AbstractAsyncAdvancedWriteDAL[AsyncSession, AsyncSQLATableT, CUModelT, int, SQLAExtra],
|
|
75
|
-
Generic[AsyncSQLATableT, DTOModelT, CUModelT],
|
|
76
|
-
):
|
|
77
|
-
"""V2 异步写入 DAL — ABC 合规接口.
|
|
78
|
-
|
|
79
|
-
与 V1 共享全部实现, 仅高级写操作使用 extra 参数.
|
|
80
|
-
"""
|
|
81
|
-
|
|
82
|
-
@classmethod
|
|
83
|
-
async def update_full_by_id( # pyright: ignore[reportIncompatibleMethodOverride]
|
|
84
|
-
cls,
|
|
85
|
-
session: AsyncSession,
|
|
86
|
-
entity_id: int,
|
|
87
|
-
cu: CUModelT,
|
|
88
|
-
extra: SQLAExtra | None = None,
|
|
89
|
-
) -> AsyncSQLATableT | None:
|
|
90
|
-
need_refresh = extra.need_refresh if extra else False
|
|
91
|
-
strict_missing = extra.strict_missing if extra else True
|
|
92
|
-
return await cls._update_full_by_id_core(session, entity_id, cu, need_refresh=need_refresh, strict_missing=strict_missing)
|
|
93
|
-
|
|
94
|
-
@classmethod
|
|
95
|
-
async def update_partial_by_id( # pyright: ignore[reportIncompatibleMethodOverride]
|
|
96
|
-
cls,
|
|
97
|
-
session: AsyncSession,
|
|
98
|
-
entity_id: int,
|
|
99
|
-
cu: CUModelT,
|
|
100
|
-
extra: SQLAExtra | None = None,
|
|
101
|
-
) -> AsyncSQLATableT | None:
|
|
102
|
-
if extra is None:
|
|
103
|
-
return await cls._update_partial_by_id_core(session, entity_id, cu)
|
|
104
|
-
return await cls._update_partial_by_id_core(
|
|
105
|
-
session,
|
|
106
|
-
entity_id,
|
|
107
|
-
cu,
|
|
108
|
-
need_refresh=extra.need_refresh,
|
|
109
|
-
fields=extra.fields,
|
|
110
|
-
none_policy=extra.none_policy,
|
|
111
|
-
none_policy_overrides=extra.none_policy_overrides,
|
|
112
|
-
strict=extra.strict,
|
|
113
|
-
)
|
|
114
|
-
|
|
115
|
-
@classmethod
|
|
116
|
-
async def batch_update_by_conditions( # pyright: ignore[reportIncompatibleMethodOverride]
|
|
117
|
-
cls,
|
|
118
|
-
session: AsyncSession,
|
|
119
|
-
extra: SQLAExtra | None = None,
|
|
120
|
-
*,
|
|
121
|
-
conditions: Any,
|
|
122
|
-
update_data: Any,
|
|
123
|
-
updater_id: int | None = None,
|
|
124
|
-
) -> int:
|
|
125
|
-
return await cls._batch_update_by_conditions_core(session, conditions=conditions, update_data=update_data, updater_id=updater_id)
|
|
126
|
-
|
|
127
|
-
@classmethod
|
|
128
|
-
async def batch_update_by_ids( # pyright: ignore[reportIncompatibleMethodOverride]
|
|
129
|
-
cls,
|
|
130
|
-
session: AsyncSession,
|
|
131
|
-
extra: SQLAExtra | None = None,
|
|
132
|
-
*,
|
|
133
|
-
entity_ids: set[int] | list[int],
|
|
134
|
-
update_data: dict[InstrumentedAttribute[Any], Any],
|
|
135
|
-
updater_id: int | None = None,
|
|
136
|
-
) -> int:
|
|
137
|
-
filtered_ids = filtered_in_sql_values(entity_ids, int)
|
|
138
|
-
if not filtered_ids:
|
|
139
|
-
return 0
|
|
140
|
-
_id_column = cls._Table.id # pyright: ignore[reportAttributeAccessIssue,reportUnknownVariableType,reportUnknownMemberType]
|
|
141
|
-
return await cls._batch_update_by_conditions_core(
|
|
142
|
-
session,
|
|
143
|
-
conditions=[_id_column.in_(filtered_ids)], # pyright: ignore[reportUnknownMemberType]
|
|
144
|
-
update_data=update_data,
|
|
145
|
-
updater_id=updater_id,
|
|
146
|
-
)
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
class AsyncBaseDALV2( # pyright: ignore[reportIncompatibleMethodOverride]
|
|
150
|
-
AsyncReadDALV2[AsyncSQLATableT, DTOModelT],
|
|
151
|
-
AsyncWriteDALV2[AsyncSQLATableT, DTOModelT, CUModelT],
|
|
152
|
-
AbstractAsyncLockDAL[AsyncSession, AsyncSQLATableT, CUModelT, int, SQLAExtra],
|
|
153
|
-
AbstractAsyncRawSQLDAL[AsyncSession, SQLAExtra],
|
|
154
|
-
Generic[AsyncSQLATableT, DTOModelT, CUModelT],
|
|
155
|
-
):
|
|
156
|
-
"""V2 异步完整 CRUD DAL — Read + Write + Lock + AdvancedWrite + BatchField + RawSQL."""
|
|
157
|
-
|
|
158
|
-
@classmethod
|
|
159
|
-
async def update_only_set_with_optimistic_lock( # pyright: ignore[reportIncompatibleMethodOverride]
|
|
160
|
-
cls,
|
|
161
|
-
session: AsyncSession,
|
|
162
|
-
entity_id: int,
|
|
163
|
-
cu: CUModelT,
|
|
164
|
-
extra: SQLAExtra | None = None,
|
|
165
|
-
*,
|
|
166
|
-
expected_version: int,
|
|
167
|
-
) -> AsyncSQLATableT | None:
|
|
168
|
-
version_field = extra.version_field if extra else "version"
|
|
169
|
-
need_refresh = extra.need_refresh if extra else False
|
|
170
|
-
return await cls._update_only_set_with_optimistic_lock_core(
|
|
171
|
-
session,
|
|
172
|
-
entity_id,
|
|
173
|
-
cu,
|
|
174
|
-
expected_version=expected_version,
|
|
175
|
-
need_refresh=need_refresh,
|
|
176
|
-
version_field=version_field,
|
|
177
|
-
)
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
__all__ = (
|
|
181
|
-
"AsyncBaseDALV2",
|
|
182
|
-
"AsyncReadDALV2",
|
|
183
|
-
"AsyncWriteDALV2",
|
|
184
|
-
)
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
"""SQLAlchemy 特定的操作扩展参数.
|
|
2
|
-
|
|
3
|
-
继承 lush-dal-protocol 的 ``Extra`` 基类, 添加 SQLAlchemy 特有选项.
|
|
4
|
-
所有 V2 DAL 方法的 ``extra`` 参数均使用此类型.
|
|
5
|
-
"""
|
|
6
|
-
|
|
7
|
-
from __future__ import annotations
|
|
8
|
-
|
|
9
|
-
from dataclasses import dataclass
|
|
10
|
-
from typing import Any, Literal
|
|
11
|
-
|
|
12
|
-
from lush_dal_protocol.params import Extra
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
@dataclass(frozen=True)
|
|
16
|
-
class SQLAExtra(Extra):
|
|
17
|
-
"""SQLAlchemy 操作扩展参数.
|
|
18
|
-
|
|
19
|
-
字段按操作类别分组, 各方法按需读取相关字段::
|
|
20
|
-
|
|
21
|
-
extra = SQLAExtra(lock_timeout=5, need_refresh=True)
|
|
22
|
-
DAL.get_by_id_for_update(session, entity_id, extra)
|
|
23
|
-
"""
|
|
24
|
-
|
|
25
|
-
lock_timeout: int | None = None
|
|
26
|
-
need_refresh: bool = False
|
|
27
|
-
version_field: str = "version"
|
|
28
|
-
strict_missing: bool = True
|
|
29
|
-
none_policy: Literal["ignore", "allow", "forbid"] = "ignore"
|
|
30
|
-
strict: bool = False
|
|
31
|
-
fields: Any = None
|
|
32
|
-
none_policy_overrides: dict[Any, Literal["ignore", "allow", "forbid"]] | None = None
|
|
@@ -1,183 +0,0 @@
|
|
|
1
|
-
"""V2 同步 DAL — 继承 lush-dal-protocol ABC, 使用统一 extra 参数.
|
|
2
|
-
|
|
3
|
-
V2 类继承 V1 实现 + ABC 接口, 仅覆写签名变更的方法.
|
|
4
|
-
不变的方法 (get_by_id, create, count 等) 直接继承自 V1 而不需要覆写.
|
|
5
|
-
"""
|
|
6
|
-
|
|
7
|
-
from __future__ import annotations
|
|
8
|
-
|
|
9
|
-
from collections.abc import Iterable
|
|
10
|
-
from typing import Any, Generic
|
|
11
|
-
|
|
12
|
-
from lush_dal_protocol.abc import (
|
|
13
|
-
AbstractSyncAdvancedWriteDAL,
|
|
14
|
-
AbstractSyncBatchFieldDAL,
|
|
15
|
-
AbstractSyncLockDAL,
|
|
16
|
-
AbstractSyncRawSQLDAL,
|
|
17
|
-
AbstractSyncReadDAL,
|
|
18
|
-
AbstractSyncWriteDAL,
|
|
19
|
-
)
|
|
20
|
-
from sqlalchemy.orm import InstrumentedAttribute, Session
|
|
21
|
-
|
|
22
|
-
from ._common import CUModelT, DTOModelT, filtered_in_sql_values
|
|
23
|
-
from ._params import SQLAExtra
|
|
24
|
-
from ._sync import SyncReadDAL, SyncSQLATableT, SyncWriteDAL
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
class SyncReadDALV2( # pyright: ignore[reportIncompatibleMethodOverride]
|
|
28
|
-
SyncReadDAL[SyncSQLATableT, DTOModelT],
|
|
29
|
-
AbstractSyncReadDAL[Session, SyncSQLATableT, DTOModelT, int, SQLAExtra],
|
|
30
|
-
AbstractSyncBatchFieldDAL[Session, SyncSQLATableT, DTOModelT, int, SQLAExtra],
|
|
31
|
-
Generic[SyncSQLATableT, DTOModelT],
|
|
32
|
-
):
|
|
33
|
-
"""V2 同步只读 DAL — ABC 合规接口.
|
|
34
|
-
|
|
35
|
-
与 V1 共享全部实现, 仅悲观锁相关方法使用 extra 参数.
|
|
36
|
-
"""
|
|
37
|
-
|
|
38
|
-
@classmethod
|
|
39
|
-
def get_by_id_for_update( # pyright: ignore[reportIncompatibleMethodOverride]
|
|
40
|
-
cls,
|
|
41
|
-
session: Session,
|
|
42
|
-
entity_id: int,
|
|
43
|
-
extra: SQLAExtra | None = None,
|
|
44
|
-
) -> SyncSQLATableT | None:
|
|
45
|
-
timeout = extra.lock_timeout if extra else None
|
|
46
|
-
return cls._get_by_id_for_update_core(session, entity_id, timeout=timeout)
|
|
47
|
-
|
|
48
|
-
@classmethod
|
|
49
|
-
def batch_get_for_update( # pyright: ignore[reportIncompatibleMethodOverride]
|
|
50
|
-
cls,
|
|
51
|
-
session: Session,
|
|
52
|
-
entity_ids: Iterable[int],
|
|
53
|
-
extra: SQLAExtra | None = None,
|
|
54
|
-
) -> list[SyncSQLATableT]:
|
|
55
|
-
timeout = extra.lock_timeout if extra else None
|
|
56
|
-
return cls._batch_get_for_update_core(session, entity_ids, timeout=timeout)
|
|
57
|
-
|
|
58
|
-
@classmethod
|
|
59
|
-
def get_one_for_update( # pyright: ignore[reportIncompatibleMethodOverride]
|
|
60
|
-
cls,
|
|
61
|
-
session: Session,
|
|
62
|
-
extra: SQLAExtra | None = None,
|
|
63
|
-
*,
|
|
64
|
-
where_clauses: Any,
|
|
65
|
-
) -> SyncSQLATableT | None:
|
|
66
|
-
timeout = extra.lock_timeout if extra else None
|
|
67
|
-
return cls._get_one_for_update_core(session, where_clauses=where_clauses, timeout=timeout)
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
class SyncWriteDALV2( # pyright: ignore[reportIncompatibleMethodOverride]
|
|
71
|
-
SyncWriteDAL[SyncSQLATableT, DTOModelT, CUModelT],
|
|
72
|
-
AbstractSyncWriteDAL[Session, SyncSQLATableT, DTOModelT, CUModelT, int, SQLAExtra],
|
|
73
|
-
AbstractSyncAdvancedWriteDAL[Session, SyncSQLATableT, CUModelT, int, SQLAExtra],
|
|
74
|
-
Generic[SyncSQLATableT, DTOModelT, CUModelT],
|
|
75
|
-
):
|
|
76
|
-
"""V2 同步写入 DAL — ABC 合规接口.
|
|
77
|
-
|
|
78
|
-
与 V1 共享全部实现, 仅高级写操作使用 extra 参数.
|
|
79
|
-
"""
|
|
80
|
-
|
|
81
|
-
@classmethod
|
|
82
|
-
def update_full_by_id( # pyright: ignore[reportIncompatibleMethodOverride]
|
|
83
|
-
cls,
|
|
84
|
-
session: Session,
|
|
85
|
-
entity_id: int,
|
|
86
|
-
cu: CUModelT,
|
|
87
|
-
extra: SQLAExtra | None = None,
|
|
88
|
-
) -> SyncSQLATableT | None:
|
|
89
|
-
need_refresh = extra.need_refresh if extra else False
|
|
90
|
-
strict_missing = extra.strict_missing if extra else True
|
|
91
|
-
return cls._update_full_by_id_core(session, entity_id, cu, need_refresh=need_refresh, strict_missing=strict_missing)
|
|
92
|
-
|
|
93
|
-
@classmethod
|
|
94
|
-
def update_partial_by_id( # pyright: ignore[reportIncompatibleMethodOverride]
|
|
95
|
-
cls,
|
|
96
|
-
session: Session,
|
|
97
|
-
entity_id: int,
|
|
98
|
-
cu: CUModelT,
|
|
99
|
-
extra: SQLAExtra | None = None,
|
|
100
|
-
) -> SyncSQLATableT | None:
|
|
101
|
-
if extra is None:
|
|
102
|
-
return cls._update_partial_by_id_core(session, entity_id, cu)
|
|
103
|
-
return cls._update_partial_by_id_core(
|
|
104
|
-
session,
|
|
105
|
-
entity_id,
|
|
106
|
-
cu,
|
|
107
|
-
need_refresh=extra.need_refresh,
|
|
108
|
-
fields=extra.fields,
|
|
109
|
-
none_policy=extra.none_policy,
|
|
110
|
-
none_policy_overrides=extra.none_policy_overrides,
|
|
111
|
-
strict=extra.strict,
|
|
112
|
-
)
|
|
113
|
-
|
|
114
|
-
@classmethod
|
|
115
|
-
def batch_update_by_conditions( # pyright: ignore[reportIncompatibleMethodOverride]
|
|
116
|
-
cls,
|
|
117
|
-
session: Session,
|
|
118
|
-
extra: SQLAExtra | None = None,
|
|
119
|
-
*,
|
|
120
|
-
conditions: Any,
|
|
121
|
-
update_data: Any,
|
|
122
|
-
updater_id: int | None = None,
|
|
123
|
-
) -> int:
|
|
124
|
-
return cls._batch_update_by_conditions_core(session, conditions=conditions, update_data=update_data, updater_id=updater_id)
|
|
125
|
-
|
|
126
|
-
@classmethod
|
|
127
|
-
def batch_update_by_ids( # pyright: ignore[reportIncompatibleMethodOverride]
|
|
128
|
-
cls,
|
|
129
|
-
session: Session,
|
|
130
|
-
extra: SQLAExtra | None = None,
|
|
131
|
-
*,
|
|
132
|
-
entity_ids: set[int] | list[int],
|
|
133
|
-
update_data: dict[InstrumentedAttribute[Any], Any],
|
|
134
|
-
updater_id: int | None = None,
|
|
135
|
-
) -> int:
|
|
136
|
-
filtered_ids = filtered_in_sql_values(entity_ids, int)
|
|
137
|
-
if not filtered_ids:
|
|
138
|
-
return 0
|
|
139
|
-
_id_column = cls._Table.id # pyright: ignore[reportAttributeAccessIssue,reportUnknownVariableType,reportUnknownMemberType]
|
|
140
|
-
return cls._batch_update_by_conditions_core(
|
|
141
|
-
session,
|
|
142
|
-
conditions=[_id_column.in_(filtered_ids)], # pyright: ignore[reportUnknownMemberType]
|
|
143
|
-
update_data=update_data,
|
|
144
|
-
updater_id=updater_id,
|
|
145
|
-
)
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
class SyncBaseDALV2( # pyright: ignore[reportIncompatibleMethodOverride]
|
|
149
|
-
SyncReadDALV2[SyncSQLATableT, DTOModelT],
|
|
150
|
-
SyncWriteDALV2[SyncSQLATableT, DTOModelT, CUModelT],
|
|
151
|
-
AbstractSyncLockDAL[Session, SyncSQLATableT, CUModelT, int, SQLAExtra],
|
|
152
|
-
AbstractSyncRawSQLDAL[Session, SQLAExtra],
|
|
153
|
-
Generic[SyncSQLATableT, DTOModelT, CUModelT],
|
|
154
|
-
):
|
|
155
|
-
"""V2 同步完整 CRUD DAL — Read + Write + Lock + AdvancedWrite + BatchField + RawSQL."""
|
|
156
|
-
|
|
157
|
-
@classmethod
|
|
158
|
-
def update_only_set_with_optimistic_lock( # pyright: ignore[reportIncompatibleMethodOverride]
|
|
159
|
-
cls,
|
|
160
|
-
session: Session,
|
|
161
|
-
entity_id: int,
|
|
162
|
-
cu: CUModelT,
|
|
163
|
-
extra: SQLAExtra | None = None,
|
|
164
|
-
*,
|
|
165
|
-
expected_version: int,
|
|
166
|
-
) -> SyncSQLATableT | None:
|
|
167
|
-
version_field = extra.version_field if extra else "version"
|
|
168
|
-
need_refresh = extra.need_refresh if extra else False
|
|
169
|
-
return cls._update_only_set_with_optimistic_lock_core(
|
|
170
|
-
session,
|
|
171
|
-
entity_id,
|
|
172
|
-
cu,
|
|
173
|
-
expected_version=expected_version,
|
|
174
|
-
need_refresh=need_refresh,
|
|
175
|
-
version_field=version_field,
|
|
176
|
-
)
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
__all__ = (
|
|
180
|
-
"SyncBaseDALV2",
|
|
181
|
-
"SyncReadDALV2",
|
|
182
|
-
"SyncWriteDALV2",
|
|
183
|
-
)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{lush_sqlalchemyx-0.3.1 → lush_sqlalchemyx-0.3.2}/src/lush_sqlalchemyx/integrations/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{lush_sqlalchemyx-0.3.1 → lush_sqlalchemyx-0.3.2}/src/lush_sqlalchemyx/integrations/flask/ext.py
RENAMED
|
File without changes
|
|
File without changes
|
{lush_sqlalchemyx-0.3.1 → lush_sqlalchemyx-0.3.2}/src/lush_sqlalchemyx/mgrs/mysql/__init__.py
RENAMED
|
File without changes
|
{lush_sqlalchemyx-0.3.1 → lush_sqlalchemyx-0.3.2}/src/lush_sqlalchemyx/mgrs/mysql/manager.py
RENAMED
|
File without changes
|
|
File without changes
|
{lush_sqlalchemyx-0.3.1 → lush_sqlalchemyx-0.3.2}/src/lush_sqlalchemyx/mgrs/mysql/sync_manager.py
RENAMED
|
File without changes
|
{lush_sqlalchemyx-0.3.1 → lush_sqlalchemyx-0.3.2}/src/lush_sqlalchemyx/mgrs/mysql/sync_mapper.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{lush_sqlalchemyx-0.3.1 → lush_sqlalchemyx-0.3.2}/src/lush_sqlalchemyx/shortcuts/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|