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.
Files changed (30) hide show
  1. {lush_sqlalchemyx-0.3.1 → lush_sqlalchemyx-0.3.2}/PKG-INFO +1 -1
  2. {lush_sqlalchemyx-0.3.1 → lush_sqlalchemyx-0.3.2}/pyproject.toml +1 -4
  3. {lush_sqlalchemyx-0.3.1 → lush_sqlalchemyx-0.3.2}/src/lush_sqlalchemyx/base/dal/__init__.py +1 -33
  4. {lush_sqlalchemyx-0.3.1 → lush_sqlalchemyx-0.3.2}/src/lush_sqlalchemyx/base/dal/_async.py +30 -129
  5. {lush_sqlalchemyx-0.3.1 → lush_sqlalchemyx-0.3.2}/src/lush_sqlalchemyx/base/dal/_pagination.py +2 -2
  6. {lush_sqlalchemyx-0.3.1 → lush_sqlalchemyx-0.3.2}/src/lush_sqlalchemyx/base/dal/_repository.py +36 -24
  7. {lush_sqlalchemyx-0.3.1 → lush_sqlalchemyx-0.3.2}/src/lush_sqlalchemyx/base/dal/_sync.py +30 -129
  8. lush_sqlalchemyx-0.3.1/src/lush_sqlalchemyx/base/dal/_async_v2.py +0 -184
  9. lush_sqlalchemyx-0.3.1/src/lush_sqlalchemyx/base/dal/_params.py +0 -32
  10. lush_sqlalchemyx-0.3.1/src/lush_sqlalchemyx/base/dal/_sync_v2.py +0 -183
  11. {lush_sqlalchemyx-0.3.1 → lush_sqlalchemyx-0.3.2}/README.md +0 -0
  12. {lush_sqlalchemyx-0.3.1 → lush_sqlalchemyx-0.3.2}/src/lush_sqlalchemyx/__init__.py +0 -0
  13. {lush_sqlalchemyx-0.3.1 → lush_sqlalchemyx-0.3.2}/src/lush_sqlalchemyx/_compat.py +0 -0
  14. {lush_sqlalchemyx-0.3.1 → lush_sqlalchemyx-0.3.2}/src/lush_sqlalchemyx/base/__init__.py +0 -0
  15. {lush_sqlalchemyx-0.3.1 → lush_sqlalchemyx-0.3.2}/src/lush_sqlalchemyx/base/dal/_common.py +0 -0
  16. {lush_sqlalchemyx-0.3.1 → lush_sqlalchemyx-0.3.2}/src/lush_sqlalchemyx/integrations/__init__.py +0 -0
  17. {lush_sqlalchemyx-0.3.1 → lush_sqlalchemyx-0.3.2}/src/lush_sqlalchemyx/integrations/fastapi/__init__.py +0 -0
  18. {lush_sqlalchemyx-0.3.1 → lush_sqlalchemyx-0.3.2}/src/lush_sqlalchemyx/integrations/fastapi/depends/__init__.py +0 -0
  19. {lush_sqlalchemyx-0.3.1 → lush_sqlalchemyx-0.3.2}/src/lush_sqlalchemyx/integrations/flask/__init__.py +0 -0
  20. {lush_sqlalchemyx-0.3.1 → lush_sqlalchemyx-0.3.2}/src/lush_sqlalchemyx/integrations/flask/ext.py +0 -0
  21. {lush_sqlalchemyx-0.3.1 → lush_sqlalchemyx-0.3.2}/src/lush_sqlalchemyx/mgrs/__init__.py +0 -0
  22. {lush_sqlalchemyx-0.3.1 → lush_sqlalchemyx-0.3.2}/src/lush_sqlalchemyx/mgrs/mysql/__init__.py +0 -0
  23. {lush_sqlalchemyx-0.3.1 → lush_sqlalchemyx-0.3.2}/src/lush_sqlalchemyx/mgrs/mysql/manager.py +0 -0
  24. {lush_sqlalchemyx-0.3.1 → lush_sqlalchemyx-0.3.2}/src/lush_sqlalchemyx/mgrs/mysql/mapper.py +0 -0
  25. {lush_sqlalchemyx-0.3.1 → lush_sqlalchemyx-0.3.2}/src/lush_sqlalchemyx/mgrs/mysql/sync_manager.py +0 -0
  26. {lush_sqlalchemyx-0.3.1 → lush_sqlalchemyx-0.3.2}/src/lush_sqlalchemyx/mgrs/mysql/sync_mapper.py +0 -0
  27. {lush_sqlalchemyx-0.3.1 → lush_sqlalchemyx-0.3.2}/src/lush_sqlalchemyx/py.typed +0 -0
  28. {lush_sqlalchemyx-0.3.1 → lush_sqlalchemyx-0.3.2}/src/lush_sqlalchemyx/same_impl_just_warn_wrapper.py +0 -0
  29. {lush_sqlalchemyx-0.3.1 → lush_sqlalchemyx-0.3.2}/src/lush_sqlalchemyx/shortcuts/__init__.py +0 -0
  30. {lush_sqlalchemyx-0.3.1 → lush_sqlalchemyx-0.3.2}/src/lush_sqlalchemyx/shortcuts/meta.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: lush-sqlalchemyx
3
- Version: 0.3.1
3
+ Version: 0.3.2
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.3.1"
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(AsyncRawReadDAL, Generic[AsyncSQLATableT, DTOModelT]):
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 _get_by_id_for_update_core(
434
+ async def get_by_id_for_update(
430
435
  cls,
431
436
  session: AsyncSession,
432
437
  entity_id: int,
433
438
  *,
434
- timeout: int | None = None,
439
+ lock_wait_timeout: int | None = None,
435
440
  ) -> AsyncSQLATableT | None:
436
441
  try:
437
- async with async_temp_set_lock_wait_timeout(session, timeout):
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 get_by_id_for_update(
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
- timeout: int | None = None,
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, timeout):
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 batch_get_for_update(
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
- timeout: int | None = None,
489
+ lock_wait_timeout: int | None = None,
505
490
  ) -> AsyncSQLATableT | None:
506
491
  try:
507
- async with async_temp_set_lock_wait_timeout(session, timeout):
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(AsyncRawDAL, AsyncRawReadDAL, Generic[AsyncSQLATableT, DTOModelT, CUModelT]):
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 _update_full_by_id_core(
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 update_full_by_id(
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 _batch_update_by_conditions_core(
763
+ async def batch_update_by_conditions(
820
764
  cls,
821
765
  session: AsyncSession,
822
766
  *,
823
- conditions: list[ColumnExpressionArgument[bool]],
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(*conditions).values(**final_update_data)
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._batch_update_by_conditions_core(
812
+ return await cls.batch_update_by_conditions(
880
813
  session,
881
- conditions=[_id_column.in_(filtered_ids)], # pyright: ignore[reportUnknownMemberType]
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 _update_only_set_with_optimistic_lock_core(
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]):
@@ -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) # pyright: ignore[reportAttributeAccessIssue]
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 # pyright: ignore[reportAttributeAccessIssue]
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:
@@ -1,15 +1,21 @@
1
1
  """SQLAlchemy 具体 Repository 实现.
2
2
 
3
- 提供高层声明式 CRUD 接口, 内部委托给 DAL V2.
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[[], Any]]
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
- @contextmanager
40
- def _get_session(cls) -> Any:
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 # pyright: ignore[reportReturnType]
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 # pyright: ignore[reportReturnType]
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[..., Any]]
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 # pyright: ignore[reportReturnType]
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 # pyright: ignore[reportReturnType]
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(SyncRawReadDAL, Generic[SyncSQLATableT, DTOModelT]):
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 _get_by_id_for_update_core(
425
+ def get_by_id_for_update(
421
426
  cls,
422
427
  session: Session,
423
428
  entity_id: int,
424
429
  *,
425
- timeout: int | None = None,
430
+ lock_wait_timeout: int | None = None,
426
431
  ) -> SyncSQLATableT | None:
427
432
  try:
428
- with sync_temp_set_lock_wait_timeout(session, timeout):
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 get_by_id_for_update(
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
- timeout: int | None = None,
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, timeout):
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 batch_get_for_update(
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
- timeout: int | None = None,
479
+ lock_wait_timeout: int | None = None,
495
480
  ) -> SyncSQLATableT | None:
496
481
  try:
497
- with sync_temp_set_lock_wait_timeout(session, timeout):
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(SyncRawDAL, SyncRawReadDAL, Generic[SyncSQLATableT, DTOModelT, CUModelT]):
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 _update_full_by_id_core(
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 update_full_by_id(
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 _batch_update_by_conditions_core(
736
+ def batch_update_by_conditions(
793
737
  cls,
794
738
  session: Session,
795
739
  *,
796
- conditions: list[ColumnExpressionArgument[bool]],
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(*conditions).values(**final_update_data)
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._batch_update_by_conditions_core(
778
+ return cls.batch_update_by_conditions(
846
779
  session,
847
- conditions=[_id_column.in_(filtered_ids)], # pyright: ignore[reportUnknownMemberType]
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 _update_only_set_with_optimistic_lock_core(
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
- )