sqlspec 0.16.1__cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.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 (148) hide show
  1. 51ff5a9eadfdefd49f98__mypyc.cpython-310-aarch64-linux-gnu.so +0 -0
  2. sqlspec/__init__.py +92 -0
  3. sqlspec/__main__.py +12 -0
  4. sqlspec/__metadata__.py +14 -0
  5. sqlspec/_serialization.py +77 -0
  6. sqlspec/_sql.py +1780 -0
  7. sqlspec/_typing.py +680 -0
  8. sqlspec/adapters/__init__.py +0 -0
  9. sqlspec/adapters/adbc/__init__.py +5 -0
  10. sqlspec/adapters/adbc/_types.py +12 -0
  11. sqlspec/adapters/adbc/config.py +361 -0
  12. sqlspec/adapters/adbc/driver.py +512 -0
  13. sqlspec/adapters/aiosqlite/__init__.py +19 -0
  14. sqlspec/adapters/aiosqlite/_types.py +13 -0
  15. sqlspec/adapters/aiosqlite/config.py +253 -0
  16. sqlspec/adapters/aiosqlite/driver.py +248 -0
  17. sqlspec/adapters/asyncmy/__init__.py +19 -0
  18. sqlspec/adapters/asyncmy/_types.py +12 -0
  19. sqlspec/adapters/asyncmy/config.py +180 -0
  20. sqlspec/adapters/asyncmy/driver.py +274 -0
  21. sqlspec/adapters/asyncpg/__init__.py +21 -0
  22. sqlspec/adapters/asyncpg/_types.py +17 -0
  23. sqlspec/adapters/asyncpg/config.py +229 -0
  24. sqlspec/adapters/asyncpg/driver.py +344 -0
  25. sqlspec/adapters/bigquery/__init__.py +18 -0
  26. sqlspec/adapters/bigquery/_types.py +12 -0
  27. sqlspec/adapters/bigquery/config.py +298 -0
  28. sqlspec/adapters/bigquery/driver.py +558 -0
  29. sqlspec/adapters/duckdb/__init__.py +22 -0
  30. sqlspec/adapters/duckdb/_types.py +12 -0
  31. sqlspec/adapters/duckdb/config.py +504 -0
  32. sqlspec/adapters/duckdb/driver.py +368 -0
  33. sqlspec/adapters/oracledb/__init__.py +32 -0
  34. sqlspec/adapters/oracledb/_types.py +14 -0
  35. sqlspec/adapters/oracledb/config.py +317 -0
  36. sqlspec/adapters/oracledb/driver.py +538 -0
  37. sqlspec/adapters/psqlpy/__init__.py +16 -0
  38. sqlspec/adapters/psqlpy/_types.py +11 -0
  39. sqlspec/adapters/psqlpy/config.py +214 -0
  40. sqlspec/adapters/psqlpy/driver.py +530 -0
  41. sqlspec/adapters/psycopg/__init__.py +32 -0
  42. sqlspec/adapters/psycopg/_types.py +17 -0
  43. sqlspec/adapters/psycopg/config.py +426 -0
  44. sqlspec/adapters/psycopg/driver.py +796 -0
  45. sqlspec/adapters/sqlite/__init__.py +15 -0
  46. sqlspec/adapters/sqlite/_types.py +11 -0
  47. sqlspec/adapters/sqlite/config.py +240 -0
  48. sqlspec/adapters/sqlite/driver.py +294 -0
  49. sqlspec/base.py +571 -0
  50. sqlspec/builder/__init__.py +62 -0
  51. sqlspec/builder/_base.py +473 -0
  52. sqlspec/builder/_column.py +320 -0
  53. sqlspec/builder/_ddl.py +1346 -0
  54. sqlspec/builder/_ddl_utils.py +103 -0
  55. sqlspec/builder/_delete.py +76 -0
  56. sqlspec/builder/_insert.py +256 -0
  57. sqlspec/builder/_merge.py +71 -0
  58. sqlspec/builder/_parsing_utils.py +140 -0
  59. sqlspec/builder/_select.py +170 -0
  60. sqlspec/builder/_update.py +188 -0
  61. sqlspec/builder/mixins/__init__.py +55 -0
  62. sqlspec/builder/mixins/_cte_and_set_ops.py +222 -0
  63. sqlspec/builder/mixins/_delete_operations.py +41 -0
  64. sqlspec/builder/mixins/_insert_operations.py +244 -0
  65. sqlspec/builder/mixins/_join_operations.py +122 -0
  66. sqlspec/builder/mixins/_merge_operations.py +476 -0
  67. sqlspec/builder/mixins/_order_limit_operations.py +135 -0
  68. sqlspec/builder/mixins/_pivot_operations.py +153 -0
  69. sqlspec/builder/mixins/_select_operations.py +603 -0
  70. sqlspec/builder/mixins/_update_operations.py +187 -0
  71. sqlspec/builder/mixins/_where_clause.py +621 -0
  72. sqlspec/cli.py +247 -0
  73. sqlspec/config.py +395 -0
  74. sqlspec/core/__init__.py +63 -0
  75. sqlspec/core/cache.cpython-310-aarch64-linux-gnu.so +0 -0
  76. sqlspec/core/cache.py +871 -0
  77. sqlspec/core/compiler.cpython-310-aarch64-linux-gnu.so +0 -0
  78. sqlspec/core/compiler.py +417 -0
  79. sqlspec/core/filters.cpython-310-aarch64-linux-gnu.so +0 -0
  80. sqlspec/core/filters.py +830 -0
  81. sqlspec/core/hashing.cpython-310-aarch64-linux-gnu.so +0 -0
  82. sqlspec/core/hashing.py +310 -0
  83. sqlspec/core/parameters.cpython-310-aarch64-linux-gnu.so +0 -0
  84. sqlspec/core/parameters.py +1237 -0
  85. sqlspec/core/result.cpython-310-aarch64-linux-gnu.so +0 -0
  86. sqlspec/core/result.py +677 -0
  87. sqlspec/core/splitter.cpython-310-aarch64-linux-gnu.so +0 -0
  88. sqlspec/core/splitter.py +819 -0
  89. sqlspec/core/statement.cpython-310-aarch64-linux-gnu.so +0 -0
  90. sqlspec/core/statement.py +676 -0
  91. sqlspec/driver/__init__.py +19 -0
  92. sqlspec/driver/_async.py +502 -0
  93. sqlspec/driver/_common.py +631 -0
  94. sqlspec/driver/_sync.py +503 -0
  95. sqlspec/driver/mixins/__init__.py +6 -0
  96. sqlspec/driver/mixins/_result_tools.py +193 -0
  97. sqlspec/driver/mixins/_sql_translator.py +86 -0
  98. sqlspec/exceptions.py +193 -0
  99. sqlspec/extensions/__init__.py +0 -0
  100. sqlspec/extensions/aiosql/__init__.py +10 -0
  101. sqlspec/extensions/aiosql/adapter.py +461 -0
  102. sqlspec/extensions/litestar/__init__.py +6 -0
  103. sqlspec/extensions/litestar/_utils.py +52 -0
  104. sqlspec/extensions/litestar/cli.py +48 -0
  105. sqlspec/extensions/litestar/config.py +92 -0
  106. sqlspec/extensions/litestar/handlers.py +260 -0
  107. sqlspec/extensions/litestar/plugin.py +145 -0
  108. sqlspec/extensions/litestar/providers.py +454 -0
  109. sqlspec/loader.cpython-310-aarch64-linux-gnu.so +0 -0
  110. sqlspec/loader.py +760 -0
  111. sqlspec/migrations/__init__.py +35 -0
  112. sqlspec/migrations/base.py +414 -0
  113. sqlspec/migrations/commands.py +443 -0
  114. sqlspec/migrations/loaders.py +402 -0
  115. sqlspec/migrations/runner.py +213 -0
  116. sqlspec/migrations/tracker.py +140 -0
  117. sqlspec/migrations/utils.py +129 -0
  118. sqlspec/protocols.py +407 -0
  119. sqlspec/py.typed +0 -0
  120. sqlspec/storage/__init__.py +23 -0
  121. sqlspec/storage/backends/__init__.py +0 -0
  122. sqlspec/storage/backends/base.py +163 -0
  123. sqlspec/storage/backends/fsspec.py +386 -0
  124. sqlspec/storage/backends/obstore.py +459 -0
  125. sqlspec/storage/capabilities.py +102 -0
  126. sqlspec/storage/registry.py +239 -0
  127. sqlspec/typing.py +299 -0
  128. sqlspec/utils/__init__.py +3 -0
  129. sqlspec/utils/correlation.py +150 -0
  130. sqlspec/utils/deprecation.py +106 -0
  131. sqlspec/utils/fixtures.cpython-310-aarch64-linux-gnu.so +0 -0
  132. sqlspec/utils/fixtures.py +58 -0
  133. sqlspec/utils/logging.py +127 -0
  134. sqlspec/utils/module_loader.py +89 -0
  135. sqlspec/utils/serializers.py +4 -0
  136. sqlspec/utils/singleton.py +32 -0
  137. sqlspec/utils/sync_tools.cpython-310-aarch64-linux-gnu.so +0 -0
  138. sqlspec/utils/sync_tools.py +237 -0
  139. sqlspec/utils/text.cpython-310-aarch64-linux-gnu.so +0 -0
  140. sqlspec/utils/text.py +96 -0
  141. sqlspec/utils/type_guards.cpython-310-aarch64-linux-gnu.so +0 -0
  142. sqlspec/utils/type_guards.py +1139 -0
  143. sqlspec-0.16.1.dist-info/METADATA +365 -0
  144. sqlspec-0.16.1.dist-info/RECORD +148 -0
  145. sqlspec-0.16.1.dist-info/WHEEL +7 -0
  146. sqlspec-0.16.1.dist-info/entry_points.txt +2 -0
  147. sqlspec-0.16.1.dist-info/licenses/LICENSE +21 -0
  148. sqlspec-0.16.1.dist-info/licenses/NOTICE +29 -0
@@ -0,0 +1,461 @@
1
+ """AioSQL adapter implementation for SQLSpec.
2
+
3
+ This module provides adapter classes that implement the aiosql adapter protocols
4
+ while using SQLSpec drivers under the hood. This enables users to load SQL queries
5
+ from files using aiosql while leveraging all of SQLSpec's advanced features.
6
+ """
7
+
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
12
+
13
+ from sqlspec.core.result import SQLResult
14
+ from sqlspec.core.statement import SQL, StatementConfig
15
+ from sqlspec.exceptions import MissingDependencyError
16
+ from sqlspec.typing import AIOSQL_INSTALLED
17
+
18
+ if TYPE_CHECKING:
19
+ from sqlspec.driver import AsyncDriverAdapterBase, SyncDriverAdapterBase
20
+
21
+ logger = logging.getLogger("sqlspec.extensions.aiosql")
22
+
23
+ __all__ = ("AiosqlAsyncAdapter", "AiosqlSyncAdapter")
24
+
25
+ T = TypeVar("T")
26
+
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
+
56
+ def _check_aiosql_available() -> None:
57
+ if not AIOSQL_INSTALLED:
58
+ msg = "aiosql"
59
+ raise MissingDependencyError(msg, "aiosql")
60
+
61
+
62
+ def _normalize_dialect(dialect: "Union[str, Any, None]") -> str:
63
+ """Normalize dialect name for SQLGlot compatibility.
64
+
65
+ Args:
66
+ dialect: Original dialect name (can be str, Dialect, type[Dialect], or None)
67
+
68
+ Returns:
69
+ Converted dialect name compatible with SQLGlot
70
+ """
71
+ if dialect is None:
72
+ return "sql"
73
+
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
+ dialect_str = dialect.lower()
80
+ else:
81
+ dialect_str = str(dialect).lower()
82
+
83
+ dialect_mapping = {
84
+ "postgresql": "postgres",
85
+ "psycopg": "postgres",
86
+ "asyncpg": "postgres",
87
+ "psqlpy": "postgres",
88
+ "sqlite3": "sqlite",
89
+ "aiosqlite": "sqlite",
90
+ }
91
+ return dialect_mapping.get(dialect_str, dialect_str)
92
+
93
+
94
+ class _AiosqlAdapterBase:
95
+ """Base adapter class providing common functionality for aiosql integration."""
96
+
97
+ def __init__(self, driver: "Union[SyncDriverAdapterBase, AsyncDriverAdapterBase]") -> None:
98
+ """Initialize the base adapter.
99
+
100
+ Args:
101
+ driver: SQLSpec driver to use for execution.
102
+ """
103
+ _check_aiosql_available()
104
+ self.driver = driver
105
+
106
+ def process_sql(self, query_name: str, op_type: "Any", sql: str) -> str:
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
+ """
117
+ return sql
118
+
119
+ def _create_sql_object(self, sql: str, parameters: "Any" = None) -> SQL:
120
+ """Create SQL object with proper configuration.
121
+
122
+ Args:
123
+ sql: SQL string
124
+ parameters: Query parameters
125
+
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
+ )
135
+
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.
142
+ """
143
+
144
+ is_aio_driver: ClassVar[bool] = False
145
+
146
+ def __init__(self, driver: "SyncDriverAdapterBase") -> None:
147
+ """Initialize the sync adapter.
148
+
149
+ Args:
150
+ driver: SQLSpec sync driver to use for execution
151
+ """
152
+ super().__init__(driver)
153
+
154
+ def select(
155
+ self, conn: Any, query_name: str, sql: str, parameters: "Any", record_class: Optional[Any] = None
156
+ ) -> Generator[Any, None, None]:
157
+ """Execute a SELECT query and return results as generator.
158
+
159
+ Args:
160
+ conn: Database connection (passed through to SQLSpec driver)
161
+ query_name: Name of the query
162
+ sql: SQL string
163
+ parameters: Query parameters
164
+ record_class: Deprecated - use schema_type in driver.execute instead
165
+
166
+ Yields:
167
+ Query result rows
168
+
169
+ Note:
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.
172
+ """
173
+ if record_class is not None:
174
+ logger.warning(
175
+ "record_class parameter is deprecated and ignored. "
176
+ "Use schema_type in driver.execute or _sqlspec_schema_type in parameters."
177
+ )
178
+
179
+ result = self.driver.execute(self._create_sql_object(sql, parameters), connection=conn)
180
+
181
+ if isinstance(result, SQLResult) and result.data is not None:
182
+ yield from result.data
183
+
184
+ def select_one(
185
+ self, conn: Any, query_name: str, sql: str, parameters: "Any", record_class: Optional[Any] = None
186
+ ) -> Optional[dict[str, Any]]:
187
+ """Execute a SELECT query and return first result.
188
+
189
+ Args:
190
+ conn: Database connection
191
+ query_name: Name of the query
192
+ sql: SQL string
193
+ parameters: Query parameters
194
+ record_class: Deprecated - use schema_type in driver.execute instead
195
+
196
+ Returns:
197
+ First result row or None
198
+
199
+ Note:
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.
202
+ """
203
+ if record_class is not None:
204
+ logger.warning(
205
+ "record_class parameter is deprecated and ignored. "
206
+ "Use schema_type in driver.execute or _sqlspec_schema_type in parameters."
207
+ )
208
+
209
+ result = cast("SQLResult", self.driver.execute(self._create_sql_object(sql, parameters), connection=conn))
210
+
211
+ if hasattr(result, "data") and result.data and isinstance(result, SQLResult):
212
+ return cast("Optional[dict[str, Any]]", result.data[0])
213
+ return None
214
+
215
+ def select_value(self, conn: Any, query_name: str, sql: str, parameters: "Any") -> Optional[Any]:
216
+ """Execute a SELECT query and return first value of first row.
217
+
218
+ Args:
219
+ conn: Database connection
220
+ query_name: Name of the query
221
+ sql: SQL string
222
+ parameters: Query parameters
223
+
224
+ Returns:
225
+ First value of first row or None
226
+ """
227
+ row = self.select_one(conn, query_name, sql, parameters)
228
+ if row is None:
229
+ return None
230
+
231
+ if isinstance(row, dict):
232
+ return next(iter(row.values())) if row else None
233
+ if hasattr(row, "__getitem__"):
234
+ return row[0] if len(row) > 0 else None
235
+ return row
236
+
237
+ @contextmanager
238
+ def select_cursor(self, conn: Any, query_name: str, sql: str, parameters: "Any") -> Generator[Any, None, None]:
239
+ """Execute a SELECT query and return cursor context manager.
240
+
241
+ Args:
242
+ conn: Database connection
243
+ query_name: Name of the query
244
+ sql: SQL string
245
+ parameters: Query parameters
246
+
247
+ Yields:
248
+ Cursor-like object with results
249
+ """
250
+ result = self.driver.execute(self._create_sql_object(sql, parameters), connection=conn)
251
+
252
+ yield CursorLike(result)
253
+
254
+ def insert_update_delete(self, conn: Any, query_name: str, sql: str, parameters: "Any") -> int:
255
+ """Execute INSERT/UPDATE/DELETE and return affected rows.
256
+
257
+ Args:
258
+ conn: Database connection
259
+ query_name: Name of the query
260
+ sql: SQL string
261
+ parameters: Query parameters
262
+
263
+ Returns:
264
+ Number of affected rows
265
+ """
266
+ result = cast("SQLResult", self.driver.execute(self._create_sql_object(sql, parameters), connection=conn))
267
+
268
+ return result.rows_affected if hasattr(result, "rows_affected") else 0
269
+
270
+ def insert_update_delete_many(self, conn: Any, query_name: str, sql: str, parameters: "Any") -> int:
271
+ """Execute INSERT/UPDATE/DELETE with many parameter sets.
272
+
273
+ Args:
274
+ conn: Database connection
275
+ query_name: Name of the query
276
+ sql: SQL string
277
+ parameters: Sequence of parameter sets
278
+
279
+ Returns:
280
+ Number of affected rows
281
+ """
282
+ result = cast(
283
+ "SQLResult", self.driver.execute_many(self._create_sql_object(sql), parameters=parameters, connection=conn)
284
+ )
285
+
286
+ return result.rows_affected if hasattr(result, "rows_affected") else 0
287
+
288
+ def insert_returning(self, conn: Any, query_name: str, sql: str, parameters: "Any") -> Optional[Any]:
289
+ """Execute INSERT with RETURNING and return result.
290
+
291
+ Args:
292
+ conn: Database connection
293
+ query_name: Name of the query
294
+ sql: SQL string
295
+ parameters: Query parameters
296
+
297
+ Returns:
298
+ Returned value or None
299
+ """
300
+ return self.select_one(conn, query_name, sql, parameters)
301
+
302
+
303
+ class AiosqlAsyncAdapter(_AiosqlAdapterBase):
304
+ """Asynchronous adapter that implements aiosql protocol using SQLSpec drivers.
305
+
306
+ This adapter bridges aiosql's async driver protocol with SQLSpec's async drivers,
307
+ enabling queries loaded by aiosql to be executed with SQLSpec async drivers.
308
+ """
309
+
310
+ is_aio_driver: ClassVar[bool] = True
311
+
312
+ def __init__(self, driver: "AsyncDriverAdapterBase") -> None:
313
+ """Initialize the async adapter.
314
+
315
+ Args:
316
+ driver: SQLSpec async driver to use for execution
317
+ """
318
+ super().__init__(driver)
319
+
320
+ async def select(
321
+ self, conn: Any, query_name: str, sql: str, parameters: "Any", record_class: Optional[Any] = None
322
+ ) -> list[Any]:
323
+ """Execute a SELECT query and return results as list.
324
+
325
+ Args:
326
+ conn: Database connection
327
+ query_name: Name of the query
328
+ sql: SQL string
329
+ parameters: Query parameters
330
+ record_class: Deprecated - use schema_type in driver.execute instead
331
+
332
+ Returns:
333
+ List of query result rows
334
+
335
+ Note:
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.
338
+ """
339
+ if record_class is not None:
340
+ logger.warning(
341
+ "record_class parameter is deprecated and ignored. "
342
+ "Use schema_type in driver.execute or _sqlspec_schema_type in parameters."
343
+ )
344
+
345
+ result = await self.driver.execute(self._create_sql_object(sql, parameters), connection=conn) # type: ignore[misc]
346
+
347
+ if hasattr(result, "data") and result.data is not None and isinstance(result, SQLResult):
348
+ return list(result.data)
349
+ return []
350
+
351
+ async def select_one(
352
+ self, conn: Any, query_name: str, sql: str, parameters: "Any", record_class: Optional[Any] = None
353
+ ) -> Optional[Any]:
354
+ """Execute a SELECT query and return first result.
355
+
356
+ Args:
357
+ conn: Database connection
358
+ query_name: Name of the query
359
+ sql: SQL string
360
+ parameters: Query parameters
361
+ record_class: Deprecated - use schema_type in driver.execute instead
362
+
363
+ Returns:
364
+ First result row or None
365
+
366
+ Note:
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.
369
+ """
370
+ if record_class is not None:
371
+ logger.warning(
372
+ "record_class parameter is deprecated and ignored. "
373
+ "Use schema_type in driver.execute or _sqlspec_schema_type in parameters."
374
+ )
375
+
376
+ result = await self.driver.execute(self._create_sql_object(sql, parameters), connection=conn) # type: ignore[misc]
377
+
378
+ if hasattr(result, "data") and result.data and isinstance(result, SQLResult):
379
+ return result.data[0]
380
+ return None
381
+
382
+ async def select_value(self, conn: Any, query_name: str, sql: str, parameters: "Any") -> Optional[Any]:
383
+ """Execute a SELECT query and return first value of first row.
384
+
385
+ Args:
386
+ conn: Database connection
387
+ query_name: Name of the query
388
+ sql: SQL string
389
+ parameters: Query parameters
390
+
391
+ Returns:
392
+ First value of first row or None
393
+ """
394
+ row = await self.select_one(conn, query_name, sql, parameters)
395
+ if row is None:
396
+ return None
397
+
398
+ if isinstance(row, dict):
399
+ return next(iter(row.values())) if row else None
400
+ if hasattr(row, "__getitem__"):
401
+ return row[0] if len(row) > 0 else None
402
+ return row
403
+
404
+ @asynccontextmanager
405
+ async def select_cursor(self, conn: Any, query_name: str, sql: str, parameters: "Any") -> AsyncGenerator[Any, None]:
406
+ """Execute a SELECT query and return cursor context manager.
407
+
408
+ Args:
409
+ conn: Database connection
410
+ query_name: Name of the query
411
+ sql: SQL string
412
+ parameters: Query parameters
413
+
414
+ Yields:
415
+ Cursor-like object with results
416
+ """
417
+ result = await self.driver.execute(self._create_sql_object(sql, parameters), connection=conn) # type: ignore[misc]
418
+
419
+ yield AsyncCursorLike(result)
420
+
421
+ async def insert_update_delete(self, conn: Any, query_name: str, sql: str, parameters: "Any") -> None:
422
+ """Execute INSERT/UPDATE/DELETE.
423
+
424
+ Args:
425
+ conn: Database connection
426
+ query_name: Name of the query
427
+ sql: SQL string
428
+ parameters: Query parameters
429
+
430
+ Note:
431
+ Returns None per aiosql async protocol
432
+ """
433
+ await self.driver.execute(self._create_sql_object(sql, parameters), connection=conn) # type: ignore[misc]
434
+
435
+ async def insert_update_delete_many(self, conn: Any, query_name: str, sql: str, parameters: "Any") -> None:
436
+ """Execute INSERT/UPDATE/DELETE with many parameter sets.
437
+
438
+ Args:
439
+ conn: Database connection
440
+ query_name: Name of the query
441
+ sql: SQL string
442
+ parameters: Sequence of parameter sets
443
+
444
+ Note:
445
+ Returns None per aiosql async protocol
446
+ """
447
+ await self.driver.execute_many(self._create_sql_object(sql), parameters=parameters, connection=conn) # type: ignore[misc]
448
+
449
+ async def insert_returning(self, conn: Any, query_name: str, sql: str, parameters: "Any") -> Optional[Any]:
450
+ """Execute INSERT with RETURNING and return result.
451
+
452
+ Args:
453
+ conn: Database connection
454
+ query_name: Name of the query
455
+ sql: SQL string
456
+ parameters: Query parameters
457
+
458
+ Returns:
459
+ Returned value or None
460
+ """
461
+ return await self.select_one(conn, query_name, sql, parameters)
@@ -0,0 +1,6 @@
1
+ from sqlspec.extensions.litestar import handlers, providers
2
+ from sqlspec.extensions.litestar.cli import database_group
3
+ from sqlspec.extensions.litestar.config import DatabaseConfig
4
+ from sqlspec.extensions.litestar.plugin import SQLSpec
5
+
6
+ __all__ = ("DatabaseConfig", "SQLSpec", "database_group", "handlers", "providers")
@@ -0,0 +1,52 @@
1
+ from typing import TYPE_CHECKING, Any
2
+
3
+ if TYPE_CHECKING:
4
+ from litestar.types import Scope
5
+
6
+ __all__ = ("delete_sqlspec_scope_state", "get_sqlspec_scope_state", "set_sqlspec_scope_state")
7
+
8
+ _SCOPE_NAMESPACE = "_sqlspec"
9
+
10
+
11
+ def get_sqlspec_scope_state(scope: "Scope", key: str, default: Any = None, pop: bool = False) -> Any:
12
+ """Get an internal value from connection scope state.
13
+
14
+ Note:
15
+ If called with a default value, this method behaves like to `dict.set_default()`, both setting the key in the
16
+ namespace to the default value, and returning it.
17
+
18
+ If called without a default value, the method behaves like `dict.get()`, returning ``None`` if the key does not
19
+ exist.
20
+
21
+ Args:
22
+ scope: The connection scope.
23
+ key: Key to get from internal namespace in scope state.
24
+ default: Default value to return.
25
+ pop: Boolean flag dictating whether the value should be deleted from the state.
26
+
27
+ Returns:
28
+ Value mapped to ``key`` in internal connection scope namespace.
29
+ """
30
+ namespace = scope.setdefault(_SCOPE_NAMESPACE, {}) # type: ignore[misc]
31
+ return namespace.pop(key, default) if pop else namespace.get(key, default) # pyright: ignore[reportUnknownVariableType,reportUnknownMemberType]
32
+
33
+
34
+ def set_sqlspec_scope_state(scope: "Scope", key: str, value: Any) -> None:
35
+ """Set an internal value in connection scope state.
36
+
37
+ Args:
38
+ scope: The connection scope.
39
+ key: Key to set under internal namespace in scope state.
40
+ value: Value for key.
41
+ """
42
+ scope.setdefault(_SCOPE_NAMESPACE, {})[key] = value # type: ignore[misc]
43
+
44
+
45
+ def delete_sqlspec_scope_state(scope: "Scope", key: str) -> None:
46
+ """Remove an internal value from connection scope state.
47
+
48
+ Args:
49
+ scope: The connection scope.
50
+ key: Key to set under internal namespace in scope state.
51
+ """
52
+ del scope.setdefault(_SCOPE_NAMESPACE, {})[key] # type: ignore[misc]
@@ -0,0 +1,48 @@
1
+ """Litestar CLI integration for SQLSpec migrations."""
2
+
3
+ from contextlib import suppress
4
+ from typing import TYPE_CHECKING
5
+
6
+ from litestar.cli._utils import LitestarGroup
7
+
8
+ from sqlspec.cli import add_migration_commands
9
+
10
+ try:
11
+ import rich_click as click
12
+ except ImportError:
13
+ import click # type: ignore[no-redef]
14
+
15
+ if TYPE_CHECKING:
16
+ from litestar import Litestar
17
+
18
+ from sqlspec.extensions.litestar.plugin import SQLSpec
19
+
20
+
21
+ def get_database_migration_plugin(app: "Litestar") -> "SQLSpec":
22
+ """Retrieve the SQLSpec plugin from the Litestar application's plugins.
23
+
24
+ Args:
25
+ app: The Litestar application
26
+
27
+ Returns:
28
+ The SQLSpec plugin
29
+
30
+ Raises:
31
+ ImproperConfigurationError: If the SQLSpec plugin is not found
32
+ """
33
+ from sqlspec.exceptions import ImproperConfigurationError
34
+ from sqlspec.extensions.litestar.plugin import SQLSpec
35
+
36
+ with suppress(KeyError):
37
+ return app.plugins.get(SQLSpec)
38
+ msg = "Failed to initialize database migrations. The required SQLSpec plugin is missing."
39
+ raise ImproperConfigurationError(msg)
40
+
41
+
42
+ @click.group(cls=LitestarGroup, name="db")
43
+ def database_group(ctx: "click.Context") -> None:
44
+ """Manage SQLSpec database components."""
45
+ ctx.obj = {"app": ctx.obj, "configs": get_database_migration_plugin(ctx.obj.app).config}
46
+
47
+
48
+ add_migration_commands(database_group)
@@ -0,0 +1,92 @@
1
+ from dataclasses import dataclass, field
2
+ from typing import TYPE_CHECKING, Any, Callable, Literal, Optional, Union
3
+
4
+ from sqlspec.exceptions import ImproperConfigurationError
5
+ from sqlspec.extensions.litestar.handlers import (
6
+ autocommit_handler_maker,
7
+ connection_provider_maker,
8
+ lifespan_handler_maker,
9
+ manual_handler_maker,
10
+ pool_provider_maker,
11
+ session_provider_maker,
12
+ )
13
+
14
+ if TYPE_CHECKING:
15
+ from collections.abc import AsyncGenerator, Awaitable
16
+ from contextlib import AbstractAsyncContextManager
17
+
18
+ from litestar import Litestar
19
+ from litestar.datastructures.state import State
20
+ from litestar.types import BeforeMessageSendHookHandler, Scope
21
+
22
+ from sqlspec.config import AsyncConfigT, DriverT, SyncConfigT
23
+ from sqlspec.typing import ConnectionT, PoolT
24
+
25
+
26
+ CommitMode = Literal["manual", "autocommit", "autocommit_include_redirect"]
27
+ DEFAULT_COMMIT_MODE: CommitMode = "manual"
28
+ DEFAULT_CONNECTION_KEY = "db_connection"
29
+ DEFAULT_POOL_KEY = "db_pool"
30
+ DEFAULT_SESSION_KEY = "db_session"
31
+
32
+ __all__ = (
33
+ "DEFAULT_COMMIT_MODE",
34
+ "DEFAULT_CONNECTION_KEY",
35
+ "DEFAULT_POOL_KEY",
36
+ "DEFAULT_SESSION_KEY",
37
+ "CommitMode",
38
+ "DatabaseConfig",
39
+ )
40
+
41
+
42
+ @dataclass
43
+ class DatabaseConfig:
44
+ config: "Union[SyncConfigT, AsyncConfigT]" = field() # type: ignore[valid-type] # pyright: ignore[reportGeneralTypeIssues]
45
+ connection_key: str = field(default=DEFAULT_CONNECTION_KEY)
46
+ pool_key: str = field(default=DEFAULT_POOL_KEY)
47
+ session_key: str = field(default=DEFAULT_SESSION_KEY)
48
+ commit_mode: "CommitMode" = field(default=DEFAULT_COMMIT_MODE)
49
+ extra_commit_statuses: "Optional[set[int]]" = field(default=None)
50
+ extra_rollback_statuses: "Optional[set[int]]" = field(default=None)
51
+ enable_correlation_middleware: bool = field(default=True)
52
+ connection_provider: "Callable[[State, Scope], AsyncGenerator[ConnectionT, None]]" = field( # pyright: ignore[reportGeneralTypeIssues]
53
+ init=False, repr=False, hash=False
54
+ )
55
+ pool_provider: "Callable[[State,Scope], Awaitable[PoolT]]" = field(init=False, repr=False, hash=False) # pyright: ignore[reportGeneralTypeIssues]
56
+ session_provider: "Callable[[Any], AsyncGenerator[DriverT, None]]" = field(init=False, repr=False, hash=False) # pyright: ignore[reportGeneralTypeIssues]
57
+ before_send_handler: "BeforeMessageSendHookHandler" = field(init=False, repr=False, hash=False)
58
+ lifespan_handler: "Callable[[Litestar], AbstractAsyncContextManager[None]]" = field(
59
+ init=False, repr=False, hash=False
60
+ )
61
+ annotation: "type[Union[SyncConfigT, AsyncConfigT]]" = field(init=False, repr=False, hash=False) # type: ignore[valid-type] # pyright: ignore[reportGeneralTypeIssues]
62
+
63
+ def __post_init__(self) -> None:
64
+ if not self.config.supports_connection_pooling and self.pool_key == DEFAULT_POOL_KEY: # type: ignore[union-attr,unused-ignore]
65
+ self.pool_key = f"_{self.pool_key}_{id(self.config)}"
66
+ if self.commit_mode == "manual":
67
+ self.before_send_handler = manual_handler_maker(connection_scope_key=self.connection_key)
68
+ elif self.commit_mode == "autocommit":
69
+ self.before_send_handler = autocommit_handler_maker(
70
+ commit_on_redirect=False,
71
+ extra_commit_statuses=self.extra_commit_statuses,
72
+ extra_rollback_statuses=self.extra_rollback_statuses,
73
+ connection_scope_key=self.connection_key,
74
+ )
75
+ elif self.commit_mode == "autocommit_include_redirect":
76
+ self.before_send_handler = autocommit_handler_maker(
77
+ commit_on_redirect=True,
78
+ extra_commit_statuses=self.extra_commit_statuses,
79
+ extra_rollback_statuses=self.extra_rollback_statuses,
80
+ connection_scope_key=self.connection_key,
81
+ )
82
+ else:
83
+ msg = f"Invalid commit mode: {self.commit_mode}"
84
+ raise ImproperConfigurationError(detail=msg)
85
+ self.lifespan_handler = lifespan_handler_maker(config=self.config, pool_key=self.pool_key)
86
+ self.connection_provider = connection_provider_maker(
87
+ connection_key=self.connection_key, pool_key=self.pool_key, config=self.config
88
+ )
89
+ self.pool_provider = pool_provider_maker(config=self.config, pool_key=self.pool_key)
90
+ self.session_provider = session_provider_maker(
91
+ config=self.config, connection_dependency_key=self.connection_key
92
+ )