sqlspec 0.14.1__py3-none-any.whl → 0.16.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 (159) hide show
  1. sqlspec/__init__.py +50 -25
  2. sqlspec/__main__.py +1 -1
  3. sqlspec/__metadata__.py +1 -3
  4. sqlspec/_serialization.py +1 -2
  5. sqlspec/_sql.py +480 -121
  6. sqlspec/_typing.py +278 -142
  7. sqlspec/adapters/adbc/__init__.py +4 -3
  8. sqlspec/adapters/adbc/_types.py +12 -0
  9. sqlspec/adapters/adbc/config.py +115 -260
  10. sqlspec/adapters/adbc/driver.py +462 -367
  11. sqlspec/adapters/aiosqlite/__init__.py +18 -3
  12. sqlspec/adapters/aiosqlite/_types.py +13 -0
  13. sqlspec/adapters/aiosqlite/config.py +199 -129
  14. sqlspec/adapters/aiosqlite/driver.py +230 -269
  15. sqlspec/adapters/asyncmy/__init__.py +18 -3
  16. sqlspec/adapters/asyncmy/_types.py +12 -0
  17. sqlspec/adapters/asyncmy/config.py +80 -168
  18. sqlspec/adapters/asyncmy/driver.py +260 -225
  19. sqlspec/adapters/asyncpg/__init__.py +19 -4
  20. sqlspec/adapters/asyncpg/_types.py +17 -0
  21. sqlspec/adapters/asyncpg/config.py +82 -181
  22. sqlspec/adapters/asyncpg/driver.py +285 -383
  23. sqlspec/adapters/bigquery/__init__.py +17 -3
  24. sqlspec/adapters/bigquery/_types.py +12 -0
  25. sqlspec/adapters/bigquery/config.py +191 -258
  26. sqlspec/adapters/bigquery/driver.py +474 -646
  27. sqlspec/adapters/duckdb/__init__.py +14 -3
  28. sqlspec/adapters/duckdb/_types.py +12 -0
  29. sqlspec/adapters/duckdb/config.py +415 -351
  30. sqlspec/adapters/duckdb/driver.py +343 -413
  31. sqlspec/adapters/oracledb/__init__.py +19 -5
  32. sqlspec/adapters/oracledb/_types.py +14 -0
  33. sqlspec/adapters/oracledb/config.py +123 -379
  34. sqlspec/adapters/oracledb/driver.py +507 -560
  35. sqlspec/adapters/psqlpy/__init__.py +13 -3
  36. sqlspec/adapters/psqlpy/_types.py +11 -0
  37. sqlspec/adapters/psqlpy/config.py +93 -254
  38. sqlspec/adapters/psqlpy/driver.py +505 -234
  39. sqlspec/adapters/psycopg/__init__.py +19 -5
  40. sqlspec/adapters/psycopg/_types.py +17 -0
  41. sqlspec/adapters/psycopg/config.py +143 -403
  42. sqlspec/adapters/psycopg/driver.py +706 -872
  43. sqlspec/adapters/sqlite/__init__.py +14 -3
  44. sqlspec/adapters/sqlite/_types.py +11 -0
  45. sqlspec/adapters/sqlite/config.py +202 -118
  46. sqlspec/adapters/sqlite/driver.py +264 -303
  47. sqlspec/base.py +105 -9
  48. sqlspec/{statement/builder → builder}/__init__.py +12 -14
  49. sqlspec/{statement/builder → builder}/_base.py +120 -55
  50. sqlspec/{statement/builder → builder}/_column.py +17 -6
  51. sqlspec/{statement/builder → builder}/_ddl.py +46 -79
  52. sqlspec/{statement/builder → builder}/_ddl_utils.py +5 -10
  53. sqlspec/{statement/builder → builder}/_delete.py +6 -25
  54. sqlspec/{statement/builder → builder}/_insert.py +18 -65
  55. sqlspec/builder/_merge.py +56 -0
  56. sqlspec/{statement/builder → builder}/_parsing_utils.py +8 -11
  57. sqlspec/{statement/builder → builder}/_select.py +11 -56
  58. sqlspec/{statement/builder → builder}/_update.py +12 -18
  59. sqlspec/{statement/builder → builder}/mixins/__init__.py +10 -14
  60. sqlspec/{statement/builder → builder}/mixins/_cte_and_set_ops.py +48 -59
  61. sqlspec/{statement/builder → builder}/mixins/_insert_operations.py +34 -18
  62. sqlspec/{statement/builder → builder}/mixins/_join_operations.py +1 -3
  63. sqlspec/{statement/builder → builder}/mixins/_merge_operations.py +19 -9
  64. sqlspec/{statement/builder → builder}/mixins/_order_limit_operations.py +3 -3
  65. sqlspec/{statement/builder → builder}/mixins/_pivot_operations.py +4 -8
  66. sqlspec/{statement/builder → builder}/mixins/_select_operations.py +25 -38
  67. sqlspec/{statement/builder → builder}/mixins/_update_operations.py +15 -16
  68. sqlspec/{statement/builder → builder}/mixins/_where_clause.py +210 -137
  69. sqlspec/cli.py +4 -5
  70. sqlspec/config.py +180 -133
  71. sqlspec/core/__init__.py +63 -0
  72. sqlspec/core/cache.py +873 -0
  73. sqlspec/core/compiler.py +396 -0
  74. sqlspec/core/filters.py +830 -0
  75. sqlspec/core/hashing.py +310 -0
  76. sqlspec/core/parameters.py +1209 -0
  77. sqlspec/core/result.py +664 -0
  78. sqlspec/{statement → core}/splitter.py +321 -191
  79. sqlspec/core/statement.py +666 -0
  80. sqlspec/driver/__init__.py +7 -10
  81. sqlspec/driver/_async.py +387 -176
  82. sqlspec/driver/_common.py +527 -289
  83. sqlspec/driver/_sync.py +390 -172
  84. sqlspec/driver/mixins/__init__.py +2 -19
  85. sqlspec/driver/mixins/_result_tools.py +164 -0
  86. sqlspec/driver/mixins/_sql_translator.py +6 -3
  87. sqlspec/exceptions.py +5 -252
  88. sqlspec/extensions/aiosql/adapter.py +93 -96
  89. sqlspec/extensions/litestar/cli.py +1 -1
  90. sqlspec/extensions/litestar/config.py +0 -1
  91. sqlspec/extensions/litestar/handlers.py +15 -26
  92. sqlspec/extensions/litestar/plugin.py +18 -16
  93. sqlspec/extensions/litestar/providers.py +17 -52
  94. sqlspec/loader.py +424 -105
  95. sqlspec/migrations/__init__.py +12 -0
  96. sqlspec/migrations/base.py +92 -68
  97. sqlspec/migrations/commands.py +24 -106
  98. sqlspec/migrations/loaders.py +402 -0
  99. sqlspec/migrations/runner.py +49 -51
  100. sqlspec/migrations/tracker.py +31 -44
  101. sqlspec/migrations/utils.py +64 -24
  102. sqlspec/protocols.py +7 -183
  103. sqlspec/storage/__init__.py +1 -1
  104. sqlspec/storage/backends/base.py +37 -40
  105. sqlspec/storage/backends/fsspec.py +136 -112
  106. sqlspec/storage/backends/obstore.py +138 -160
  107. sqlspec/storage/capabilities.py +5 -4
  108. sqlspec/storage/registry.py +57 -106
  109. sqlspec/typing.py +136 -115
  110. sqlspec/utils/__init__.py +2 -3
  111. sqlspec/utils/correlation.py +0 -3
  112. sqlspec/utils/deprecation.py +6 -6
  113. sqlspec/utils/fixtures.py +6 -6
  114. sqlspec/utils/logging.py +0 -2
  115. sqlspec/utils/module_loader.py +7 -12
  116. sqlspec/utils/singleton.py +0 -1
  117. sqlspec/utils/sync_tools.py +17 -38
  118. sqlspec/utils/text.py +12 -51
  119. sqlspec/utils/type_guards.py +443 -232
  120. {sqlspec-0.14.1.dist-info → sqlspec-0.16.0.dist-info}/METADATA +7 -2
  121. sqlspec-0.16.0.dist-info/RECORD +134 -0
  122. sqlspec/adapters/adbc/transformers.py +0 -108
  123. sqlspec/driver/connection.py +0 -207
  124. sqlspec/driver/mixins/_cache.py +0 -114
  125. sqlspec/driver/mixins/_csv_writer.py +0 -91
  126. sqlspec/driver/mixins/_pipeline.py +0 -508
  127. sqlspec/driver/mixins/_query_tools.py +0 -796
  128. sqlspec/driver/mixins/_result_utils.py +0 -138
  129. sqlspec/driver/mixins/_storage.py +0 -912
  130. sqlspec/driver/mixins/_type_coercion.py +0 -128
  131. sqlspec/driver/parameters.py +0 -138
  132. sqlspec/statement/__init__.py +0 -21
  133. sqlspec/statement/builder/_merge.py +0 -95
  134. sqlspec/statement/cache.py +0 -50
  135. sqlspec/statement/filters.py +0 -625
  136. sqlspec/statement/parameters.py +0 -956
  137. sqlspec/statement/pipelines/__init__.py +0 -210
  138. sqlspec/statement/pipelines/analyzers/__init__.py +0 -9
  139. sqlspec/statement/pipelines/analyzers/_analyzer.py +0 -646
  140. sqlspec/statement/pipelines/context.py +0 -109
  141. sqlspec/statement/pipelines/transformers/__init__.py +0 -7
  142. sqlspec/statement/pipelines/transformers/_expression_simplifier.py +0 -88
  143. sqlspec/statement/pipelines/transformers/_literal_parameterizer.py +0 -1247
  144. sqlspec/statement/pipelines/transformers/_remove_comments_and_hints.py +0 -76
  145. sqlspec/statement/pipelines/validators/__init__.py +0 -23
  146. sqlspec/statement/pipelines/validators/_dml_safety.py +0 -290
  147. sqlspec/statement/pipelines/validators/_parameter_style.py +0 -370
  148. sqlspec/statement/pipelines/validators/_performance.py +0 -714
  149. sqlspec/statement/pipelines/validators/_security.py +0 -967
  150. sqlspec/statement/result.py +0 -435
  151. sqlspec/statement/sql.py +0 -1774
  152. sqlspec/utils/cached_property.py +0 -25
  153. sqlspec/utils/statement_hashing.py +0 -203
  154. sqlspec-0.14.1.dist-info/RECORD +0 -145
  155. /sqlspec/{statement/builder → builder}/mixins/_delete_operations.py +0 -0
  156. {sqlspec-0.14.1.dist-info → sqlspec-0.16.0.dist-info}/WHEEL +0 -0
  157. {sqlspec-0.14.1.dist-info → sqlspec-0.16.0.dist-info}/entry_points.txt +0 -0
  158. {sqlspec-0.14.1.dist-info → sqlspec-0.16.0.dist-info}/licenses/LICENSE +0 -0
  159. {sqlspec-0.14.1.dist-info → sqlspec-0.16.0.dist-info}/licenses/NOTICE +0 -0
@@ -10,13 +10,13 @@ from collections.abc import AsyncGenerator, Generator
10
10
  from contextlib import asynccontextmanager, contextmanager
11
11
  from typing import TYPE_CHECKING, Any, ClassVar, Optional, TypeVar, Union, cast
12
12
 
13
+ from sqlspec.core.result import SQLResult
14
+ from sqlspec.core.statement import SQL, StatementConfig
13
15
  from sqlspec.exceptions import MissingDependencyError
14
- from sqlspec.statement.result import SQLResult
15
- from sqlspec.statement.sql import SQL, SQLConfig
16
- from sqlspec.typing import AIOSQL_INSTALLED, RowT
16
+ from sqlspec.typing import AIOSQL_INSTALLED
17
17
 
18
18
  if TYPE_CHECKING:
19
- from sqlspec.driver import AsyncDriverAdapterProtocol, SyncDriverAdapterProtocol
19
+ from sqlspec.driver import AsyncDriverAdapterBase, SyncDriverAdapterBase
20
20
 
21
21
  logger = logging.getLogger("sqlspec.extensions.aiosql")
22
22
 
@@ -25,6 +25,34 @@ __all__ = ("AiosqlAsyncAdapter", "AiosqlSyncAdapter")
25
25
  T = TypeVar("T")
26
26
 
27
27
 
28
+ class AsyncCursorLike:
29
+ def __init__(self, result: Any) -> None:
30
+ self.result = result
31
+
32
+ async def fetchall(self) -> list[Any]:
33
+ if isinstance(self.result, SQLResult) and self.result.data is not None:
34
+ return list(self.result.data)
35
+ return []
36
+
37
+ async def fetchone(self) -> Optional[Any]:
38
+ rows = await self.fetchall()
39
+ return rows[0] if rows else None
40
+
41
+
42
+ class CursorLike:
43
+ def __init__(self, result: Any) -> None:
44
+ self.result = result
45
+
46
+ def fetchall(self) -> list[Any]:
47
+ if isinstance(self.result, SQLResult) and self.result.data is not None:
48
+ return list(self.result.data)
49
+ return []
50
+
51
+ def fetchone(self) -> Optional[Any]:
52
+ rows = self.fetchall()
53
+ return rows[0] if rows else None
54
+
55
+
28
56
  def _check_aiosql_available() -> None:
29
57
  if not AIOSQL_INSTALLED:
30
58
  msg = "aiosql"
@@ -38,21 +66,20 @@ def _normalize_dialect(dialect: "Union[str, Any, None]") -> str:
38
66
  dialect: Original dialect name (can be str, Dialect, type[Dialect], or None)
39
67
 
40
68
  Returns:
41
- converted dialect name
69
+ Converted dialect name compatible with SQLGlot
42
70
  """
43
71
  if dialect is None:
44
72
  return "sql"
45
73
 
46
- if hasattr(dialect, "__name__"): # It's a class
74
+ if hasattr(dialect, "__name__"):
47
75
  dialect_str = str(dialect.__name__).lower() # pyright: ignore
48
- elif hasattr(dialect, "name"): # It's an instance with name attribute
76
+ elif hasattr(dialect, "name"):
49
77
  dialect_str = str(dialect.name).lower() # pyright: ignore
50
78
  elif isinstance(dialect, str):
51
79
  dialect_str = dialect.lower()
52
80
  else:
53
81
  dialect_str = str(dialect).lower()
54
82
 
55
- # Map common dialect aliases to SQLGlot names
56
83
  dialect_mapping = {
57
84
  "postgresql": "postgres",
58
85
  "psycopg": "postgres",
@@ -65,11 +92,9 @@ def _normalize_dialect(dialect: "Union[str, Any, None]") -> str:
65
92
 
66
93
 
67
94
  class _AiosqlAdapterBase:
68
- """Base adapter for common logic."""
95
+ """Base adapter class providing common functionality for aiosql integration."""
69
96
 
70
- def __init__(
71
- self, driver: "Union[SyncDriverAdapterProtocol[Any, Any], AsyncDriverAdapterProtocol[Any, Any]]"
72
- ) -> None:
97
+ def __init__(self, driver: "Union[SyncDriverAdapterBase, AsyncDriverAdapterBase]") -> None:
73
98
  """Initialize the base adapter.
74
99
 
75
100
  Args:
@@ -79,27 +104,46 @@ class _AiosqlAdapterBase:
79
104
  self.driver = driver
80
105
 
81
106
  def process_sql(self, query_name: str, op_type: "Any", sql: str) -> str:
82
- """Process SQL for aiosql compatibility."""
107
+ """Process SQL string for aiosql compatibility.
108
+
109
+ Args:
110
+ query_name: Name of the query
111
+ op_type: Operation type
112
+ sql: SQL string to process
113
+
114
+ Returns:
115
+ Processed SQL string
116
+ """
83
117
  return sql
84
118
 
85
119
  def _create_sql_object(self, sql: str, parameters: "Any" = None) -> SQL:
86
- """Create SQL object with proper configuration."""
87
- config = SQLConfig(enable_validation=False)
88
- converted_dialect = _normalize_dialect(self.driver.dialect)
89
- return SQL(sql, parameters, config=config, dialect=converted_dialect)
120
+ """Create SQL object with proper configuration.
90
121
 
122
+ Args:
123
+ sql: SQL string
124
+ parameters: Query parameters
91
125
 
92
- class AiosqlSyncAdapter(_AiosqlAdapterBase):
93
- """Sync adapter that implements aiosql protocol using SQLSpec drivers.
126
+ Returns:
127
+ Configured SQL object
128
+ """
129
+ return SQL(
130
+ sql,
131
+ parameters,
132
+ config=StatementConfig(enable_validation=False),
133
+ dialect=_normalize_dialect(getattr(self.driver, "dialect", "sqlite")),
134
+ )
94
135
 
95
- This adapter bridges aiosql's sync driver protocol with SQLSpec's sync drivers,
96
- enabling all of SQLSpec's drivers to work with queries loaded by aiosql.
97
136
 
137
+ class AiosqlSyncAdapter(_AiosqlAdapterBase):
138
+ """Synchronous adapter that implements aiosql protocol using SQLSpec drivers.
139
+
140
+ This adapter bridges aiosql's synchronous driver protocol with SQLSpec's sync drivers,
141
+ enabling queries loaded by aiosql to be executed with SQLSpec drivers.
98
142
  """
99
143
 
100
144
  is_aio_driver: ClassVar[bool] = False
101
145
 
102
- def __init__(self, driver: "SyncDriverAdapterProtocol[Any, Any]") -> None:
146
+ def __init__(self, driver: "SyncDriverAdapterBase") -> None:
103
147
  """Initialize the sync adapter.
104
148
 
105
149
  Args:
@@ -123,8 +167,8 @@ class AiosqlSyncAdapter(_AiosqlAdapterBase):
123
167
  Query result rows
124
168
 
125
169
  Note:
126
- record_class parameter is ignored. Use schema_type in driver.execute
127
- or _sqlspec_schema_type in parameters for type mapping.
170
+ The record_class parameter is ignored for compatibility. Use schema_type
171
+ in driver.execute or _sqlspec_schema_type in parameters for type mapping.
128
172
  """
129
173
  if record_class is not None:
130
174
  logger.warning(
@@ -132,16 +176,14 @@ class AiosqlSyncAdapter(_AiosqlAdapterBase):
132
176
  "Use schema_type in driver.execute or _sqlspec_schema_type in parameters."
133
177
  )
134
178
 
135
- sql_obj = self._create_sql_object(sql, parameters)
136
- # Execute using SQLSpec driver
137
- result = self.driver.execute(sql_obj, connection=conn)
179
+ result = self.driver.execute(self._create_sql_object(sql, parameters), connection=conn)
138
180
 
139
181
  if isinstance(result, SQLResult) and result.data is not None:
140
182
  yield from result.data
141
183
 
142
184
  def select_one(
143
185
  self, conn: Any, query_name: str, sql: str, parameters: "Any", record_class: Optional[Any] = None
144
- ) -> Optional[RowT]:
186
+ ) -> Optional[dict[str, Any]]:
145
187
  """Execute a SELECT query and return first result.
146
188
 
147
189
  Args:
@@ -155,8 +197,8 @@ class AiosqlSyncAdapter(_AiosqlAdapterBase):
155
197
  First result row or None
156
198
 
157
199
  Note:
158
- record_class parameter is ignored. Use schema_type in driver.execute
159
- or _sqlspec_schema_type in parameters for type mapping.
200
+ The record_class parameter is ignored for compatibility. Use schema_type
201
+ in driver.execute or _sqlspec_schema_type in parameters for type mapping.
160
202
  """
161
203
  if record_class is not None:
162
204
  logger.warning(
@@ -164,12 +206,10 @@ class AiosqlSyncAdapter(_AiosqlAdapterBase):
164
206
  "Use schema_type in driver.execute or _sqlspec_schema_type in parameters."
165
207
  )
166
208
 
167
- sql_obj = self._create_sql_object(sql, parameters)
168
-
169
- result = cast("SQLResult[RowT]", self.driver.execute(sql_obj, connection=conn))
209
+ result = cast("SQLResult", self.driver.execute(self._create_sql_object(sql, parameters), connection=conn))
170
210
 
171
211
  if hasattr(result, "data") and result.data and isinstance(result, SQLResult):
172
- return cast("Optional[RowT]", result.data[0])
212
+ return cast("Optional[dict[str, Any]]", result.data[0])
173
213
  return None
174
214
 
175
215
  def select_value(self, conn: Any, query_name: str, sql: str, parameters: "Any") -> Optional[Any]:
@@ -207,21 +247,7 @@ class AiosqlSyncAdapter(_AiosqlAdapterBase):
207
247
  Yields:
208
248
  Cursor-like object with results
209
249
  """
210
- sql_obj = self._create_sql_object(sql, parameters)
211
- result = self.driver.execute(sql_obj, connection=conn)
212
-
213
- class CursorLike:
214
- def __init__(self, result: Any) -> None:
215
- self.result = result
216
-
217
- def fetchall(self) -> list[Any]:
218
- if isinstance(result, SQLResult) and result.data is not None:
219
- return list(result.data)
220
- return []
221
-
222
- def fetchone(self) -> Optional[Any]:
223
- rows = self.fetchall()
224
- return rows[0] if rows else None
250
+ result = self.driver.execute(self._create_sql_object(sql, parameters), connection=conn)
225
251
 
226
252
  yield CursorLike(result)
227
253
 
@@ -237,10 +263,8 @@ class AiosqlSyncAdapter(_AiosqlAdapterBase):
237
263
  Returns:
238
264
  Number of affected rows
239
265
  """
240
- sql_obj = self._create_sql_object(sql, parameters)
241
- result = cast("SQLResult[Any]", self.driver.execute(sql_obj, connection=conn))
266
+ result = cast("SQLResult", self.driver.execute(self._create_sql_object(sql, parameters), connection=conn))
242
267
 
243
- # SQLResult has rows_affected attribute
244
268
  return result.rows_affected if hasattr(result, "rows_affected") else 0
245
269
 
246
270
  def insert_update_delete_many(self, conn: Any, query_name: str, sql: str, parameters: "Any") -> int:
@@ -255,12 +279,10 @@ class AiosqlSyncAdapter(_AiosqlAdapterBase):
255
279
  Returns:
256
280
  Number of affected rows
257
281
  """
258
- # For executemany, we don't extract sqlspec filters from individual parameter sets
259
- sql_obj = self._create_sql_object(sql)
260
-
261
- result = cast("SQLResult[Any]", self.driver.execute_many(sql_obj, parameters=parameters, connection=conn))
282
+ result = cast(
283
+ "SQLResult", self.driver.execute_many(self._create_sql_object(sql), parameters=parameters, connection=conn)
284
+ )
262
285
 
263
- # SQLResult has rows_affected attribute
264
286
  return result.rows_affected if hasattr(result, "rows_affected") else 0
265
287
 
266
288
  def insert_returning(self, conn: Any, query_name: str, sql: str, parameters: "Any") -> Optional[Any]:
@@ -275,20 +297,19 @@ class AiosqlSyncAdapter(_AiosqlAdapterBase):
275
297
  Returns:
276
298
  Returned value or None
277
299
  """
278
- # INSERT RETURNING is treated like a select that returns data
279
300
  return self.select_one(conn, query_name, sql, parameters)
280
301
 
281
302
 
282
303
  class AiosqlAsyncAdapter(_AiosqlAdapterBase):
283
- """Async adapter that implements aiosql protocol using SQLSpec drivers.
304
+ """Asynchronous adapter that implements aiosql protocol using SQLSpec drivers.
284
305
 
285
306
  This adapter bridges aiosql's async driver protocol with SQLSpec's async drivers,
286
- enabling all of SQLSpec's features to work with queries loaded by aiosql.
307
+ enabling queries loaded by aiosql to be executed with SQLSpec async drivers.
287
308
  """
288
309
 
289
310
  is_aio_driver: ClassVar[bool] = True
290
311
 
291
- def __init__(self, driver: "AsyncDriverAdapterProtocol[Any, Any]") -> None:
312
+ def __init__(self, driver: "AsyncDriverAdapterBase") -> None:
292
313
  """Initialize the async adapter.
293
314
 
294
315
  Args:
@@ -312,8 +333,8 @@ class AiosqlAsyncAdapter(_AiosqlAdapterBase):
312
333
  List of query result rows
313
334
 
314
335
  Note:
315
- record_class parameter is ignored. Use schema_type in driver.execute
316
- or _sqlspec_schema_type in parameters for type mapping.
336
+ The record_class parameter is ignored for compatibility. Use schema_type
337
+ in driver.execute or _sqlspec_schema_type in parameters for type mapping.
317
338
  """
318
339
  if record_class is not None:
319
340
  logger.warning(
@@ -321,9 +342,7 @@ class AiosqlAsyncAdapter(_AiosqlAdapterBase):
321
342
  "Use schema_type in driver.execute or _sqlspec_schema_type in parameters."
322
343
  )
323
344
 
324
- sql_obj = self._create_sql_object(sql, parameters)
325
-
326
- result = await self.driver.execute(sql_obj, connection=conn) # type: ignore[misc]
345
+ result = await self.driver.execute(self._create_sql_object(sql, parameters), connection=conn) # type: ignore[misc]
327
346
 
328
347
  if hasattr(result, "data") and result.data is not None and isinstance(result, SQLResult):
329
348
  return list(result.data)
@@ -345,8 +364,8 @@ class AiosqlAsyncAdapter(_AiosqlAdapterBase):
345
364
  First result row or None
346
365
 
347
366
  Note:
348
- record_class parameter is ignored. Use schema_type in driver.execute
349
- or _sqlspec_schema_type in parameters for type mapping.
367
+ The record_class parameter is ignored for compatibility. Use schema_type
368
+ in driver.execute or _sqlspec_schema_type in parameters for type mapping.
350
369
  """
351
370
  if record_class is not None:
352
371
  logger.warning(
@@ -354,9 +373,7 @@ class AiosqlAsyncAdapter(_AiosqlAdapterBase):
354
373
  "Use schema_type in driver.execute or _sqlspec_schema_type in parameters."
355
374
  )
356
375
 
357
- sql_obj = self._create_sql_object(sql, parameters)
358
-
359
- result = await self.driver.execute(sql_obj, connection=conn) # type: ignore[misc]
376
+ result = await self.driver.execute(self._create_sql_object(sql, parameters), connection=conn) # type: ignore[misc]
360
377
 
361
378
  if hasattr(result, "data") and result.data and isinstance(result, SQLResult):
362
379
  return result.data[0]
@@ -397,22 +414,7 @@ class AiosqlAsyncAdapter(_AiosqlAdapterBase):
397
414
  Yields:
398
415
  Cursor-like object with results
399
416
  """
400
- sql_obj = self._create_sql_object(sql, parameters)
401
- result = await self.driver.execute(sql_obj, connection=conn) # type: ignore[misc]
402
-
403
- class AsyncCursorLike:
404
- def __init__(self, result: Any) -> None:
405
- self.result = result
406
-
407
- @staticmethod
408
- async def fetchall() -> list[Any]:
409
- if isinstance(result, SQLResult) and result.data is not None:
410
- return list(result.data)
411
- return []
412
-
413
- async def fetchone(self) -> Optional[Any]:
414
- rows = await self.fetchall()
415
- return rows[0] if rows else None
417
+ result = await self.driver.execute(self._create_sql_object(sql, parameters), connection=conn) # type: ignore[misc]
416
418
 
417
419
  yield AsyncCursorLike(result)
418
420
 
@@ -426,11 +428,9 @@ class AiosqlAsyncAdapter(_AiosqlAdapterBase):
426
428
  parameters: Query parameters
427
429
 
428
430
  Note:
429
- Async version returns None per aiosql protocol
431
+ Returns None per aiosql async protocol
430
432
  """
431
- sql_obj = self._create_sql_object(sql, parameters)
432
-
433
- await self.driver.execute(sql_obj, connection=conn) # type: ignore[misc]
433
+ await self.driver.execute(self._create_sql_object(sql, parameters), connection=conn) # type: ignore[misc]
434
434
 
435
435
  async def insert_update_delete_many(self, conn: Any, query_name: str, sql: str, parameters: "Any") -> None:
436
436
  """Execute INSERT/UPDATE/DELETE with many parameter sets.
@@ -442,11 +442,9 @@ class AiosqlAsyncAdapter(_AiosqlAdapterBase):
442
442
  parameters: Sequence of parameter sets
443
443
 
444
444
  Note:
445
- Async version returns None per aiosql protocol
445
+ Returns None per aiosql async protocol
446
446
  """
447
- # For executemany, we don't extract sqlspec filters from individual parameter sets
448
- sql_obj = self._create_sql_object(sql)
449
- await self.driver.execute_many(sql_obj, parameters=parameters, connection=conn) # type: ignore[misc]
447
+ await self.driver.execute_many(self._create_sql_object(sql), parameters=parameters, connection=conn) # type: ignore[misc]
450
448
 
451
449
  async def insert_returning(self, conn: Any, query_name: str, sql: str, parameters: "Any") -> Optional[Any]:
452
450
  """Execute INSERT with RETURNING and return result.
@@ -460,5 +458,4 @@ class AiosqlAsyncAdapter(_AiosqlAdapterBase):
460
458
  Returns:
461
459
  Returned value or None
462
460
  """
463
- # INSERT RETURNING is treated like a select that returns data
464
461
  return await self.select_one(conn, query_name, sql, parameters)
@@ -39,7 +39,7 @@ def get_database_migration_plugin(app: "Litestar") -> "SQLSpec":
39
39
  raise ImproperConfigurationError(msg)
40
40
 
41
41
 
42
- @click.group(cls=LitestarGroup, name="database")
42
+ @click.group(cls=LitestarGroup, name="db")
43
43
  def database_group(ctx: "click.Context") -> None:
44
44
  """Manage SQLSpec database components."""
45
45
  ctx.obj = {"app": ctx.obj, "configs": get_database_migration_plugin(ctx.obj.app).config}
@@ -62,7 +62,6 @@ class DatabaseConfig:
62
62
 
63
63
  def __post_init__(self) -> None:
64
64
  if not self.config.supports_connection_pooling and self.pool_key == DEFAULT_POOL_KEY: # type: ignore[union-attr,unused-ignore]
65
- """If the database configuration does not support connection pooling, the pool key must be unique. We just automatically generate a unique identify so it won't conflict with other configs that may get added"""
66
65
  self.pool_key = f"_{self.pool_key}_{id(self.config)}"
67
66
  if self.commit_mode == "manual":
68
67
  self.before_send_handler = manual_handler_maker(connection_scope_key=self.connection_key)
@@ -25,7 +25,6 @@ if TYPE_CHECKING:
25
25
  from sqlspec.typing import ConnectionT, PoolT
26
26
 
27
27
  SESSION_TERMINUS_ASGI_EVENTS = {HTTP_RESPONSE_START, HTTP_DISCONNECT, WEBSOCKET_DISCONNECT, WEBSOCKET_CLOSE}
28
- """ASGI events that terminate a session scope."""
29
28
 
30
29
  __all__ = (
31
30
  "SESSION_TERMINUS_ASGI_EVENTS",
@@ -39,7 +38,7 @@ __all__ = (
39
38
 
40
39
 
41
40
  def manual_handler_maker(connection_scope_key: str) -> "Callable[[Message, Scope], Coroutine[Any, Any, None]]":
42
- """Set up the handler to close the connection.
41
+ """Create handler for manual connection management.
43
42
 
44
43
  Args:
45
44
  connection_scope_key: The key used to store the connection in the ASGI scope.
@@ -70,7 +69,7 @@ def autocommit_handler_maker(
70
69
  extra_commit_statuses: "Optional[set[int]]" = None,
71
70
  extra_rollback_statuses: "Optional[set[int]]" = None,
72
71
  ) -> "Callable[[Message, Scope], Coroutine[Any, Any, None]]":
73
- """Set up the handler to issue a transaction commit or rollback based on response status codes.
72
+ """Create handler for automatic transaction commit/rollback based on response status.
74
73
 
75
74
  Args:
76
75
  connection_scope_key: The key used to store the connection in the ASGI scope.
@@ -125,27 +124,25 @@ def autocommit_handler_maker(
125
124
  def lifespan_handler_maker(
126
125
  config: "DatabaseConfigProtocol[Any, Any, Any]", pool_key: str
127
126
  ) -> "Callable[[Litestar], AbstractAsyncContextManager[None]]":
128
- """Build the lifespan handler for managing the database connection pool.
129
-
130
- The pool is created on application startup and closed on shutdown.
127
+ """Create lifespan handler for managing database connection pool lifecycle.
131
128
 
132
129
  Args:
133
130
  config: The database configuration object.
134
131
  pool_key: The key under which the connection pool will be stored in `app.state`.
135
132
 
136
133
  Returns:
137
- The generated lifespan handler.
134
+ The lifespan handler function.
138
135
  """
139
136
 
140
137
  @contextlib.asynccontextmanager
141
138
  async def lifespan_handler(app: "Litestar") -> "AsyncGenerator[None, None]":
142
- """Manages the database pool lifecycle.
139
+ """Manage database pool lifecycle for the application.
143
140
 
144
141
  Args:
145
142
  app: The Litestar application instance.
146
143
 
147
144
  Yields:
148
- The generated lifespan handler.
145
+ Control to application during pool lifetime.
149
146
  """
150
147
  db_pool = await ensure_async_(config.create_pool)()
151
148
  app.state.update({pool_key: db_pool})
@@ -156,7 +153,7 @@ def lifespan_handler_maker(
156
153
  try:
157
154
  await ensure_async_(config.close_pool)()
158
155
  except Exception as e:
159
- if app.logger: # pragma: no cover
156
+ if app.logger:
160
157
  app.logger.warning("Error closing database pool for %s. Error: %s", pool_key, e)
161
158
 
162
159
  return lifespan_handler
@@ -165,35 +162,30 @@ def lifespan_handler_maker(
165
162
  def pool_provider_maker(
166
163
  config: "DatabaseConfigProtocol[ConnectionT, PoolT, DriverT]", pool_key: str
167
164
  ) -> "Callable[[State, Scope], Awaitable[PoolT]]":
168
- """Build the pool provider to inject the application-level database pool.
165
+ """Create provider for injecting the application-level database pool.
169
166
 
170
167
  Args:
171
168
  config: The database configuration object.
172
169
  pool_key: The key used to store the connection pool in `app.state`.
173
170
 
174
171
  Returns:
175
- The generated pool provider.
172
+ The pool provider function.
176
173
  """
177
174
 
178
175
  async def provide_pool(state: "State", scope: "Scope") -> "PoolT":
179
- """Provides the database pool from `app.state`.
176
+ """Provide the database pool from application state.
180
177
 
181
178
  Args:
182
179
  state: The Litestar application State object.
183
180
  scope: The ASGI scope (unused for app-level pool).
184
181
 
185
-
186
182
  Returns:
187
183
  The database connection pool.
188
184
 
189
185
  Raises:
190
186
  ImproperConfigurationError: If the pool is not found in `app.state`.
191
187
  """
192
- # The pool is stored in app.state by the lifespan handler.
193
- # state.get(key) accesses app.state[key]
194
- db_pool = state.get(pool_key)
195
- if db_pool is None:
196
- # This case should ideally not happen if the lifespan handler ran correctly.
188
+ if (db_pool := state.get(pool_key)) is None:
197
189
  msg = (
198
190
  f"Database pool with key '{pool_key}' not found in application state. "
199
191
  "Ensure the SQLSpec lifespan handler is correctly configured and has run."
@@ -208,8 +200,7 @@ def connection_provider_maker(
208
200
  config: "DatabaseConfigProtocol[ConnectionT, PoolT, DriverT]", pool_key: str, connection_key: str
209
201
  ) -> "Callable[[State, Scope], AsyncGenerator[ConnectionT, None]]":
210
202
  async def provide_connection(state: "State", scope: "Scope") -> "AsyncGenerator[ConnectionT, None]":
211
- db_pool = state.get(pool_key)
212
- if db_pool is None:
203
+ if (db_pool := state.get(pool_key)) is None:
213
204
  msg = f"Database pool with key '{pool_key}' not found. Cannot create a connection."
214
205
  raise ImproperConfigurationError(msg)
215
206
 
@@ -225,15 +216,13 @@ def connection_provider_maker(
225
216
  yield conn_instance
226
217
  return
227
218
 
228
- entered_connection: Optional[ConnectionT] = None
219
+ entered_connection = await connection_cm.__aenter__()
229
220
  try:
230
- entered_connection = await connection_cm.__aenter__()
231
221
  set_sqlspec_scope_state(scope, connection_key, entered_connection)
232
222
  yield entered_connection
233
223
  finally:
234
- if entered_connection is not None:
235
- await connection_cm.__aexit__(None, None, None)
236
- delete_sqlspec_scope_state(scope, connection_key) # Clear from scope
224
+ await connection_cm.__aexit__(None, None, None)
225
+ delete_sqlspec_scope_state(scope, connection_key)
237
226
 
238
227
  return provide_connection
239
228
 
@@ -1,7 +1,7 @@
1
1
  from typing import TYPE_CHECKING, Any, Union
2
2
 
3
3
  from litestar.di import Provide
4
- from litestar.plugins import InitPluginProtocol
4
+ from litestar.plugins import CLIPlugin, InitPluginProtocol
5
5
 
6
6
  from sqlspec.base import SQLSpec as SQLSpecBase
7
7
  from sqlspec.config import AsyncConfigT, DatabaseConfigProtocol, DriverT, SyncConfigT
@@ -17,16 +17,16 @@ if TYPE_CHECKING:
17
17
  logger = get_logger("extensions.litestar")
18
18
 
19
19
 
20
- class SQLSpec(InitPluginProtocol, SQLSpecBase):
21
- """SQLSpec plugin."""
20
+ class SQLSpec(InitPluginProtocol, CLIPlugin, SQLSpecBase):
21
+ """Litestar plugin for SQLSpec database integration."""
22
22
 
23
23
  __slots__ = ("_config", "_plugin_configs")
24
24
 
25
25
  def __init__(self, config: Union["SyncConfigT", "AsyncConfigT", "DatabaseConfig", list["DatabaseConfig"]]) -> None:
26
- """Initialize ``SQLSpecPlugin``.
26
+ """Initialize SQLSpec plugin.
27
27
 
28
28
  Args:
29
- config: configure SQLSpec plugin for use with Litestar.
29
+ config: Database configuration for SQLSpec plugin.
30
30
  """
31
31
  self._configs: dict[Any, DatabaseConfigProtocol[Any, Any, Any]] = {}
32
32
  if isinstance(config, DatabaseConfigProtocol):
@@ -38,27 +38,31 @@ class SQLSpec(InitPluginProtocol, SQLSpecBase):
38
38
 
39
39
  @property
40
40
  def config(self) -> "list[DatabaseConfig]": # pyright: ignore[reportInvalidTypeVarUse]
41
- """Return the plugin config.
41
+ """Return the plugin configuration.
42
42
 
43
43
  Returns:
44
- ConfigManager.
44
+ List of database configurations.
45
45
  """
46
46
  return self._plugin_configs
47
47
 
48
48
  def on_cli_init(self, cli: "Group") -> None:
49
- """Configure the CLI for use with SQLSpec."""
49
+ """Configure CLI commands for SQLSpec database operations.
50
+
51
+ Args:
52
+ cli: The Click command group to add commands to.
53
+ """
50
54
  from sqlspec.extensions.litestar.cli import database_group
51
55
 
52
56
  cli.add_command(database_group)
53
57
 
54
58
  def on_app_init(self, app_config: "AppConfig") -> "AppConfig":
55
- """Configure application for use with SQLSpec.
59
+ """Configure Litestar application with SQLSpec database integration.
56
60
 
57
61
  Args:
58
- app_config: The :class:`AppConfig <.config.app.AppConfig>` instance.
62
+ app_config: The Litestar application configuration instance.
59
63
 
60
64
  Returns:
61
- The updated :class:`AppConfig <.config.app.AppConfig>` instance.
65
+ The updated application configuration instance.
62
66
  """
63
67
 
64
68
  self._validate_dependency_keys()
@@ -67,7 +71,6 @@ class SQLSpec(InitPluginProtocol, SQLSpecBase):
67
71
  app_config.state.sqlspec = self
68
72
 
69
73
  app_config.on_startup.append(store_sqlspec_in_state)
70
- # Register types for injection
71
74
  app_config.signature_types.extend(
72
75
  [SQLSpec, ConnectionT, PoolT, DriverT, DatabaseConfig, DatabaseConfigProtocol, SyncConfigT, AsyncConfigT]
73
76
  )
@@ -81,8 +84,7 @@ class SQLSpec(InitPluginProtocol, SQLSpecBase):
81
84
  app_config.signature_types.append(c.config.driver_type) # type: ignore[union-attr]
82
85
 
83
86
  if hasattr(c.config, "get_signature_namespace"):
84
- config_namespace = c.config.get_signature_namespace() # type: ignore[attr-defined]
85
- signature_namespace.update(config_namespace)
87
+ signature_namespace.update(c.config.get_signature_namespace()) # type: ignore[attr-defined]
86
88
 
87
89
  app_config.before_send.append(c.before_send_handler)
88
90
  app_config.lifespan.append(c.lifespan_handler) # pyright: ignore[reportUnknownMemberType]
@@ -128,10 +130,10 @@ class SQLSpec(InitPluginProtocol, SQLSpecBase):
128
130
  raise KeyError(msg)
129
131
 
130
132
  def _validate_dependency_keys(self) -> None:
131
- """Verify uniqueness of ``connection_key`` and ``pool_key``.
133
+ """Validate that connection and pool keys are unique across configurations.
132
134
 
133
135
  Raises:
134
- ImproperConfigurationError: If session keys or pool keys are not unique.
136
+ ImproperConfigurationError: If connection keys or pool keys are not unique.
135
137
  """
136
138
  connection_keys = [c.connection_key for c in self.config]
137
139
  pool_keys = [c.pool_key for c in self.config]