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.
- sqlspec/__init__.py +15 -0
- sqlspec/_serialization.py +16 -2
- sqlspec/_typing.py +40 -7
- sqlspec/adapters/adbc/__init__.py +7 -0
- sqlspec/adapters/adbc/config.py +183 -17
- sqlspec/adapters/adbc/driver.py +392 -0
- sqlspec/adapters/aiosqlite/__init__.py +5 -1
- sqlspec/adapters/aiosqlite/config.py +24 -6
- sqlspec/adapters/aiosqlite/driver.py +264 -0
- sqlspec/adapters/asyncmy/__init__.py +7 -2
- sqlspec/adapters/asyncmy/config.py +71 -11
- sqlspec/adapters/asyncmy/driver.py +246 -0
- sqlspec/adapters/asyncpg/__init__.py +9 -0
- sqlspec/adapters/asyncpg/config.py +102 -25
- sqlspec/adapters/asyncpg/driver.py +444 -0
- sqlspec/adapters/duckdb/__init__.py +5 -1
- sqlspec/adapters/duckdb/config.py +194 -12
- sqlspec/adapters/duckdb/driver.py +225 -0
- sqlspec/adapters/oracledb/__init__.py +7 -4
- sqlspec/adapters/oracledb/config/__init__.py +4 -4
- sqlspec/adapters/oracledb/config/_asyncio.py +96 -12
- sqlspec/adapters/oracledb/config/_common.py +1 -1
- sqlspec/adapters/oracledb/config/_sync.py +96 -12
- sqlspec/adapters/oracledb/driver.py +571 -0
- sqlspec/adapters/psqlpy/__init__.py +0 -0
- sqlspec/adapters/psqlpy/config.py +258 -0
- sqlspec/adapters/psqlpy/driver.py +335 -0
- sqlspec/adapters/psycopg/__init__.py +16 -0
- sqlspec/adapters/psycopg/config/__init__.py +6 -6
- sqlspec/adapters/psycopg/config/_async.py +107 -15
- sqlspec/adapters/psycopg/config/_common.py +2 -2
- sqlspec/adapters/psycopg/config/_sync.py +107 -15
- sqlspec/adapters/psycopg/driver.py +578 -0
- sqlspec/adapters/sqlite/__init__.py +7 -0
- sqlspec/adapters/sqlite/config.py +24 -6
- sqlspec/adapters/sqlite/driver.py +305 -0
- sqlspec/base.py +565 -63
- sqlspec/exceptions.py +30 -0
- sqlspec/extensions/litestar/__init__.py +19 -0
- sqlspec/extensions/litestar/_utils.py +56 -0
- sqlspec/extensions/litestar/config.py +87 -0
- sqlspec/extensions/litestar/handlers.py +213 -0
- sqlspec/extensions/litestar/plugin.py +105 -11
- sqlspec/statement.py +373 -0
- sqlspec/typing.py +81 -17
- sqlspec/utils/__init__.py +3 -0
- sqlspec/utils/fixtures.py +4 -5
- sqlspec/utils/sync_tools.py +335 -0
- {sqlspec-0.7.1.dist-info → sqlspec-0.9.0.dist-info}/METADATA +4 -1
- sqlspec-0.9.0.dist-info/RECORD +61 -0
- sqlspec-0.7.1.dist-info/RECORD +0 -46
- {sqlspec-0.7.1.dist-info → sqlspec-0.9.0.dist-info}/WHEEL +0 -0
- {sqlspec-0.7.1.dist-info → sqlspec-0.9.0.dist-info}/licenses/LICENSE +0 -0
- {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"
|
|
@@ -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
|
-
|
|
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(
|
|
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)
|