sqlspec 0.25.0__py3-none-any.whl → 0.27.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of sqlspec might be problematic. Click here for more details.

Files changed (199) hide show
  1. sqlspec/__init__.py +7 -15
  2. sqlspec/_serialization.py +256 -24
  3. sqlspec/_typing.py +71 -52
  4. sqlspec/adapters/adbc/_types.py +1 -1
  5. sqlspec/adapters/adbc/adk/__init__.py +5 -0
  6. sqlspec/adapters/adbc/adk/store.py +870 -0
  7. sqlspec/adapters/adbc/config.py +69 -12
  8. sqlspec/adapters/adbc/data_dictionary.py +340 -0
  9. sqlspec/adapters/adbc/driver.py +266 -58
  10. sqlspec/adapters/adbc/litestar/__init__.py +5 -0
  11. sqlspec/adapters/adbc/litestar/store.py +504 -0
  12. sqlspec/adapters/adbc/type_converter.py +153 -0
  13. sqlspec/adapters/aiosqlite/_types.py +1 -1
  14. sqlspec/adapters/aiosqlite/adk/__init__.py +5 -0
  15. sqlspec/adapters/aiosqlite/adk/store.py +527 -0
  16. sqlspec/adapters/aiosqlite/config.py +88 -15
  17. sqlspec/adapters/aiosqlite/data_dictionary.py +149 -0
  18. sqlspec/adapters/aiosqlite/driver.py +143 -40
  19. sqlspec/adapters/aiosqlite/litestar/__init__.py +5 -0
  20. sqlspec/adapters/aiosqlite/litestar/store.py +281 -0
  21. sqlspec/adapters/aiosqlite/pool.py +7 -7
  22. sqlspec/adapters/asyncmy/__init__.py +7 -1
  23. sqlspec/adapters/asyncmy/_types.py +2 -2
  24. sqlspec/adapters/asyncmy/adk/__init__.py +5 -0
  25. sqlspec/adapters/asyncmy/adk/store.py +493 -0
  26. sqlspec/adapters/asyncmy/config.py +68 -23
  27. sqlspec/adapters/asyncmy/data_dictionary.py +161 -0
  28. sqlspec/adapters/asyncmy/driver.py +313 -58
  29. sqlspec/adapters/asyncmy/litestar/__init__.py +5 -0
  30. sqlspec/adapters/asyncmy/litestar/store.py +296 -0
  31. sqlspec/adapters/asyncpg/__init__.py +2 -1
  32. sqlspec/adapters/asyncpg/_type_handlers.py +71 -0
  33. sqlspec/adapters/asyncpg/_types.py +11 -7
  34. sqlspec/adapters/asyncpg/adk/__init__.py +5 -0
  35. sqlspec/adapters/asyncpg/adk/store.py +450 -0
  36. sqlspec/adapters/asyncpg/config.py +59 -35
  37. sqlspec/adapters/asyncpg/data_dictionary.py +173 -0
  38. sqlspec/adapters/asyncpg/driver.py +170 -25
  39. sqlspec/adapters/asyncpg/litestar/__init__.py +5 -0
  40. sqlspec/adapters/asyncpg/litestar/store.py +253 -0
  41. sqlspec/adapters/bigquery/_types.py +1 -1
  42. sqlspec/adapters/bigquery/adk/__init__.py +5 -0
  43. sqlspec/adapters/bigquery/adk/store.py +576 -0
  44. sqlspec/adapters/bigquery/config.py +27 -10
  45. sqlspec/adapters/bigquery/data_dictionary.py +149 -0
  46. sqlspec/adapters/bigquery/driver.py +368 -142
  47. sqlspec/adapters/bigquery/litestar/__init__.py +5 -0
  48. sqlspec/adapters/bigquery/litestar/store.py +327 -0
  49. sqlspec/adapters/bigquery/type_converter.py +125 -0
  50. sqlspec/adapters/duckdb/_types.py +1 -1
  51. sqlspec/adapters/duckdb/adk/__init__.py +14 -0
  52. sqlspec/adapters/duckdb/adk/store.py +553 -0
  53. sqlspec/adapters/duckdb/config.py +80 -20
  54. sqlspec/adapters/duckdb/data_dictionary.py +163 -0
  55. sqlspec/adapters/duckdb/driver.py +167 -45
  56. sqlspec/adapters/duckdb/litestar/__init__.py +5 -0
  57. sqlspec/adapters/duckdb/litestar/store.py +332 -0
  58. sqlspec/adapters/duckdb/pool.py +4 -4
  59. sqlspec/adapters/duckdb/type_converter.py +133 -0
  60. sqlspec/adapters/oracledb/_numpy_handlers.py +133 -0
  61. sqlspec/adapters/oracledb/_types.py +20 -2
  62. sqlspec/adapters/oracledb/adk/__init__.py +5 -0
  63. sqlspec/adapters/oracledb/adk/store.py +1745 -0
  64. sqlspec/adapters/oracledb/config.py +122 -32
  65. sqlspec/adapters/oracledb/data_dictionary.py +509 -0
  66. sqlspec/adapters/oracledb/driver.py +353 -91
  67. sqlspec/adapters/oracledb/litestar/__init__.py +5 -0
  68. sqlspec/adapters/oracledb/litestar/store.py +767 -0
  69. sqlspec/adapters/oracledb/migrations.py +348 -73
  70. sqlspec/adapters/oracledb/type_converter.py +207 -0
  71. sqlspec/adapters/psqlpy/_type_handlers.py +44 -0
  72. sqlspec/adapters/psqlpy/_types.py +2 -1
  73. sqlspec/adapters/psqlpy/adk/__init__.py +5 -0
  74. sqlspec/adapters/psqlpy/adk/store.py +482 -0
  75. sqlspec/adapters/psqlpy/config.py +46 -17
  76. sqlspec/adapters/psqlpy/data_dictionary.py +172 -0
  77. sqlspec/adapters/psqlpy/driver.py +123 -209
  78. sqlspec/adapters/psqlpy/litestar/__init__.py +5 -0
  79. sqlspec/adapters/psqlpy/litestar/store.py +272 -0
  80. sqlspec/adapters/psqlpy/type_converter.py +102 -0
  81. sqlspec/adapters/psycopg/_type_handlers.py +80 -0
  82. sqlspec/adapters/psycopg/_types.py +2 -1
  83. sqlspec/adapters/psycopg/adk/__init__.py +5 -0
  84. sqlspec/adapters/psycopg/adk/store.py +944 -0
  85. sqlspec/adapters/psycopg/config.py +69 -35
  86. sqlspec/adapters/psycopg/data_dictionary.py +331 -0
  87. sqlspec/adapters/psycopg/driver.py +238 -81
  88. sqlspec/adapters/psycopg/litestar/__init__.py +5 -0
  89. sqlspec/adapters/psycopg/litestar/store.py +554 -0
  90. sqlspec/adapters/sqlite/__init__.py +2 -1
  91. sqlspec/adapters/sqlite/_type_handlers.py +86 -0
  92. sqlspec/adapters/sqlite/_types.py +1 -1
  93. sqlspec/adapters/sqlite/adk/__init__.py +5 -0
  94. sqlspec/adapters/sqlite/adk/store.py +572 -0
  95. sqlspec/adapters/sqlite/config.py +87 -15
  96. sqlspec/adapters/sqlite/data_dictionary.py +149 -0
  97. sqlspec/adapters/sqlite/driver.py +137 -54
  98. sqlspec/adapters/sqlite/litestar/__init__.py +5 -0
  99. sqlspec/adapters/sqlite/litestar/store.py +318 -0
  100. sqlspec/adapters/sqlite/pool.py +18 -9
  101. sqlspec/base.py +45 -26
  102. sqlspec/builder/__init__.py +73 -4
  103. sqlspec/builder/_base.py +162 -89
  104. sqlspec/builder/_column.py +62 -29
  105. sqlspec/builder/_ddl.py +180 -121
  106. sqlspec/builder/_delete.py +5 -4
  107. sqlspec/builder/_dml.py +388 -0
  108. sqlspec/{_sql.py → builder/_factory.py} +53 -94
  109. sqlspec/builder/_insert.py +32 -131
  110. sqlspec/builder/_join.py +375 -0
  111. sqlspec/builder/_merge.py +446 -11
  112. sqlspec/builder/_parsing_utils.py +111 -17
  113. sqlspec/builder/_select.py +1457 -24
  114. sqlspec/builder/_update.py +11 -42
  115. sqlspec/cli.py +307 -194
  116. sqlspec/config.py +252 -67
  117. sqlspec/core/__init__.py +5 -4
  118. sqlspec/core/cache.py +17 -17
  119. sqlspec/core/compiler.py +62 -9
  120. sqlspec/core/filters.py +37 -37
  121. sqlspec/core/hashing.py +9 -9
  122. sqlspec/core/parameters.py +83 -48
  123. sqlspec/core/result.py +102 -46
  124. sqlspec/core/splitter.py +16 -17
  125. sqlspec/core/statement.py +36 -30
  126. sqlspec/core/type_conversion.py +235 -0
  127. sqlspec/driver/__init__.py +7 -6
  128. sqlspec/driver/_async.py +188 -151
  129. sqlspec/driver/_common.py +285 -80
  130. sqlspec/driver/_sync.py +188 -152
  131. sqlspec/driver/mixins/_result_tools.py +20 -236
  132. sqlspec/driver/mixins/_sql_translator.py +4 -4
  133. sqlspec/exceptions.py +75 -7
  134. sqlspec/extensions/adk/__init__.py +53 -0
  135. sqlspec/extensions/adk/_types.py +51 -0
  136. sqlspec/extensions/adk/converters.py +172 -0
  137. sqlspec/extensions/adk/migrations/0001_create_adk_tables.py +144 -0
  138. sqlspec/extensions/adk/migrations/__init__.py +0 -0
  139. sqlspec/extensions/adk/service.py +181 -0
  140. sqlspec/extensions/adk/store.py +536 -0
  141. sqlspec/extensions/aiosql/adapter.py +73 -53
  142. sqlspec/extensions/litestar/__init__.py +21 -4
  143. sqlspec/extensions/litestar/cli.py +54 -10
  144. sqlspec/extensions/litestar/config.py +59 -266
  145. sqlspec/extensions/litestar/handlers.py +46 -17
  146. sqlspec/extensions/litestar/migrations/0001_create_session_table.py +137 -0
  147. sqlspec/extensions/litestar/migrations/__init__.py +3 -0
  148. sqlspec/extensions/litestar/plugin.py +324 -223
  149. sqlspec/extensions/litestar/providers.py +25 -25
  150. sqlspec/extensions/litestar/store.py +265 -0
  151. sqlspec/loader.py +30 -49
  152. sqlspec/migrations/__init__.py +4 -3
  153. sqlspec/migrations/base.py +302 -39
  154. sqlspec/migrations/commands.py +611 -144
  155. sqlspec/migrations/context.py +142 -0
  156. sqlspec/migrations/fix.py +199 -0
  157. sqlspec/migrations/loaders.py +68 -23
  158. sqlspec/migrations/runner.py +543 -107
  159. sqlspec/migrations/tracker.py +237 -21
  160. sqlspec/migrations/utils.py +51 -3
  161. sqlspec/migrations/validation.py +177 -0
  162. sqlspec/protocols.py +66 -36
  163. sqlspec/storage/_utils.py +98 -0
  164. sqlspec/storage/backends/fsspec.py +134 -106
  165. sqlspec/storage/backends/local.py +78 -51
  166. sqlspec/storage/backends/obstore.py +278 -162
  167. sqlspec/storage/registry.py +75 -39
  168. sqlspec/typing.py +16 -84
  169. sqlspec/utils/config_resolver.py +153 -0
  170. sqlspec/utils/correlation.py +4 -5
  171. sqlspec/utils/data_transformation.py +3 -2
  172. sqlspec/utils/deprecation.py +9 -8
  173. sqlspec/utils/fixtures.py +4 -4
  174. sqlspec/utils/logging.py +46 -6
  175. sqlspec/utils/module_loader.py +2 -2
  176. sqlspec/utils/schema.py +288 -0
  177. sqlspec/utils/serializers.py +50 -2
  178. sqlspec/utils/sync_tools.py +21 -17
  179. sqlspec/utils/text.py +1 -2
  180. sqlspec/utils/type_guards.py +111 -20
  181. sqlspec/utils/version.py +433 -0
  182. {sqlspec-0.25.0.dist-info → sqlspec-0.27.0.dist-info}/METADATA +40 -21
  183. sqlspec-0.27.0.dist-info/RECORD +207 -0
  184. sqlspec/builder/mixins/__init__.py +0 -55
  185. sqlspec/builder/mixins/_cte_and_set_ops.py +0 -254
  186. sqlspec/builder/mixins/_delete_operations.py +0 -50
  187. sqlspec/builder/mixins/_insert_operations.py +0 -282
  188. sqlspec/builder/mixins/_join_operations.py +0 -389
  189. sqlspec/builder/mixins/_merge_operations.py +0 -592
  190. sqlspec/builder/mixins/_order_limit_operations.py +0 -152
  191. sqlspec/builder/mixins/_pivot_operations.py +0 -157
  192. sqlspec/builder/mixins/_select_operations.py +0 -936
  193. sqlspec/builder/mixins/_update_operations.py +0 -218
  194. sqlspec/builder/mixins/_where_clause.py +0 -1304
  195. sqlspec-0.25.0.dist-info/RECORD +0 -139
  196. sqlspec-0.25.0.dist-info/licenses/NOTICE +0 -29
  197. {sqlspec-0.25.0.dist-info → sqlspec-0.27.0.dist-info}/WHEEL +0 -0
  198. {sqlspec-0.25.0.dist-info → sqlspec-0.27.0.dist-info}/entry_points.txt +0 -0
  199. {sqlspec-0.25.0.dist-info → sqlspec-0.27.0.dist-info}/licenses/LICENSE +0 -0
@@ -6,23 +6,28 @@ from files using aiosql while using SQLSpec's features for execution and type ma
6
6
  """
7
7
 
8
8
  import logging
9
- from collections.abc import AsyncGenerator, Generator
10
- from contextlib import asynccontextmanager, contextmanager
11
- from typing import TYPE_CHECKING, Any, ClassVar, Optional, TypeVar, Union, cast
9
+ from collections.abc import Generator
10
+ from contextlib import contextmanager
11
+ from typing import Any, ClassVar, Generic, TypeVar
12
12
 
13
13
  from sqlspec.core.result import SQLResult
14
14
  from sqlspec.core.statement import SQL, StatementConfig
15
+ from sqlspec.driver import AsyncDriverAdapterBase, SyncDriverAdapterBase
15
16
  from sqlspec.exceptions import MissingDependencyError
16
- from sqlspec.typing import AIOSQL_INSTALLED
17
-
18
- if TYPE_CHECKING:
19
- from sqlspec.driver import AsyncDriverAdapterBase, SyncDriverAdapterBase
17
+ from sqlspec.typing import (
18
+ AIOSQL_INSTALLED,
19
+ AiosqlAsyncProtocol,
20
+ AiosqlParamType,
21
+ AiosqlSQLOperationType,
22
+ AiosqlSyncProtocol,
23
+ )
20
24
 
21
25
  logger = logging.getLogger("sqlspec.extensions.aiosql")
22
26
 
23
27
  __all__ = ("AiosqlAsyncAdapter", "AiosqlSyncAdapter")
24
28
 
25
29
  T = TypeVar("T")
30
+ DriverT = TypeVar("DriverT", bound="SyncDriverAdapterBase | AsyncDriverAdapterBase")
26
31
 
27
32
 
28
33
  class AsyncCursorLike:
@@ -34,7 +39,7 @@ class AsyncCursorLike:
34
39
  return list(self.result.data)
35
40
  return []
36
41
 
37
- async def fetchone(self) -> Optional[Any]:
42
+ async def fetchone(self) -> Any | None:
38
43
  rows = await self.fetchall()
39
44
  return rows[0] if rows else None
40
45
 
@@ -48,7 +53,7 @@ class CursorLike:
48
53
  return list(self.result.data)
49
54
  return []
50
55
 
51
- def fetchone(self) -> Optional[Any]:
56
+ def fetchone(self) -> Any | None:
52
57
  rows = self.fetchall()
53
58
  return rows[0] if rows else None
54
59
 
@@ -59,7 +64,7 @@ def _check_aiosql_available() -> None:
59
64
  raise MissingDependencyError(msg, "aiosql")
60
65
 
61
66
 
62
- def _normalize_dialect(dialect: "Union[str, Any, None]") -> str:
67
+ def _normalize_dialect(dialect: "str | Any | None") -> str:
63
68
  """Normalize dialect name for SQLGlot compatibility.
64
69
 
65
70
  Args:
@@ -71,12 +76,12 @@ def _normalize_dialect(dialect: "Union[str, Any, None]") -> str:
71
76
  if dialect is None:
72
77
  return "sql"
73
78
 
74
- if hasattr(dialect, "__name__"):
75
- dialect_str = str(dialect.__name__).lower() # pyright: ignore
76
- elif hasattr(dialect, "name"):
77
- dialect_str = str(dialect.name).lower() # pyright: ignore
78
- elif isinstance(dialect, str):
79
+ if isinstance(dialect, str):
79
80
  dialect_str = dialect.lower()
81
+ elif hasattr(dialect, "__name__"):
82
+ dialect_str = str(dialect.__name__).lower()
83
+ elif hasattr(dialect, "name"):
84
+ dialect_str = str(dialect.name).lower()
80
85
  else:
81
86
  dialect_str = str(dialect).lower()
82
87
 
@@ -91,19 +96,19 @@ def _normalize_dialect(dialect: "Union[str, Any, None]") -> str:
91
96
  return dialect_mapping.get(dialect_str, dialect_str)
92
97
 
93
98
 
94
- class _AiosqlAdapterBase:
99
+ class _AiosqlAdapterBase(Generic[DriverT]):
95
100
  """Base adapter class providing common functionality for aiosql integration."""
96
101
 
97
- def __init__(self, driver: "Union[SyncDriverAdapterBase, AsyncDriverAdapterBase]") -> None:
102
+ def __init__(self, driver: DriverT) -> None:
98
103
  """Initialize the base adapter.
99
104
 
100
105
  Args:
101
106
  driver: SQLSpec driver to use for execution.
102
107
  """
103
108
  _check_aiosql_available()
104
- self.driver = driver
109
+ self.driver: DriverT = driver
105
110
 
106
- def process_sql(self, query_name: str, op_type: "Any", sql: str) -> str:
111
+ def process_sql(self, query_name: str, op_type: "AiosqlSQLOperationType", sql: str) -> str:
107
112
  """Process SQL string for aiosql compatibility.
108
113
 
109
114
  Args:
@@ -116,7 +121,7 @@ class _AiosqlAdapterBase:
116
121
  """
117
122
  return sql
118
123
 
119
- def _create_sql_object(self, sql: str, parameters: "Any" = None) -> SQL:
124
+ def _create_sql_object(self, sql: str, parameters: "AiosqlParamType" = None) -> SQL:
120
125
  """Create SQL object with proper configuration.
121
126
 
122
127
  Args:
@@ -134,7 +139,7 @@ class _AiosqlAdapterBase:
134
139
  )
135
140
 
136
141
 
137
- class AiosqlSyncAdapter(_AiosqlAdapterBase):
142
+ class AiosqlSyncAdapter(_AiosqlAdapterBase[SyncDriverAdapterBase], AiosqlSyncProtocol):
138
143
  """Synchronous adapter that implements aiosql protocol using SQLSpec drivers.
139
144
 
140
145
  This adapter bridges aiosql's synchronous driver protocol with SQLSpec's sync drivers,
@@ -152,7 +157,7 @@ class AiosqlSyncAdapter(_AiosqlAdapterBase):
152
157
  super().__init__(driver)
153
158
 
154
159
  def select(
155
- self, conn: Any, query_name: str, sql: str, parameters: "Any", record_class: Optional[Any] = None
160
+ self, conn: Any, query_name: str, sql: str, parameters: "AiosqlParamType", record_class: Any | None = None
156
161
  ) -> Generator[Any, None, None]:
157
162
  """Execute a SELECT query and return results as generator.
158
163
 
@@ -182,8 +187,8 @@ class AiosqlSyncAdapter(_AiosqlAdapterBase):
182
187
  yield from result.data
183
188
 
184
189
  def select_one(
185
- self, conn: Any, query_name: str, sql: str, parameters: "Any", record_class: Optional[Any] = None
186
- ) -> Optional[dict[str, Any]]:
190
+ self, conn: Any, query_name: str, sql: str, parameters: "AiosqlParamType", record_class: Any | None = None
191
+ ) -> tuple[Any, ...] | None:
187
192
  """Execute a SELECT query and return first result.
188
193
 
189
194
  Args:
@@ -206,13 +211,14 @@ class AiosqlSyncAdapter(_AiosqlAdapterBase):
206
211
  "Use schema_type in driver.execute or _sqlspec_schema_type in parameters."
207
212
  )
208
213
 
209
- result = cast("SQLResult", self.driver.execute(self._create_sql_object(sql, parameters), connection=conn))
214
+ result = self.driver.execute(self._create_sql_object(sql, parameters), connection=conn)
210
215
 
211
216
  if hasattr(result, "data") and result.data and isinstance(result, SQLResult):
212
- return cast("Optional[dict[str, Any]]", result.data[0])
217
+ row = result.data[0]
218
+ return tuple(row.values()) if isinstance(row, dict) else row
213
219
  return None
214
220
 
215
- def select_value(self, conn: Any, query_name: str, sql: str, parameters: "Any") -> Optional[Any]:
221
+ def select_value(self, conn: Any, query_name: str, sql: str, parameters: "AiosqlParamType") -> Any | None:
216
222
  """Execute a SELECT query and return first value of first row.
217
223
 
218
224
  Args:
@@ -235,7 +241,9 @@ class AiosqlSyncAdapter(_AiosqlAdapterBase):
235
241
  return row
236
242
 
237
243
  @contextmanager
238
- def select_cursor(self, conn: Any, query_name: str, sql: str, parameters: "Any") -> Generator[Any, None, None]:
244
+ def select_cursor(
245
+ self, conn: Any, query_name: str, sql: str, parameters: "AiosqlParamType"
246
+ ) -> Generator[Any, None, None]:
239
247
  """Execute a SELECT query and return cursor context manager.
240
248
 
241
249
  Args:
@@ -251,7 +259,7 @@ class AiosqlSyncAdapter(_AiosqlAdapterBase):
251
259
 
252
260
  yield CursorLike(result)
253
261
 
254
- def insert_update_delete(self, conn: Any, query_name: str, sql: str, parameters: "Any") -> int:
262
+ def insert_update_delete(self, conn: Any, query_name: str, sql: str, parameters: "AiosqlParamType") -> int:
255
263
  """Execute INSERT/UPDATE/DELETE and return affected rows.
256
264
 
257
265
  Args:
@@ -263,11 +271,11 @@ class AiosqlSyncAdapter(_AiosqlAdapterBase):
263
271
  Returns:
264
272
  Number of affected rows
265
273
  """
266
- result = cast("SQLResult", self.driver.execute(self._create_sql_object(sql, parameters), connection=conn))
274
+ result = self.driver.execute(self._create_sql_object(sql, parameters), connection=conn)
267
275
 
268
276
  return result.rows_affected if hasattr(result, "rows_affected") else 0
269
277
 
270
- def insert_update_delete_many(self, conn: Any, query_name: str, sql: str, parameters: "Any") -> int:
278
+ def insert_update_delete_many(self, conn: Any, query_name: str, sql: str, parameters: "AiosqlParamType") -> int:
271
279
  """Execute INSERT/UPDATE/DELETE with many parameter sets.
272
280
 
273
281
  Args:
@@ -279,13 +287,11 @@ class AiosqlSyncAdapter(_AiosqlAdapterBase):
279
287
  Returns:
280
288
  Number of affected rows
281
289
  """
282
- result = cast(
283
- "SQLResult", self.driver.execute_many(self._create_sql_object(sql), parameters=parameters, connection=conn)
284
- )
290
+ result = self.driver.execute(self._create_sql_object(sql, parameters), connection=conn)
285
291
 
286
292
  return result.rows_affected if hasattr(result, "rows_affected") else 0
287
293
 
288
- def insert_returning(self, conn: Any, query_name: str, sql: str, parameters: "Any") -> Optional[Any]:
294
+ def insert_returning(self, conn: Any, query_name: str, sql: str, parameters: "AiosqlParamType") -> Any | None:
289
295
  """Execute INSERT with RETURNING and return result.
290
296
 
291
297
  Args:
@@ -300,7 +306,18 @@ class AiosqlSyncAdapter(_AiosqlAdapterBase):
300
306
  return self.select_one(conn, query_name, sql, parameters)
301
307
 
302
308
 
303
- class AiosqlAsyncAdapter(_AiosqlAdapterBase):
309
+ class _AsyncCursorContextManager(Generic[T]):
310
+ def __init__(self, cursor_like: T) -> None:
311
+ self._cursor_like = cursor_like
312
+
313
+ async def __aenter__(self) -> T:
314
+ return self._cursor_like
315
+
316
+ async def __aexit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
317
+ pass
318
+
319
+
320
+ class AiosqlAsyncAdapter(_AiosqlAdapterBase[AsyncDriverAdapterBase], AiosqlAsyncProtocol):
304
321
  """Asynchronous adapter that implements aiosql protocol using SQLSpec drivers.
305
322
 
306
323
  This adapter bridges aiosql's async driver protocol with SQLSpec's async drivers,
@@ -318,7 +335,7 @@ class AiosqlAsyncAdapter(_AiosqlAdapterBase):
318
335
  super().__init__(driver)
319
336
 
320
337
  async def select(
321
- self, conn: Any, query_name: str, sql: str, parameters: "Any", record_class: Optional[Any] = None
338
+ self, conn: Any, query_name: str, sql: str, parameters: "AiosqlParamType", record_class: Any | None = None
322
339
  ) -> list[Any]:
323
340
  """Execute a SELECT query and return results as list.
324
341
 
@@ -342,15 +359,15 @@ class AiosqlAsyncAdapter(_AiosqlAdapterBase):
342
359
  "Use schema_type in driver.execute or _sqlspec_schema_type in parameters."
343
360
  )
344
361
 
345
- result = await self.driver.execute(self._create_sql_object(sql, parameters), connection=conn) # type: ignore[misc]
362
+ result = await self.driver.execute(self._create_sql_object(sql, parameters), connection=conn)
346
363
 
347
364
  if hasattr(result, "data") and result.data is not None and isinstance(result, SQLResult):
348
365
  return list(result.data)
349
366
  return []
350
367
 
351
368
  async def select_one(
352
- self, conn: Any, query_name: str, sql: str, parameters: "Any", record_class: Optional[Any] = None
353
- ) -> Optional[Any]:
369
+ self, conn: Any, query_name: str, sql: str, parameters: "AiosqlParamType", record_class: Any | None = None
370
+ ) -> Any | None:
354
371
  """Execute a SELECT query and return first result.
355
372
 
356
373
  Args:
@@ -373,13 +390,14 @@ class AiosqlAsyncAdapter(_AiosqlAdapterBase):
373
390
  "Use schema_type in driver.execute or _sqlspec_schema_type in parameters."
374
391
  )
375
392
 
376
- result = await self.driver.execute(self._create_sql_object(sql, parameters), connection=conn) # type: ignore[misc]
393
+ result = await self.driver.execute(self._create_sql_object(sql, parameters), connection=conn)
377
394
 
378
395
  if hasattr(result, "data") and result.data and isinstance(result, SQLResult):
379
- return result.data[0]
396
+ row = result.data[0]
397
+ return tuple(row.values()) if isinstance(row, dict) else row
380
398
  return None
381
399
 
382
- async def select_value(self, conn: Any, query_name: str, sql: str, parameters: "Any") -> Optional[Any]:
400
+ async def select_value(self, conn: Any, query_name: str, sql: str, parameters: "AiosqlParamType") -> Any | None:
383
401
  """Execute a SELECT query and return first value of first row.
384
402
 
385
403
  Args:
@@ -401,8 +419,9 @@ class AiosqlAsyncAdapter(_AiosqlAdapterBase):
401
419
  return row[0] if len(row) > 0 else None
402
420
  return row
403
421
 
404
- @asynccontextmanager
405
- async def select_cursor(self, conn: Any, query_name: str, sql: str, parameters: "Any") -> AsyncGenerator[Any, None]:
422
+ async def select_cursor(
423
+ self, conn: Any, query_name: str, sql: str, parameters: "AiosqlParamType"
424
+ ) -> "_AsyncCursorContextManager[Any]":
406
425
  """Execute a SELECT query and return cursor context manager.
407
426
 
408
427
  Args:
@@ -414,11 +433,10 @@ class AiosqlAsyncAdapter(_AiosqlAdapterBase):
414
433
  Yields:
415
434
  Cursor-like object with results
416
435
  """
417
- result = await self.driver.execute(self._create_sql_object(sql, parameters), connection=conn) # type: ignore[misc]
418
-
419
- yield AsyncCursorLike(result)
436
+ result = await self.driver.execute(self._create_sql_object(sql, parameters), connection=conn)
437
+ return _AsyncCursorContextManager(AsyncCursorLike(result))
420
438
 
421
- async def insert_update_delete(self, conn: Any, query_name: str, sql: str, parameters: "Any") -> None:
439
+ async def insert_update_delete(self, conn: Any, query_name: str, sql: str, parameters: "AiosqlParamType") -> None:
422
440
  """Execute INSERT/UPDATE/DELETE.
423
441
 
424
442
  Args:
@@ -430,9 +448,11 @@ class AiosqlAsyncAdapter(_AiosqlAdapterBase):
430
448
  Note:
431
449
  Returns None per aiosql async protocol
432
450
  """
433
- await self.driver.execute(self._create_sql_object(sql, parameters), connection=conn) # type: ignore[misc]
451
+ await self.driver.execute(self._create_sql_object(sql, parameters), connection=conn)
434
452
 
435
- async def insert_update_delete_many(self, conn: Any, query_name: str, sql: str, parameters: "Any") -> None:
453
+ async def insert_update_delete_many(
454
+ self, conn: Any, query_name: str, sql: str, parameters: "AiosqlParamType"
455
+ ) -> None:
436
456
  """Execute INSERT/UPDATE/DELETE with many parameter sets.
437
457
 
438
458
  Args:
@@ -444,9 +464,9 @@ class AiosqlAsyncAdapter(_AiosqlAdapterBase):
444
464
  Note:
445
465
  Returns None per aiosql async protocol
446
466
  """
447
- await self.driver.execute_many(self._create_sql_object(sql), parameters=parameters, connection=conn) # type: ignore[misc]
467
+ await self.driver.execute(self._create_sql_object(sql, parameters), connection=conn)
448
468
 
449
- async def insert_returning(self, conn: Any, query_name: str, sql: str, parameters: "Any") -> Optional[Any]:
469
+ async def insert_returning(self, conn: Any, query_name: str, sql: str, parameters: "AiosqlParamType") -> Any | None:
450
470
  """Execute INSERT with RETURNING and return result.
451
471
 
452
472
  Args:
@@ -1,6 +1,23 @@
1
- from sqlspec.extensions.litestar import handlers, providers
2
1
  from sqlspec.extensions.litestar.cli import database_group
3
- from sqlspec.extensions.litestar.config import DatabaseConfig
4
- from sqlspec.extensions.litestar.plugin import SQLSpec
2
+ from sqlspec.extensions.litestar.config import LitestarConfig
3
+ from sqlspec.extensions.litestar.plugin import (
4
+ DEFAULT_COMMIT_MODE,
5
+ DEFAULT_CONNECTION_KEY,
6
+ DEFAULT_POOL_KEY,
7
+ DEFAULT_SESSION_KEY,
8
+ CommitMode,
9
+ SQLSpecPlugin,
10
+ )
11
+ from sqlspec.extensions.litestar.store import BaseSQLSpecStore
5
12
 
6
- __all__ = ("DatabaseConfig", "SQLSpec", "database_group", "handlers", "providers")
13
+ __all__ = (
14
+ "DEFAULT_COMMIT_MODE",
15
+ "DEFAULT_CONNECTION_KEY",
16
+ "DEFAULT_POOL_KEY",
17
+ "DEFAULT_SESSION_KEY",
18
+ "BaseSQLSpecStore",
19
+ "CommitMode",
20
+ "LitestarConfig",
21
+ "SQLSpecPlugin",
22
+ "database_group",
23
+ )
@@ -3,22 +3,18 @@
3
3
  from contextlib import suppress
4
4
  from typing import TYPE_CHECKING
5
5
 
6
+ import rich_click as click
6
7
  from litestar.cli._utils import LitestarGroup
7
8
 
8
9
  from sqlspec.cli import add_migration_commands
9
10
 
10
- try:
11
- import rich_click as click
12
- except ImportError:
13
- import click # type: ignore[no-redef]
14
-
15
11
  if TYPE_CHECKING:
16
12
  from litestar import Litestar
17
13
 
18
- from sqlspec.extensions.litestar.plugin import SQLSpec
14
+ from sqlspec.extensions.litestar.plugin import SQLSpecPlugin
19
15
 
20
16
 
21
- def get_database_migration_plugin(app: "Litestar") -> "SQLSpec":
17
+ def get_database_migration_plugin(app: "Litestar") -> "SQLSpecPlugin":
22
18
  """Retrieve the SQLSpec plugin from the Litestar application's plugins.
23
19
 
24
20
  Args:
@@ -31,18 +27,66 @@ def get_database_migration_plugin(app: "Litestar") -> "SQLSpec":
31
27
  ImproperConfigurationError: If the SQLSpec plugin is not found
32
28
  """
33
29
  from sqlspec.exceptions import ImproperConfigurationError
34
- from sqlspec.extensions.litestar.plugin import SQLSpec
30
+ from sqlspec.extensions.litestar.plugin import SQLSpecPlugin
35
31
 
36
32
  with suppress(KeyError):
37
- return app.plugins.get(SQLSpec)
33
+ return app.plugins.get(SQLSpecPlugin)
38
34
  msg = "Failed to initialize database migrations. The required SQLSpec plugin is missing."
39
35
  raise ImproperConfigurationError(msg)
40
36
 
41
37
 
42
- @click.group(cls=LitestarGroup, name="db")
38
+ @click.group(cls=LitestarGroup, name="db", aliases=["database"])
43
39
  def database_group(ctx: "click.Context") -> None:
44
40
  """Manage SQLSpec database components."""
45
41
  ctx.obj = {"app": ctx.obj, "configs": get_database_migration_plugin(ctx.obj.app).config}
46
42
 
47
43
 
48
44
  add_migration_commands(database_group)
45
+
46
+
47
+ def add_sessions_delete_expired_command() -> None:
48
+ """Add delete-expired command to Litestar's sessions CLI group."""
49
+ try:
50
+ from litestar.cli._utils import console
51
+ from litestar.cli.commands.sessions import get_session_backend, sessions_group
52
+ except ImportError:
53
+ return
54
+
55
+ @sessions_group.command("delete-expired") # type: ignore[misc]
56
+ @click.option(
57
+ "--verbose", is_flag=True, default=False, help="Show detailed information about the cleanup operation"
58
+ )
59
+ def delete_expired_sessions_command(app: "Litestar", verbose: bool) -> None:
60
+ """Delete expired sessions from the session store.
61
+
62
+ This command removes all sessions that have passed their expiration time.
63
+ It can be scheduled via cron or systemd timers for automatic maintenance.
64
+
65
+ Examples:
66
+ litestar sessions delete-expired
67
+ litestar sessions delete-expired --verbose
68
+ """
69
+ import anyio
70
+
71
+ backend = get_session_backend(app)
72
+ store = backend.config.get_store_from_app(app)
73
+
74
+ if not hasattr(store, "delete_expired"):
75
+ console.print(f"[red]{type(store).__name__} does not support deleting expired sessions")
76
+ return
77
+
78
+ async def _delete_expired() -> int:
79
+ return await store.delete_expired() # type: ignore[no-any-return]
80
+
81
+ count = anyio.run(_delete_expired)
82
+
83
+ if count > 0:
84
+ if verbose:
85
+ console.print(f"[green]Successfully deleted {count} expired session(s)")
86
+ else:
87
+ console.print(f"[green]Deleted {count} expired session(s)")
88
+ else:
89
+ console.print("[yellow]No expired sessions found")
90
+
91
+
92
+ add_sessions_delete_expired_command()