sqlspec 0.7.0__py3-none-any.whl → 0.8.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 (50) hide show
  1. sqlspec/__init__.py +15 -0
  2. sqlspec/_serialization.py +16 -2
  3. sqlspec/_typing.py +1 -1
  4. sqlspec/adapters/adbc/__init__.py +7 -0
  5. sqlspec/adapters/adbc/config.py +160 -17
  6. sqlspec/adapters/adbc/driver.py +333 -0
  7. sqlspec/adapters/aiosqlite/__init__.py +6 -2
  8. sqlspec/adapters/aiosqlite/config.py +25 -7
  9. sqlspec/adapters/aiosqlite/driver.py +275 -0
  10. sqlspec/adapters/asyncmy/__init__.py +7 -2
  11. sqlspec/adapters/asyncmy/config.py +75 -14
  12. sqlspec/adapters/asyncmy/driver.py +255 -0
  13. sqlspec/adapters/asyncpg/__init__.py +9 -0
  14. sqlspec/adapters/asyncpg/config.py +99 -20
  15. sqlspec/adapters/asyncpg/driver.py +288 -0
  16. sqlspec/adapters/duckdb/__init__.py +6 -2
  17. sqlspec/adapters/duckdb/config.py +197 -15
  18. sqlspec/adapters/duckdb/driver.py +225 -0
  19. sqlspec/adapters/oracledb/__init__.py +11 -8
  20. sqlspec/adapters/oracledb/config/__init__.py +6 -6
  21. sqlspec/adapters/oracledb/config/_asyncio.py +98 -13
  22. sqlspec/adapters/oracledb/config/_common.py +1 -1
  23. sqlspec/adapters/oracledb/config/_sync.py +99 -14
  24. sqlspec/adapters/oracledb/driver.py +498 -0
  25. sqlspec/adapters/psycopg/__init__.py +11 -0
  26. sqlspec/adapters/psycopg/config/__init__.py +6 -6
  27. sqlspec/adapters/psycopg/config/_async.py +105 -13
  28. sqlspec/adapters/psycopg/config/_common.py +2 -2
  29. sqlspec/adapters/psycopg/config/_sync.py +105 -13
  30. sqlspec/adapters/psycopg/driver.py +616 -0
  31. sqlspec/adapters/sqlite/__init__.py +7 -0
  32. sqlspec/adapters/sqlite/config.py +25 -7
  33. sqlspec/adapters/sqlite/driver.py +303 -0
  34. sqlspec/base.py +416 -36
  35. sqlspec/extensions/litestar/__init__.py +19 -0
  36. sqlspec/extensions/litestar/_utils.py +56 -0
  37. sqlspec/extensions/litestar/config.py +81 -0
  38. sqlspec/extensions/litestar/handlers.py +188 -0
  39. sqlspec/extensions/litestar/plugin.py +103 -11
  40. sqlspec/typing.py +72 -17
  41. sqlspec/utils/__init__.py +3 -0
  42. sqlspec/utils/deprecation.py +1 -1
  43. sqlspec/utils/fixtures.py +4 -5
  44. sqlspec/utils/sync_tools.py +335 -0
  45. {sqlspec-0.7.0.dist-info → sqlspec-0.8.0.dist-info}/METADATA +1 -1
  46. sqlspec-0.8.0.dist-info/RECORD +57 -0
  47. sqlspec-0.7.0.dist-info/RECORD +0 -46
  48. {sqlspec-0.7.0.dist-info → sqlspec-0.8.0.dist-info}/WHEEL +0 -0
  49. {sqlspec-0.7.0.dist-info → sqlspec-0.8.0.dist-info}/licenses/LICENSE +0 -0
  50. {sqlspec-0.7.0.dist-info → sqlspec-0.8.0.dist-info}/licenses/NOTICE +0 -0
@@ -0,0 +1,616 @@
1
+ from contextlib import asynccontextmanager, contextmanager
2
+ from typing import TYPE_CHECKING, Any, Optional, Union, cast
3
+
4
+ from psycopg.rows import dict_row
5
+
6
+ from sqlspec.base import PARAM_REGEX, AsyncDriverAdapterProtocol, SyncDriverAdapterProtocol, T
7
+
8
+ if TYPE_CHECKING:
9
+ from collections.abc import AsyncGenerator, Generator
10
+
11
+ from psycopg import AsyncConnection, Connection
12
+
13
+ from sqlspec.typing import ModelDTOT, StatementParameterType
14
+
15
+ __all__ = ("PsycopgAsyncDriver", "PsycopgSyncDriver")
16
+
17
+
18
+ class PsycopgSyncDriver(SyncDriverAdapterProtocol["Connection"]):
19
+ """Psycopg Sync Driver Adapter."""
20
+
21
+ connection: "Connection"
22
+ param_style: str = "%s"
23
+
24
+ def __init__(self, connection: "Connection") -> None:
25
+ self.connection = connection
26
+
27
+ @staticmethod
28
+ @contextmanager
29
+ def _with_cursor(connection: "Connection") -> "Generator[Any, None, None]":
30
+ cursor = connection.cursor(row_factory=dict_row)
31
+ try:
32
+ yield cursor
33
+ finally:
34
+ cursor.close()
35
+
36
+ def _process_sql_params(
37
+ self, sql: str, parameters: "Optional[StatementParameterType]" = None
38
+ ) -> "tuple[str, Optional[Union[tuple[Any, ...], list[Any], dict[str, Any]]]]":
39
+ """Process SQL query and parameters for DB-API execution.
40
+
41
+ Converts named parameters (:name) to positional parameters (%s)
42
+ if the input parameters are a dictionary.
43
+
44
+ Args:
45
+ sql: The SQL query string.
46
+ parameters: The parameters for the query (dict, tuple, list, or None).
47
+
48
+ Returns:
49
+ A tuple containing the processed SQL string and the processed parameters
50
+ (always a tuple or None if the input was a dictionary, otherwise the original type).
51
+
52
+ Raises:
53
+ ValueError: If a named parameter in the SQL is not found in the dictionary
54
+ or if a parameter in the dictionary is not used in the SQL.
55
+ """
56
+ if not isinstance(parameters, dict) or not parameters:
57
+ # If parameters are not a dict, or empty dict, assume positional/no params
58
+ # Let the underlying driver handle tuples/lists directly
59
+ return sql, parameters
60
+
61
+ processed_sql = ""
62
+ processed_params_list: list[Any] = []
63
+ last_end = 0
64
+ found_params: set[str] = set()
65
+
66
+ for match in PARAM_REGEX.finditer(sql):
67
+ if match.group("dquote") is not None or match.group("squote") is not None:
68
+ # Skip placeholders within quotes
69
+ continue
70
+
71
+ var_name = match.group("var_name")
72
+ if var_name is None: # Should not happen with the regex, but safeguard
73
+ continue
74
+
75
+ if var_name not in parameters:
76
+ msg = f"Named parameter ':{var_name}' found in SQL but not provided in parameters dictionary."
77
+ raise ValueError(msg)
78
+
79
+ # Append segment before the placeholder + the driver's positional placeholder
80
+ processed_sql += sql[last_end : match.start("var_name") - 1] + "%s"
81
+ processed_params_list.append(parameters[var_name])
82
+ found_params.add(var_name)
83
+ last_end = match.end("var_name")
84
+
85
+ # Append the rest of the SQL string
86
+ processed_sql += sql[last_end:]
87
+
88
+ # Check if all provided parameters were used
89
+ unused_params = set(parameters.keys()) - found_params
90
+ if unused_params:
91
+ msg = f"Parameters provided but not found in SQL: {unused_params}"
92
+ # Depending on desired strictness, this could be a warning or an error
93
+ # For now, let's raise an error for clarity
94
+ raise ValueError(msg)
95
+
96
+ return processed_sql, tuple(processed_params_list)
97
+
98
+ def select(
99
+ self,
100
+ sql: str,
101
+ parameters: "Optional[StatementParameterType]" = None,
102
+ /,
103
+ connection: "Optional[Connection]" = None,
104
+ schema_type: "Optional[type[ModelDTOT]]" = None,
105
+ ) -> "list[Union[ModelDTOT, dict[str, Any]]]":
106
+ """Fetch data from the database.
107
+
108
+ Returns:
109
+ List of row data as either model instances or dictionaries.
110
+ """
111
+ connection = self._connection(connection)
112
+ sql, parameters = self._process_sql_params(sql, parameters)
113
+ with self._with_cursor(connection) as cursor:
114
+ cursor.execute(sql, parameters)
115
+ results = cursor.fetchall()
116
+ if not results:
117
+ return []
118
+
119
+ if schema_type is not None:
120
+ return [cast("ModelDTOT", schema_type(**row)) for row in results] # pyright: ignore[reportUnknownArgumentType]
121
+ return [cast("dict[str,Any]", row) for row in results] # pyright: ignore[reportUnknownArgumentType]
122
+
123
+ def select_one(
124
+ self,
125
+ sql: str,
126
+ parameters: "Optional[StatementParameterType]" = None,
127
+ /,
128
+ connection: "Optional[Connection]" = None,
129
+ schema_type: "Optional[type[ModelDTOT]]" = None,
130
+ ) -> "Union[ModelDTOT, dict[str, Any]]":
131
+ """Fetch one row from the database.
132
+
133
+ Returns:
134
+ The first row of the query results.
135
+ """
136
+ connection = self._connection(connection)
137
+ sql, parameters = self._process_sql_params(sql, parameters)
138
+
139
+ with self._with_cursor(connection) as cursor:
140
+ cursor.execute(sql, parameters)
141
+ row = cursor.fetchone()
142
+ row = self.check_not_found(row)
143
+ if schema_type is not None:
144
+ return cast("ModelDTOT", schema_type(**cast("dict[str,Any]", row)))
145
+ return cast("dict[str,Any]", row)
146
+
147
+ def select_one_or_none(
148
+ self,
149
+ sql: str,
150
+ parameters: "Optional[StatementParameterType]" = None,
151
+ /,
152
+ connection: "Optional[Connection]" = None,
153
+ schema_type: "Optional[type[ModelDTOT]]" = None,
154
+ ) -> "Optional[Union[ModelDTOT, dict[str, Any]]]":
155
+ """Fetch one row from the database.
156
+
157
+ Returns:
158
+ The first row of the query results.
159
+ """
160
+ connection = self._connection(connection)
161
+ sql, parameters = self._process_sql_params(sql, parameters)
162
+
163
+ with self._with_cursor(connection) as cursor:
164
+ cursor.execute(sql, parameters)
165
+ row = cursor.fetchone()
166
+ if row is None:
167
+ return None
168
+ if schema_type is not None:
169
+ return cast("ModelDTOT", schema_type(**cast("dict[str,Any]", row)))
170
+ return cast("dict[str,Any]", row)
171
+
172
+ def select_value(
173
+ self,
174
+ sql: str,
175
+ parameters: "Optional[StatementParameterType]" = None,
176
+ /,
177
+ connection: "Optional[Connection]" = None,
178
+ schema_type: "Optional[type[T]]" = None,
179
+ ) -> "Union[T, Any]":
180
+ """Fetch a single value from the database.
181
+
182
+ Returns:
183
+ The first value from the first row of results, or None if no results.
184
+ """
185
+ connection = self._connection(connection)
186
+ sql, parameters = self._process_sql_params(sql, parameters)
187
+
188
+ with self._with_cursor(connection) as cursor:
189
+ cursor.execute(sql, parameters)
190
+ row = cursor.fetchone()
191
+ row = self.check_not_found(row)
192
+ val = next(iter(row))
193
+ if schema_type is not None:
194
+ return schema_type(val) # type: ignore[call-arg]
195
+ return val
196
+
197
+ def select_value_or_none(
198
+ self,
199
+ sql: str,
200
+ parameters: "Optional[StatementParameterType]" = None,
201
+ /,
202
+ connection: "Optional[Connection]" = None,
203
+ schema_type: "Optional[type[T]]" = None,
204
+ ) -> "Optional[Union[T, Any]]":
205
+ """Fetch a single value from the database.
206
+
207
+ Returns:
208
+ The first value from the first row of results, or None if no results.
209
+ """
210
+ connection = self._connection(connection)
211
+ sql, parameters = self._process_sql_params(sql, parameters)
212
+
213
+ with self._with_cursor(connection) as cursor:
214
+ cursor.execute(sql, parameters)
215
+ row = cursor.fetchone()
216
+ if row is None:
217
+ return None
218
+ val = next(iter(row))
219
+ if schema_type is not None:
220
+ return schema_type(val) # type: ignore[call-arg]
221
+ return val
222
+
223
+ def insert_update_delete(
224
+ self,
225
+ sql: str,
226
+ parameters: "Optional[StatementParameterType]" = None,
227
+ /,
228
+ connection: "Optional[Connection]" = None,
229
+ ) -> int:
230
+ """Insert, update, or delete data from the database.
231
+
232
+ Returns:
233
+ Row count affected by the operation.
234
+ """
235
+ connection = self._connection(connection)
236
+ sql, parameters = self._process_sql_params(sql, parameters)
237
+
238
+ with self._with_cursor(connection) as cursor:
239
+ cursor.execute(sql, parameters)
240
+ return cursor.rowcount if hasattr(cursor, "rowcount") else -1
241
+
242
+ def insert_update_delete_returning(
243
+ self,
244
+ sql: str,
245
+ parameters: "Optional[StatementParameterType]" = None,
246
+ /,
247
+ connection: "Optional[Connection]" = None,
248
+ schema_type: "Optional[type[ModelDTOT]]" = None,
249
+ ) -> "Optional[Union[dict[str, Any], ModelDTOT]]":
250
+ """Insert, update, or delete data from the database and return result.
251
+
252
+ Returns:
253
+ The first row of results.
254
+ """
255
+ connection = self._connection(connection)
256
+ sql, parameters = self._process_sql_params(sql, parameters)
257
+
258
+ with self._with_cursor(connection) as cursor:
259
+ cursor.execute(sql, parameters)
260
+ result = cursor.fetchone()
261
+
262
+ if result is None:
263
+ return None
264
+
265
+ if schema_type is not None:
266
+ return cast("ModelDTOT", schema_type(**result)) # pyright: ignore[reportUnknownArgumentType]
267
+ return cast("dict[str, Any]", result) # pyright: ignore[reportUnknownArgumentType,reportUnknownVariableType]
268
+
269
+ def execute_script(
270
+ self,
271
+ sql: str,
272
+ parameters: "Optional[StatementParameterType]" = None,
273
+ /,
274
+ connection: "Optional[Connection]" = None,
275
+ ) -> str:
276
+ """Execute a script.
277
+
278
+ Returns:
279
+ Status message for the operation.
280
+ """
281
+ connection = self._connection(connection)
282
+ sql, parameters = self._process_sql_params(sql, parameters)
283
+
284
+ with self._with_cursor(connection) as cursor:
285
+ cursor.execute(sql, parameters)
286
+ return str(cursor.rowcount)
287
+
288
+ def execute_script_returning(
289
+ self,
290
+ sql: str,
291
+ parameters: "Optional[StatementParameterType]" = None,
292
+ /,
293
+ connection: "Optional[Connection]" = None,
294
+ schema_type: "Optional[type[ModelDTOT]]" = None,
295
+ ) -> "Optional[Union[dict[str, Any], ModelDTOT]]":
296
+ """Execute a script and return result.
297
+
298
+ Returns:
299
+ The first row of results.
300
+ """
301
+ connection = self._connection(connection)
302
+ sql, parameters = self._process_sql_params(sql, parameters)
303
+
304
+ with self._with_cursor(connection) as cursor:
305
+ cursor.execute(sql, parameters)
306
+ result = cursor.fetchone()
307
+
308
+ if result is None:
309
+ return None
310
+
311
+ if schema_type is not None:
312
+ return cast("ModelDTOT", schema_type(**result)) # pyright: ignore[reportUnknownArgumentType]
313
+ return cast("dict[str, Any]", result) # pyright: ignore[reportUnknownArgumentType,reportUnknownVariableType]
314
+
315
+
316
+ class PsycopgAsyncDriver(AsyncDriverAdapterProtocol["AsyncConnection"]):
317
+ """Psycopg Async Driver Adapter."""
318
+
319
+ connection: "AsyncConnection"
320
+ param_style: str = "%s"
321
+
322
+ def __init__(self, connection: "AsyncConnection") -> None:
323
+ self.connection = connection
324
+
325
+ @staticmethod
326
+ @asynccontextmanager
327
+ async def _with_cursor(connection: "AsyncConnection") -> "AsyncGenerator[Any, None]":
328
+ cursor = connection.cursor(row_factory=dict_row)
329
+ try:
330
+ yield cursor
331
+ finally:
332
+ await cursor.close()
333
+
334
+ def _process_sql_params(
335
+ self, sql: str, parameters: "Optional[StatementParameterType]" = None
336
+ ) -> "tuple[str, Optional[Union[tuple[Any, ...], list[Any], dict[str, Any]]]]":
337
+ """Process SQL query and parameters for DB-API execution.
338
+
339
+ Converts named parameters (:name) to positional parameters (%s)
340
+ if the input parameters are a dictionary.
341
+
342
+ Args:
343
+ sql: The SQL query string.
344
+ parameters: The parameters for the query (dict, tuple, list, or None).
345
+
346
+ Returns:
347
+ A tuple containing the processed SQL string and the processed parameters
348
+ (always a tuple or None if the input was a dictionary, otherwise the original type).
349
+
350
+ Raises:
351
+ ValueError: If a named parameter in the SQL is not found in the dictionary
352
+ or if a parameter in the dictionary is not used in the SQL.
353
+ """
354
+ if not isinstance(parameters, dict) or not parameters:
355
+ # If parameters are not a dict, or empty dict, assume positional/no params
356
+ # Let the underlying driver handle tuples/lists directly
357
+ return sql, parameters
358
+
359
+ processed_sql = ""
360
+ processed_params_list: list[Any] = []
361
+ last_end = 0
362
+ found_params: set[str] = set()
363
+
364
+ for match in PARAM_REGEX.finditer(sql):
365
+ if match.group("dquote") is not None or match.group("squote") is not None:
366
+ # Skip placeholders within quotes
367
+ continue
368
+
369
+ var_name = match.group("var_name")
370
+ if var_name is None: # Should not happen with the regex, but safeguard
371
+ continue
372
+
373
+ if var_name not in parameters:
374
+ msg = f"Named parameter ':{var_name}' found in SQL but not provided in parameters dictionary."
375
+ raise ValueError(msg)
376
+
377
+ # Append segment before the placeholder + the driver's positional placeholder
378
+ processed_sql += sql[last_end : match.start("var_name") - 1] + "%s"
379
+ processed_params_list.append(parameters[var_name])
380
+ found_params.add(var_name)
381
+ last_end = match.end("var_name")
382
+
383
+ # Append the rest of the SQL string
384
+ processed_sql += sql[last_end:]
385
+
386
+ # Check if all provided parameters were used
387
+ unused_params = set(parameters.keys()) - found_params
388
+ if unused_params:
389
+ msg = f"Parameters provided but not found in SQL: {unused_params}"
390
+ # Depending on desired strictness, this could be a warning or an error
391
+ # For now, let's raise an error for clarity
392
+ raise ValueError(msg)
393
+
394
+ return processed_sql, tuple(processed_params_list)
395
+
396
+ async def select(
397
+ self,
398
+ sql: str,
399
+ parameters: "Optional[StatementParameterType]" = None,
400
+ /,
401
+ connection: "Optional[AsyncConnection]" = None,
402
+ schema_type: "Optional[type[ModelDTOT]]" = None,
403
+ ) -> "list[Union[ModelDTOT, dict[str, Any]]]":
404
+ """Fetch data from the database.
405
+
406
+ Returns:
407
+ List of row data as either model instances or dictionaries.
408
+ """
409
+ connection = self._connection(connection)
410
+ sql, parameters = self._process_sql_params(sql, parameters)
411
+ results: list[Union[ModelDTOT, dict[str, Any]]] = []
412
+
413
+ async with self._with_cursor(connection) as cursor:
414
+ await cursor.execute(sql, parameters)
415
+ results = await cursor.fetchall()
416
+ if not results:
417
+ return []
418
+ if schema_type is not None:
419
+ return [cast("ModelDTOT", schema_type(**cast("dict[str,Any]", row))) for row in results] # pyright: ignore[reportUnknownArgumentType]
420
+ return [cast("dict[str,Any]", row) for row in results] # pyright: ignore[reportUnknownArgumentType]
421
+
422
+ async def select_one(
423
+ self,
424
+ sql: str,
425
+ parameters: "Optional[StatementParameterType]" = None,
426
+ /,
427
+ connection: "Optional[AsyncConnection]" = None,
428
+ schema_type: "Optional[type[ModelDTOT]]" = None,
429
+ ) -> "Union[ModelDTOT, dict[str, Any]]":
430
+ """Fetch one row from the database.
431
+
432
+ Returns:
433
+ The first row of the query results.
434
+ """
435
+ connection = self._connection(connection)
436
+ sql, parameters = self._process_sql_params(sql, parameters)
437
+
438
+ async with self._with_cursor(connection) as cursor:
439
+ await cursor.execute(sql, parameters)
440
+ row = await cursor.fetchone()
441
+ row = self.check_not_found(row)
442
+ if schema_type is not None:
443
+ return cast("ModelDTOT", schema_type(**cast("dict[str,Any]", row)))
444
+ return cast("dict[str,Any]", row)
445
+
446
+ async def select_one_or_none(
447
+ self,
448
+ sql: str,
449
+ parameters: "Optional[StatementParameterType]" = None,
450
+ /,
451
+ connection: "Optional[AsyncConnection]" = None,
452
+ schema_type: "Optional[type[ModelDTOT]]" = None,
453
+ ) -> "Optional[Union[ModelDTOT, dict[str, Any]]]":
454
+ """Fetch one row from the database.
455
+
456
+ Returns:
457
+ The first row of the query results.
458
+ """
459
+ connection = self._connection(connection)
460
+ sql, parameters = self._process_sql_params(sql, parameters)
461
+
462
+ async with self._with_cursor(connection) as cursor:
463
+ await cursor.execute(sql, parameters)
464
+ row = await cursor.fetchone()
465
+ if row is None:
466
+ return None
467
+ if schema_type is not None:
468
+ return cast("ModelDTOT", schema_type(**cast("dict[str,Any]", row)))
469
+ return cast("dict[str,Any]", row)
470
+
471
+ async def select_value(
472
+ self,
473
+ sql: str,
474
+ parameters: "Optional[StatementParameterType]" = None,
475
+ /,
476
+ connection: "Optional[AsyncConnection]" = None,
477
+ schema_type: "Optional[type[T]]" = None,
478
+ ) -> "Optional[Union[T, Any]]":
479
+ """Fetch a single value from the database.
480
+
481
+ Returns:
482
+ The first value from the first row of results, or None if no results.
483
+ """
484
+ connection = self._connection(connection)
485
+ sql, parameters = self._process_sql_params(sql, parameters)
486
+
487
+ async with self._with_cursor(connection) as cursor:
488
+ await cursor.execute(sql, parameters)
489
+ row = await cursor.fetchone()
490
+ row = self.check_not_found(row)
491
+ val = next(iter(row))
492
+ if schema_type is not None:
493
+ return schema_type(val) # type: ignore[call-arg]
494
+ return val
495
+
496
+ async def select_value_or_none(
497
+ self,
498
+ sql: str,
499
+ parameters: "Optional[StatementParameterType]" = None,
500
+ /,
501
+ connection: "Optional[AsyncConnection]" = None,
502
+ schema_type: "Optional[type[T]]" = None,
503
+ ) -> "Optional[Union[T, Any]]":
504
+ """Fetch a single value from the database.
505
+
506
+ Returns:
507
+ The first value from the first row of results, or None if no results.
508
+ """
509
+ connection = self._connection(connection)
510
+ sql, parameters = self._process_sql_params(sql, parameters)
511
+
512
+ async with self._with_cursor(connection) as cursor:
513
+ await cursor.execute(sql, parameters)
514
+ row = await cursor.fetchone()
515
+ if row is None:
516
+ return None
517
+ val = next(iter(row))
518
+ if schema_type is not None:
519
+ return schema_type(val) # type: ignore[call-arg]
520
+ return val
521
+
522
+ async def insert_update_delete(
523
+ self,
524
+ sql: str,
525
+ parameters: "Optional[StatementParameterType]" = None,
526
+ /,
527
+ connection: "Optional[AsyncConnection]" = None,
528
+ ) -> int:
529
+ """Insert, update, or delete data from the database.
530
+
531
+ Returns:
532
+ Row count affected by the operation.
533
+ """
534
+ connection = self._connection(connection)
535
+ sql, parameters = self._process_sql_params(sql, parameters)
536
+
537
+ async with self._with_cursor(connection) as cursor:
538
+ await cursor.execute(sql, parameters)
539
+ try:
540
+ rowcount = int(cursor.rowcount)
541
+ except (TypeError, ValueError):
542
+ rowcount = -1
543
+ return rowcount
544
+
545
+ async def insert_update_delete_returning(
546
+ self,
547
+ sql: str,
548
+ parameters: "Optional[StatementParameterType]" = None,
549
+ /,
550
+ connection: "Optional[AsyncConnection]" = None,
551
+ schema_type: "Optional[type[ModelDTOT]]" = None,
552
+ ) -> "Optional[Union[dict[str, Any], ModelDTOT]]":
553
+ """Insert, update, or delete data from the database and return result.
554
+
555
+ Returns:
556
+ The first row of results.
557
+ """
558
+ connection = self._connection(connection)
559
+ sql, parameters = self._process_sql_params(sql, parameters)
560
+
561
+ async with self._with_cursor(connection) as cursor:
562
+ await cursor.execute(sql, parameters)
563
+ result = await cursor.fetchone()
564
+
565
+ if result is None:
566
+ return None
567
+
568
+ if schema_type is not None:
569
+ return cast("ModelDTOT", schema_type(**result)) # pyright: ignore[reportUnknownArgumentType]
570
+ return cast("dict[str, Any]", result) # pyright: ignore[reportUnknownArgumentType,reportUnknownVariableType]
571
+
572
+ async def execute_script(
573
+ self,
574
+ sql: str,
575
+ parameters: "Optional[StatementParameterType]" = None,
576
+ /,
577
+ connection: "Optional[AsyncConnection]" = None,
578
+ ) -> str:
579
+ """Execute a script.
580
+
581
+ Returns:
582
+ Status message for the operation.
583
+ """
584
+ connection = self._connection(connection)
585
+ sql, parameters = self._process_sql_params(sql, parameters)
586
+
587
+ async with self._with_cursor(connection) as cursor:
588
+ await cursor.execute(sql, parameters)
589
+ return str(cursor.rowcount)
590
+
591
+ async def execute_script_returning(
592
+ self,
593
+ sql: str,
594
+ parameters: "Optional[StatementParameterType]" = None,
595
+ /,
596
+ connection: "Optional[AsyncConnection]" = None,
597
+ schema_type: "Optional[type[ModelDTOT]]" = None,
598
+ ) -> "Optional[Union[dict[str, Any], ModelDTOT]]":
599
+ """Execute a script and return result.
600
+
601
+ Returns:
602
+ The first row of results.
603
+ """
604
+ connection = self._connection(connection)
605
+ sql, parameters = self._process_sql_params(sql, parameters)
606
+
607
+ async with self._with_cursor(connection) as cursor:
608
+ await cursor.execute(sql, parameters)
609
+ result = await cursor.fetchone()
610
+
611
+ if result is None:
612
+ return None
613
+
614
+ if schema_type is not None:
615
+ return cast("ModelDTOT", schema_type(**result)) # pyright: ignore[reportUnknownArgumentType]
616
+ return cast("dict[str, Any]", result) # pyright: ignore[reportUnknownArgumentType,reportUnknownVariableType]
@@ -0,0 +1,7 @@
1
+ from sqlspec.adapters.sqlite.config import Sqlite
2
+ from sqlspec.adapters.sqlite.driver import SqliteDriver
3
+
4
+ __all__ = (
5
+ "Sqlite",
6
+ "SqliteDriver",
7
+ )
@@ -1,20 +1,22 @@
1
1
  from contextlib import contextmanager
2
- from dataclasses import dataclass
2
+ from dataclasses import dataclass, field
3
+ from sqlite3 import Connection
3
4
  from typing import TYPE_CHECKING, Any, Literal, Optional, Union
4
5
 
6
+ from sqlspec.adapters.sqlite.driver import SqliteDriver
5
7
  from sqlspec.base import NoPoolSyncConfig
6
8
  from sqlspec.exceptions import ImproperConfigurationError
7
9
  from sqlspec.typing import Empty, EmptyType, dataclass_to_dict
8
10
 
9
11
  if TYPE_CHECKING:
10
12
  from collections.abc import Generator
11
- from sqlite3 import Connection
12
13
 
13
- __all__ = ("SqliteConfig",)
14
+
15
+ __all__ = ("Sqlite",)
14
16
 
15
17
 
16
18
  @dataclass
17
- class SqliteConfig(NoPoolSyncConfig["Connection"]):
19
+ class Sqlite(NoPoolSyncConfig["Connection", "SqliteDriver"]):
18
20
  """Configuration for SQLite database connections.
19
21
 
20
22
  This class provides configuration options for SQLite database connections, wrapping all parameters
@@ -46,6 +48,10 @@ class SqliteConfig(NoPoolSyncConfig["Connection"]):
46
48
 
47
49
  uri: "Union[bool, EmptyType]" = Empty
48
50
  """If set to True, database is interpreted as a URI with supported options."""
51
+ driver_type: "type[SqliteDriver]" = field(init=False, default_factory=lambda: SqliteDriver)
52
+ """Type of the driver object"""
53
+ connection_type: "type[Connection]" = field(init=False, default_factory=lambda: Connection)
54
+ """Type of the connection object"""
49
55
 
50
56
  @property
51
57
  def connection_config_dict(self) -> "dict[str, Any]":
@@ -54,7 +60,9 @@ class SqliteConfig(NoPoolSyncConfig["Connection"]):
54
60
  Returns:
55
61
  A string keyed dict of config kwargs for the sqlite3.connect() function.
56
62
  """
57
- return dataclass_to_dict(self, exclude_empty=True, convert_nested=False)
63
+ return dataclass_to_dict(
64
+ self, exclude_empty=True, convert_nested=False, exclude={"pool_instance", "driver_type", "connection_type"}
65
+ )
58
66
 
59
67
  def create_connection(self) -> "Connection":
60
68
  """Create and return a new database connection.
@@ -80,11 +88,21 @@ class SqliteConfig(NoPoolSyncConfig["Connection"]):
80
88
  Yields:
81
89
  A SQLite connection instance.
82
90
 
83
- Raises:
84
- ImproperConfigurationError: If the connection could not be established.
85
91
  """
86
92
  connection = self.create_connection()
87
93
  try:
88
94
  yield connection
89
95
  finally:
90
96
  connection.close()
97
+
98
+ @contextmanager
99
+ def provide_session(self, *args: Any, **kwargs: Any) -> "Generator[SqliteDriver, None, None]":
100
+ """Create and provide a database connection.
101
+
102
+ Yields:
103
+ A DuckDB driver instance.
104
+
105
+
106
+ """
107
+ with self.provide_connection(*args, **kwargs) as connection:
108
+ yield self.driver_type(connection)