sqlspec 0.63.0__py3-none-any.whl → 0.63.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,5 @@
1
+ """Slotted application service bases backed by the compiled service runtime."""
2
+
3
+ from sqlspec.service._base import SQLSpecAsyncService, SQLSpecSyncService
4
+
5
+ __all__ = ("SQLSpecAsyncService", "SQLSpecSyncService")
@@ -1,26 +1,29 @@
1
1
  """Service base classes for SQLSpec application services."""
2
2
 
3
- import asyncio
4
- import sys
5
- from contextlib import AbstractAsyncContextManager, AbstractContextManager, AsyncExitStack, ExitStack, nullcontext
6
- from contextvars import ContextVar, Token
7
- from threading import get_ident
8
- from typing import TYPE_CHECKING, Any, Generic, Literal, cast, overload
9
-
10
- from mypy_extensions import mypyc_attr
3
+ from contextlib import AbstractAsyncContextManager, AbstractContextManager, nullcontext
4
+ from typing import TYPE_CHECKING, Any, Generic, cast, overload
5
+
11
6
  from typing_extensions import TypeVar
12
7
 
13
8
  from sqlspec.core import OffsetPagination
14
- from sqlspec.core.filters import LimitOffsetFilter
15
9
  from sqlspec.driver._async import AsyncDriverAdapterBase
16
10
  from sqlspec.driver._sync import SyncDriverAdapterBase
17
- from sqlspec.exceptions import ImproperConfigurationError, NotFoundError
11
+ from sqlspec.exceptions import ImproperConfigurationError
12
+ from sqlspec.service._core import (
13
+ _async_exists,
14
+ _async_get_one,
15
+ _async_paginate,
16
+ _AsyncBeginTransactionContext,
17
+ _sync_exists,
18
+ _sync_get_one,
19
+ _sync_paginate,
20
+ _SyncBeginTransactionContext,
21
+ _transaction_session,
22
+ _TransactionState,
23
+ )
18
24
  from sqlspec.typing import SchemaT
19
- from sqlspec.utils.logging import get_logger
20
25
 
21
26
  if TYPE_CHECKING:
22
- from types import TracebackType
23
-
24
27
  from sqlspec.builder import QueryBuilder
25
28
  from sqlspec.config import AsyncDatabaseConfig, NoPoolAsyncConfig, NoPoolSyncConfig, SyncDatabaseConfig
26
29
  from sqlspec.core.filters import StatementFilter
@@ -34,36 +37,7 @@ __all__ = ("SQLSpecAsyncService", "SQLSpecSyncService")
34
37
  AsyncDriverT = TypeVar("AsyncDriverT", bound=AsyncDriverAdapterBase, default=AsyncDriverAdapterBase)
35
38
  SyncDriverT = TypeVar("SyncDriverT", bound=SyncDriverAdapterBase, default=SyncDriverAdapterBase)
36
39
 
37
- logger = get_logger("sqlspec.service")
38
-
39
-
40
- def _execution_owner() -> tuple[int, object]:
41
- try:
42
- task = asyncio.current_task()
43
- except RuntimeError:
44
- task = None
45
- return get_ident(), task
46
-
47
-
48
- def _owner_identity(owner: tuple[int, object]) -> tuple[int, int]:
49
- return owner[0], id(owner[1])
50
-
51
-
52
- class _TransactionState:
53
- __slots__ = ("driver", "origin", "owner")
54
-
55
- def __init__(self, driver: AsyncDriverAdapterBase | SyncDriverAdapterBase) -> None:
56
- self.driver: AsyncDriverAdapterBase | SyncDriverAdapterBase | None = driver
57
- self.owner = _execution_owner()
58
- self.origin = _owner_identity(self.owner)
59
40
 
60
-
61
- _TRANSACTIONS: ContextVar[dict[object, _TransactionState] | None] = ContextVar(
62
- "sqlspec_service_transactions", default=None
63
- )
64
-
65
-
66
- @mypyc_attr(allow_interpreted_subclasses=True)
67
41
  class SQLSpecAsyncService(Generic[AsyncDriverT]):
68
42
  """Base class for asynchronous SQLSpec services.
69
43
 
@@ -194,27 +168,9 @@ class SQLSpecAsyncService(Generic[AsyncDriverT]):
194
168
  Returns:
195
169
  An OffsetPagination instance containing items and total count.
196
170
  """
197
- async with self.provide_session(session) as driver:
198
- limit_offset: LimitOffsetFilter | None = driver.find_filter(LimitOffsetFilter, parameters)
199
-
200
- items, total = await driver.select_with_total(
201
- statement, *parameters, schema_type=schema_type, count_with_window=count_with_window, **kwargs
202
- )
203
-
204
- if schema_type is None:
205
- return OffsetPagination(
206
- items=cast("list[dict[str, Any]]", items),
207
- limit=limit_offset.limit if limit_offset is not None else len(items),
208
- offset=limit_offset.offset if limit_offset is not None else 0,
209
- total=total,
210
- )
211
-
212
- return OffsetPagination(
213
- items=cast("list[SchemaT]", items),
214
- limit=limit_offset.limit if limit_offset is not None else len(items),
215
- offset=limit_offset.offset if limit_offset is not None else 0,
216
- total=total,
217
- )
171
+ return await _async_paginate(
172
+ self.provide_session(session), statement, parameters, schema_type, count_with_window, kwargs
173
+ )
218
174
 
219
175
  @overload
220
176
  async def get_one(
@@ -283,11 +239,9 @@ class SQLSpecAsyncService(Generic[AsyncDriverT]):
283
239
  Raises:
284
240
  NotFoundError: If the query returns zero rows.
285
241
  """
286
- async with self.provide_session(session) as driver:
287
- result = await driver.select_one_or_none(statement, *parameters, schema_type=schema_type, **kwargs)
288
- if result is None:
289
- raise NotFoundError(error_message or "Record not found")
290
- return result
242
+ return await _async_get_one(
243
+ self.provide_session(session), statement, parameters, schema_type, error_message, kwargs
244
+ )
291
245
 
292
246
  async def exists(
293
247
  self,
@@ -308,8 +262,7 @@ class SQLSpecAsyncService(Generic[AsyncDriverT]):
308
262
  Returns:
309
263
  True if at least one row exists, False otherwise.
310
264
  """
311
- async with self.provide_session(session) as driver:
312
- return await driver.select_one_or_none(statement, *parameters, **kwargs) is not None
265
+ return await _async_exists(self.provide_session(session), statement, parameters, kwargs)
313
266
 
314
267
  async def begin(self, *, session: AsyncDriverT | None = None) -> None:
315
268
  """Begin a database transaction on the underlying session.
@@ -346,7 +299,6 @@ class SQLSpecAsyncService(Generic[AsyncDriverT]):
346
299
  return _AsyncBeginTransactionContext(self)
347
300
 
348
301
 
349
- @mypyc_attr(allow_interpreted_subclasses=True)
350
302
  class SQLSpecSyncService(Generic[SyncDriverT]):
351
303
  """Base class for synchronous SQLSpec services.
352
304
 
@@ -477,27 +429,9 @@ class SQLSpecSyncService(Generic[SyncDriverT]):
477
429
  Returns:
478
430
  An OffsetPagination instance containing items and total count.
479
431
  """
480
- with self.provide_session(session) as driver:
481
- limit_offset: LimitOffsetFilter | None = driver.find_filter(LimitOffsetFilter, parameters)
482
-
483
- items, total = driver.select_with_total(
484
- statement, *parameters, schema_type=schema_type, count_with_window=count_with_window, **kwargs
485
- )
486
-
487
- if schema_type is None:
488
- return OffsetPagination(
489
- items=cast("list[dict[str, Any]]", items),
490
- limit=limit_offset.limit if limit_offset is not None else len(items),
491
- offset=limit_offset.offset if limit_offset is not None else 0,
492
- total=total,
493
- )
494
-
495
- return OffsetPagination(
496
- items=cast("list[SchemaT]", items),
497
- limit=limit_offset.limit if limit_offset is not None else len(items),
498
- offset=limit_offset.offset if limit_offset is not None else 0,
499
- total=total,
500
- )
432
+ return _sync_paginate(
433
+ self.provide_session(session), statement, parameters, schema_type, count_with_window, kwargs
434
+ )
501
435
 
502
436
  @overload
503
437
  def get_one(
@@ -566,11 +500,7 @@ class SQLSpecSyncService(Generic[SyncDriverT]):
566
500
  Raises:
567
501
  NotFoundError: If the query returns zero rows.
568
502
  """
569
- with self.provide_session(session) as driver:
570
- result = driver.select_one_or_none(statement, *parameters, schema_type=schema_type, **kwargs)
571
- if result is None:
572
- raise NotFoundError(error_message or "Record not found")
573
- return result
503
+ return _sync_get_one(self.provide_session(session), statement, parameters, schema_type, error_message, kwargs)
574
504
 
575
505
  def exists(
576
506
  self,
@@ -591,8 +521,7 @@ class SQLSpecSyncService(Generic[SyncDriverT]):
591
521
  Returns:
592
522
  True if at least one row exists, False otherwise.
593
523
  """
594
- with self.provide_session(session) as driver:
595
- return driver.select_one_or_none(statement, *parameters, **kwargs) is not None
524
+ return _sync_exists(self.provide_session(session), statement, parameters, kwargs)
596
525
 
597
526
  def begin(self, *, session: SyncDriverT | None = None) -> None:
598
527
  """Begin a database transaction on the underlying session.
@@ -627,251 +556,3 @@ class SQLSpecSyncService(Generic[SyncDriverT]):
627
556
  The underlying driver session bound to the active transaction.
628
557
  """
629
558
  return _SyncBeginTransactionContext(self)
630
-
631
-
632
- def _active_transaction(key: object) -> _TransactionState | None:
633
- state = (_TRANSACTIONS.get() or {}).get(key)
634
- if state is None:
635
- return None
636
- if state.driver is None:
637
- if state.origin == _owner_identity(_execution_owner()):
638
- _discard_transaction(state)
639
- return None
640
- msg = "The inherited service transaction is no longer active."
641
- raise ImproperConfigurationError(msg)
642
- if state.owner != _execution_owner():
643
- msg = "A service transaction cannot be implicitly reused by another task or thread; pass session= explicitly."
644
- raise ImproperConfigurationError(msg)
645
- return state
646
-
647
-
648
- def _live_transaction(key: object) -> _TransactionState | None:
649
- current = _TRANSACTIONS.get()
650
- state = None if current is None else current.get(key)
651
- if state is None:
652
- return None
653
- if state.driver is None:
654
- _discard_transaction(state)
655
- return None
656
- return _active_transaction(key)
657
-
658
-
659
- def _discard_transaction(state: _TransactionState) -> None:
660
- current = _TRANSACTIONS.get()
661
- if current is not None and any(value is state for value in current.values()):
662
- _TRANSACTIONS.set({key: value for key, value in current.items() if value is not state} or None)
663
-
664
-
665
- def _session_transaction(state: _TransactionState | None) -> _TransactionState | None:
666
- if state is None or state.driver is None:
667
- return None
668
- if state.owner != _execution_owner():
669
- msg = (
670
- "A service transaction is active in another task or thread; nested begin_transaction() blocks must "
671
- "run in the task or thread that entered the outer block."
672
- )
673
- raise ImproperConfigurationError(msg)
674
- return state
675
-
676
-
677
- def _connection_in_transaction(driver: AsyncDriverAdapterBase | SyncDriverAdapterBase) -> bool:
678
- try:
679
- return driver._connection_in_transaction()
680
- except NotImplementedError:
681
- return False
682
-
683
-
684
- def _transaction_session(key: object) -> AsyncDriverAdapterBase | SyncDriverAdapterBase | None:
685
- state = _active_transaction(key)
686
- return None if state is None else state.driver
687
-
688
-
689
- def _bind_transaction(
690
- key: object, driver: AsyncDriverAdapterBase | SyncDriverAdapterBase
691
- ) -> tuple[_TransactionState, Token[dict[object, _TransactionState] | None]]:
692
- state = _TransactionState(driver)
693
- token = _TRANSACTIONS.set({**(_TRANSACTIONS.get() or {}), key: state})
694
- return state, token
695
-
696
-
697
- def _release_transaction(state: _TransactionState, token: Token[dict[object, _TransactionState] | None]) -> None:
698
- state.driver = None
699
- state.owner = (0, None)
700
- try:
701
- _TRANSACTIONS.reset(token)
702
- except ValueError:
703
- _discard_transaction(state)
704
-
705
-
706
- class _AsyncBeginTransactionContext(Generic[AsyncDriverT]):
707
- __slots__ = ("_nested", "_service", "_stack", "_state")
708
-
709
- def __init__(self, service: "SQLSpecAsyncService[AsyncDriverT]") -> None:
710
- self._service = service
711
- self._stack: AsyncExitStack | None = None
712
- self._state: _TransactionState | None = None
713
- self._nested: Any = None
714
-
715
- async def __aenter__(self) -> AsyncDriverT:
716
- service = self._service
717
- if service._config is None:
718
- active = _session_transaction(service._transaction_state)
719
- session = service.session
720
- if active is not None or session._transaction_depth > 0:
721
- nested = session.transaction()
722
- await nested.__aenter__()
723
- self._nested = nested
724
- return session
725
- if not _connection_in_transaction(session):
726
- await service.begin()
727
- session._transaction_depth += 1
728
- state = _TransactionState(session)
729
- service._transaction_state = state
730
- self._state = state
731
- return session
732
- key = service._transaction_key
733
- live = _live_transaction(key)
734
- if live is not None:
735
- driver = cast("AsyncDriverT", live.driver)
736
- nested = driver.transaction()
737
- await nested.__aenter__()
738
- self._nested = nested
739
- return driver
740
- stack = AsyncExitStack()
741
- try:
742
- driver = await stack.enter_async_context(service.provide_session())
743
- await stack.enter_async_context(driver.transaction())
744
- state, token = _bind_transaction(key, driver)
745
- stack.callback(_release_transaction, state, token)
746
- except BaseException:
747
- await stack.__aexit__(*sys.exc_info())
748
- raise
749
- self._stack = stack
750
- self._state = state
751
- return driver
752
-
753
- async def __aexit__(
754
- self, exc_type: "type[BaseException] | None", exc: "BaseException | None", traceback: "TracebackType | None"
755
- ) -> "Literal[False]":
756
- service = self._service
757
- nested = self._nested
758
- state = self._state
759
- stack = self._stack
760
- self._nested = None
761
- self._state = None
762
- self._stack = None
763
- if nested is not None:
764
- await nested.__aexit__(exc_type, exc, traceback)
765
- return False
766
- if stack is not None:
767
- await stack.__aexit__(exc_type, exc, traceback)
768
- return False
769
- if state is None:
770
- return False
771
- session = cast("AsyncDriverAdapterBase", state.driver)
772
- try:
773
- if exc_type is not None:
774
- await service.rollback()
775
- return False
776
- try:
777
- await service.commit()
778
- except BaseException:
779
- try:
780
- await service.rollback()
781
- except Exception:
782
- logger.debug("Rollback after a failed commit also failed", exc_info=True)
783
- raise
784
- return False
785
- finally:
786
- if session._transaction_depth > 0:
787
- session._transaction_depth -= 1
788
- state.driver = None
789
- if service._transaction_state is state:
790
- service._transaction_state = None
791
-
792
-
793
- class _SyncBeginTransactionContext(Generic[SyncDriverT]):
794
- __slots__ = ("_nested", "_service", "_stack", "_state")
795
-
796
- def __init__(self, service: "SQLSpecSyncService[SyncDriverT]") -> None:
797
- self._service = service
798
- self._stack: ExitStack | None = None
799
- self._state: _TransactionState | None = None
800
- self._nested: Any = None
801
-
802
- def __enter__(self) -> SyncDriverT:
803
- service = self._service
804
- if service._config is None:
805
- active = _session_transaction(service._transaction_state)
806
- session = service.session
807
- if active is not None or session._transaction_depth > 0:
808
- nested = session.transaction()
809
- nested.__enter__()
810
- self._nested = nested
811
- return session
812
- if not _connection_in_transaction(session):
813
- service.begin()
814
- session._transaction_depth += 1
815
- state = _TransactionState(session)
816
- service._transaction_state = state
817
- self._state = state
818
- return session
819
- key = service._transaction_key
820
- live = _live_transaction(key)
821
- if live is not None:
822
- driver = cast("SyncDriverT", live.driver)
823
- nested = driver.transaction()
824
- nested.__enter__()
825
- self._nested = nested
826
- return driver
827
- stack = ExitStack()
828
- try:
829
- driver = stack.enter_context(service.provide_session())
830
- stack.enter_context(driver.transaction())
831
- state, token = _bind_transaction(key, driver)
832
- stack.callback(_release_transaction, state, token)
833
- except BaseException:
834
- stack.__exit__(*sys.exc_info())
835
- raise
836
- self._stack = stack
837
- self._state = state
838
- return driver
839
-
840
- def __exit__(
841
- self, exc_type: "type[BaseException] | None", exc: "BaseException | None", traceback: "TracebackType | None"
842
- ) -> "Literal[False]":
843
- service = self._service
844
- nested = self._nested
845
- state = self._state
846
- stack = self._stack
847
- self._nested = None
848
- self._state = None
849
- self._stack = None
850
- if nested is not None:
851
- nested.__exit__(exc_type, exc, traceback)
852
- return False
853
- if stack is not None:
854
- stack.__exit__(exc_type, exc, traceback)
855
- return False
856
- if state is None:
857
- return False
858
- session = cast("SyncDriverAdapterBase", state.driver)
859
- try:
860
- if exc_type is not None:
861
- service.rollback()
862
- return False
863
- try:
864
- service.commit()
865
- except BaseException:
866
- try:
867
- service.rollback()
868
- except Exception:
869
- logger.debug("Rollback after a failed commit also failed", exc_info=True)
870
- raise
871
- return False
872
- finally:
873
- if session._transaction_depth > 0:
874
- session._transaction_depth -= 1
875
- state.driver = None
876
- if service._transaction_state is state:
877
- service._transaction_state = None
@@ -0,0 +1,423 @@
1
+ """Compiled query and transaction runtime for the Python service bases."""
2
+
3
+ import asyncio
4
+ import sys
5
+ from contextlib import AbstractAsyncContextManager, AbstractContextManager, AsyncExitStack, ExitStack
6
+ from contextvars import ContextVar, Token
7
+ from threading import get_ident
8
+ from typing import TYPE_CHECKING, Any, Generic, Literal, cast
9
+
10
+ from typing_extensions import TypeVar
11
+
12
+ from sqlspec.core import OffsetPagination
13
+ from sqlspec.core.filters import LimitOffsetFilter
14
+ from sqlspec.driver._async import AsyncDriverAdapterBase
15
+ from sqlspec.driver._sync import SyncDriverAdapterBase
16
+ from sqlspec.exceptions import ImproperConfigurationError, NotFoundError
17
+ from sqlspec.utils.logging import get_logger
18
+
19
+ if TYPE_CHECKING:
20
+ from types import TracebackType
21
+
22
+ from sqlspec.builder import QueryBuilder
23
+ from sqlspec.core.filters import StatementFilter
24
+ from sqlspec.core.statement import Statement
25
+ from sqlspec.service import SQLSpecAsyncService, SQLSpecSyncService
26
+ from sqlspec.typing import SchemaT, StatementParameters
27
+
28
+ AsyncDriverT = TypeVar("AsyncDriverT", bound=AsyncDriverAdapterBase, default=AsyncDriverAdapterBase)
29
+ SyncDriverT = TypeVar("SyncDriverT", bound=SyncDriverAdapterBase, default=SyncDriverAdapterBase)
30
+
31
+ logger = get_logger("sqlspec.service")
32
+
33
+
34
+ def _execution_owner() -> tuple[int, object]:
35
+ try:
36
+ task = asyncio.current_task()
37
+ except RuntimeError:
38
+ task = None
39
+ return get_ident(), task
40
+
41
+
42
+ def _owner_identity(owner: tuple[int, object]) -> tuple[int, int]:
43
+ return owner[0], id(owner[1])
44
+
45
+
46
+ class _TransactionState:
47
+ __slots__ = ("driver", "origin", "owner")
48
+
49
+ def __init__(self, driver: AsyncDriverAdapterBase | SyncDriverAdapterBase) -> None:
50
+ self.driver: AsyncDriverAdapterBase | SyncDriverAdapterBase | None = driver
51
+ self.owner = _execution_owner()
52
+ self.origin = _owner_identity(self.owner)
53
+
54
+
55
+ _TRANSACTIONS: ContextVar[dict[object, _TransactionState] | None] = ContextVar(
56
+ "sqlspec_service_transactions", default=None
57
+ )
58
+
59
+
60
+ async def _async_paginate(
61
+ context: AbstractAsyncContextManager[AsyncDriverAdapterBase],
62
+ statement: "Statement | QueryBuilder",
63
+ parameters: "tuple[StatementParameters | StatementFilter, ...]",
64
+ schema_type: "type[SchemaT] | None",
65
+ count_with_window: bool,
66
+ kwargs: dict[str, Any],
67
+ ) -> "OffsetPagination[SchemaT] | OffsetPagination[dict[str, Any]]":
68
+ """Execute the async service paginate operation in the supplied session context."""
69
+ async with context as driver:
70
+ limit_offset: LimitOffsetFilter | None = driver.find_filter(LimitOffsetFilter, parameters)
71
+
72
+ items, total = await driver.select_with_total(
73
+ statement, *parameters, schema_type=schema_type, count_with_window=count_with_window, **kwargs
74
+ )
75
+
76
+ if schema_type is None:
77
+ return OffsetPagination(
78
+ items=cast("list[dict[str, Any]]", items),
79
+ limit=limit_offset.limit if limit_offset is not None else len(items),
80
+ offset=limit_offset.offset if limit_offset is not None else 0,
81
+ total=total,
82
+ )
83
+
84
+ return OffsetPagination(
85
+ items=cast("list[SchemaT]", items),
86
+ limit=limit_offset.limit if limit_offset is not None else len(items),
87
+ offset=limit_offset.offset if limit_offset is not None else 0,
88
+ total=total,
89
+ )
90
+
91
+
92
+ async def _async_get_one(
93
+ context: AbstractAsyncContextManager[AsyncDriverAdapterBase],
94
+ statement: "Statement | QueryBuilder",
95
+ parameters: "tuple[StatementParameters | StatementFilter, ...]",
96
+ schema_type: "type[SchemaT] | None",
97
+ error_message: str | None,
98
+ kwargs: dict[str, Any],
99
+ ) -> "SchemaT | dict[str, Any]":
100
+ """Execute the async service get_one operation in the supplied session context."""
101
+ async with context as driver:
102
+ result = await driver.select_one_or_none(statement, *parameters, schema_type=schema_type, **kwargs)
103
+ if result is None:
104
+ raise NotFoundError(error_message or "Record not found")
105
+ return result
106
+
107
+
108
+ async def _async_exists(
109
+ context: AbstractAsyncContextManager[AsyncDriverAdapterBase],
110
+ statement: "Statement | QueryBuilder",
111
+ parameters: "tuple[StatementParameters | StatementFilter, ...]",
112
+ kwargs: dict[str, Any],
113
+ ) -> bool:
114
+ """Execute the async service exists operation in the supplied session context."""
115
+ async with context as driver:
116
+ return await driver.select_one_or_none(statement, *parameters, **kwargs) is not None
117
+
118
+
119
+ def _sync_paginate(
120
+ context: AbstractContextManager[SyncDriverAdapterBase],
121
+ statement: "Statement | QueryBuilder",
122
+ parameters: "tuple[StatementParameters | StatementFilter, ...]",
123
+ schema_type: "type[SchemaT] | None",
124
+ count_with_window: bool,
125
+ kwargs: dict[str, Any],
126
+ ) -> "OffsetPagination[SchemaT] | OffsetPagination[dict[str, Any]]":
127
+ """Execute the sync service paginate operation in the supplied session context."""
128
+ with context as driver:
129
+ limit_offset: LimitOffsetFilter | None = driver.find_filter(LimitOffsetFilter, parameters)
130
+
131
+ items, total = driver.select_with_total(
132
+ statement, *parameters, schema_type=schema_type, count_with_window=count_with_window, **kwargs
133
+ )
134
+
135
+ if schema_type is None:
136
+ return OffsetPagination(
137
+ items=cast("list[dict[str, Any]]", items),
138
+ limit=limit_offset.limit if limit_offset is not None else len(items),
139
+ offset=limit_offset.offset if limit_offset is not None else 0,
140
+ total=total,
141
+ )
142
+
143
+ return OffsetPagination(
144
+ items=cast("list[SchemaT]", items),
145
+ limit=limit_offset.limit if limit_offset is not None else len(items),
146
+ offset=limit_offset.offset if limit_offset is not None else 0,
147
+ total=total,
148
+ )
149
+
150
+
151
+ def _sync_get_one(
152
+ context: AbstractContextManager[SyncDriverAdapterBase],
153
+ statement: "Statement | QueryBuilder",
154
+ parameters: "tuple[StatementParameters | StatementFilter, ...]",
155
+ schema_type: "type[SchemaT] | None",
156
+ error_message: str | None,
157
+ kwargs: dict[str, Any],
158
+ ) -> "SchemaT | dict[str, Any]":
159
+ """Execute the sync service get_one operation in the supplied session context."""
160
+ with context as driver:
161
+ result = driver.select_one_or_none(statement, *parameters, schema_type=schema_type, **kwargs)
162
+ if result is None:
163
+ raise NotFoundError(error_message or "Record not found")
164
+ return result
165
+
166
+
167
+ def _sync_exists(
168
+ context: AbstractContextManager[SyncDriverAdapterBase],
169
+ statement: "Statement | QueryBuilder",
170
+ parameters: "tuple[StatementParameters | StatementFilter, ...]",
171
+ kwargs: dict[str, Any],
172
+ ) -> bool:
173
+ """Execute the sync service exists operation in the supplied session context."""
174
+ with context as driver:
175
+ return driver.select_one_or_none(statement, *parameters, **kwargs) is not None
176
+
177
+
178
+ def _active_transaction(key: object) -> _TransactionState | None:
179
+ state = (_TRANSACTIONS.get() or {}).get(key)
180
+ if state is None:
181
+ return None
182
+ if state.driver is None:
183
+ if state.origin == _owner_identity(_execution_owner()):
184
+ _discard_transaction(state)
185
+ return None
186
+ msg = "The inherited service transaction is no longer active."
187
+ raise ImproperConfigurationError(msg)
188
+ if state.owner != _execution_owner():
189
+ msg = "A service transaction cannot be implicitly reused by another task or thread; pass session= explicitly."
190
+ raise ImproperConfigurationError(msg)
191
+ return state
192
+
193
+
194
+ def _live_transaction(key: object) -> _TransactionState | None:
195
+ current = _TRANSACTIONS.get()
196
+ state = None if current is None else current.get(key)
197
+ if state is None:
198
+ return None
199
+ if state.driver is None:
200
+ _discard_transaction(state)
201
+ return None
202
+ return _active_transaction(key)
203
+
204
+
205
+ def _discard_transaction(state: _TransactionState) -> None:
206
+ current = _TRANSACTIONS.get()
207
+ if current is not None and any(value is state for value in current.values()):
208
+ _TRANSACTIONS.set({key: value for key, value in current.items() if value is not state} or None)
209
+
210
+
211
+ def _session_transaction(state: _TransactionState | None) -> _TransactionState | None:
212
+ if state is None or state.driver is None:
213
+ return None
214
+ if state.owner != _execution_owner():
215
+ msg = (
216
+ "A service transaction is active in another task or thread; nested begin_transaction() blocks must "
217
+ "run in the task or thread that entered the outer block."
218
+ )
219
+ raise ImproperConfigurationError(msg)
220
+ return state
221
+
222
+
223
+ def _connection_in_transaction(driver: AsyncDriverAdapterBase | SyncDriverAdapterBase) -> bool:
224
+ try:
225
+ return driver._connection_in_transaction()
226
+ except NotImplementedError:
227
+ return False
228
+
229
+
230
+ def _transaction_session(key: object) -> AsyncDriverAdapterBase | SyncDriverAdapterBase | None:
231
+ state = _active_transaction(key)
232
+ return None if state is None else state.driver
233
+
234
+
235
+ def _bind_transaction(
236
+ key: object, driver: AsyncDriverAdapterBase | SyncDriverAdapterBase
237
+ ) -> tuple[_TransactionState, Token[dict[object, _TransactionState] | None]]:
238
+ state = _TransactionState(driver)
239
+ token = _TRANSACTIONS.set({**(_TRANSACTIONS.get() or {}), key: state})
240
+ return state, token
241
+
242
+
243
+ def _release_transaction(state: _TransactionState, token: Token[dict[object, _TransactionState] | None]) -> None:
244
+ state.driver = None
245
+ state.owner = (0, None)
246
+ try:
247
+ _TRANSACTIONS.reset(token)
248
+ except ValueError:
249
+ _discard_transaction(state)
250
+
251
+
252
+ class _AsyncBeginTransactionContext(Generic[AsyncDriverT]):
253
+ __slots__ = ("_nested", "_service", "_stack", "_state")
254
+
255
+ def __init__(self, service: "SQLSpecAsyncService[AsyncDriverT]") -> None:
256
+ self._service = service
257
+ self._stack: AsyncExitStack | None = None
258
+ self._state: _TransactionState | None = None
259
+ self._nested: Any = None
260
+
261
+ async def __aenter__(self) -> AsyncDriverT:
262
+ service = self._service
263
+ if service._config is None:
264
+ active = _session_transaction(service._transaction_state)
265
+ session = service.session
266
+ if active is not None or session._transaction_depth > 0:
267
+ nested = session.transaction()
268
+ await nested.__aenter__()
269
+ self._nested = nested
270
+ return session
271
+ if not _connection_in_transaction(session):
272
+ await service.begin()
273
+ session._transaction_depth += 1
274
+ state = _TransactionState(session)
275
+ service._transaction_state = state
276
+ self._state = state
277
+ return session
278
+ key = service._transaction_key
279
+ live = _live_transaction(key)
280
+ if live is not None:
281
+ driver = cast("AsyncDriverT", live.driver)
282
+ nested = driver.transaction()
283
+ await nested.__aenter__()
284
+ self._nested = nested
285
+ return driver
286
+ stack = AsyncExitStack()
287
+ try:
288
+ driver = await stack.enter_async_context(service.provide_session())
289
+ await stack.enter_async_context(driver.transaction())
290
+ state, token = _bind_transaction(key, driver)
291
+ stack.callback(_release_transaction, state, token)
292
+ except BaseException:
293
+ await stack.__aexit__(*sys.exc_info())
294
+ raise
295
+ self._stack = stack
296
+ self._state = state
297
+ return driver
298
+
299
+ async def __aexit__(
300
+ self, exc_type: "type[BaseException] | None", exc: "BaseException | None", traceback: "TracebackType | None"
301
+ ) -> "Literal[False]":
302
+ service = self._service
303
+ nested = self._nested
304
+ state = self._state
305
+ stack = self._stack
306
+ self._nested = None
307
+ self._state = None
308
+ self._stack = None
309
+ if nested is not None:
310
+ await nested.__aexit__(exc_type, exc, traceback)
311
+ return False
312
+ if stack is not None:
313
+ await stack.__aexit__(exc_type, exc, traceback)
314
+ return False
315
+ if state is None:
316
+ return False
317
+ session = cast("AsyncDriverAdapterBase", state.driver)
318
+ try:
319
+ if exc_type is not None:
320
+ await service.rollback()
321
+ return False
322
+ try:
323
+ await service.commit()
324
+ except BaseException:
325
+ try:
326
+ await service.rollback()
327
+ except Exception:
328
+ logger.debug("Rollback after a failed commit also failed", exc_info=True)
329
+ raise
330
+ return False
331
+ finally:
332
+ if session._transaction_depth > 0:
333
+ session._transaction_depth -= 1
334
+ state.driver = None
335
+ if service._transaction_state is state:
336
+ service._transaction_state = None
337
+
338
+
339
+ class _SyncBeginTransactionContext(Generic[SyncDriverT]):
340
+ __slots__ = ("_nested", "_service", "_stack", "_state")
341
+
342
+ def __init__(self, service: "SQLSpecSyncService[SyncDriverT]") -> None:
343
+ self._service = service
344
+ self._stack: ExitStack | None = None
345
+ self._state: _TransactionState | None = None
346
+ self._nested: Any = None
347
+
348
+ def __enter__(self) -> SyncDriverT:
349
+ service = self._service
350
+ if service._config is None:
351
+ active = _session_transaction(service._transaction_state)
352
+ session = service.session
353
+ if active is not None or session._transaction_depth > 0:
354
+ nested = session.transaction()
355
+ nested.__enter__()
356
+ self._nested = nested
357
+ return session
358
+ if not _connection_in_transaction(session):
359
+ service.begin()
360
+ session._transaction_depth += 1
361
+ state = _TransactionState(session)
362
+ service._transaction_state = state
363
+ self._state = state
364
+ return session
365
+ key = service._transaction_key
366
+ live = _live_transaction(key)
367
+ if live is not None:
368
+ driver = cast("SyncDriverT", live.driver)
369
+ nested = driver.transaction()
370
+ nested.__enter__()
371
+ self._nested = nested
372
+ return driver
373
+ stack = ExitStack()
374
+ try:
375
+ driver = stack.enter_context(service.provide_session())
376
+ stack.enter_context(driver.transaction())
377
+ state, token = _bind_transaction(key, driver)
378
+ stack.callback(_release_transaction, state, token)
379
+ except BaseException:
380
+ stack.__exit__(*sys.exc_info())
381
+ raise
382
+ self._stack = stack
383
+ self._state = state
384
+ return driver
385
+
386
+ def __exit__(
387
+ self, exc_type: "type[BaseException] | None", exc: "BaseException | None", traceback: "TracebackType | None"
388
+ ) -> "Literal[False]":
389
+ service = self._service
390
+ nested = self._nested
391
+ state = self._state
392
+ stack = self._stack
393
+ self._nested = None
394
+ self._state = None
395
+ self._stack = None
396
+ if nested is not None:
397
+ nested.__exit__(exc_type, exc, traceback)
398
+ return False
399
+ if stack is not None:
400
+ stack.__exit__(exc_type, exc, traceback)
401
+ return False
402
+ if state is None:
403
+ return False
404
+ session = cast("SyncDriverAdapterBase", state.driver)
405
+ try:
406
+ if exc_type is not None:
407
+ service.rollback()
408
+ return False
409
+ try:
410
+ service.commit()
411
+ except BaseException:
412
+ try:
413
+ service.rollback()
414
+ except Exception:
415
+ logger.debug("Rollback after a failed commit also failed", exc_info=True)
416
+ raise
417
+ return False
418
+ finally:
419
+ if session._transaction_depth > 0:
420
+ session._transaction_depth -= 1
421
+ state.driver = None
422
+ if service._transaction_state is state:
423
+ service._transaction_state = None
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: sqlspec
3
- Version: 0.63.0
3
+ Version: 0.63.1
4
4
  Summary: SQL Experiments in Python
5
5
  Project-URL: Discord, https://discord.gg/litestar
6
6
  Project-URL: Issue, https://github.com/litestar-org/sqlspec/issues/
@@ -9,7 +9,6 @@ sqlspec/exceptions.py,sha256=RjCfh90cUJuh4wmAgSzb6KvKhCMknM6a--Xsffq8OkM,16194
9
9
  sqlspec/loader.py,sha256=CsyQrkRplxmMLVKW1WeGMKUlGZftk_JyW-wrP8CAexo,65609
10
10
  sqlspec/protocols.py,sha256=Yyag7aGdyUi6JDSDJwbt-rMrooy3ezQ1PjsJm0UVjgE,33966
11
11
  sqlspec/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
- sqlspec/service.py,sha256=GeHviEoJxWraMnIvONGbcsUvYcZBcWJWoNqdU5Hvnvg,33295
13
12
  sqlspec/typing.py,sha256=L3AhW4Xr0uYuaIlghGzcgznwEto8UGy6Qbnb9yrf6-k,6380
14
13
  sqlspec/adapters/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
14
  sqlspec/adapters/adbc/__init__.py,sha256=iwWnfPYTR0c0uFQZAKbeVt0-W-9soyuhSd7On8QomEo,410
@@ -635,6 +634,9 @@ sqlspec/observability/_formatters/_aws.py,sha256=ZVIc3s2JI7Gpgwcpa3NtXVZf2GSDMaj
635
634
  sqlspec/observability/_formatters/_azure.py,sha256=Om13FLalqhtlDJjVUdHQnTzDBP0cVdJp3TXlCraF0mM,2498
636
635
  sqlspec/observability/_formatters/_base.py,sha256=a0Th_NkyAB0iBH61tXHyvVZPrj4RMLJ9_ADvCpuzMgo,1202
637
636
  sqlspec/observability/_formatters/_gcp.py,sha256=Nek2Nt8kqJ4fDy6qwPcHUMPITtDa6xLQA6VxotmCeFk,4067
637
+ sqlspec/service/__init__.py,sha256=aRsQimIvairp_rUJPNevlRpCKAr17FClxKYwyQanLhY,212
638
+ sqlspec/service/_base.py,sha256=6Lqp8Mq394p61uIK20rbwmHdk65424ArHomx2HerZ-Y,20992
639
+ sqlspec/service/_core.py,sha256=l-G57o3iV-ZIfqEz7AFDZxP4ICEAam_Dfh8SHpEcckc,16211
638
640
  sqlspec/storage/__init__.py,sha256=KXu6tW_7WCM-djHPXPSgmiUPROxsCxO1W4G17tUdnU8,1281
639
641
  sqlspec/storage/_arrow_payload.py,sha256=kAt7ciGlFZFeuWFF4ddkSPrKWIZ2MQc3y2PRvjqnPnM,2738
640
642
  sqlspec/storage/_arrow_stream.py,sha256=pwjTfpiAKyPB9LGyx6anGUl5TKj0qdFwZAjlajHl6Dw,2117
@@ -669,8 +671,8 @@ sqlspec/utils/serializers/__init__.py,sha256=X99t8YMHVGmKwrqPxumnSXu4JMJL8SpKoU7
669
671
  sqlspec/utils/serializers/_json.py,sha256=5PBan5kHoDAGqbOqT33SdEK17VZzNjzZKneWI_BnDzY,18136
670
672
  sqlspec/utils/serializers/_numpy.py,sha256=_LllCtVn_gyLrRG2naHM7Unjtpot86BPTvGPlZuAGRw,1742
671
673
  sqlspec/utils/serializers/_schema.py,sha256=kN8lW8GJa08yMi_qN_x8A7XpO7V6aAaoYqKq72rxaCk,11416
672
- sqlspec-0.63.0.dist-info/METADATA,sha256=T_grrYdnBYEpkWH1jqybZMwwtcbkGcLD9OAKQFoOnik,9080
673
- sqlspec-0.63.0.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
674
- sqlspec-0.63.0.dist-info/entry_points.txt,sha256=Odw9xs3HsdFEuJXZ_GE0iemkXwkPQzn1w16PuIYDpqc,459
675
- sqlspec-0.63.0.dist-info/licenses/LICENSE,sha256=MdujfZ6l5HuLz4mElxlu049itenOR3gnhN1_Nd3nVcM,1078
676
- sqlspec-0.63.0.dist-info/RECORD,,
674
+ sqlspec-0.63.1.dist-info/METADATA,sha256=wdAq669tcEN-ef6HgWYOfDuHE088KHXaP3ipj31WeTY,9080
675
+ sqlspec-0.63.1.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
676
+ sqlspec-0.63.1.dist-info/entry_points.txt,sha256=Odw9xs3HsdFEuJXZ_GE0iemkXwkPQzn1w16PuIYDpqc,459
677
+ sqlspec-0.63.1.dist-info/licenses/LICENSE,sha256=MdujfZ6l5HuLz4mElxlu049itenOR3gnhN1_Nd3nVcM,1078
678
+ sqlspec-0.63.1.dist-info/RECORD,,