sqlspec 0.26.0__py3-none-any.whl → 0.28.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 (212) hide show
  1. sqlspec/__init__.py +7 -15
  2. sqlspec/_serialization.py +55 -25
  3. sqlspec/_typing.py +155 -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 +880 -0
  7. sqlspec/adapters/adbc/config.py +62 -12
  8. sqlspec/adapters/adbc/data_dictionary.py +74 -2
  9. sqlspec/adapters/adbc/driver.py +226 -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 +44 -50
  13. sqlspec/adapters/aiosqlite/_types.py +1 -1
  14. sqlspec/adapters/aiosqlite/adk/__init__.py +5 -0
  15. sqlspec/adapters/aiosqlite/adk/store.py +536 -0
  16. sqlspec/adapters/aiosqlite/config.py +86 -16
  17. sqlspec/adapters/aiosqlite/data_dictionary.py +34 -2
  18. sqlspec/adapters/aiosqlite/driver.py +127 -38
  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 +1 -1
  24. sqlspec/adapters/asyncmy/adk/__init__.py +5 -0
  25. sqlspec/adapters/asyncmy/adk/store.py +503 -0
  26. sqlspec/adapters/asyncmy/config.py +59 -17
  27. sqlspec/adapters/asyncmy/data_dictionary.py +41 -2
  28. sqlspec/adapters/asyncmy/driver.py +293 -62
  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 +460 -0
  36. sqlspec/adapters/asyncpg/config.py +57 -36
  37. sqlspec/adapters/asyncpg/data_dictionary.py +48 -2
  38. sqlspec/adapters/asyncpg/driver.py +153 -23
  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 +585 -0
  44. sqlspec/adapters/bigquery/config.py +36 -11
  45. sqlspec/adapters/bigquery/data_dictionary.py +42 -2
  46. sqlspec/adapters/bigquery/driver.py +489 -144
  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 +55 -23
  50. sqlspec/adapters/duckdb/_types.py +2 -2
  51. sqlspec/adapters/duckdb/adk/__init__.py +14 -0
  52. sqlspec/adapters/duckdb/adk/store.py +563 -0
  53. sqlspec/adapters/duckdb/config.py +79 -21
  54. sqlspec/adapters/duckdb/data_dictionary.py +41 -2
  55. sqlspec/adapters/duckdb/driver.py +225 -44
  56. sqlspec/adapters/duckdb/litestar/__init__.py +5 -0
  57. sqlspec/adapters/duckdb/litestar/store.py +332 -0
  58. sqlspec/adapters/duckdb/pool.py +5 -5
  59. sqlspec/adapters/duckdb/type_converter.py +51 -21
  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 +1628 -0
  64. sqlspec/adapters/oracledb/config.py +120 -36
  65. sqlspec/adapters/oracledb/data_dictionary.py +87 -20
  66. sqlspec/adapters/oracledb/driver.py +475 -86
  67. sqlspec/adapters/oracledb/litestar/__init__.py +5 -0
  68. sqlspec/adapters/oracledb/litestar/store.py +765 -0
  69. sqlspec/adapters/oracledb/migrations.py +316 -25
  70. sqlspec/adapters/oracledb/type_converter.py +91 -16
  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 +483 -0
  75. sqlspec/adapters/psqlpy/config.py +45 -19
  76. sqlspec/adapters/psqlpy/data_dictionary.py +48 -2
  77. sqlspec/adapters/psqlpy/driver.py +108 -41
  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 +40 -11
  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 +962 -0
  85. sqlspec/adapters/psycopg/config.py +65 -37
  86. sqlspec/adapters/psycopg/data_dictionary.py +91 -3
  87. sqlspec/adapters/psycopg/driver.py +200 -78
  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 +582 -0
  95. sqlspec/adapters/sqlite/config.py +85 -16
  96. sqlspec/adapters/sqlite/data_dictionary.py +34 -2
  97. sqlspec/adapters/sqlite/driver.py +120 -52
  98. sqlspec/adapters/sqlite/litestar/__init__.py +5 -0
  99. sqlspec/adapters/sqlite/litestar/store.py +318 -0
  100. sqlspec/adapters/sqlite/pool.py +5 -5
  101. sqlspec/base.py +45 -26
  102. sqlspec/builder/__init__.py +73 -4
  103. sqlspec/builder/_base.py +91 -58
  104. sqlspec/builder/_column.py +5 -5
  105. sqlspec/builder/_ddl.py +98 -89
  106. sqlspec/builder/_delete.py +5 -4
  107. sqlspec/builder/_dml.py +388 -0
  108. sqlspec/{_sql.py → builder/_factory.py} +41 -44
  109. sqlspec/builder/_insert.py +5 -82
  110. sqlspec/builder/{mixins/_join_operations.py → _join.py} +145 -143
  111. sqlspec/builder/_merge.py +446 -11
  112. sqlspec/builder/_parsing_utils.py +9 -11
  113. sqlspec/builder/_select.py +1313 -25
  114. sqlspec/builder/_update.py +11 -42
  115. sqlspec/cli.py +76 -69
  116. sqlspec/config.py +331 -62
  117. sqlspec/core/__init__.py +5 -4
  118. sqlspec/core/cache.py +18 -18
  119. sqlspec/core/compiler.py +6 -8
  120. sqlspec/core/filters.py +55 -47
  121. sqlspec/core/hashing.py +9 -9
  122. sqlspec/core/parameters.py +76 -45
  123. sqlspec/core/result.py +234 -47
  124. sqlspec/core/splitter.py +16 -17
  125. sqlspec/core/statement.py +32 -31
  126. sqlspec/core/type_conversion.py +3 -2
  127. sqlspec/driver/__init__.py +1 -3
  128. sqlspec/driver/_async.py +183 -160
  129. sqlspec/driver/_common.py +197 -109
  130. sqlspec/driver/_sync.py +189 -161
  131. sqlspec/driver/mixins/_result_tools.py +20 -236
  132. sqlspec/driver/mixins/_sql_translator.py +4 -4
  133. sqlspec/exceptions.py +70 -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 +69 -61
  142. sqlspec/extensions/fastapi/__init__.py +21 -0
  143. sqlspec/extensions/fastapi/extension.py +331 -0
  144. sqlspec/extensions/fastapi/providers.py +543 -0
  145. sqlspec/extensions/flask/__init__.py +36 -0
  146. sqlspec/extensions/flask/_state.py +71 -0
  147. sqlspec/extensions/flask/_utils.py +40 -0
  148. sqlspec/extensions/flask/extension.py +389 -0
  149. sqlspec/extensions/litestar/__init__.py +21 -4
  150. sqlspec/extensions/litestar/cli.py +54 -10
  151. sqlspec/extensions/litestar/config.py +56 -266
  152. sqlspec/extensions/litestar/handlers.py +46 -17
  153. sqlspec/extensions/litestar/migrations/0001_create_session_table.py +137 -0
  154. sqlspec/extensions/litestar/migrations/__init__.py +3 -0
  155. sqlspec/extensions/litestar/plugin.py +349 -224
  156. sqlspec/extensions/litestar/providers.py +25 -25
  157. sqlspec/extensions/litestar/store.py +265 -0
  158. sqlspec/extensions/starlette/__init__.py +10 -0
  159. sqlspec/extensions/starlette/_state.py +25 -0
  160. sqlspec/extensions/starlette/_utils.py +52 -0
  161. sqlspec/extensions/starlette/extension.py +254 -0
  162. sqlspec/extensions/starlette/middleware.py +154 -0
  163. sqlspec/loader.py +30 -49
  164. sqlspec/migrations/base.py +200 -76
  165. sqlspec/migrations/commands.py +591 -62
  166. sqlspec/migrations/context.py +6 -9
  167. sqlspec/migrations/fix.py +199 -0
  168. sqlspec/migrations/loaders.py +47 -19
  169. sqlspec/migrations/runner.py +241 -75
  170. sqlspec/migrations/tracker.py +237 -21
  171. sqlspec/migrations/utils.py +51 -3
  172. sqlspec/migrations/validation.py +177 -0
  173. sqlspec/protocols.py +106 -36
  174. sqlspec/storage/_utils.py +85 -0
  175. sqlspec/storage/backends/fsspec.py +133 -107
  176. sqlspec/storage/backends/local.py +78 -51
  177. sqlspec/storage/backends/obstore.py +276 -168
  178. sqlspec/storage/registry.py +75 -39
  179. sqlspec/typing.py +30 -84
  180. sqlspec/utils/__init__.py +25 -4
  181. sqlspec/utils/arrow_helpers.py +81 -0
  182. sqlspec/utils/config_resolver.py +6 -6
  183. sqlspec/utils/correlation.py +4 -5
  184. sqlspec/utils/data_transformation.py +3 -2
  185. sqlspec/utils/deprecation.py +9 -8
  186. sqlspec/utils/fixtures.py +4 -4
  187. sqlspec/utils/logging.py +46 -6
  188. sqlspec/utils/module_loader.py +205 -5
  189. sqlspec/utils/portal.py +311 -0
  190. sqlspec/utils/schema.py +288 -0
  191. sqlspec/utils/serializers.py +113 -4
  192. sqlspec/utils/sync_tools.py +36 -22
  193. sqlspec/utils/text.py +1 -2
  194. sqlspec/utils/type_guards.py +136 -20
  195. sqlspec/utils/version.py +433 -0
  196. {sqlspec-0.26.0.dist-info → sqlspec-0.28.0.dist-info}/METADATA +41 -22
  197. sqlspec-0.28.0.dist-info/RECORD +221 -0
  198. sqlspec/builder/mixins/__init__.py +0 -55
  199. sqlspec/builder/mixins/_cte_and_set_ops.py +0 -253
  200. sqlspec/builder/mixins/_delete_operations.py +0 -50
  201. sqlspec/builder/mixins/_insert_operations.py +0 -282
  202. sqlspec/builder/mixins/_merge_operations.py +0 -698
  203. sqlspec/builder/mixins/_order_limit_operations.py +0 -145
  204. sqlspec/builder/mixins/_pivot_operations.py +0 -157
  205. sqlspec/builder/mixins/_select_operations.py +0 -930
  206. sqlspec/builder/mixins/_update_operations.py +0 -199
  207. sqlspec/builder/mixins/_where_clause.py +0 -1298
  208. sqlspec-0.26.0.dist-info/RECORD +0 -157
  209. sqlspec-0.26.0.dist-info/licenses/NOTICE +0 -29
  210. {sqlspec-0.26.0.dist-info → sqlspec-0.28.0.dist-info}/WHEEL +0 -0
  211. {sqlspec-0.26.0.dist-info → sqlspec-0.28.0.dist-info}/entry_points.txt +0 -0
  212. {sqlspec-0.26.0.dist-info → sqlspec-0.28.0.dist-info}/licenses/LICENSE +0 -0
@@ -6,23 +6,22 @@ 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.exceptions import MissingDependencyError
16
- from sqlspec.typing import AIOSQL_INSTALLED
17
-
18
- if TYPE_CHECKING:
19
- from sqlspec.driver import AsyncDriverAdapterBase, SyncDriverAdapterBase
15
+ from sqlspec.driver import AsyncDriverAdapterBase, SyncDriverAdapterBase
16
+ from sqlspec.typing import AiosqlAsyncProtocol, AiosqlParamType, AiosqlSQLOperationType, AiosqlSyncProtocol
17
+ from sqlspec.utils.module_loader import ensure_aiosql
20
18
 
21
19
  logger = logging.getLogger("sqlspec.extensions.aiosql")
22
20
 
23
21
  __all__ = ("AiosqlAsyncAdapter", "AiosqlSyncAdapter")
24
22
 
25
23
  T = TypeVar("T")
24
+ DriverT = TypeVar("DriverT", bound="SyncDriverAdapterBase | AsyncDriverAdapterBase")
26
25
 
27
26
 
28
27
  class AsyncCursorLike:
@@ -34,7 +33,7 @@ class AsyncCursorLike:
34
33
  return list(self.result.data)
35
34
  return []
36
35
 
37
- async def fetchone(self) -> Optional[Any]:
36
+ async def fetchone(self) -> Any | None:
38
37
  rows = await self.fetchall()
39
38
  return rows[0] if rows else None
40
39
 
@@ -48,18 +47,12 @@ class CursorLike:
48
47
  return list(self.result.data)
49
48
  return []
50
49
 
51
- def fetchone(self) -> Optional[Any]:
50
+ def fetchone(self) -> Any | None:
52
51
  rows = self.fetchall()
53
52
  return rows[0] if rows else None
54
53
 
55
54
 
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:
55
+ def _normalize_dialect(dialect: "str | Any | None") -> str:
63
56
  """Normalize dialect name for SQLGlot compatibility.
64
57
 
65
58
  Args:
@@ -71,12 +64,12 @@ def _normalize_dialect(dialect: "Union[str, Any, None]") -> str:
71
64
  if dialect is None:
72
65
  return "sql"
73
66
 
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):
67
+ if isinstance(dialect, str):
79
68
  dialect_str = dialect.lower()
69
+ elif hasattr(dialect, "__name__"):
70
+ dialect_str = str(dialect.__name__).lower()
71
+ elif hasattr(dialect, "name"):
72
+ dialect_str = str(dialect.name).lower()
80
73
  else:
81
74
  dialect_str = str(dialect).lower()
82
75
 
@@ -91,19 +84,19 @@ def _normalize_dialect(dialect: "Union[str, Any, None]") -> str:
91
84
  return dialect_mapping.get(dialect_str, dialect_str)
92
85
 
93
86
 
94
- class _AiosqlAdapterBase:
87
+ class _AiosqlAdapterBase(Generic[DriverT]):
95
88
  """Base adapter class providing common functionality for aiosql integration."""
96
89
 
97
- def __init__(self, driver: "Union[SyncDriverAdapterBase, AsyncDriverAdapterBase]") -> None:
90
+ def __init__(self, driver: DriverT) -> None:
98
91
  """Initialize the base adapter.
99
92
 
100
93
  Args:
101
94
  driver: SQLSpec driver to use for execution.
102
95
  """
103
- _check_aiosql_available()
104
- self.driver = driver
96
+ ensure_aiosql()
97
+ self.driver: DriverT = driver
105
98
 
106
- def process_sql(self, query_name: str, op_type: "Any", sql: str) -> str:
99
+ def process_sql(self, query_name: str, op_type: "AiosqlSQLOperationType", sql: str) -> str:
107
100
  """Process SQL string for aiosql compatibility.
108
101
 
109
102
  Args:
@@ -116,7 +109,7 @@ class _AiosqlAdapterBase:
116
109
  """
117
110
  return sql
118
111
 
119
- def _create_sql_object(self, sql: str, parameters: "Any" = None) -> SQL:
112
+ def _create_sql_object(self, sql: str, parameters: "AiosqlParamType" = None) -> SQL:
120
113
  """Create SQL object with proper configuration.
121
114
 
122
115
  Args:
@@ -134,7 +127,7 @@ class _AiosqlAdapterBase:
134
127
  )
135
128
 
136
129
 
137
- class AiosqlSyncAdapter(_AiosqlAdapterBase):
130
+ class AiosqlSyncAdapter(_AiosqlAdapterBase[SyncDriverAdapterBase], AiosqlSyncProtocol):
138
131
  """Synchronous adapter that implements aiosql protocol using SQLSpec drivers.
139
132
 
140
133
  This adapter bridges aiosql's synchronous driver protocol with SQLSpec's sync drivers,
@@ -152,7 +145,7 @@ class AiosqlSyncAdapter(_AiosqlAdapterBase):
152
145
  super().__init__(driver)
153
146
 
154
147
  def select(
155
- self, conn: Any, query_name: str, sql: str, parameters: "Any", record_class: Optional[Any] = None
148
+ self, conn: Any, query_name: str, sql: str, parameters: "AiosqlParamType", record_class: Any | None = None
156
149
  ) -> Generator[Any, None, None]:
157
150
  """Execute a SELECT query and return results as generator.
158
151
 
@@ -182,8 +175,8 @@ class AiosqlSyncAdapter(_AiosqlAdapterBase):
182
175
  yield from result.data
183
176
 
184
177
  def select_one(
185
- self, conn: Any, query_name: str, sql: str, parameters: "Any", record_class: Optional[Any] = None
186
- ) -> Optional[dict[str, Any]]:
178
+ self, conn: Any, query_name: str, sql: str, parameters: "AiosqlParamType", record_class: Any | None = None
179
+ ) -> tuple[Any, ...] | None:
187
180
  """Execute a SELECT query and return first result.
188
181
 
189
182
  Args:
@@ -206,13 +199,14 @@ class AiosqlSyncAdapter(_AiosqlAdapterBase):
206
199
  "Use schema_type in driver.execute or _sqlspec_schema_type in parameters."
207
200
  )
208
201
 
209
- result = cast("SQLResult", self.driver.execute(self._create_sql_object(sql, parameters), connection=conn))
202
+ result = self.driver.execute(self._create_sql_object(sql, parameters), connection=conn)
210
203
 
211
204
  if hasattr(result, "data") and result.data and isinstance(result, SQLResult):
212
- return cast("Optional[dict[str, Any]]", result.data[0])
205
+ row = result.data[0]
206
+ return tuple(row.values()) if isinstance(row, dict) else row
213
207
  return None
214
208
 
215
- def select_value(self, conn: Any, query_name: str, sql: str, parameters: "Any") -> Optional[Any]:
209
+ def select_value(self, conn: Any, query_name: str, sql: str, parameters: "AiosqlParamType") -> Any | None:
216
210
  """Execute a SELECT query and return first value of first row.
217
211
 
218
212
  Args:
@@ -235,7 +229,9 @@ class AiosqlSyncAdapter(_AiosqlAdapterBase):
235
229
  return row
236
230
 
237
231
  @contextmanager
238
- def select_cursor(self, conn: Any, query_name: str, sql: str, parameters: "Any") -> Generator[Any, None, None]:
232
+ def select_cursor(
233
+ self, conn: Any, query_name: str, sql: str, parameters: "AiosqlParamType"
234
+ ) -> Generator[Any, None, None]:
239
235
  """Execute a SELECT query and return cursor context manager.
240
236
 
241
237
  Args:
@@ -251,7 +247,7 @@ class AiosqlSyncAdapter(_AiosqlAdapterBase):
251
247
 
252
248
  yield CursorLike(result)
253
249
 
254
- def insert_update_delete(self, conn: Any, query_name: str, sql: str, parameters: "Any") -> int:
250
+ def insert_update_delete(self, conn: Any, query_name: str, sql: str, parameters: "AiosqlParamType") -> int:
255
251
  """Execute INSERT/UPDATE/DELETE and return affected rows.
256
252
 
257
253
  Args:
@@ -263,11 +259,11 @@ class AiosqlSyncAdapter(_AiosqlAdapterBase):
263
259
  Returns:
264
260
  Number of affected rows
265
261
  """
266
- result = cast("SQLResult", self.driver.execute(self._create_sql_object(sql, parameters), connection=conn))
262
+ result = self.driver.execute(self._create_sql_object(sql, parameters), connection=conn)
267
263
 
268
264
  return result.rows_affected if hasattr(result, "rows_affected") else 0
269
265
 
270
- def insert_update_delete_many(self, conn: Any, query_name: str, sql: str, parameters: "Any") -> int:
266
+ def insert_update_delete_many(self, conn: Any, query_name: str, sql: str, parameters: "AiosqlParamType") -> int:
271
267
  """Execute INSERT/UPDATE/DELETE with many parameter sets.
272
268
 
273
269
  Args:
@@ -279,13 +275,11 @@ class AiosqlSyncAdapter(_AiosqlAdapterBase):
279
275
  Returns:
280
276
  Number of affected rows
281
277
  """
282
- result = cast(
283
- "SQLResult", self.driver.execute_many(self._create_sql_object(sql), parameters=parameters, connection=conn)
284
- )
278
+ result = self.driver.execute(self._create_sql_object(sql, parameters), connection=conn)
285
279
 
286
280
  return result.rows_affected if hasattr(result, "rows_affected") else 0
287
281
 
288
- def insert_returning(self, conn: Any, query_name: str, sql: str, parameters: "Any") -> Optional[Any]:
282
+ def insert_returning(self, conn: Any, query_name: str, sql: str, parameters: "AiosqlParamType") -> Any | None:
289
283
  """Execute INSERT with RETURNING and return result.
290
284
 
291
285
  Args:
@@ -300,7 +294,18 @@ class AiosqlSyncAdapter(_AiosqlAdapterBase):
300
294
  return self.select_one(conn, query_name, sql, parameters)
301
295
 
302
296
 
303
- class AiosqlAsyncAdapter(_AiosqlAdapterBase):
297
+ class _AsyncCursorContextManager(Generic[T]):
298
+ def __init__(self, cursor_like: T) -> None:
299
+ self._cursor_like = cursor_like
300
+
301
+ async def __aenter__(self) -> T:
302
+ return self._cursor_like
303
+
304
+ async def __aexit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
305
+ pass
306
+
307
+
308
+ class AiosqlAsyncAdapter(_AiosqlAdapterBase[AsyncDriverAdapterBase], AiosqlAsyncProtocol):
304
309
  """Asynchronous adapter that implements aiosql protocol using SQLSpec drivers.
305
310
 
306
311
  This adapter bridges aiosql's async driver protocol with SQLSpec's async drivers,
@@ -318,7 +323,7 @@ class AiosqlAsyncAdapter(_AiosqlAdapterBase):
318
323
  super().__init__(driver)
319
324
 
320
325
  async def select(
321
- self, conn: Any, query_name: str, sql: str, parameters: "Any", record_class: Optional[Any] = None
326
+ self, conn: Any, query_name: str, sql: str, parameters: "AiosqlParamType", record_class: Any | None = None
322
327
  ) -> list[Any]:
323
328
  """Execute a SELECT query and return results as list.
324
329
 
@@ -342,15 +347,15 @@ class AiosqlAsyncAdapter(_AiosqlAdapterBase):
342
347
  "Use schema_type in driver.execute or _sqlspec_schema_type in parameters."
343
348
  )
344
349
 
345
- result = await self.driver.execute(self._create_sql_object(sql, parameters), connection=conn) # type: ignore[misc]
350
+ result = await self.driver.execute(self._create_sql_object(sql, parameters), connection=conn)
346
351
 
347
352
  if hasattr(result, "data") and result.data is not None and isinstance(result, SQLResult):
348
353
  return list(result.data)
349
354
  return []
350
355
 
351
356
  async def select_one(
352
- self, conn: Any, query_name: str, sql: str, parameters: "Any", record_class: Optional[Any] = None
353
- ) -> Optional[Any]:
357
+ self, conn: Any, query_name: str, sql: str, parameters: "AiosqlParamType", record_class: Any | None = None
358
+ ) -> Any | None:
354
359
  """Execute a SELECT query and return first result.
355
360
 
356
361
  Args:
@@ -373,13 +378,14 @@ class AiosqlAsyncAdapter(_AiosqlAdapterBase):
373
378
  "Use schema_type in driver.execute or _sqlspec_schema_type in parameters."
374
379
  )
375
380
 
376
- result = await self.driver.execute(self._create_sql_object(sql, parameters), connection=conn) # type: ignore[misc]
381
+ result = await self.driver.execute(self._create_sql_object(sql, parameters), connection=conn)
377
382
 
378
383
  if hasattr(result, "data") and result.data and isinstance(result, SQLResult):
379
- return result.data[0]
384
+ row = result.data[0]
385
+ return tuple(row.values()) if isinstance(row, dict) else row
380
386
  return None
381
387
 
382
- async def select_value(self, conn: Any, query_name: str, sql: str, parameters: "Any") -> Optional[Any]:
388
+ async def select_value(self, conn: Any, query_name: str, sql: str, parameters: "AiosqlParamType") -> Any | None:
383
389
  """Execute a SELECT query and return first value of first row.
384
390
 
385
391
  Args:
@@ -401,8 +407,9 @@ class AiosqlAsyncAdapter(_AiosqlAdapterBase):
401
407
  return row[0] if len(row) > 0 else None
402
408
  return row
403
409
 
404
- @asynccontextmanager
405
- async def select_cursor(self, conn: Any, query_name: str, sql: str, parameters: "Any") -> AsyncGenerator[Any, None]:
410
+ async def select_cursor(
411
+ self, conn: Any, query_name: str, sql: str, parameters: "AiosqlParamType"
412
+ ) -> "_AsyncCursorContextManager[Any]":
406
413
  """Execute a SELECT query and return cursor context manager.
407
414
 
408
415
  Args:
@@ -414,11 +421,10 @@ class AiosqlAsyncAdapter(_AiosqlAdapterBase):
414
421
  Yields:
415
422
  Cursor-like object with results
416
423
  """
417
- result = await self.driver.execute(self._create_sql_object(sql, parameters), connection=conn) # type: ignore[misc]
418
-
419
- yield AsyncCursorLike(result)
424
+ result = await self.driver.execute(self._create_sql_object(sql, parameters), connection=conn)
425
+ return _AsyncCursorContextManager(AsyncCursorLike(result))
420
426
 
421
- async def insert_update_delete(self, conn: Any, query_name: str, sql: str, parameters: "Any") -> None:
427
+ async def insert_update_delete(self, conn: Any, query_name: str, sql: str, parameters: "AiosqlParamType") -> None:
422
428
  """Execute INSERT/UPDATE/DELETE.
423
429
 
424
430
  Args:
@@ -430,9 +436,11 @@ class AiosqlAsyncAdapter(_AiosqlAdapterBase):
430
436
  Note:
431
437
  Returns None per aiosql async protocol
432
438
  """
433
- await self.driver.execute(self._create_sql_object(sql, parameters), connection=conn) # type: ignore[misc]
439
+ await self.driver.execute(self._create_sql_object(sql, parameters), connection=conn)
434
440
 
435
- async def insert_update_delete_many(self, conn: Any, query_name: str, sql: str, parameters: "Any") -> None:
441
+ async def insert_update_delete_many(
442
+ self, conn: Any, query_name: str, sql: str, parameters: "AiosqlParamType"
443
+ ) -> None:
436
444
  """Execute INSERT/UPDATE/DELETE with many parameter sets.
437
445
 
438
446
  Args:
@@ -444,9 +452,9 @@ class AiosqlAsyncAdapter(_AiosqlAdapterBase):
444
452
  Note:
445
453
  Returns None per aiosql async protocol
446
454
  """
447
- await self.driver.execute_many(self._create_sql_object(sql), parameters=parameters, connection=conn) # type: ignore[misc]
455
+ await self.driver.execute(self._create_sql_object(sql, parameters), connection=conn)
448
456
 
449
- async def insert_returning(self, conn: Any, query_name: str, sql: str, parameters: "Any") -> Optional[Any]:
457
+ async def insert_returning(self, conn: Any, query_name: str, sql: str, parameters: "AiosqlParamType") -> Any | None:
450
458
  """Execute INSERT with RETURNING and return result.
451
459
 
452
460
  Args:
@@ -0,0 +1,21 @@
1
+ """FastAPI extension for SQLSpec.
2
+
3
+ Extends Starlette integration with dependency injection helpers for FastAPI's
4
+ Depends() system, including filter dependency builders.
5
+ """
6
+
7
+ from sqlspec.config import StarletteConfig
8
+ from sqlspec.extensions.fastapi.extension import SQLSpecPlugin
9
+ from sqlspec.extensions.fastapi.providers import DependencyDefaults, FieldNameType, FilterConfig, provide_filters
10
+ from sqlspec.extensions.starlette.middleware import SQLSpecAutocommitMiddleware, SQLSpecManualMiddleware
11
+
12
+ __all__ = (
13
+ "DependencyDefaults",
14
+ "FieldNameType",
15
+ "FilterConfig",
16
+ "SQLSpecAutocommitMiddleware",
17
+ "SQLSpecManualMiddleware",
18
+ "SQLSpecPlugin",
19
+ "StarletteConfig",
20
+ "provide_filters",
21
+ )
@@ -0,0 +1,331 @@
1
+ from typing import TYPE_CHECKING, Any, overload
2
+
3
+ from fastapi import Request
4
+
5
+ from sqlspec.extensions.fastapi.providers import DEPENDENCY_DEFAULTS
6
+ from sqlspec.extensions.fastapi.providers import provide_filters as _provide_filters
7
+ from sqlspec.extensions.starlette.extension import SQLSpecPlugin as _StarlettePlugin
8
+
9
+ if TYPE_CHECKING:
10
+ from collections.abc import Callable
11
+
12
+ from sqlspec.config import AsyncDatabaseConfig, SyncDatabaseConfig
13
+ from sqlspec.core.filters import FilterTypes
14
+ from sqlspec.driver import AsyncDriverAdapterBase, SyncDriverAdapterBase
15
+ from sqlspec.extensions.fastapi.providers import DependencyDefaults, FilterConfig
16
+
17
+ __all__ = ("SQLSpecPlugin",)
18
+
19
+
20
+ class SQLSpecPlugin(_StarlettePlugin):
21
+ """SQLSpec integration for FastAPI applications.
22
+
23
+ Extends Starlette integration with dependency injection helpers for FastAPI's
24
+ Depends() system.
25
+
26
+ Example:
27
+ from fastapi import Depends, FastAPI
28
+ from sqlspec import SQLSpec
29
+ from sqlspec.adapters.asyncpg import AsyncpgConfig
30
+ from sqlspec.extensions.fastapi import SQLSpecPlugin
31
+
32
+ sqlspec = SQLSpec()
33
+ config = AsyncpgConfig(
34
+ pool_config={"dsn": "postgresql://localhost/mydb"},
35
+ extension_config={
36
+ "starlette": {
37
+ "commit_mode": "autocommit",
38
+ "session_key": "db"
39
+ }
40
+ }
41
+ )
42
+ sqlspec.add_config(config, name="default")
43
+
44
+ app = FastAPI()
45
+ db_ext = SQLSpecPlugin(sqlspec, app)
46
+
47
+ @app.get("/users")
48
+ async def list_users(db = Depends(db_ext.provide_session())):
49
+ result = await db.execute("SELECT * FROM users")
50
+ return {"users": result.all()}
51
+ """
52
+
53
+ @overload
54
+ def provide_session(
55
+ self, key: None = None
56
+ ) -> "Callable[[Request], AsyncDriverAdapterBase | SyncDriverAdapterBase]": ...
57
+
58
+ @overload
59
+ def provide_session(self, key: str) -> "Callable[[Request], AsyncDriverAdapterBase | SyncDriverAdapterBase]": ...
60
+
61
+ @overload
62
+ def provide_session(self, key: "type[AsyncDatabaseConfig]") -> "Callable[[Request], AsyncDriverAdapterBase]": ...
63
+
64
+ @overload
65
+ def provide_session(self, key: "type[SyncDatabaseConfig]") -> "Callable[[Request], SyncDriverAdapterBase]": ...
66
+
67
+ @overload
68
+ def provide_session(self, key: "AsyncDatabaseConfig") -> "Callable[[Request], AsyncDriverAdapterBase]": ...
69
+
70
+ @overload
71
+ def provide_session(self, key: "SyncDatabaseConfig") -> "Callable[[Request], SyncDriverAdapterBase]": ...
72
+
73
+ def provide_session(
74
+ self,
75
+ key: "str | type[AsyncDatabaseConfig | SyncDatabaseConfig] | AsyncDatabaseConfig | SyncDatabaseConfig | None" = None,
76
+ ) -> "Callable[[Request], AsyncDriverAdapterBase | SyncDriverAdapterBase]":
77
+ """Create dependency factory for session injection.
78
+
79
+ Returns a callable that can be used with FastAPI's Depends() to inject
80
+ a database session into route handlers.
81
+
82
+ Args:
83
+ key: Optional session key (str), config type for type narrowing, or None.
84
+
85
+ Returns:
86
+ Dependency callable for FastAPI Depends().
87
+
88
+ Example:
89
+ # No args - returns union type
90
+ @app.get("/users")
91
+ async def get_users(db = Depends(db_ext.provide_session())):
92
+ return await db.execute("SELECT * FROM users")
93
+
94
+ # String key for multi-database
95
+ @app.get("/products")
96
+ async def get_products(db = Depends(db_ext.provide_session("products"))):
97
+ return await db.execute("SELECT * FROM products")
98
+
99
+ # Config instance for type narrowing
100
+ config = AsyncpgConfig(...)
101
+ @app.get("/typed")
102
+ async def typed_query(db = Depends(db_ext.provide_session(config))):
103
+ # db is properly typed as AsyncDriverAdapterBase
104
+ return await db.execute("SELECT 1")
105
+
106
+ # Config type/class for type narrowing
107
+ @app.get("/typed2")
108
+ async def typed_query2(db = Depends(db_ext.provide_session(AsyncpgConfig))):
109
+ # db is properly typed as AsyncDriverAdapterBase
110
+ return await db.execute("SELECT 1")
111
+ """
112
+ # Extract string key if provided, ignore config types/instances (used only for type narrowing)
113
+ session_key = key if isinstance(key, str) or key is None else None
114
+
115
+ def dependency(request: Request) -> "AsyncDriverAdapterBase | SyncDriverAdapterBase":
116
+ return self.get_session(request, session_key) # type: ignore[no-any-return]
117
+
118
+ return dependency
119
+
120
+ def provide_async_session(self, key: "str | None" = None) -> "Callable[[Request], AsyncDriverAdapterBase]":
121
+ """Create dependency factory for async session injection.
122
+
123
+ Type-narrowed version of provide_session() that returns AsyncDriverAdapterBase.
124
+ Useful when using string keys and you know the config is async.
125
+
126
+ Args:
127
+ key: Optional session key for multi-database configurations.
128
+
129
+ Returns:
130
+ Dependency callable that returns AsyncDriverAdapterBase.
131
+
132
+ Example:
133
+ @app.get("/users")
134
+ async def get_users(db = Depends(db_ext.provide_async_session())):
135
+ # db is AsyncDriverAdapterBase
136
+ return await db.execute("SELECT * FROM users")
137
+
138
+ @app.get("/products")
139
+ async def get_products(db = Depends(db_ext.provide_async_session("products_db"))):
140
+ # db is AsyncDriverAdapterBase for "products_db" key
141
+ return await db.execute("SELECT * FROM products")
142
+ """
143
+
144
+ def dependency(request: Request) -> "AsyncDriverAdapterBase":
145
+ return self.get_session(request, key) # type: ignore[no-any-return]
146
+
147
+ return dependency
148
+
149
+ def provide_sync_session(self, key: "str | None" = None) -> "Callable[[Request], SyncDriverAdapterBase]":
150
+ """Create dependency factory for sync session injection.
151
+
152
+ Type-narrowed version of provide_session() that returns SyncDriverAdapterBase.
153
+ Useful when using string keys and you know the config is sync.
154
+
155
+ Args:
156
+ key: Optional session key for multi-database configurations.
157
+
158
+ Returns:
159
+ Dependency callable that returns SyncDriverAdapterBase.
160
+
161
+ Example:
162
+ @app.get("/users")
163
+ def get_users(db = Depends(db_ext.provide_sync_session())):
164
+ # db is SyncDriverAdapterBase
165
+ return db.execute("SELECT * FROM users")
166
+ """
167
+
168
+ def dependency(request: Request) -> "SyncDriverAdapterBase":
169
+ return self.get_session(request, key) # type: ignore[no-any-return]
170
+
171
+ return dependency
172
+
173
+ @overload
174
+ def provide_connection(self, key: None = None) -> "Callable[[Request], Any]": ...
175
+
176
+ @overload
177
+ def provide_connection(self, key: str) -> "Callable[[Request], Any]": ...
178
+
179
+ @overload
180
+ def provide_connection(self, key: "type[AsyncDatabaseConfig]") -> "Callable[[Request], Any]": ...
181
+
182
+ @overload
183
+ def provide_connection(self, key: "type[SyncDatabaseConfig]") -> "Callable[[Request], Any]": ...
184
+
185
+ @overload
186
+ def provide_connection(self, key: "AsyncDatabaseConfig") -> "Callable[[Request], Any]": ...
187
+
188
+ @overload
189
+ def provide_connection(self, key: "SyncDatabaseConfig") -> "Callable[[Request], Any]": ...
190
+
191
+ def provide_connection(
192
+ self,
193
+ key: "str | type[AsyncDatabaseConfig | SyncDatabaseConfig] | AsyncDatabaseConfig | SyncDatabaseConfig | None" = None,
194
+ ) -> "Callable[[Request], Any]":
195
+ """Create dependency factory for connection injection.
196
+
197
+ Returns a callable that can be used with FastAPI's Depends() to inject
198
+ a database connection into route handlers.
199
+
200
+ Args:
201
+ key: Optional session key (str), config type for type narrowing, or None.
202
+
203
+ Returns:
204
+ Dependency callable for FastAPI Depends().
205
+
206
+ Example:
207
+ # No args
208
+ @app.get("/raw")
209
+ async def raw_query(conn = Depends(db_ext.provide_connection())):
210
+ cursor = await conn.cursor()
211
+ await cursor.execute("SELECT 1")
212
+ return await cursor.fetchone()
213
+
214
+ # With config instance
215
+ config = AsyncpgConfig(...)
216
+ @app.get("/typed")
217
+ async def typed_query(conn = Depends(db_ext.provide_connection(config))):
218
+ cursor = await conn.cursor()
219
+ await cursor.execute("SELECT 1")
220
+ return await cursor.fetchone()
221
+
222
+ # With config type/class
223
+ @app.get("/typed2")
224
+ async def typed_query2(conn = Depends(db_ext.provide_connection(AsyncpgConfig))):
225
+ cursor = await conn.cursor()
226
+ await cursor.execute("SELECT 1")
227
+ return await cursor.fetchone()
228
+ """
229
+ # Extract string key if provided, ignore config types/instances (used only for type narrowing)
230
+ connection_key = key if isinstance(key, str) or key is None else None
231
+
232
+ def dependency(request: Request) -> Any:
233
+ return self.get_connection(request, connection_key)
234
+
235
+ return dependency
236
+
237
+ def provide_async_connection(self, key: "str | None" = None) -> "Callable[[Request], Any]":
238
+ """Create dependency factory for async connection injection.
239
+
240
+ Type-narrowed version of provide_connection() for async connections.
241
+ Useful when using string keys and you know the config is async.
242
+
243
+ Args:
244
+ key: Optional session key for multi-database configurations.
245
+
246
+ Returns:
247
+ Dependency callable for async connection.
248
+
249
+ Example:
250
+ @app.get("/raw")
251
+ async def raw_query(conn = Depends(db_ext.provide_async_connection())):
252
+ cursor = await conn.cursor()
253
+ await cursor.execute("SELECT 1")
254
+ return await cursor.fetchone()
255
+ """
256
+
257
+ def dependency(request: Request) -> Any:
258
+ return self.get_connection(request, key)
259
+
260
+ return dependency
261
+
262
+ def provide_sync_connection(self, key: "str | None" = None) -> "Callable[[Request], Any]":
263
+ """Create dependency factory for sync connection injection.
264
+
265
+ Type-narrowed version of provide_connection() for sync connections.
266
+ Useful when using string keys and you know the config is sync.
267
+
268
+ Args:
269
+ key: Optional session key for multi-database configurations.
270
+
271
+ Returns:
272
+ Dependency callable for sync connection.
273
+
274
+ Example:
275
+ @app.get("/raw")
276
+ def raw_query(conn = Depends(db_ext.provide_sync_connection())):
277
+ cursor = conn.cursor()
278
+ cursor.execute("SELECT 1")
279
+ return cursor.fetchone()
280
+ """
281
+
282
+ def dependency(request: Request) -> Any:
283
+ return self.get_connection(request, key)
284
+
285
+ return dependency
286
+
287
+ @staticmethod
288
+ def provide_filters(
289
+ config: "FilterConfig", dep_defaults: "DependencyDefaults | None" = None
290
+ ) -> "Callable[..., list[FilterTypes]]":
291
+ """Create filter dependency for FastAPI routes.
292
+
293
+ Dynamically generates a FastAPI dependency function that parses query
294
+ parameters into SQLSpec filter objects. The returned callable can be used
295
+ with FastAPI's Depends() for automatic filter injection.
296
+
297
+ Args:
298
+ config: Filter configuration specifying which filters to enable.
299
+ dep_defaults: Optional dependency defaults for customization.
300
+
301
+ Returns:
302
+ Callable for use with Depends() that returns list of filters.
303
+
304
+ Example:
305
+ from fastapi import Depends
306
+ from sqlspec.extensions.fastapi import FilterConfig
307
+
308
+ @app.get("/users")
309
+ async def list_users(
310
+ db = Depends(db_ext.provide_session()),
311
+ filters = Depends(
312
+ db_ext.provide_filters({
313
+ "id_filter": UUID,
314
+ "search": "name,email",
315
+ "search_ignore_case": True,
316
+ "pagination_type": "limit_offset",
317
+ "sort_field": "created_at",
318
+ })
319
+ ),
320
+ ):
321
+ stmt = sql("SELECT * FROM users")
322
+ for filter in filters:
323
+ stmt = filter.append_to_statement(stmt)
324
+ result = await db.execute(stmt)
325
+ return result.all()
326
+ """
327
+
328
+ if dep_defaults is None:
329
+ dep_defaults = DEPENDENCY_DEFAULTS
330
+
331
+ return _provide_filters(config, dep_defaults=dep_defaults)