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.
- sqlspec/__init__.py +15 -0
- sqlspec/_serialization.py +16 -2
- sqlspec/_typing.py +1 -1
- sqlspec/adapters/adbc/__init__.py +7 -0
- sqlspec/adapters/adbc/config.py +160 -17
- sqlspec/adapters/adbc/driver.py +333 -0
- sqlspec/adapters/aiosqlite/__init__.py +6 -2
- sqlspec/adapters/aiosqlite/config.py +25 -7
- sqlspec/adapters/aiosqlite/driver.py +275 -0
- sqlspec/adapters/asyncmy/__init__.py +7 -2
- sqlspec/adapters/asyncmy/config.py +75 -14
- sqlspec/adapters/asyncmy/driver.py +255 -0
- sqlspec/adapters/asyncpg/__init__.py +9 -0
- sqlspec/adapters/asyncpg/config.py +99 -20
- sqlspec/adapters/asyncpg/driver.py +288 -0
- sqlspec/adapters/duckdb/__init__.py +6 -2
- sqlspec/adapters/duckdb/config.py +197 -15
- sqlspec/adapters/duckdb/driver.py +225 -0
- sqlspec/adapters/oracledb/__init__.py +11 -8
- sqlspec/adapters/oracledb/config/__init__.py +6 -6
- sqlspec/adapters/oracledb/config/_asyncio.py +98 -13
- sqlspec/adapters/oracledb/config/_common.py +1 -1
- sqlspec/adapters/oracledb/config/_sync.py +99 -14
- sqlspec/adapters/oracledb/driver.py +498 -0
- sqlspec/adapters/psycopg/__init__.py +11 -0
- sqlspec/adapters/psycopg/config/__init__.py +6 -6
- sqlspec/adapters/psycopg/config/_async.py +105 -13
- sqlspec/adapters/psycopg/config/_common.py +2 -2
- sqlspec/adapters/psycopg/config/_sync.py +105 -13
- sqlspec/adapters/psycopg/driver.py +616 -0
- sqlspec/adapters/sqlite/__init__.py +7 -0
- sqlspec/adapters/sqlite/config.py +25 -7
- sqlspec/adapters/sqlite/driver.py +303 -0
- sqlspec/base.py +416 -36
- sqlspec/extensions/litestar/__init__.py +19 -0
- sqlspec/extensions/litestar/_utils.py +56 -0
- sqlspec/extensions/litestar/config.py +81 -0
- sqlspec/extensions/litestar/handlers.py +188 -0
- sqlspec/extensions/litestar/plugin.py +103 -11
- sqlspec/typing.py +72 -17
- sqlspec/utils/__init__.py +3 -0
- sqlspec/utils/deprecation.py +1 -1
- sqlspec/utils/fixtures.py +4 -5
- sqlspec/utils/sync_tools.py +335 -0
- {sqlspec-0.7.0.dist-info → sqlspec-0.8.0.dist-info}/METADATA +1 -1
- sqlspec-0.8.0.dist-info/RECORD +57 -0
- sqlspec-0.7.0.dist-info/RECORD +0 -46
- {sqlspec-0.7.0.dist-info → sqlspec-0.8.0.dist-info}/WHEEL +0 -0
- {sqlspec-0.7.0.dist-info → sqlspec-0.8.0.dist-info}/licenses/LICENSE +0 -0
- {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]
|
|
@@ -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
|
-
|
|
14
|
+
|
|
15
|
+
__all__ = ("Sqlite",)
|
|
14
16
|
|
|
15
17
|
|
|
16
18
|
@dataclass
|
|
17
|
-
class
|
|
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(
|
|
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)
|