sqlspec 0.7.1__py3-none-any.whl → 0.9.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 (54) hide show
  1. sqlspec/__init__.py +15 -0
  2. sqlspec/_serialization.py +16 -2
  3. sqlspec/_typing.py +40 -7
  4. sqlspec/adapters/adbc/__init__.py +7 -0
  5. sqlspec/adapters/adbc/config.py +183 -17
  6. sqlspec/adapters/adbc/driver.py +392 -0
  7. sqlspec/adapters/aiosqlite/__init__.py +5 -1
  8. sqlspec/adapters/aiosqlite/config.py +24 -6
  9. sqlspec/adapters/aiosqlite/driver.py +264 -0
  10. sqlspec/adapters/asyncmy/__init__.py +7 -2
  11. sqlspec/adapters/asyncmy/config.py +71 -11
  12. sqlspec/adapters/asyncmy/driver.py +246 -0
  13. sqlspec/adapters/asyncpg/__init__.py +9 -0
  14. sqlspec/adapters/asyncpg/config.py +102 -25
  15. sqlspec/adapters/asyncpg/driver.py +444 -0
  16. sqlspec/adapters/duckdb/__init__.py +5 -1
  17. sqlspec/adapters/duckdb/config.py +194 -12
  18. sqlspec/adapters/duckdb/driver.py +225 -0
  19. sqlspec/adapters/oracledb/__init__.py +7 -4
  20. sqlspec/adapters/oracledb/config/__init__.py +4 -4
  21. sqlspec/adapters/oracledb/config/_asyncio.py +96 -12
  22. sqlspec/adapters/oracledb/config/_common.py +1 -1
  23. sqlspec/adapters/oracledb/config/_sync.py +96 -12
  24. sqlspec/adapters/oracledb/driver.py +571 -0
  25. sqlspec/adapters/psqlpy/__init__.py +0 -0
  26. sqlspec/adapters/psqlpy/config.py +258 -0
  27. sqlspec/adapters/psqlpy/driver.py +335 -0
  28. sqlspec/adapters/psycopg/__init__.py +16 -0
  29. sqlspec/adapters/psycopg/config/__init__.py +6 -6
  30. sqlspec/adapters/psycopg/config/_async.py +107 -15
  31. sqlspec/adapters/psycopg/config/_common.py +2 -2
  32. sqlspec/adapters/psycopg/config/_sync.py +107 -15
  33. sqlspec/adapters/psycopg/driver.py +578 -0
  34. sqlspec/adapters/sqlite/__init__.py +7 -0
  35. sqlspec/adapters/sqlite/config.py +24 -6
  36. sqlspec/adapters/sqlite/driver.py +305 -0
  37. sqlspec/base.py +565 -63
  38. sqlspec/exceptions.py +30 -0
  39. sqlspec/extensions/litestar/__init__.py +19 -0
  40. sqlspec/extensions/litestar/_utils.py +56 -0
  41. sqlspec/extensions/litestar/config.py +87 -0
  42. sqlspec/extensions/litestar/handlers.py +213 -0
  43. sqlspec/extensions/litestar/plugin.py +105 -11
  44. sqlspec/statement.py +373 -0
  45. sqlspec/typing.py +81 -17
  46. sqlspec/utils/__init__.py +3 -0
  47. sqlspec/utils/fixtures.py +4 -5
  48. sqlspec/utils/sync_tools.py +335 -0
  49. {sqlspec-0.7.1.dist-info → sqlspec-0.9.0.dist-info}/METADATA +4 -1
  50. sqlspec-0.9.0.dist-info/RECORD +61 -0
  51. sqlspec-0.7.1.dist-info/RECORD +0 -46
  52. {sqlspec-0.7.1.dist-info → sqlspec-0.9.0.dist-info}/WHEEL +0 -0
  53. {sqlspec-0.7.1.dist-info → sqlspec-0.9.0.dist-info}/licenses/LICENSE +0 -0
  54. {sqlspec-0.7.1.dist-info → sqlspec-0.9.0.dist-info}/licenses/NOTICE +0 -0
@@ -0,0 +1,578 @@
1
+ import logging
2
+ from contextlib import asynccontextmanager, contextmanager
3
+ from typing import TYPE_CHECKING, Any, Optional, Union, cast
4
+
5
+ from psycopg.rows import dict_row
6
+
7
+ from sqlspec.base import AsyncDriverAdapterProtocol, SyncDriverAdapterProtocol, T
8
+ from sqlspec.exceptions import SQLParsingError
9
+ from sqlspec.statement import PARAM_REGEX, SQLStatement
10
+
11
+ if TYPE_CHECKING:
12
+ from collections.abc import AsyncGenerator, Generator
13
+
14
+ from psycopg import AsyncConnection, Connection
15
+
16
+ from sqlspec.typing import ModelDTOT, StatementParameterType
17
+
18
+ logger = logging.getLogger("sqlspec")
19
+
20
+ __all__ = ("PsycopgAsyncDriver", "PsycopgSyncDriver")
21
+
22
+
23
+ class PsycopgSyncDriver(SyncDriverAdapterProtocol["Connection"]):
24
+ """Psycopg Sync Driver Adapter."""
25
+
26
+ connection: "Connection"
27
+ dialect: str = "postgres"
28
+
29
+ def __init__(self, connection: "Connection") -> None:
30
+ self.connection = connection
31
+
32
+ def _process_sql_params(
33
+ self,
34
+ sql: str,
35
+ parameters: "Optional[StatementParameterType]" = None,
36
+ /,
37
+ **kwargs: Any,
38
+ ) -> "tuple[str, Optional[Union[tuple[Any, ...], list[Any], dict[str, Any]]]]":
39
+ """Process SQL and parameters, converting :name -> %(name)s if needed."""
40
+ stmt = SQLStatement(sql=sql, parameters=parameters, dialect=self.dialect, kwargs=kwargs or None)
41
+ processed_sql, processed_params = stmt.process()
42
+
43
+ if isinstance(processed_params, dict):
44
+ parameter_dict = processed_params
45
+ processed_sql_parts: list[str] = []
46
+ last_end = 0
47
+ found_params_regex: list[str] = []
48
+
49
+ for match in PARAM_REGEX.finditer(processed_sql):
50
+ if match.group("dquote") or match.group("squote") or match.group("comment"):
51
+ continue
52
+
53
+ if match.group("var_name"):
54
+ var_name = match.group("var_name")
55
+ found_params_regex.append(var_name)
56
+ start = match.start("var_name") - 1
57
+ end = match.end("var_name")
58
+
59
+ if var_name not in parameter_dict:
60
+ msg = (
61
+ f"Named parameter ':{var_name}' found in SQL but missing from processed parameters. "
62
+ f"Processed SQL: {processed_sql}"
63
+ )
64
+ raise SQLParsingError(msg)
65
+
66
+ processed_sql_parts.extend((processed_sql[last_end:start], f"%({var_name})s"))
67
+ last_end = end
68
+
69
+ processed_sql_parts.append(processed_sql[last_end:])
70
+ final_sql = "".join(processed_sql_parts)
71
+
72
+ if not found_params_regex and parameter_dict:
73
+ logger.warning(
74
+ "Dict params provided (%s), but no :name placeholders found. SQL: %s",
75
+ list(parameter_dict.keys()),
76
+ processed_sql,
77
+ )
78
+ return processed_sql, parameter_dict
79
+
80
+ return final_sql, parameter_dict
81
+
82
+ return processed_sql, processed_params
83
+
84
+ @staticmethod
85
+ @contextmanager
86
+ def _with_cursor(connection: "Connection") -> "Generator[Any, None, None]":
87
+ cursor = connection.cursor(row_factory=dict_row)
88
+ try:
89
+ yield cursor
90
+ finally:
91
+ cursor.close()
92
+
93
+ def select(
94
+ self,
95
+ sql: str,
96
+ parameters: "Optional[StatementParameterType]" = None,
97
+ /,
98
+ *,
99
+ schema_type: "Optional[type[ModelDTOT]]" = None,
100
+ connection: "Optional[Connection]" = None,
101
+ **kwargs: Any,
102
+ ) -> "list[Union[ModelDTOT, dict[str, Any]]]":
103
+ """Fetch data from the database.
104
+
105
+ Returns:
106
+ List of row data as either model instances or dictionaries.
107
+ """
108
+ connection = self._connection(connection)
109
+ sql, parameters = self._process_sql_params(sql, parameters, **kwargs)
110
+ with self._with_cursor(connection) as cursor:
111
+ cursor.execute(sql, parameters)
112
+ results = cursor.fetchall()
113
+ if not results:
114
+ return []
115
+
116
+ if schema_type is not None:
117
+ return [cast("ModelDTOT", schema_type(**row)) for row in results] # pyright: ignore[reportUnknownArgumentType]
118
+ return [cast("dict[str,Any]", row) for row in results] # pyright: ignore[reportUnknownArgumentType]
119
+
120
+ def select_one(
121
+ self,
122
+ sql: str,
123
+ parameters: "Optional[StatementParameterType]" = None,
124
+ /,
125
+ *,
126
+ connection: "Optional[Connection]" = None,
127
+ schema_type: "Optional[type[ModelDTOT]]" = None,
128
+ **kwargs: Any,
129
+ ) -> "Union[ModelDTOT, dict[str, Any]]":
130
+ """Fetch one row from the database.
131
+
132
+ Returns:
133
+ The first row of the query results.
134
+ """
135
+ connection = self._connection(connection)
136
+ sql, parameters = self._process_sql_params(sql, parameters, **kwargs)
137
+ with self._with_cursor(connection) as cursor:
138
+ cursor.execute(sql, parameters)
139
+ row = cursor.fetchone()
140
+ row = self.check_not_found(row)
141
+ if schema_type is not None:
142
+ return cast("ModelDTOT", schema_type(**cast("dict[str,Any]", row)))
143
+ return cast("dict[str,Any]", row)
144
+
145
+ def select_one_or_none(
146
+ self,
147
+ sql: str,
148
+ parameters: "Optional[StatementParameterType]" = None,
149
+ /,
150
+ *,
151
+ connection: "Optional[Connection]" = None,
152
+ schema_type: "Optional[type[ModelDTOT]]" = None,
153
+ **kwargs: Any,
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, **kwargs)
162
+ with self._with_cursor(connection) as cursor:
163
+ cursor.execute(sql, parameters)
164
+ row = cursor.fetchone()
165
+ if row is None:
166
+ return None
167
+ if schema_type is not None:
168
+ return cast("ModelDTOT", schema_type(**cast("dict[str,Any]", row)))
169
+ return cast("dict[str,Any]", row)
170
+
171
+ def select_value(
172
+ self,
173
+ sql: str,
174
+ parameters: "Optional[StatementParameterType]" = None,
175
+ /,
176
+ *,
177
+ connection: "Optional[Connection]" = None,
178
+ schema_type: "Optional[type[T]]" = None,
179
+ **kwargs: Any,
180
+ ) -> "Union[T, Any]":
181
+ """Fetch a single value from the database.
182
+
183
+ Returns:
184
+ The first value from the first row of results, or None if no results.
185
+ """
186
+ connection = self._connection(connection)
187
+ sql, parameters = self._process_sql_params(sql, parameters, **kwargs)
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.values())) if row else None
193
+ val = self.check_not_found(val)
194
+ if schema_type is not None:
195
+ return schema_type(val) # type: ignore[call-arg]
196
+ return val
197
+
198
+ def select_value_or_none(
199
+ self,
200
+ sql: str,
201
+ parameters: "Optional[StatementParameterType]" = None,
202
+ /,
203
+ *,
204
+ connection: "Optional[Connection]" = None,
205
+ schema_type: "Optional[type[T]]" = None,
206
+ **kwargs: Any,
207
+ ) -> "Optional[Union[T, Any]]":
208
+ """Fetch a single value from the database.
209
+
210
+ Returns:
211
+ The first value from the first row of results, or None if no results.
212
+ """
213
+ connection = self._connection(connection)
214
+ sql, parameters = self._process_sql_params(sql, parameters, **kwargs)
215
+ with self._with_cursor(connection) as cursor:
216
+ cursor.execute(sql, parameters)
217
+ row = cursor.fetchone()
218
+ if row is None:
219
+ return None
220
+ val = next(iter(row.values())) if row else None
221
+ if val is None:
222
+ return None
223
+ if schema_type is not None:
224
+ return schema_type(val) # type: ignore[call-arg]
225
+ return val
226
+
227
+ def insert_update_delete(
228
+ self,
229
+ sql: str,
230
+ parameters: "Optional[StatementParameterType]" = None,
231
+ /,
232
+ *,
233
+ connection: "Optional[Connection]" = None,
234
+ **kwargs: Any,
235
+ ) -> int:
236
+ """Execute an INSERT, UPDATE, or DELETE query and return the number of affected rows.
237
+
238
+ Returns:
239
+ The number of rows affected by the operation.
240
+ """
241
+ connection = self._connection(connection)
242
+ sql, parameters = self._process_sql_params(sql, parameters, **kwargs)
243
+ with self._with_cursor(connection) as cursor:
244
+ cursor.execute(sql, parameters)
245
+ return getattr(cursor, "rowcount", -1) # pyright: ignore[reportUnknownMemberType]
246
+
247
+ def insert_update_delete_returning(
248
+ self,
249
+ sql: str,
250
+ parameters: "Optional[StatementParameterType]" = None,
251
+ /,
252
+ *,
253
+ connection: "Optional[Connection]" = None,
254
+ schema_type: "Optional[type[ModelDTOT]]" = None,
255
+ **kwargs: Any,
256
+ ) -> "Optional[Union[dict[str, Any], ModelDTOT]]":
257
+ """Insert, update, or delete data from the database and return result.
258
+
259
+ Returns:
260
+ The first row of results.
261
+ """
262
+ connection = self._connection(connection)
263
+ sql, parameters = self._process_sql_params(sql, parameters, **kwargs)
264
+ with self._with_cursor(connection) as cursor:
265
+ cursor.execute(sql, parameters)
266
+ result = cursor.fetchone()
267
+
268
+ if result is None:
269
+ return None
270
+
271
+ if schema_type is not None:
272
+ return cast("ModelDTOT", schema_type(**result)) # pyright: ignore[reportUnknownArgumentType]
273
+ return cast("dict[str, Any]", result) # pyright: ignore[reportUnknownArgumentType,reportUnknownVariableType]
274
+
275
+ def execute_script(
276
+ self,
277
+ sql: str,
278
+ parameters: "Optional[StatementParameterType]" = None,
279
+ /,
280
+ *,
281
+ connection: "Optional[Connection]" = None,
282
+ **kwargs: Any,
283
+ ) -> str:
284
+ """Execute a script.
285
+
286
+ Returns:
287
+ Status message for the operation.
288
+ """
289
+ connection = self._connection(connection)
290
+ sql, parameters = self._process_sql_params(sql, parameters, **kwargs)
291
+ with self._with_cursor(connection) as cursor:
292
+ cursor.execute(sql, parameters)
293
+ return str(cursor.statusmessage) if cursor.statusmessage is not None else "DONE"
294
+
295
+
296
+ class PsycopgAsyncDriver(AsyncDriverAdapterProtocol["AsyncConnection"]):
297
+ """Psycopg Async Driver Adapter."""
298
+
299
+ connection: "AsyncConnection"
300
+ dialect: str = "postgres"
301
+
302
+ def __init__(self, connection: "AsyncConnection") -> None:
303
+ self.connection = connection
304
+
305
+ def _process_sql_params(
306
+ self,
307
+ sql: str,
308
+ parameters: "Optional[StatementParameterType]" = None,
309
+ /,
310
+ **kwargs: Any,
311
+ ) -> "tuple[str, Optional[Union[tuple[Any, ...], list[Any], dict[str, Any]]]]":
312
+ """Process SQL and parameters, converting :name -> %(name)s if needed."""
313
+ stmt = SQLStatement(sql=sql, parameters=parameters, dialect=self.dialect, kwargs=kwargs or None)
314
+ processed_sql, processed_params = stmt.process()
315
+
316
+ if isinstance(processed_params, dict):
317
+ parameter_dict = processed_params
318
+ processed_sql_parts: list[str] = []
319
+ last_end = 0
320
+ found_params_regex: list[str] = []
321
+
322
+ for match in PARAM_REGEX.finditer(processed_sql):
323
+ if match.group("dquote") or match.group("squote") or match.group("comment"):
324
+ continue
325
+
326
+ if match.group("var_name"):
327
+ var_name = match.group("var_name")
328
+ found_params_regex.append(var_name)
329
+ start = match.start("var_name") - 1
330
+ end = match.end("var_name")
331
+
332
+ if var_name not in parameter_dict:
333
+ msg = (
334
+ f"Named parameter ':{var_name}' found in SQL but missing from processed parameters. "
335
+ f"Processed SQL: {processed_sql}"
336
+ )
337
+ raise SQLParsingError(msg)
338
+
339
+ processed_sql_parts.extend((processed_sql[last_end:start], f"%({var_name})s"))
340
+ last_end = end
341
+
342
+ processed_sql_parts.append(processed_sql[last_end:])
343
+ final_sql = "".join(processed_sql_parts)
344
+
345
+ if not found_params_regex and parameter_dict:
346
+ logger.warning(
347
+ "Dict params provided (%s), but no :name placeholders found. SQL: %s",
348
+ list(parameter_dict.keys()),
349
+ processed_sql,
350
+ )
351
+ return processed_sql, parameter_dict
352
+
353
+ return final_sql, parameter_dict
354
+
355
+ return processed_sql, processed_params
356
+
357
+ @staticmethod
358
+ @asynccontextmanager
359
+ async def _with_cursor(connection: "AsyncConnection") -> "AsyncGenerator[Any, None]":
360
+ cursor = connection.cursor(row_factory=dict_row)
361
+ try:
362
+ yield cursor
363
+ finally:
364
+ await cursor.close()
365
+
366
+ async def select(
367
+ self,
368
+ sql: str,
369
+ parameters: "Optional[StatementParameterType]" = None,
370
+ /,
371
+ *,
372
+ connection: "Optional[AsyncConnection]" = None,
373
+ schema_type: "Optional[type[ModelDTOT]]" = None,
374
+ **kwargs: Any,
375
+ ) -> "list[Union[ModelDTOT, dict[str, Any]]]":
376
+ """Fetch data from the database.
377
+
378
+ Returns:
379
+ List of row data as either model instances or dictionaries.
380
+ """
381
+ connection = self._connection(connection)
382
+ sql, parameters = self._process_sql_params(sql, parameters, **kwargs)
383
+ results: list[Union[ModelDTOT, dict[str, Any]]] = []
384
+
385
+ async with self._with_cursor(connection) as cursor:
386
+ await cursor.execute(sql, parameters)
387
+ results = await cursor.fetchall()
388
+ if not results:
389
+ return []
390
+ if schema_type is not None:
391
+ return [cast("ModelDTOT", schema_type(**cast("dict[str,Any]", row))) for row in results] # pyright: ignore[reportUnknownArgumentType]
392
+ return [cast("dict[str,Any]", row) for row in results] # pyright: ignore[reportUnknownArgumentType]
393
+
394
+ async def select_one(
395
+ self,
396
+ sql: str,
397
+ parameters: "Optional[StatementParameterType]" = None,
398
+ /,
399
+ *,
400
+ connection: "Optional[AsyncConnection]" = None,
401
+ schema_type: "Optional[type[ModelDTOT]]" = None,
402
+ **kwargs: Any,
403
+ ) -> "Union[ModelDTOT, dict[str, Any]]":
404
+ """Fetch one row from the database.
405
+
406
+ Returns:
407
+ The first row of the query results.
408
+ """
409
+ connection = self._connection(connection)
410
+ sql, parameters = self._process_sql_params(sql, parameters, **kwargs)
411
+
412
+ async with self._with_cursor(connection) as cursor:
413
+ await cursor.execute(sql, parameters)
414
+ row = await cursor.fetchone()
415
+ row = self.check_not_found(row)
416
+ if schema_type is not None:
417
+ return cast("ModelDTOT", schema_type(**cast("dict[str,Any]", row)))
418
+ return cast("dict[str,Any]", row)
419
+
420
+ async def select_one_or_none(
421
+ self,
422
+ sql: str,
423
+ parameters: "Optional[StatementParameterType]" = None,
424
+ /,
425
+ *,
426
+ schema_type: "Optional[type[ModelDTOT]]" = None,
427
+ connection: "Optional[AsyncConnection]" = None,
428
+ **kwargs: Any,
429
+ ) -> "Optional[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, **kwargs)
437
+
438
+ async with self._with_cursor(connection) as cursor:
439
+ await cursor.execute(sql, parameters)
440
+ row = await cursor.fetchone()
441
+ if row is None:
442
+ return None
443
+ if schema_type is not None:
444
+ return cast("ModelDTOT", schema_type(**cast("dict[str,Any]", row)))
445
+ return cast("dict[str,Any]", row)
446
+
447
+ async def select_value(
448
+ self,
449
+ sql: str,
450
+ parameters: "Optional[StatementParameterType]" = None,
451
+ /,
452
+ *,
453
+ connection: "Optional[AsyncConnection]" = None,
454
+ schema_type: "Optional[type[T]]" = None,
455
+ **kwargs: Any,
456
+ ) -> "Union[T, Any]":
457
+ """Fetch a single value from the database.
458
+
459
+ Returns:
460
+ The first value from the first row of results, or None if no results.
461
+ """
462
+ connection = self._connection(connection)
463
+ sql, parameters = self._process_sql_params(sql, parameters, **kwargs)
464
+
465
+ async with self._with_cursor(connection) as cursor:
466
+ await cursor.execute(sql, parameters)
467
+ row = await cursor.fetchone()
468
+ row = self.check_not_found(row)
469
+ val = next(iter(row.values())) if row else None
470
+ val = self.check_not_found(val)
471
+ if schema_type is not None:
472
+ return schema_type(val) # type: ignore[call-arg]
473
+ return val
474
+
475
+ async def select_value_or_none(
476
+ self,
477
+ sql: str,
478
+ parameters: "Optional[StatementParameterType]" = None,
479
+ /,
480
+ *,
481
+ connection: "Optional[AsyncConnection]" = None,
482
+ schema_type: "Optional[type[T]]" = None,
483
+ **kwargs: Any,
484
+ ) -> "Optional[Union[T, Any]]":
485
+ """Fetch a single value from the database.
486
+
487
+ Returns:
488
+ The first value from the first row of results, or None if no results.
489
+ """
490
+ connection = self._connection(connection)
491
+ sql, parameters = self._process_sql_params(sql, parameters, **kwargs)
492
+
493
+ async with self._with_cursor(connection) as cursor:
494
+ await cursor.execute(sql, parameters)
495
+ row = await cursor.fetchone()
496
+ if row is None:
497
+ return None
498
+ val = next(iter(row.values())) if row else None
499
+ if val is None:
500
+ return None
501
+ if schema_type is not None:
502
+ return schema_type(val) # type: ignore[call-arg]
503
+ return val
504
+
505
+ async def insert_update_delete(
506
+ self,
507
+ sql: str,
508
+ parameters: "Optional[StatementParameterType]" = None,
509
+ /,
510
+ *,
511
+ connection: "Optional[AsyncConnection]" = None,
512
+ **kwargs: Any,
513
+ ) -> int:
514
+ """Execute an INSERT, UPDATE, or DELETE query and return the number of affected rows.
515
+
516
+ Returns:
517
+ The number of rows affected by the operation.
518
+ """
519
+ connection = self._connection(connection)
520
+ sql, parameters = self._process_sql_params(sql, parameters, **kwargs)
521
+
522
+ async with self._with_cursor(connection) as cursor:
523
+ await cursor.execute(sql, parameters)
524
+ try:
525
+ rowcount = int(cursor.rowcount)
526
+ except (TypeError, ValueError):
527
+ rowcount = -1
528
+ return rowcount
529
+
530
+ async def insert_update_delete_returning(
531
+ self,
532
+ sql: str,
533
+ parameters: "Optional[StatementParameterType]" = None,
534
+ /,
535
+ *,
536
+ connection: "Optional[AsyncConnection]" = None,
537
+ schema_type: "Optional[type[ModelDTOT]]" = None,
538
+ **kwargs: Any,
539
+ ) -> "Optional[Union[dict[str, Any], ModelDTOT]]":
540
+ """Insert, update, or delete data from the database and return result.
541
+
542
+ Returns:
543
+ The first row of results.
544
+ """
545
+ connection = self._connection(connection)
546
+ sql, parameters = self._process_sql_params(sql, parameters, **kwargs)
547
+
548
+ async with self._with_cursor(connection) as cursor:
549
+ await cursor.execute(sql, parameters)
550
+ result = await cursor.fetchone()
551
+
552
+ if result is None:
553
+ return None
554
+
555
+ if schema_type is not None:
556
+ return cast("ModelDTOT", schema_type(**result)) # pyright: ignore[reportUnknownArgumentType]
557
+ return cast("dict[str, Any]", result) # pyright: ignore[reportUnknownArgumentType,reportUnknownVariableType]
558
+
559
+ async def execute_script(
560
+ self,
561
+ sql: str,
562
+ parameters: "Optional[StatementParameterType]" = None,
563
+ /,
564
+ *,
565
+ connection: "Optional[AsyncConnection]" = None,
566
+ **kwargs: Any,
567
+ ) -> str:
568
+ """Execute a script.
569
+
570
+ Returns:
571
+ Status message for the operation.
572
+ """
573
+ connection = self._connection(connection)
574
+ sql, parameters = self._process_sql_params(sql, parameters, **kwargs)
575
+
576
+ async with self._with_cursor(connection) as cursor:
577
+ await cursor.execute(sql, parameters)
578
+ return str(cursor.statusmessage) if cursor.statusmessage is not None else "DONE"
@@ -0,0 +1,7 @@
1
+ from sqlspec.adapters.sqlite.config import SqliteConfig
2
+ from sqlspec.adapters.sqlite.driver import SqliteDriver
3
+
4
+ __all__ = (
5
+ "SqliteConfig",
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
13
+
12
14
 
13
15
  __all__ = ("SqliteConfig",)
14
16
 
15
17
 
16
18
  @dataclass
17
- class SqliteConfig(NoPoolSyncConfig["Connection"]):
19
+ class SqliteConfig(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)