sqlspec 0.14.1__py3-none-any.whl → 0.16.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 +50 -25
- sqlspec/__main__.py +1 -1
- sqlspec/__metadata__.py +1 -3
- sqlspec/_serialization.py +1 -2
- sqlspec/_sql.py +480 -121
- sqlspec/_typing.py +278 -142
- sqlspec/adapters/adbc/__init__.py +4 -3
- sqlspec/adapters/adbc/_types.py +12 -0
- sqlspec/adapters/adbc/config.py +115 -260
- sqlspec/adapters/adbc/driver.py +462 -367
- sqlspec/adapters/aiosqlite/__init__.py +18 -3
- sqlspec/adapters/aiosqlite/_types.py +13 -0
- sqlspec/adapters/aiosqlite/config.py +199 -129
- sqlspec/adapters/aiosqlite/driver.py +230 -269
- sqlspec/adapters/asyncmy/__init__.py +18 -3
- sqlspec/adapters/asyncmy/_types.py +12 -0
- sqlspec/adapters/asyncmy/config.py +80 -168
- sqlspec/adapters/asyncmy/driver.py +260 -225
- sqlspec/adapters/asyncpg/__init__.py +19 -4
- sqlspec/adapters/asyncpg/_types.py +17 -0
- sqlspec/adapters/asyncpg/config.py +82 -181
- sqlspec/adapters/asyncpg/driver.py +285 -383
- sqlspec/adapters/bigquery/__init__.py +17 -3
- sqlspec/adapters/bigquery/_types.py +12 -0
- sqlspec/adapters/bigquery/config.py +191 -258
- sqlspec/adapters/bigquery/driver.py +474 -646
- sqlspec/adapters/duckdb/__init__.py +14 -3
- sqlspec/adapters/duckdb/_types.py +12 -0
- sqlspec/adapters/duckdb/config.py +415 -351
- sqlspec/adapters/duckdb/driver.py +343 -413
- sqlspec/adapters/oracledb/__init__.py +19 -5
- sqlspec/adapters/oracledb/_types.py +14 -0
- sqlspec/adapters/oracledb/config.py +123 -379
- sqlspec/adapters/oracledb/driver.py +507 -560
- sqlspec/adapters/psqlpy/__init__.py +13 -3
- sqlspec/adapters/psqlpy/_types.py +11 -0
- sqlspec/adapters/psqlpy/config.py +93 -254
- sqlspec/adapters/psqlpy/driver.py +505 -234
- sqlspec/adapters/psycopg/__init__.py +19 -5
- sqlspec/adapters/psycopg/_types.py +17 -0
- sqlspec/adapters/psycopg/config.py +143 -403
- sqlspec/adapters/psycopg/driver.py +706 -872
- sqlspec/adapters/sqlite/__init__.py +14 -3
- sqlspec/adapters/sqlite/_types.py +11 -0
- sqlspec/adapters/sqlite/config.py +202 -118
- sqlspec/adapters/sqlite/driver.py +264 -303
- sqlspec/base.py +105 -9
- sqlspec/{statement/builder → builder}/__init__.py +12 -14
- sqlspec/{statement/builder → builder}/_base.py +120 -55
- sqlspec/{statement/builder → builder}/_column.py +17 -6
- sqlspec/{statement/builder → builder}/_ddl.py +46 -79
- sqlspec/{statement/builder → builder}/_ddl_utils.py +5 -10
- sqlspec/{statement/builder → builder}/_delete.py +6 -25
- sqlspec/{statement/builder → builder}/_insert.py +18 -65
- sqlspec/builder/_merge.py +56 -0
- sqlspec/{statement/builder → builder}/_parsing_utils.py +8 -11
- sqlspec/{statement/builder → builder}/_select.py +11 -56
- sqlspec/{statement/builder → builder}/_update.py +12 -18
- sqlspec/{statement/builder → builder}/mixins/__init__.py +10 -14
- sqlspec/{statement/builder → builder}/mixins/_cte_and_set_ops.py +48 -59
- sqlspec/{statement/builder → builder}/mixins/_insert_operations.py +34 -18
- sqlspec/{statement/builder → builder}/mixins/_join_operations.py +1 -3
- sqlspec/{statement/builder → builder}/mixins/_merge_operations.py +19 -9
- sqlspec/{statement/builder → builder}/mixins/_order_limit_operations.py +3 -3
- sqlspec/{statement/builder → builder}/mixins/_pivot_operations.py +4 -8
- sqlspec/{statement/builder → builder}/mixins/_select_operations.py +25 -38
- sqlspec/{statement/builder → builder}/mixins/_update_operations.py +15 -16
- sqlspec/{statement/builder → builder}/mixins/_where_clause.py +210 -137
- sqlspec/cli.py +4 -5
- sqlspec/config.py +180 -133
- sqlspec/core/__init__.py +63 -0
- sqlspec/core/cache.py +873 -0
- sqlspec/core/compiler.py +396 -0
- sqlspec/core/filters.py +830 -0
- sqlspec/core/hashing.py +310 -0
- sqlspec/core/parameters.py +1209 -0
- sqlspec/core/result.py +664 -0
- sqlspec/{statement → core}/splitter.py +321 -191
- sqlspec/core/statement.py +666 -0
- sqlspec/driver/__init__.py +7 -10
- sqlspec/driver/_async.py +387 -176
- sqlspec/driver/_common.py +527 -289
- sqlspec/driver/_sync.py +390 -172
- sqlspec/driver/mixins/__init__.py +2 -19
- sqlspec/driver/mixins/_result_tools.py +164 -0
- sqlspec/driver/mixins/_sql_translator.py +6 -3
- sqlspec/exceptions.py +5 -252
- sqlspec/extensions/aiosql/adapter.py +93 -96
- sqlspec/extensions/litestar/cli.py +1 -1
- sqlspec/extensions/litestar/config.py +0 -1
- sqlspec/extensions/litestar/handlers.py +15 -26
- sqlspec/extensions/litestar/plugin.py +18 -16
- sqlspec/extensions/litestar/providers.py +17 -52
- sqlspec/loader.py +424 -105
- sqlspec/migrations/__init__.py +12 -0
- sqlspec/migrations/base.py +92 -68
- sqlspec/migrations/commands.py +24 -106
- sqlspec/migrations/loaders.py +402 -0
- sqlspec/migrations/runner.py +49 -51
- sqlspec/migrations/tracker.py +31 -44
- sqlspec/migrations/utils.py +64 -24
- sqlspec/protocols.py +7 -183
- sqlspec/storage/__init__.py +1 -1
- sqlspec/storage/backends/base.py +37 -40
- sqlspec/storage/backends/fsspec.py +136 -112
- sqlspec/storage/backends/obstore.py +138 -160
- sqlspec/storage/capabilities.py +5 -4
- sqlspec/storage/registry.py +57 -106
- sqlspec/typing.py +136 -115
- sqlspec/utils/__init__.py +2 -3
- sqlspec/utils/correlation.py +0 -3
- sqlspec/utils/deprecation.py +6 -6
- sqlspec/utils/fixtures.py +6 -6
- sqlspec/utils/logging.py +0 -2
- sqlspec/utils/module_loader.py +7 -12
- sqlspec/utils/singleton.py +0 -1
- sqlspec/utils/sync_tools.py +17 -38
- sqlspec/utils/text.py +12 -51
- sqlspec/utils/type_guards.py +443 -232
- {sqlspec-0.14.1.dist-info → sqlspec-0.16.0.dist-info}/METADATA +7 -2
- sqlspec-0.16.0.dist-info/RECORD +134 -0
- sqlspec/adapters/adbc/transformers.py +0 -108
- sqlspec/driver/connection.py +0 -207
- sqlspec/driver/mixins/_cache.py +0 -114
- sqlspec/driver/mixins/_csv_writer.py +0 -91
- sqlspec/driver/mixins/_pipeline.py +0 -508
- sqlspec/driver/mixins/_query_tools.py +0 -796
- sqlspec/driver/mixins/_result_utils.py +0 -138
- sqlspec/driver/mixins/_storage.py +0 -912
- sqlspec/driver/mixins/_type_coercion.py +0 -128
- sqlspec/driver/parameters.py +0 -138
- sqlspec/statement/__init__.py +0 -21
- sqlspec/statement/builder/_merge.py +0 -95
- sqlspec/statement/cache.py +0 -50
- sqlspec/statement/filters.py +0 -625
- sqlspec/statement/parameters.py +0 -956
- sqlspec/statement/pipelines/__init__.py +0 -210
- sqlspec/statement/pipelines/analyzers/__init__.py +0 -9
- sqlspec/statement/pipelines/analyzers/_analyzer.py +0 -646
- sqlspec/statement/pipelines/context.py +0 -109
- sqlspec/statement/pipelines/transformers/__init__.py +0 -7
- sqlspec/statement/pipelines/transformers/_expression_simplifier.py +0 -88
- sqlspec/statement/pipelines/transformers/_literal_parameterizer.py +0 -1247
- sqlspec/statement/pipelines/transformers/_remove_comments_and_hints.py +0 -76
- sqlspec/statement/pipelines/validators/__init__.py +0 -23
- sqlspec/statement/pipelines/validators/_dml_safety.py +0 -290
- sqlspec/statement/pipelines/validators/_parameter_style.py +0 -370
- sqlspec/statement/pipelines/validators/_performance.py +0 -714
- sqlspec/statement/pipelines/validators/_security.py +0 -967
- sqlspec/statement/result.py +0 -435
- sqlspec/statement/sql.py +0 -1774
- sqlspec/utils/cached_property.py +0 -25
- sqlspec/utils/statement_hashing.py +0 -203
- sqlspec-0.14.1.dist-info/RECORD +0 -145
- /sqlspec/{statement/builder → builder}/mixins/_delete_operations.py +0 -0
- {sqlspec-0.14.1.dist-info → sqlspec-0.16.0.dist-info}/WHEEL +0 -0
- {sqlspec-0.14.1.dist-info → sqlspec-0.16.0.dist-info}/entry_points.txt +0 -0
- {sqlspec-0.14.1.dist-info → sqlspec-0.16.0.dist-info}/licenses/LICENSE +0 -0
- {sqlspec-0.14.1.dist-info → sqlspec-0.16.0.dist-info}/licenses/NOTICE +0 -0
|
@@ -10,13 +10,13 @@ from collections.abc import AsyncGenerator, Generator
|
|
|
10
10
|
from contextlib import asynccontextmanager, contextmanager
|
|
11
11
|
from typing import TYPE_CHECKING, Any, ClassVar, Optional, TypeVar, Union, cast
|
|
12
12
|
|
|
13
|
+
from sqlspec.core.result import SQLResult
|
|
14
|
+
from sqlspec.core.statement import SQL, StatementConfig
|
|
13
15
|
from sqlspec.exceptions import MissingDependencyError
|
|
14
|
-
from sqlspec.
|
|
15
|
-
from sqlspec.statement.sql import SQL, SQLConfig
|
|
16
|
-
from sqlspec.typing import AIOSQL_INSTALLED, RowT
|
|
16
|
+
from sqlspec.typing import AIOSQL_INSTALLED
|
|
17
17
|
|
|
18
18
|
if TYPE_CHECKING:
|
|
19
|
-
from sqlspec.driver import
|
|
19
|
+
from sqlspec.driver import AsyncDriverAdapterBase, SyncDriverAdapterBase
|
|
20
20
|
|
|
21
21
|
logger = logging.getLogger("sqlspec.extensions.aiosql")
|
|
22
22
|
|
|
@@ -25,6 +25,34 @@ __all__ = ("AiosqlAsyncAdapter", "AiosqlSyncAdapter")
|
|
|
25
25
|
T = TypeVar("T")
|
|
26
26
|
|
|
27
27
|
|
|
28
|
+
class AsyncCursorLike:
|
|
29
|
+
def __init__(self, result: Any) -> None:
|
|
30
|
+
self.result = result
|
|
31
|
+
|
|
32
|
+
async def fetchall(self) -> list[Any]:
|
|
33
|
+
if isinstance(self.result, SQLResult) and self.result.data is not None:
|
|
34
|
+
return list(self.result.data)
|
|
35
|
+
return []
|
|
36
|
+
|
|
37
|
+
async def fetchone(self) -> Optional[Any]:
|
|
38
|
+
rows = await self.fetchall()
|
|
39
|
+
return rows[0] if rows else None
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class CursorLike:
|
|
43
|
+
def __init__(self, result: Any) -> None:
|
|
44
|
+
self.result = result
|
|
45
|
+
|
|
46
|
+
def fetchall(self) -> list[Any]:
|
|
47
|
+
if isinstance(self.result, SQLResult) and self.result.data is not None:
|
|
48
|
+
return list(self.result.data)
|
|
49
|
+
return []
|
|
50
|
+
|
|
51
|
+
def fetchone(self) -> Optional[Any]:
|
|
52
|
+
rows = self.fetchall()
|
|
53
|
+
return rows[0] if rows else None
|
|
54
|
+
|
|
55
|
+
|
|
28
56
|
def _check_aiosql_available() -> None:
|
|
29
57
|
if not AIOSQL_INSTALLED:
|
|
30
58
|
msg = "aiosql"
|
|
@@ -38,21 +66,20 @@ def _normalize_dialect(dialect: "Union[str, Any, None]") -> str:
|
|
|
38
66
|
dialect: Original dialect name (can be str, Dialect, type[Dialect], or None)
|
|
39
67
|
|
|
40
68
|
Returns:
|
|
41
|
-
|
|
69
|
+
Converted dialect name compatible with SQLGlot
|
|
42
70
|
"""
|
|
43
71
|
if dialect is None:
|
|
44
72
|
return "sql"
|
|
45
73
|
|
|
46
|
-
if hasattr(dialect, "__name__"):
|
|
74
|
+
if hasattr(dialect, "__name__"):
|
|
47
75
|
dialect_str = str(dialect.__name__).lower() # pyright: ignore
|
|
48
|
-
elif hasattr(dialect, "name"):
|
|
76
|
+
elif hasattr(dialect, "name"):
|
|
49
77
|
dialect_str = str(dialect.name).lower() # pyright: ignore
|
|
50
78
|
elif isinstance(dialect, str):
|
|
51
79
|
dialect_str = dialect.lower()
|
|
52
80
|
else:
|
|
53
81
|
dialect_str = str(dialect).lower()
|
|
54
82
|
|
|
55
|
-
# Map common dialect aliases to SQLGlot names
|
|
56
83
|
dialect_mapping = {
|
|
57
84
|
"postgresql": "postgres",
|
|
58
85
|
"psycopg": "postgres",
|
|
@@ -65,11 +92,9 @@ def _normalize_dialect(dialect: "Union[str, Any, None]") -> str:
|
|
|
65
92
|
|
|
66
93
|
|
|
67
94
|
class _AiosqlAdapterBase:
|
|
68
|
-
"""Base adapter
|
|
95
|
+
"""Base adapter class providing common functionality for aiosql integration."""
|
|
69
96
|
|
|
70
|
-
def __init__(
|
|
71
|
-
self, driver: "Union[SyncDriverAdapterProtocol[Any, Any], AsyncDriverAdapterProtocol[Any, Any]]"
|
|
72
|
-
) -> None:
|
|
97
|
+
def __init__(self, driver: "Union[SyncDriverAdapterBase, AsyncDriverAdapterBase]") -> None:
|
|
73
98
|
"""Initialize the base adapter.
|
|
74
99
|
|
|
75
100
|
Args:
|
|
@@ -79,27 +104,46 @@ class _AiosqlAdapterBase:
|
|
|
79
104
|
self.driver = driver
|
|
80
105
|
|
|
81
106
|
def process_sql(self, query_name: str, op_type: "Any", sql: str) -> str:
|
|
82
|
-
"""Process SQL for aiosql compatibility.
|
|
107
|
+
"""Process SQL string for aiosql compatibility.
|
|
108
|
+
|
|
109
|
+
Args:
|
|
110
|
+
query_name: Name of the query
|
|
111
|
+
op_type: Operation type
|
|
112
|
+
sql: SQL string to process
|
|
113
|
+
|
|
114
|
+
Returns:
|
|
115
|
+
Processed SQL string
|
|
116
|
+
"""
|
|
83
117
|
return sql
|
|
84
118
|
|
|
85
119
|
def _create_sql_object(self, sql: str, parameters: "Any" = None) -> SQL:
|
|
86
|
-
"""Create SQL object with proper configuration.
|
|
87
|
-
config = SQLConfig(enable_validation=False)
|
|
88
|
-
converted_dialect = _normalize_dialect(self.driver.dialect)
|
|
89
|
-
return SQL(sql, parameters, config=config, dialect=converted_dialect)
|
|
120
|
+
"""Create SQL object with proper configuration.
|
|
90
121
|
|
|
122
|
+
Args:
|
|
123
|
+
sql: SQL string
|
|
124
|
+
parameters: Query parameters
|
|
91
125
|
|
|
92
|
-
|
|
93
|
-
|
|
126
|
+
Returns:
|
|
127
|
+
Configured SQL object
|
|
128
|
+
"""
|
|
129
|
+
return SQL(
|
|
130
|
+
sql,
|
|
131
|
+
parameters,
|
|
132
|
+
config=StatementConfig(enable_validation=False),
|
|
133
|
+
dialect=_normalize_dialect(getattr(self.driver, "dialect", "sqlite")),
|
|
134
|
+
)
|
|
94
135
|
|
|
95
|
-
This adapter bridges aiosql's sync driver protocol with SQLSpec's sync drivers,
|
|
96
|
-
enabling all of SQLSpec's drivers to work with queries loaded by aiosql.
|
|
97
136
|
|
|
137
|
+
class AiosqlSyncAdapter(_AiosqlAdapterBase):
|
|
138
|
+
"""Synchronous adapter that implements aiosql protocol using SQLSpec drivers.
|
|
139
|
+
|
|
140
|
+
This adapter bridges aiosql's synchronous driver protocol with SQLSpec's sync drivers,
|
|
141
|
+
enabling queries loaded by aiosql to be executed with SQLSpec drivers.
|
|
98
142
|
"""
|
|
99
143
|
|
|
100
144
|
is_aio_driver: ClassVar[bool] = False
|
|
101
145
|
|
|
102
|
-
def __init__(self, driver: "
|
|
146
|
+
def __init__(self, driver: "SyncDriverAdapterBase") -> None:
|
|
103
147
|
"""Initialize the sync adapter.
|
|
104
148
|
|
|
105
149
|
Args:
|
|
@@ -123,8 +167,8 @@ class AiosqlSyncAdapter(_AiosqlAdapterBase):
|
|
|
123
167
|
Query result rows
|
|
124
168
|
|
|
125
169
|
Note:
|
|
126
|
-
record_class parameter is ignored. Use schema_type
|
|
127
|
-
or _sqlspec_schema_type in parameters for type mapping.
|
|
170
|
+
The record_class parameter is ignored for compatibility. Use schema_type
|
|
171
|
+
in driver.execute or _sqlspec_schema_type in parameters for type mapping.
|
|
128
172
|
"""
|
|
129
173
|
if record_class is not None:
|
|
130
174
|
logger.warning(
|
|
@@ -132,16 +176,14 @@ class AiosqlSyncAdapter(_AiosqlAdapterBase):
|
|
|
132
176
|
"Use schema_type in driver.execute or _sqlspec_schema_type in parameters."
|
|
133
177
|
)
|
|
134
178
|
|
|
135
|
-
|
|
136
|
-
# Execute using SQLSpec driver
|
|
137
|
-
result = self.driver.execute(sql_obj, connection=conn)
|
|
179
|
+
result = self.driver.execute(self._create_sql_object(sql, parameters), connection=conn)
|
|
138
180
|
|
|
139
181
|
if isinstance(result, SQLResult) and result.data is not None:
|
|
140
182
|
yield from result.data
|
|
141
183
|
|
|
142
184
|
def select_one(
|
|
143
185
|
self, conn: Any, query_name: str, sql: str, parameters: "Any", record_class: Optional[Any] = None
|
|
144
|
-
) -> Optional[
|
|
186
|
+
) -> Optional[dict[str, Any]]:
|
|
145
187
|
"""Execute a SELECT query and return first result.
|
|
146
188
|
|
|
147
189
|
Args:
|
|
@@ -155,8 +197,8 @@ class AiosqlSyncAdapter(_AiosqlAdapterBase):
|
|
|
155
197
|
First result row or None
|
|
156
198
|
|
|
157
199
|
Note:
|
|
158
|
-
record_class parameter is ignored. Use schema_type
|
|
159
|
-
or _sqlspec_schema_type in parameters for type mapping.
|
|
200
|
+
The record_class parameter is ignored for compatibility. Use schema_type
|
|
201
|
+
in driver.execute or _sqlspec_schema_type in parameters for type mapping.
|
|
160
202
|
"""
|
|
161
203
|
if record_class is not None:
|
|
162
204
|
logger.warning(
|
|
@@ -164,12 +206,10 @@ class AiosqlSyncAdapter(_AiosqlAdapterBase):
|
|
|
164
206
|
"Use schema_type in driver.execute or _sqlspec_schema_type in parameters."
|
|
165
207
|
)
|
|
166
208
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
result = cast("SQLResult[RowT]", self.driver.execute(sql_obj, connection=conn))
|
|
209
|
+
result = cast("SQLResult", self.driver.execute(self._create_sql_object(sql, parameters), connection=conn))
|
|
170
210
|
|
|
171
211
|
if hasattr(result, "data") and result.data and isinstance(result, SQLResult):
|
|
172
|
-
return cast("Optional[
|
|
212
|
+
return cast("Optional[dict[str, Any]]", result.data[0])
|
|
173
213
|
return None
|
|
174
214
|
|
|
175
215
|
def select_value(self, conn: Any, query_name: str, sql: str, parameters: "Any") -> Optional[Any]:
|
|
@@ -207,21 +247,7 @@ class AiosqlSyncAdapter(_AiosqlAdapterBase):
|
|
|
207
247
|
Yields:
|
|
208
248
|
Cursor-like object with results
|
|
209
249
|
"""
|
|
210
|
-
|
|
211
|
-
result = self.driver.execute(sql_obj, connection=conn)
|
|
212
|
-
|
|
213
|
-
class CursorLike:
|
|
214
|
-
def __init__(self, result: Any) -> None:
|
|
215
|
-
self.result = result
|
|
216
|
-
|
|
217
|
-
def fetchall(self) -> list[Any]:
|
|
218
|
-
if isinstance(result, SQLResult) and result.data is not None:
|
|
219
|
-
return list(result.data)
|
|
220
|
-
return []
|
|
221
|
-
|
|
222
|
-
def fetchone(self) -> Optional[Any]:
|
|
223
|
-
rows = self.fetchall()
|
|
224
|
-
return rows[0] if rows else None
|
|
250
|
+
result = self.driver.execute(self._create_sql_object(sql, parameters), connection=conn)
|
|
225
251
|
|
|
226
252
|
yield CursorLike(result)
|
|
227
253
|
|
|
@@ -237,10 +263,8 @@ class AiosqlSyncAdapter(_AiosqlAdapterBase):
|
|
|
237
263
|
Returns:
|
|
238
264
|
Number of affected rows
|
|
239
265
|
"""
|
|
240
|
-
|
|
241
|
-
result = cast("SQLResult[Any]", self.driver.execute(sql_obj, connection=conn))
|
|
266
|
+
result = cast("SQLResult", self.driver.execute(self._create_sql_object(sql, parameters), connection=conn))
|
|
242
267
|
|
|
243
|
-
# SQLResult has rows_affected attribute
|
|
244
268
|
return result.rows_affected if hasattr(result, "rows_affected") else 0
|
|
245
269
|
|
|
246
270
|
def insert_update_delete_many(self, conn: Any, query_name: str, sql: str, parameters: "Any") -> int:
|
|
@@ -255,12 +279,10 @@ class AiosqlSyncAdapter(_AiosqlAdapterBase):
|
|
|
255
279
|
Returns:
|
|
256
280
|
Number of affected rows
|
|
257
281
|
"""
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
result = cast("SQLResult[Any]", self.driver.execute_many(sql_obj, parameters=parameters, connection=conn))
|
|
282
|
+
result = cast(
|
|
283
|
+
"SQLResult", self.driver.execute_many(self._create_sql_object(sql), parameters=parameters, connection=conn)
|
|
284
|
+
)
|
|
262
285
|
|
|
263
|
-
# SQLResult has rows_affected attribute
|
|
264
286
|
return result.rows_affected if hasattr(result, "rows_affected") else 0
|
|
265
287
|
|
|
266
288
|
def insert_returning(self, conn: Any, query_name: str, sql: str, parameters: "Any") -> Optional[Any]:
|
|
@@ -275,20 +297,19 @@ class AiosqlSyncAdapter(_AiosqlAdapterBase):
|
|
|
275
297
|
Returns:
|
|
276
298
|
Returned value or None
|
|
277
299
|
"""
|
|
278
|
-
# INSERT RETURNING is treated like a select that returns data
|
|
279
300
|
return self.select_one(conn, query_name, sql, parameters)
|
|
280
301
|
|
|
281
302
|
|
|
282
303
|
class AiosqlAsyncAdapter(_AiosqlAdapterBase):
|
|
283
|
-
"""
|
|
304
|
+
"""Asynchronous adapter that implements aiosql protocol using SQLSpec drivers.
|
|
284
305
|
|
|
285
306
|
This adapter bridges aiosql's async driver protocol with SQLSpec's async drivers,
|
|
286
|
-
enabling
|
|
307
|
+
enabling queries loaded by aiosql to be executed with SQLSpec async drivers.
|
|
287
308
|
"""
|
|
288
309
|
|
|
289
310
|
is_aio_driver: ClassVar[bool] = True
|
|
290
311
|
|
|
291
|
-
def __init__(self, driver: "
|
|
312
|
+
def __init__(self, driver: "AsyncDriverAdapterBase") -> None:
|
|
292
313
|
"""Initialize the async adapter.
|
|
293
314
|
|
|
294
315
|
Args:
|
|
@@ -312,8 +333,8 @@ class AiosqlAsyncAdapter(_AiosqlAdapterBase):
|
|
|
312
333
|
List of query result rows
|
|
313
334
|
|
|
314
335
|
Note:
|
|
315
|
-
record_class parameter is ignored. Use schema_type
|
|
316
|
-
or _sqlspec_schema_type in parameters for type mapping.
|
|
336
|
+
The record_class parameter is ignored for compatibility. Use schema_type
|
|
337
|
+
in driver.execute or _sqlspec_schema_type in parameters for type mapping.
|
|
317
338
|
"""
|
|
318
339
|
if record_class is not None:
|
|
319
340
|
logger.warning(
|
|
@@ -321,9 +342,7 @@ class AiosqlAsyncAdapter(_AiosqlAdapterBase):
|
|
|
321
342
|
"Use schema_type in driver.execute or _sqlspec_schema_type in parameters."
|
|
322
343
|
)
|
|
323
344
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
result = await self.driver.execute(sql_obj, connection=conn) # type: ignore[misc]
|
|
345
|
+
result = await self.driver.execute(self._create_sql_object(sql, parameters), connection=conn) # type: ignore[misc]
|
|
327
346
|
|
|
328
347
|
if hasattr(result, "data") and result.data is not None and isinstance(result, SQLResult):
|
|
329
348
|
return list(result.data)
|
|
@@ -345,8 +364,8 @@ class AiosqlAsyncAdapter(_AiosqlAdapterBase):
|
|
|
345
364
|
First result row or None
|
|
346
365
|
|
|
347
366
|
Note:
|
|
348
|
-
record_class parameter is ignored. Use schema_type
|
|
349
|
-
or _sqlspec_schema_type in parameters for type mapping.
|
|
367
|
+
The record_class parameter is ignored for compatibility. Use schema_type
|
|
368
|
+
in driver.execute or _sqlspec_schema_type in parameters for type mapping.
|
|
350
369
|
"""
|
|
351
370
|
if record_class is not None:
|
|
352
371
|
logger.warning(
|
|
@@ -354,9 +373,7 @@ class AiosqlAsyncAdapter(_AiosqlAdapterBase):
|
|
|
354
373
|
"Use schema_type in driver.execute or _sqlspec_schema_type in parameters."
|
|
355
374
|
)
|
|
356
375
|
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
result = await self.driver.execute(sql_obj, connection=conn) # type: ignore[misc]
|
|
376
|
+
result = await self.driver.execute(self._create_sql_object(sql, parameters), connection=conn) # type: ignore[misc]
|
|
360
377
|
|
|
361
378
|
if hasattr(result, "data") and result.data and isinstance(result, SQLResult):
|
|
362
379
|
return result.data[0]
|
|
@@ -397,22 +414,7 @@ class AiosqlAsyncAdapter(_AiosqlAdapterBase):
|
|
|
397
414
|
Yields:
|
|
398
415
|
Cursor-like object with results
|
|
399
416
|
"""
|
|
400
|
-
|
|
401
|
-
result = await self.driver.execute(sql_obj, connection=conn) # type: ignore[misc]
|
|
402
|
-
|
|
403
|
-
class AsyncCursorLike:
|
|
404
|
-
def __init__(self, result: Any) -> None:
|
|
405
|
-
self.result = result
|
|
406
|
-
|
|
407
|
-
@staticmethod
|
|
408
|
-
async def fetchall() -> list[Any]:
|
|
409
|
-
if isinstance(result, SQLResult) and result.data is not None:
|
|
410
|
-
return list(result.data)
|
|
411
|
-
return []
|
|
412
|
-
|
|
413
|
-
async def fetchone(self) -> Optional[Any]:
|
|
414
|
-
rows = await self.fetchall()
|
|
415
|
-
return rows[0] if rows else None
|
|
417
|
+
result = await self.driver.execute(self._create_sql_object(sql, parameters), connection=conn) # type: ignore[misc]
|
|
416
418
|
|
|
417
419
|
yield AsyncCursorLike(result)
|
|
418
420
|
|
|
@@ -426,11 +428,9 @@ class AiosqlAsyncAdapter(_AiosqlAdapterBase):
|
|
|
426
428
|
parameters: Query parameters
|
|
427
429
|
|
|
428
430
|
Note:
|
|
429
|
-
|
|
431
|
+
Returns None per aiosql async protocol
|
|
430
432
|
"""
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
await self.driver.execute(sql_obj, connection=conn) # type: ignore[misc]
|
|
433
|
+
await self.driver.execute(self._create_sql_object(sql, parameters), connection=conn) # type: ignore[misc]
|
|
434
434
|
|
|
435
435
|
async def insert_update_delete_many(self, conn: Any, query_name: str, sql: str, parameters: "Any") -> None:
|
|
436
436
|
"""Execute INSERT/UPDATE/DELETE with many parameter sets.
|
|
@@ -442,11 +442,9 @@ class AiosqlAsyncAdapter(_AiosqlAdapterBase):
|
|
|
442
442
|
parameters: Sequence of parameter sets
|
|
443
443
|
|
|
444
444
|
Note:
|
|
445
|
-
|
|
445
|
+
Returns None per aiosql async protocol
|
|
446
446
|
"""
|
|
447
|
-
|
|
448
|
-
sql_obj = self._create_sql_object(sql)
|
|
449
|
-
await self.driver.execute_many(sql_obj, parameters=parameters, connection=conn) # type: ignore[misc]
|
|
447
|
+
await self.driver.execute_many(self._create_sql_object(sql), parameters=parameters, connection=conn) # type: ignore[misc]
|
|
450
448
|
|
|
451
449
|
async def insert_returning(self, conn: Any, query_name: str, sql: str, parameters: "Any") -> Optional[Any]:
|
|
452
450
|
"""Execute INSERT with RETURNING and return result.
|
|
@@ -460,5 +458,4 @@ class AiosqlAsyncAdapter(_AiosqlAdapterBase):
|
|
|
460
458
|
Returns:
|
|
461
459
|
Returned value or None
|
|
462
460
|
"""
|
|
463
|
-
# INSERT RETURNING is treated like a select that returns data
|
|
464
461
|
return await self.select_one(conn, query_name, sql, parameters)
|
|
@@ -39,7 +39,7 @@ def get_database_migration_plugin(app: "Litestar") -> "SQLSpec":
|
|
|
39
39
|
raise ImproperConfigurationError(msg)
|
|
40
40
|
|
|
41
41
|
|
|
42
|
-
@click.group(cls=LitestarGroup, name="
|
|
42
|
+
@click.group(cls=LitestarGroup, name="db")
|
|
43
43
|
def database_group(ctx: "click.Context") -> None:
|
|
44
44
|
"""Manage SQLSpec database components."""
|
|
45
45
|
ctx.obj = {"app": ctx.obj, "configs": get_database_migration_plugin(ctx.obj.app).config}
|
|
@@ -62,7 +62,6 @@ class DatabaseConfig:
|
|
|
62
62
|
|
|
63
63
|
def __post_init__(self) -> None:
|
|
64
64
|
if not self.config.supports_connection_pooling and self.pool_key == DEFAULT_POOL_KEY: # type: ignore[union-attr,unused-ignore]
|
|
65
|
-
"""If the database configuration does not support connection pooling, the pool key must be unique. We just automatically generate a unique identify so it won't conflict with other configs that may get added"""
|
|
66
65
|
self.pool_key = f"_{self.pool_key}_{id(self.config)}"
|
|
67
66
|
if self.commit_mode == "manual":
|
|
68
67
|
self.before_send_handler = manual_handler_maker(connection_scope_key=self.connection_key)
|
|
@@ -25,7 +25,6 @@ if TYPE_CHECKING:
|
|
|
25
25
|
from sqlspec.typing import ConnectionT, PoolT
|
|
26
26
|
|
|
27
27
|
SESSION_TERMINUS_ASGI_EVENTS = {HTTP_RESPONSE_START, HTTP_DISCONNECT, WEBSOCKET_DISCONNECT, WEBSOCKET_CLOSE}
|
|
28
|
-
"""ASGI events that terminate a session scope."""
|
|
29
28
|
|
|
30
29
|
__all__ = (
|
|
31
30
|
"SESSION_TERMINUS_ASGI_EVENTS",
|
|
@@ -39,7 +38,7 @@ __all__ = (
|
|
|
39
38
|
|
|
40
39
|
|
|
41
40
|
def manual_handler_maker(connection_scope_key: str) -> "Callable[[Message, Scope], Coroutine[Any, Any, None]]":
|
|
42
|
-
"""
|
|
41
|
+
"""Create handler for manual connection management.
|
|
43
42
|
|
|
44
43
|
Args:
|
|
45
44
|
connection_scope_key: The key used to store the connection in the ASGI scope.
|
|
@@ -70,7 +69,7 @@ def autocommit_handler_maker(
|
|
|
70
69
|
extra_commit_statuses: "Optional[set[int]]" = None,
|
|
71
70
|
extra_rollback_statuses: "Optional[set[int]]" = None,
|
|
72
71
|
) -> "Callable[[Message, Scope], Coroutine[Any, Any, None]]":
|
|
73
|
-
"""
|
|
72
|
+
"""Create handler for automatic transaction commit/rollback based on response status.
|
|
74
73
|
|
|
75
74
|
Args:
|
|
76
75
|
connection_scope_key: The key used to store the connection in the ASGI scope.
|
|
@@ -125,27 +124,25 @@ def autocommit_handler_maker(
|
|
|
125
124
|
def lifespan_handler_maker(
|
|
126
125
|
config: "DatabaseConfigProtocol[Any, Any, Any]", pool_key: str
|
|
127
126
|
) -> "Callable[[Litestar], AbstractAsyncContextManager[None]]":
|
|
128
|
-
"""
|
|
129
|
-
|
|
130
|
-
The pool is created on application startup and closed on shutdown.
|
|
127
|
+
"""Create lifespan handler for managing database connection pool lifecycle.
|
|
131
128
|
|
|
132
129
|
Args:
|
|
133
130
|
config: The database configuration object.
|
|
134
131
|
pool_key: The key under which the connection pool will be stored in `app.state`.
|
|
135
132
|
|
|
136
133
|
Returns:
|
|
137
|
-
The
|
|
134
|
+
The lifespan handler function.
|
|
138
135
|
"""
|
|
139
136
|
|
|
140
137
|
@contextlib.asynccontextmanager
|
|
141
138
|
async def lifespan_handler(app: "Litestar") -> "AsyncGenerator[None, None]":
|
|
142
|
-
"""
|
|
139
|
+
"""Manage database pool lifecycle for the application.
|
|
143
140
|
|
|
144
141
|
Args:
|
|
145
142
|
app: The Litestar application instance.
|
|
146
143
|
|
|
147
144
|
Yields:
|
|
148
|
-
|
|
145
|
+
Control to application during pool lifetime.
|
|
149
146
|
"""
|
|
150
147
|
db_pool = await ensure_async_(config.create_pool)()
|
|
151
148
|
app.state.update({pool_key: db_pool})
|
|
@@ -156,7 +153,7 @@ def lifespan_handler_maker(
|
|
|
156
153
|
try:
|
|
157
154
|
await ensure_async_(config.close_pool)()
|
|
158
155
|
except Exception as e:
|
|
159
|
-
if app.logger:
|
|
156
|
+
if app.logger:
|
|
160
157
|
app.logger.warning("Error closing database pool for %s. Error: %s", pool_key, e)
|
|
161
158
|
|
|
162
159
|
return lifespan_handler
|
|
@@ -165,35 +162,30 @@ def lifespan_handler_maker(
|
|
|
165
162
|
def pool_provider_maker(
|
|
166
163
|
config: "DatabaseConfigProtocol[ConnectionT, PoolT, DriverT]", pool_key: str
|
|
167
164
|
) -> "Callable[[State, Scope], Awaitable[PoolT]]":
|
|
168
|
-
"""
|
|
165
|
+
"""Create provider for injecting the application-level database pool.
|
|
169
166
|
|
|
170
167
|
Args:
|
|
171
168
|
config: The database configuration object.
|
|
172
169
|
pool_key: The key used to store the connection pool in `app.state`.
|
|
173
170
|
|
|
174
171
|
Returns:
|
|
175
|
-
The
|
|
172
|
+
The pool provider function.
|
|
176
173
|
"""
|
|
177
174
|
|
|
178
175
|
async def provide_pool(state: "State", scope: "Scope") -> "PoolT":
|
|
179
|
-
"""
|
|
176
|
+
"""Provide the database pool from application state.
|
|
180
177
|
|
|
181
178
|
Args:
|
|
182
179
|
state: The Litestar application State object.
|
|
183
180
|
scope: The ASGI scope (unused for app-level pool).
|
|
184
181
|
|
|
185
|
-
|
|
186
182
|
Returns:
|
|
187
183
|
The database connection pool.
|
|
188
184
|
|
|
189
185
|
Raises:
|
|
190
186
|
ImproperConfigurationError: If the pool is not found in `app.state`.
|
|
191
187
|
"""
|
|
192
|
-
|
|
193
|
-
# state.get(key) accesses app.state[key]
|
|
194
|
-
db_pool = state.get(pool_key)
|
|
195
|
-
if db_pool is None:
|
|
196
|
-
# This case should ideally not happen if the lifespan handler ran correctly.
|
|
188
|
+
if (db_pool := state.get(pool_key)) is None:
|
|
197
189
|
msg = (
|
|
198
190
|
f"Database pool with key '{pool_key}' not found in application state. "
|
|
199
191
|
"Ensure the SQLSpec lifespan handler is correctly configured and has run."
|
|
@@ -208,8 +200,7 @@ def connection_provider_maker(
|
|
|
208
200
|
config: "DatabaseConfigProtocol[ConnectionT, PoolT, DriverT]", pool_key: str, connection_key: str
|
|
209
201
|
) -> "Callable[[State, Scope], AsyncGenerator[ConnectionT, None]]":
|
|
210
202
|
async def provide_connection(state: "State", scope: "Scope") -> "AsyncGenerator[ConnectionT, None]":
|
|
211
|
-
db_pool
|
|
212
|
-
if db_pool is None:
|
|
203
|
+
if (db_pool := state.get(pool_key)) is None:
|
|
213
204
|
msg = f"Database pool with key '{pool_key}' not found. Cannot create a connection."
|
|
214
205
|
raise ImproperConfigurationError(msg)
|
|
215
206
|
|
|
@@ -225,15 +216,13 @@ def connection_provider_maker(
|
|
|
225
216
|
yield conn_instance
|
|
226
217
|
return
|
|
227
218
|
|
|
228
|
-
entered_connection
|
|
219
|
+
entered_connection = await connection_cm.__aenter__()
|
|
229
220
|
try:
|
|
230
|
-
entered_connection = await connection_cm.__aenter__()
|
|
231
221
|
set_sqlspec_scope_state(scope, connection_key, entered_connection)
|
|
232
222
|
yield entered_connection
|
|
233
223
|
finally:
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
delete_sqlspec_scope_state(scope, connection_key) # Clear from scope
|
|
224
|
+
await connection_cm.__aexit__(None, None, None)
|
|
225
|
+
delete_sqlspec_scope_state(scope, connection_key)
|
|
237
226
|
|
|
238
227
|
return provide_connection
|
|
239
228
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from typing import TYPE_CHECKING, Any, Union
|
|
2
2
|
|
|
3
3
|
from litestar.di import Provide
|
|
4
|
-
from litestar.plugins import InitPluginProtocol
|
|
4
|
+
from litestar.plugins import CLIPlugin, InitPluginProtocol
|
|
5
5
|
|
|
6
6
|
from sqlspec.base import SQLSpec as SQLSpecBase
|
|
7
7
|
from sqlspec.config import AsyncConfigT, DatabaseConfigProtocol, DriverT, SyncConfigT
|
|
@@ -17,16 +17,16 @@ if TYPE_CHECKING:
|
|
|
17
17
|
logger = get_logger("extensions.litestar")
|
|
18
18
|
|
|
19
19
|
|
|
20
|
-
class SQLSpec(InitPluginProtocol, SQLSpecBase):
|
|
21
|
-
"""SQLSpec
|
|
20
|
+
class SQLSpec(InitPluginProtocol, CLIPlugin, SQLSpecBase):
|
|
21
|
+
"""Litestar plugin for SQLSpec database integration."""
|
|
22
22
|
|
|
23
23
|
__slots__ = ("_config", "_plugin_configs")
|
|
24
24
|
|
|
25
25
|
def __init__(self, config: Union["SyncConfigT", "AsyncConfigT", "DatabaseConfig", list["DatabaseConfig"]]) -> None:
|
|
26
|
-
"""Initialize
|
|
26
|
+
"""Initialize SQLSpec plugin.
|
|
27
27
|
|
|
28
28
|
Args:
|
|
29
|
-
config:
|
|
29
|
+
config: Database configuration for SQLSpec plugin.
|
|
30
30
|
"""
|
|
31
31
|
self._configs: dict[Any, DatabaseConfigProtocol[Any, Any, Any]] = {}
|
|
32
32
|
if isinstance(config, DatabaseConfigProtocol):
|
|
@@ -38,27 +38,31 @@ class SQLSpec(InitPluginProtocol, SQLSpecBase):
|
|
|
38
38
|
|
|
39
39
|
@property
|
|
40
40
|
def config(self) -> "list[DatabaseConfig]": # pyright: ignore[reportInvalidTypeVarUse]
|
|
41
|
-
"""Return the plugin
|
|
41
|
+
"""Return the plugin configuration.
|
|
42
42
|
|
|
43
43
|
Returns:
|
|
44
|
-
|
|
44
|
+
List of database configurations.
|
|
45
45
|
"""
|
|
46
46
|
return self._plugin_configs
|
|
47
47
|
|
|
48
48
|
def on_cli_init(self, cli: "Group") -> None:
|
|
49
|
-
"""Configure
|
|
49
|
+
"""Configure CLI commands for SQLSpec database operations.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
cli: The Click command group to add commands to.
|
|
53
|
+
"""
|
|
50
54
|
from sqlspec.extensions.litestar.cli import database_group
|
|
51
55
|
|
|
52
56
|
cli.add_command(database_group)
|
|
53
57
|
|
|
54
58
|
def on_app_init(self, app_config: "AppConfig") -> "AppConfig":
|
|
55
|
-
"""Configure application
|
|
59
|
+
"""Configure Litestar application with SQLSpec database integration.
|
|
56
60
|
|
|
57
61
|
Args:
|
|
58
|
-
app_config: The
|
|
62
|
+
app_config: The Litestar application configuration instance.
|
|
59
63
|
|
|
60
64
|
Returns:
|
|
61
|
-
The updated
|
|
65
|
+
The updated application configuration instance.
|
|
62
66
|
"""
|
|
63
67
|
|
|
64
68
|
self._validate_dependency_keys()
|
|
@@ -67,7 +71,6 @@ class SQLSpec(InitPluginProtocol, SQLSpecBase):
|
|
|
67
71
|
app_config.state.sqlspec = self
|
|
68
72
|
|
|
69
73
|
app_config.on_startup.append(store_sqlspec_in_state)
|
|
70
|
-
# Register types for injection
|
|
71
74
|
app_config.signature_types.extend(
|
|
72
75
|
[SQLSpec, ConnectionT, PoolT, DriverT, DatabaseConfig, DatabaseConfigProtocol, SyncConfigT, AsyncConfigT]
|
|
73
76
|
)
|
|
@@ -81,8 +84,7 @@ class SQLSpec(InitPluginProtocol, SQLSpecBase):
|
|
|
81
84
|
app_config.signature_types.append(c.config.driver_type) # type: ignore[union-attr]
|
|
82
85
|
|
|
83
86
|
if hasattr(c.config, "get_signature_namespace"):
|
|
84
|
-
|
|
85
|
-
signature_namespace.update(config_namespace)
|
|
87
|
+
signature_namespace.update(c.config.get_signature_namespace()) # type: ignore[attr-defined]
|
|
86
88
|
|
|
87
89
|
app_config.before_send.append(c.before_send_handler)
|
|
88
90
|
app_config.lifespan.append(c.lifespan_handler) # pyright: ignore[reportUnknownMemberType]
|
|
@@ -128,10 +130,10 @@ class SQLSpec(InitPluginProtocol, SQLSpecBase):
|
|
|
128
130
|
raise KeyError(msg)
|
|
129
131
|
|
|
130
132
|
def _validate_dependency_keys(self) -> None:
|
|
131
|
-
"""
|
|
133
|
+
"""Validate that connection and pool keys are unique across configurations.
|
|
132
134
|
|
|
133
135
|
Raises:
|
|
134
|
-
ImproperConfigurationError: If
|
|
136
|
+
ImproperConfigurationError: If connection keys or pool keys are not unique.
|
|
135
137
|
"""
|
|
136
138
|
connection_keys = [c.connection_key for c in self.config]
|
|
137
139
|
pool_keys = [c.pool_key for c in self.config]
|