sqlspec 0.16.2__cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.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.
- 51ff5a9eadfdefd49f98__mypyc.cpython-39-aarch64-linux-gnu.so +0 -0
- sqlspec/__init__.py +92 -0
- sqlspec/__main__.py +12 -0
- sqlspec/__metadata__.py +14 -0
- sqlspec/_serialization.py +77 -0
- sqlspec/_sql.py +1782 -0
- sqlspec/_typing.py +680 -0
- sqlspec/adapters/__init__.py +0 -0
- sqlspec/adapters/adbc/__init__.py +5 -0
- sqlspec/adapters/adbc/_types.py +12 -0
- sqlspec/adapters/adbc/config.py +361 -0
- sqlspec/adapters/adbc/driver.py +512 -0
- sqlspec/adapters/aiosqlite/__init__.py +19 -0
- sqlspec/adapters/aiosqlite/_types.py +13 -0
- sqlspec/adapters/aiosqlite/config.py +253 -0
- sqlspec/adapters/aiosqlite/driver.py +248 -0
- sqlspec/adapters/asyncmy/__init__.py +19 -0
- sqlspec/adapters/asyncmy/_types.py +12 -0
- sqlspec/adapters/asyncmy/config.py +180 -0
- sqlspec/adapters/asyncmy/driver.py +274 -0
- sqlspec/adapters/asyncpg/__init__.py +21 -0
- sqlspec/adapters/asyncpg/_types.py +17 -0
- sqlspec/adapters/asyncpg/config.py +229 -0
- sqlspec/adapters/asyncpg/driver.py +344 -0
- sqlspec/adapters/bigquery/__init__.py +18 -0
- sqlspec/adapters/bigquery/_types.py +12 -0
- sqlspec/adapters/bigquery/config.py +298 -0
- sqlspec/adapters/bigquery/driver.py +558 -0
- sqlspec/adapters/duckdb/__init__.py +22 -0
- sqlspec/adapters/duckdb/_types.py +12 -0
- sqlspec/adapters/duckdb/config.py +504 -0
- sqlspec/adapters/duckdb/driver.py +368 -0
- sqlspec/adapters/oracledb/__init__.py +32 -0
- sqlspec/adapters/oracledb/_types.py +14 -0
- sqlspec/adapters/oracledb/config.py +317 -0
- sqlspec/adapters/oracledb/driver.py +538 -0
- sqlspec/adapters/psqlpy/__init__.py +16 -0
- sqlspec/adapters/psqlpy/_types.py +11 -0
- sqlspec/adapters/psqlpy/config.py +214 -0
- sqlspec/adapters/psqlpy/driver.py +530 -0
- sqlspec/adapters/psycopg/__init__.py +32 -0
- sqlspec/adapters/psycopg/_types.py +17 -0
- sqlspec/adapters/psycopg/config.py +426 -0
- sqlspec/adapters/psycopg/driver.py +796 -0
- sqlspec/adapters/sqlite/__init__.py +15 -0
- sqlspec/adapters/sqlite/_types.py +11 -0
- sqlspec/adapters/sqlite/config.py +240 -0
- sqlspec/adapters/sqlite/driver.py +294 -0
- sqlspec/base.py +571 -0
- sqlspec/builder/__init__.py +62 -0
- sqlspec/builder/_base.py +473 -0
- sqlspec/builder/_column.py +320 -0
- sqlspec/builder/_ddl.py +1346 -0
- sqlspec/builder/_ddl_utils.py +103 -0
- sqlspec/builder/_delete.py +76 -0
- sqlspec/builder/_insert.py +421 -0
- sqlspec/builder/_merge.py +71 -0
- sqlspec/builder/_parsing_utils.py +164 -0
- sqlspec/builder/_select.py +170 -0
- sqlspec/builder/_update.py +188 -0
- sqlspec/builder/mixins/__init__.py +55 -0
- sqlspec/builder/mixins/_cte_and_set_ops.py +222 -0
- sqlspec/builder/mixins/_delete_operations.py +41 -0
- sqlspec/builder/mixins/_insert_operations.py +244 -0
- sqlspec/builder/mixins/_join_operations.py +149 -0
- sqlspec/builder/mixins/_merge_operations.py +562 -0
- sqlspec/builder/mixins/_order_limit_operations.py +135 -0
- sqlspec/builder/mixins/_pivot_operations.py +153 -0
- sqlspec/builder/mixins/_select_operations.py +604 -0
- sqlspec/builder/mixins/_update_operations.py +202 -0
- sqlspec/builder/mixins/_where_clause.py +644 -0
- sqlspec/cli.py +247 -0
- sqlspec/config.py +395 -0
- sqlspec/core/__init__.py +63 -0
- sqlspec/core/cache.cpython-39-aarch64-linux-gnu.so +0 -0
- sqlspec/core/cache.py +871 -0
- sqlspec/core/compiler.cpython-39-aarch64-linux-gnu.so +0 -0
- sqlspec/core/compiler.py +417 -0
- sqlspec/core/filters.cpython-39-aarch64-linux-gnu.so +0 -0
- sqlspec/core/filters.py +830 -0
- sqlspec/core/hashing.cpython-39-aarch64-linux-gnu.so +0 -0
- sqlspec/core/hashing.py +310 -0
- sqlspec/core/parameters.cpython-39-aarch64-linux-gnu.so +0 -0
- sqlspec/core/parameters.py +1237 -0
- sqlspec/core/result.cpython-39-aarch64-linux-gnu.so +0 -0
- sqlspec/core/result.py +677 -0
- sqlspec/core/splitter.cpython-39-aarch64-linux-gnu.so +0 -0
- sqlspec/core/splitter.py +819 -0
- sqlspec/core/statement.cpython-39-aarch64-linux-gnu.so +0 -0
- sqlspec/core/statement.py +676 -0
- sqlspec/driver/__init__.py +19 -0
- sqlspec/driver/_async.py +502 -0
- sqlspec/driver/_common.py +631 -0
- sqlspec/driver/_sync.py +503 -0
- sqlspec/driver/mixins/__init__.py +6 -0
- sqlspec/driver/mixins/_result_tools.py +193 -0
- sqlspec/driver/mixins/_sql_translator.py +86 -0
- sqlspec/exceptions.py +193 -0
- sqlspec/extensions/__init__.py +0 -0
- sqlspec/extensions/aiosql/__init__.py +10 -0
- sqlspec/extensions/aiosql/adapter.py +461 -0
- sqlspec/extensions/litestar/__init__.py +6 -0
- sqlspec/extensions/litestar/_utils.py +52 -0
- sqlspec/extensions/litestar/cli.py +48 -0
- sqlspec/extensions/litestar/config.py +92 -0
- sqlspec/extensions/litestar/handlers.py +260 -0
- sqlspec/extensions/litestar/plugin.py +145 -0
- sqlspec/extensions/litestar/providers.py +454 -0
- sqlspec/loader.cpython-39-aarch64-linux-gnu.so +0 -0
- sqlspec/loader.py +760 -0
- sqlspec/migrations/__init__.py +35 -0
- sqlspec/migrations/base.py +414 -0
- sqlspec/migrations/commands.py +443 -0
- sqlspec/migrations/loaders.py +402 -0
- sqlspec/migrations/runner.py +213 -0
- sqlspec/migrations/tracker.py +140 -0
- sqlspec/migrations/utils.py +129 -0
- sqlspec/protocols.py +407 -0
- sqlspec/py.typed +0 -0
- sqlspec/storage/__init__.py +23 -0
- sqlspec/storage/backends/__init__.py +0 -0
- sqlspec/storage/backends/base.py +163 -0
- sqlspec/storage/backends/fsspec.py +386 -0
- sqlspec/storage/backends/obstore.py +459 -0
- sqlspec/storage/capabilities.py +102 -0
- sqlspec/storage/registry.py +239 -0
- sqlspec/typing.py +299 -0
- sqlspec/utils/__init__.py +3 -0
- sqlspec/utils/correlation.py +150 -0
- sqlspec/utils/deprecation.py +106 -0
- sqlspec/utils/fixtures.cpython-39-aarch64-linux-gnu.so +0 -0
- sqlspec/utils/fixtures.py +58 -0
- sqlspec/utils/logging.py +127 -0
- sqlspec/utils/module_loader.py +89 -0
- sqlspec/utils/serializers.py +4 -0
- sqlspec/utils/singleton.py +32 -0
- sqlspec/utils/sync_tools.cpython-39-aarch64-linux-gnu.so +0 -0
- sqlspec/utils/sync_tools.py +237 -0
- sqlspec/utils/text.cpython-39-aarch64-linux-gnu.so +0 -0
- sqlspec/utils/text.py +96 -0
- sqlspec/utils/type_guards.cpython-39-aarch64-linux-gnu.so +0 -0
- sqlspec/utils/type_guards.py +1139 -0
- sqlspec-0.16.2.dist-info/METADATA +365 -0
- sqlspec-0.16.2.dist-info/RECORD +148 -0
- sqlspec-0.16.2.dist-info/WHEEL +7 -0
- sqlspec-0.16.2.dist-info/entry_points.txt +2 -0
- sqlspec-0.16.2.dist-info/licenses/LICENSE +21 -0
- sqlspec-0.16.2.dist-info/licenses/NOTICE +29 -0
|
@@ -0,0 +1,461 @@
|
|
|
1
|
+
"""AioSQL adapter implementation for SQLSpec.
|
|
2
|
+
|
|
3
|
+
This module provides adapter classes that implement the aiosql adapter protocols
|
|
4
|
+
while using SQLSpec drivers under the hood. This enables users to load SQL queries
|
|
5
|
+
from files using aiosql while leveraging all of SQLSpec's advanced features.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import logging
|
|
9
|
+
from collections.abc import AsyncGenerator, Generator
|
|
10
|
+
from contextlib import asynccontextmanager, contextmanager
|
|
11
|
+
from typing import TYPE_CHECKING, Any, ClassVar, Optional, TypeVar, Union, cast
|
|
12
|
+
|
|
13
|
+
from sqlspec.core.result import SQLResult
|
|
14
|
+
from sqlspec.core.statement import SQL, StatementConfig
|
|
15
|
+
from sqlspec.exceptions import MissingDependencyError
|
|
16
|
+
from sqlspec.typing import AIOSQL_INSTALLED
|
|
17
|
+
|
|
18
|
+
if TYPE_CHECKING:
|
|
19
|
+
from sqlspec.driver import AsyncDriverAdapterBase, SyncDriverAdapterBase
|
|
20
|
+
|
|
21
|
+
logger = logging.getLogger("sqlspec.extensions.aiosql")
|
|
22
|
+
|
|
23
|
+
__all__ = ("AiosqlAsyncAdapter", "AiosqlSyncAdapter")
|
|
24
|
+
|
|
25
|
+
T = TypeVar("T")
|
|
26
|
+
|
|
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
|
+
|
|
56
|
+
def _check_aiosql_available() -> None:
|
|
57
|
+
if not AIOSQL_INSTALLED:
|
|
58
|
+
msg = "aiosql"
|
|
59
|
+
raise MissingDependencyError(msg, "aiosql")
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _normalize_dialect(dialect: "Union[str, Any, None]") -> str:
|
|
63
|
+
"""Normalize dialect name for SQLGlot compatibility.
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
dialect: Original dialect name (can be str, Dialect, type[Dialect], or None)
|
|
67
|
+
|
|
68
|
+
Returns:
|
|
69
|
+
Converted dialect name compatible with SQLGlot
|
|
70
|
+
"""
|
|
71
|
+
if dialect is None:
|
|
72
|
+
return "sql"
|
|
73
|
+
|
|
74
|
+
if hasattr(dialect, "__name__"):
|
|
75
|
+
dialect_str = str(dialect.__name__).lower() # pyright: ignore
|
|
76
|
+
elif hasattr(dialect, "name"):
|
|
77
|
+
dialect_str = str(dialect.name).lower() # pyright: ignore
|
|
78
|
+
elif isinstance(dialect, str):
|
|
79
|
+
dialect_str = dialect.lower()
|
|
80
|
+
else:
|
|
81
|
+
dialect_str = str(dialect).lower()
|
|
82
|
+
|
|
83
|
+
dialect_mapping = {
|
|
84
|
+
"postgresql": "postgres",
|
|
85
|
+
"psycopg": "postgres",
|
|
86
|
+
"asyncpg": "postgres",
|
|
87
|
+
"psqlpy": "postgres",
|
|
88
|
+
"sqlite3": "sqlite",
|
|
89
|
+
"aiosqlite": "sqlite",
|
|
90
|
+
}
|
|
91
|
+
return dialect_mapping.get(dialect_str, dialect_str)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class _AiosqlAdapterBase:
|
|
95
|
+
"""Base adapter class providing common functionality for aiosql integration."""
|
|
96
|
+
|
|
97
|
+
def __init__(self, driver: "Union[SyncDriverAdapterBase, AsyncDriverAdapterBase]") -> None:
|
|
98
|
+
"""Initialize the base adapter.
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
driver: SQLSpec driver to use for execution.
|
|
102
|
+
"""
|
|
103
|
+
_check_aiosql_available()
|
|
104
|
+
self.driver = driver
|
|
105
|
+
|
|
106
|
+
def process_sql(self, query_name: str, op_type: "Any", sql: str) -> str:
|
|
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
|
+
"""
|
|
117
|
+
return sql
|
|
118
|
+
|
|
119
|
+
def _create_sql_object(self, sql: str, parameters: "Any" = None) -> SQL:
|
|
120
|
+
"""Create SQL object with proper configuration.
|
|
121
|
+
|
|
122
|
+
Args:
|
|
123
|
+
sql: SQL string
|
|
124
|
+
parameters: Query parameters
|
|
125
|
+
|
|
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
|
+
)
|
|
135
|
+
|
|
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.
|
|
142
|
+
"""
|
|
143
|
+
|
|
144
|
+
is_aio_driver: ClassVar[bool] = False
|
|
145
|
+
|
|
146
|
+
def __init__(self, driver: "SyncDriverAdapterBase") -> None:
|
|
147
|
+
"""Initialize the sync adapter.
|
|
148
|
+
|
|
149
|
+
Args:
|
|
150
|
+
driver: SQLSpec sync driver to use for execution
|
|
151
|
+
"""
|
|
152
|
+
super().__init__(driver)
|
|
153
|
+
|
|
154
|
+
def select(
|
|
155
|
+
self, conn: Any, query_name: str, sql: str, parameters: "Any", record_class: Optional[Any] = None
|
|
156
|
+
) -> Generator[Any, None, None]:
|
|
157
|
+
"""Execute a SELECT query and return results as generator.
|
|
158
|
+
|
|
159
|
+
Args:
|
|
160
|
+
conn: Database connection (passed through to SQLSpec driver)
|
|
161
|
+
query_name: Name of the query
|
|
162
|
+
sql: SQL string
|
|
163
|
+
parameters: Query parameters
|
|
164
|
+
record_class: Deprecated - use schema_type in driver.execute instead
|
|
165
|
+
|
|
166
|
+
Yields:
|
|
167
|
+
Query result rows
|
|
168
|
+
|
|
169
|
+
Note:
|
|
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.
|
|
172
|
+
"""
|
|
173
|
+
if record_class is not None:
|
|
174
|
+
logger.warning(
|
|
175
|
+
"record_class parameter is deprecated and ignored. "
|
|
176
|
+
"Use schema_type in driver.execute or _sqlspec_schema_type in parameters."
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
result = self.driver.execute(self._create_sql_object(sql, parameters), connection=conn)
|
|
180
|
+
|
|
181
|
+
if isinstance(result, SQLResult) and result.data is not None:
|
|
182
|
+
yield from result.data
|
|
183
|
+
|
|
184
|
+
def select_one(
|
|
185
|
+
self, conn: Any, query_name: str, sql: str, parameters: "Any", record_class: Optional[Any] = None
|
|
186
|
+
) -> Optional[dict[str, Any]]:
|
|
187
|
+
"""Execute a SELECT query and return first result.
|
|
188
|
+
|
|
189
|
+
Args:
|
|
190
|
+
conn: Database connection
|
|
191
|
+
query_name: Name of the query
|
|
192
|
+
sql: SQL string
|
|
193
|
+
parameters: Query parameters
|
|
194
|
+
record_class: Deprecated - use schema_type in driver.execute instead
|
|
195
|
+
|
|
196
|
+
Returns:
|
|
197
|
+
First result row or None
|
|
198
|
+
|
|
199
|
+
Note:
|
|
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.
|
|
202
|
+
"""
|
|
203
|
+
if record_class is not None:
|
|
204
|
+
logger.warning(
|
|
205
|
+
"record_class parameter is deprecated and ignored. "
|
|
206
|
+
"Use schema_type in driver.execute or _sqlspec_schema_type in parameters."
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
result = cast("SQLResult", self.driver.execute(self._create_sql_object(sql, parameters), connection=conn))
|
|
210
|
+
|
|
211
|
+
if hasattr(result, "data") and result.data and isinstance(result, SQLResult):
|
|
212
|
+
return cast("Optional[dict[str, Any]]", result.data[0])
|
|
213
|
+
return None
|
|
214
|
+
|
|
215
|
+
def select_value(self, conn: Any, query_name: str, sql: str, parameters: "Any") -> Optional[Any]:
|
|
216
|
+
"""Execute a SELECT query and return first value of first row.
|
|
217
|
+
|
|
218
|
+
Args:
|
|
219
|
+
conn: Database connection
|
|
220
|
+
query_name: Name of the query
|
|
221
|
+
sql: SQL string
|
|
222
|
+
parameters: Query parameters
|
|
223
|
+
|
|
224
|
+
Returns:
|
|
225
|
+
First value of first row or None
|
|
226
|
+
"""
|
|
227
|
+
row = self.select_one(conn, query_name, sql, parameters)
|
|
228
|
+
if row is None:
|
|
229
|
+
return None
|
|
230
|
+
|
|
231
|
+
if isinstance(row, dict):
|
|
232
|
+
return next(iter(row.values())) if row else None
|
|
233
|
+
if hasattr(row, "__getitem__"):
|
|
234
|
+
return row[0] if len(row) > 0 else None
|
|
235
|
+
return row
|
|
236
|
+
|
|
237
|
+
@contextmanager
|
|
238
|
+
def select_cursor(self, conn: Any, query_name: str, sql: str, parameters: "Any") -> Generator[Any, None, None]:
|
|
239
|
+
"""Execute a SELECT query and return cursor context manager.
|
|
240
|
+
|
|
241
|
+
Args:
|
|
242
|
+
conn: Database connection
|
|
243
|
+
query_name: Name of the query
|
|
244
|
+
sql: SQL string
|
|
245
|
+
parameters: Query parameters
|
|
246
|
+
|
|
247
|
+
Yields:
|
|
248
|
+
Cursor-like object with results
|
|
249
|
+
"""
|
|
250
|
+
result = self.driver.execute(self._create_sql_object(sql, parameters), connection=conn)
|
|
251
|
+
|
|
252
|
+
yield CursorLike(result)
|
|
253
|
+
|
|
254
|
+
def insert_update_delete(self, conn: Any, query_name: str, sql: str, parameters: "Any") -> int:
|
|
255
|
+
"""Execute INSERT/UPDATE/DELETE and return affected rows.
|
|
256
|
+
|
|
257
|
+
Args:
|
|
258
|
+
conn: Database connection
|
|
259
|
+
query_name: Name of the query
|
|
260
|
+
sql: SQL string
|
|
261
|
+
parameters: Query parameters
|
|
262
|
+
|
|
263
|
+
Returns:
|
|
264
|
+
Number of affected rows
|
|
265
|
+
"""
|
|
266
|
+
result = cast("SQLResult", self.driver.execute(self._create_sql_object(sql, parameters), connection=conn))
|
|
267
|
+
|
|
268
|
+
return result.rows_affected if hasattr(result, "rows_affected") else 0
|
|
269
|
+
|
|
270
|
+
def insert_update_delete_many(self, conn: Any, query_name: str, sql: str, parameters: "Any") -> int:
|
|
271
|
+
"""Execute INSERT/UPDATE/DELETE with many parameter sets.
|
|
272
|
+
|
|
273
|
+
Args:
|
|
274
|
+
conn: Database connection
|
|
275
|
+
query_name: Name of the query
|
|
276
|
+
sql: SQL string
|
|
277
|
+
parameters: Sequence of parameter sets
|
|
278
|
+
|
|
279
|
+
Returns:
|
|
280
|
+
Number of affected rows
|
|
281
|
+
"""
|
|
282
|
+
result = cast(
|
|
283
|
+
"SQLResult", self.driver.execute_many(self._create_sql_object(sql), parameters=parameters, connection=conn)
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
return result.rows_affected if hasattr(result, "rows_affected") else 0
|
|
287
|
+
|
|
288
|
+
def insert_returning(self, conn: Any, query_name: str, sql: str, parameters: "Any") -> Optional[Any]:
|
|
289
|
+
"""Execute INSERT with RETURNING and return result.
|
|
290
|
+
|
|
291
|
+
Args:
|
|
292
|
+
conn: Database connection
|
|
293
|
+
query_name: Name of the query
|
|
294
|
+
sql: SQL string
|
|
295
|
+
parameters: Query parameters
|
|
296
|
+
|
|
297
|
+
Returns:
|
|
298
|
+
Returned value or None
|
|
299
|
+
"""
|
|
300
|
+
return self.select_one(conn, query_name, sql, parameters)
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
class AiosqlAsyncAdapter(_AiosqlAdapterBase):
|
|
304
|
+
"""Asynchronous adapter that implements aiosql protocol using SQLSpec drivers.
|
|
305
|
+
|
|
306
|
+
This adapter bridges aiosql's async driver protocol with SQLSpec's async drivers,
|
|
307
|
+
enabling queries loaded by aiosql to be executed with SQLSpec async drivers.
|
|
308
|
+
"""
|
|
309
|
+
|
|
310
|
+
is_aio_driver: ClassVar[bool] = True
|
|
311
|
+
|
|
312
|
+
def __init__(self, driver: "AsyncDriverAdapterBase") -> None:
|
|
313
|
+
"""Initialize the async adapter.
|
|
314
|
+
|
|
315
|
+
Args:
|
|
316
|
+
driver: SQLSpec async driver to use for execution
|
|
317
|
+
"""
|
|
318
|
+
super().__init__(driver)
|
|
319
|
+
|
|
320
|
+
async def select(
|
|
321
|
+
self, conn: Any, query_name: str, sql: str, parameters: "Any", record_class: Optional[Any] = None
|
|
322
|
+
) -> list[Any]:
|
|
323
|
+
"""Execute a SELECT query and return results as list.
|
|
324
|
+
|
|
325
|
+
Args:
|
|
326
|
+
conn: Database connection
|
|
327
|
+
query_name: Name of the query
|
|
328
|
+
sql: SQL string
|
|
329
|
+
parameters: Query parameters
|
|
330
|
+
record_class: Deprecated - use schema_type in driver.execute instead
|
|
331
|
+
|
|
332
|
+
Returns:
|
|
333
|
+
List of query result rows
|
|
334
|
+
|
|
335
|
+
Note:
|
|
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.
|
|
338
|
+
"""
|
|
339
|
+
if record_class is not None:
|
|
340
|
+
logger.warning(
|
|
341
|
+
"record_class parameter is deprecated and ignored. "
|
|
342
|
+
"Use schema_type in driver.execute or _sqlspec_schema_type in parameters."
|
|
343
|
+
)
|
|
344
|
+
|
|
345
|
+
result = await self.driver.execute(self._create_sql_object(sql, parameters), connection=conn) # type: ignore[misc]
|
|
346
|
+
|
|
347
|
+
if hasattr(result, "data") and result.data is not None and isinstance(result, SQLResult):
|
|
348
|
+
return list(result.data)
|
|
349
|
+
return []
|
|
350
|
+
|
|
351
|
+
async def select_one(
|
|
352
|
+
self, conn: Any, query_name: str, sql: str, parameters: "Any", record_class: Optional[Any] = None
|
|
353
|
+
) -> Optional[Any]:
|
|
354
|
+
"""Execute a SELECT query and return first result.
|
|
355
|
+
|
|
356
|
+
Args:
|
|
357
|
+
conn: Database connection
|
|
358
|
+
query_name: Name of the query
|
|
359
|
+
sql: SQL string
|
|
360
|
+
parameters: Query parameters
|
|
361
|
+
record_class: Deprecated - use schema_type in driver.execute instead
|
|
362
|
+
|
|
363
|
+
Returns:
|
|
364
|
+
First result row or None
|
|
365
|
+
|
|
366
|
+
Note:
|
|
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.
|
|
369
|
+
"""
|
|
370
|
+
if record_class is not None:
|
|
371
|
+
logger.warning(
|
|
372
|
+
"record_class parameter is deprecated and ignored. "
|
|
373
|
+
"Use schema_type in driver.execute or _sqlspec_schema_type in parameters."
|
|
374
|
+
)
|
|
375
|
+
|
|
376
|
+
result = await self.driver.execute(self._create_sql_object(sql, parameters), connection=conn) # type: ignore[misc]
|
|
377
|
+
|
|
378
|
+
if hasattr(result, "data") and result.data and isinstance(result, SQLResult):
|
|
379
|
+
return result.data[0]
|
|
380
|
+
return None
|
|
381
|
+
|
|
382
|
+
async def select_value(self, conn: Any, query_name: str, sql: str, parameters: "Any") -> Optional[Any]:
|
|
383
|
+
"""Execute a SELECT query and return first value of first row.
|
|
384
|
+
|
|
385
|
+
Args:
|
|
386
|
+
conn: Database connection
|
|
387
|
+
query_name: Name of the query
|
|
388
|
+
sql: SQL string
|
|
389
|
+
parameters: Query parameters
|
|
390
|
+
|
|
391
|
+
Returns:
|
|
392
|
+
First value of first row or None
|
|
393
|
+
"""
|
|
394
|
+
row = await self.select_one(conn, query_name, sql, parameters)
|
|
395
|
+
if row is None:
|
|
396
|
+
return None
|
|
397
|
+
|
|
398
|
+
if isinstance(row, dict):
|
|
399
|
+
return next(iter(row.values())) if row else None
|
|
400
|
+
if hasattr(row, "__getitem__"):
|
|
401
|
+
return row[0] if len(row) > 0 else None
|
|
402
|
+
return row
|
|
403
|
+
|
|
404
|
+
@asynccontextmanager
|
|
405
|
+
async def select_cursor(self, conn: Any, query_name: str, sql: str, parameters: "Any") -> AsyncGenerator[Any, None]:
|
|
406
|
+
"""Execute a SELECT query and return cursor context manager.
|
|
407
|
+
|
|
408
|
+
Args:
|
|
409
|
+
conn: Database connection
|
|
410
|
+
query_name: Name of the query
|
|
411
|
+
sql: SQL string
|
|
412
|
+
parameters: Query parameters
|
|
413
|
+
|
|
414
|
+
Yields:
|
|
415
|
+
Cursor-like object with results
|
|
416
|
+
"""
|
|
417
|
+
result = await self.driver.execute(self._create_sql_object(sql, parameters), connection=conn) # type: ignore[misc]
|
|
418
|
+
|
|
419
|
+
yield AsyncCursorLike(result)
|
|
420
|
+
|
|
421
|
+
async def insert_update_delete(self, conn: Any, query_name: str, sql: str, parameters: "Any") -> None:
|
|
422
|
+
"""Execute INSERT/UPDATE/DELETE.
|
|
423
|
+
|
|
424
|
+
Args:
|
|
425
|
+
conn: Database connection
|
|
426
|
+
query_name: Name of the query
|
|
427
|
+
sql: SQL string
|
|
428
|
+
parameters: Query parameters
|
|
429
|
+
|
|
430
|
+
Note:
|
|
431
|
+
Returns None per aiosql async protocol
|
|
432
|
+
"""
|
|
433
|
+
await self.driver.execute(self._create_sql_object(sql, parameters), connection=conn) # type: ignore[misc]
|
|
434
|
+
|
|
435
|
+
async def insert_update_delete_many(self, conn: Any, query_name: str, sql: str, parameters: "Any") -> None:
|
|
436
|
+
"""Execute INSERT/UPDATE/DELETE with many parameter sets.
|
|
437
|
+
|
|
438
|
+
Args:
|
|
439
|
+
conn: Database connection
|
|
440
|
+
query_name: Name of the query
|
|
441
|
+
sql: SQL string
|
|
442
|
+
parameters: Sequence of parameter sets
|
|
443
|
+
|
|
444
|
+
Note:
|
|
445
|
+
Returns None per aiosql async protocol
|
|
446
|
+
"""
|
|
447
|
+
await self.driver.execute_many(self._create_sql_object(sql), parameters=parameters, connection=conn) # type: ignore[misc]
|
|
448
|
+
|
|
449
|
+
async def insert_returning(self, conn: Any, query_name: str, sql: str, parameters: "Any") -> Optional[Any]:
|
|
450
|
+
"""Execute INSERT with RETURNING and return result.
|
|
451
|
+
|
|
452
|
+
Args:
|
|
453
|
+
conn: Database connection
|
|
454
|
+
query_name: Name of the query
|
|
455
|
+
sql: SQL string
|
|
456
|
+
parameters: Query parameters
|
|
457
|
+
|
|
458
|
+
Returns:
|
|
459
|
+
Returned value or None
|
|
460
|
+
"""
|
|
461
|
+
return await self.select_one(conn, query_name, sql, parameters)
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
from sqlspec.extensions.litestar import handlers, providers
|
|
2
|
+
from sqlspec.extensions.litestar.cli import database_group
|
|
3
|
+
from sqlspec.extensions.litestar.config import DatabaseConfig
|
|
4
|
+
from sqlspec.extensions.litestar.plugin import SQLSpec
|
|
5
|
+
|
|
6
|
+
__all__ = ("DatabaseConfig", "SQLSpec", "database_group", "handlers", "providers")
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
from typing import TYPE_CHECKING, Any
|
|
2
|
+
|
|
3
|
+
if TYPE_CHECKING:
|
|
4
|
+
from litestar.types import Scope
|
|
5
|
+
|
|
6
|
+
__all__ = ("delete_sqlspec_scope_state", "get_sqlspec_scope_state", "set_sqlspec_scope_state")
|
|
7
|
+
|
|
8
|
+
_SCOPE_NAMESPACE = "_sqlspec"
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def get_sqlspec_scope_state(scope: "Scope", key: str, default: Any = None, pop: bool = False) -> Any:
|
|
12
|
+
"""Get an internal value from connection scope state.
|
|
13
|
+
|
|
14
|
+
Note:
|
|
15
|
+
If called with a default value, this method behaves like to `dict.set_default()`, both setting the key in the
|
|
16
|
+
namespace to the default value, and returning it.
|
|
17
|
+
|
|
18
|
+
If called without a default value, the method behaves like `dict.get()`, returning ``None`` if the key does not
|
|
19
|
+
exist.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
scope: The connection scope.
|
|
23
|
+
key: Key to get from internal namespace in scope state.
|
|
24
|
+
default: Default value to return.
|
|
25
|
+
pop: Boolean flag dictating whether the value should be deleted from the state.
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
Value mapped to ``key`` in internal connection scope namespace.
|
|
29
|
+
"""
|
|
30
|
+
namespace = scope.setdefault(_SCOPE_NAMESPACE, {}) # type: ignore[misc]
|
|
31
|
+
return namespace.pop(key, default) if pop else namespace.get(key, default) # pyright: ignore[reportUnknownVariableType,reportUnknownMemberType]
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def set_sqlspec_scope_state(scope: "Scope", key: str, value: Any) -> None:
|
|
35
|
+
"""Set an internal value in connection scope state.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
scope: The connection scope.
|
|
39
|
+
key: Key to set under internal namespace in scope state.
|
|
40
|
+
value: Value for key.
|
|
41
|
+
"""
|
|
42
|
+
scope.setdefault(_SCOPE_NAMESPACE, {})[key] = value # type: ignore[misc]
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def delete_sqlspec_scope_state(scope: "Scope", key: str) -> None:
|
|
46
|
+
"""Remove an internal value from connection scope state.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
scope: The connection scope.
|
|
50
|
+
key: Key to set under internal namespace in scope state.
|
|
51
|
+
"""
|
|
52
|
+
del scope.setdefault(_SCOPE_NAMESPACE, {})[key] # type: ignore[misc]
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"""Litestar CLI integration for SQLSpec migrations."""
|
|
2
|
+
|
|
3
|
+
from contextlib import suppress
|
|
4
|
+
from typing import TYPE_CHECKING
|
|
5
|
+
|
|
6
|
+
from litestar.cli._utils import LitestarGroup
|
|
7
|
+
|
|
8
|
+
from sqlspec.cli import add_migration_commands
|
|
9
|
+
|
|
10
|
+
try:
|
|
11
|
+
import rich_click as click
|
|
12
|
+
except ImportError:
|
|
13
|
+
import click # type: ignore[no-redef]
|
|
14
|
+
|
|
15
|
+
if TYPE_CHECKING:
|
|
16
|
+
from litestar import Litestar
|
|
17
|
+
|
|
18
|
+
from sqlspec.extensions.litestar.plugin import SQLSpec
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def get_database_migration_plugin(app: "Litestar") -> "SQLSpec":
|
|
22
|
+
"""Retrieve the SQLSpec plugin from the Litestar application's plugins.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
app: The Litestar application
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
The SQLSpec plugin
|
|
29
|
+
|
|
30
|
+
Raises:
|
|
31
|
+
ImproperConfigurationError: If the SQLSpec plugin is not found
|
|
32
|
+
"""
|
|
33
|
+
from sqlspec.exceptions import ImproperConfigurationError
|
|
34
|
+
from sqlspec.extensions.litestar.plugin import SQLSpec
|
|
35
|
+
|
|
36
|
+
with suppress(KeyError):
|
|
37
|
+
return app.plugins.get(SQLSpec)
|
|
38
|
+
msg = "Failed to initialize database migrations. The required SQLSpec plugin is missing."
|
|
39
|
+
raise ImproperConfigurationError(msg)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@click.group(cls=LitestarGroup, name="db")
|
|
43
|
+
def database_group(ctx: "click.Context") -> None:
|
|
44
|
+
"""Manage SQLSpec database components."""
|
|
45
|
+
ctx.obj = {"app": ctx.obj, "configs": get_database_migration_plugin(ctx.obj.app).config}
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
add_migration_commands(database_group)
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
from dataclasses import dataclass, field
|
|
2
|
+
from typing import TYPE_CHECKING, Any, Callable, Literal, Optional, Union
|
|
3
|
+
|
|
4
|
+
from sqlspec.exceptions import ImproperConfigurationError
|
|
5
|
+
from sqlspec.extensions.litestar.handlers import (
|
|
6
|
+
autocommit_handler_maker,
|
|
7
|
+
connection_provider_maker,
|
|
8
|
+
lifespan_handler_maker,
|
|
9
|
+
manual_handler_maker,
|
|
10
|
+
pool_provider_maker,
|
|
11
|
+
session_provider_maker,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from collections.abc import AsyncGenerator, Awaitable
|
|
16
|
+
from contextlib import AbstractAsyncContextManager
|
|
17
|
+
|
|
18
|
+
from litestar import Litestar
|
|
19
|
+
from litestar.datastructures.state import State
|
|
20
|
+
from litestar.types import BeforeMessageSendHookHandler, Scope
|
|
21
|
+
|
|
22
|
+
from sqlspec.config import AsyncConfigT, DriverT, SyncConfigT
|
|
23
|
+
from sqlspec.typing import ConnectionT, PoolT
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
CommitMode = Literal["manual", "autocommit", "autocommit_include_redirect"]
|
|
27
|
+
DEFAULT_COMMIT_MODE: CommitMode = "manual"
|
|
28
|
+
DEFAULT_CONNECTION_KEY = "db_connection"
|
|
29
|
+
DEFAULT_POOL_KEY = "db_pool"
|
|
30
|
+
DEFAULT_SESSION_KEY = "db_session"
|
|
31
|
+
|
|
32
|
+
__all__ = (
|
|
33
|
+
"DEFAULT_COMMIT_MODE",
|
|
34
|
+
"DEFAULT_CONNECTION_KEY",
|
|
35
|
+
"DEFAULT_POOL_KEY",
|
|
36
|
+
"DEFAULT_SESSION_KEY",
|
|
37
|
+
"CommitMode",
|
|
38
|
+
"DatabaseConfig",
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@dataclass
|
|
43
|
+
class DatabaseConfig:
|
|
44
|
+
config: "Union[SyncConfigT, AsyncConfigT]" = field() # type: ignore[valid-type] # pyright: ignore[reportGeneralTypeIssues]
|
|
45
|
+
connection_key: str = field(default=DEFAULT_CONNECTION_KEY)
|
|
46
|
+
pool_key: str = field(default=DEFAULT_POOL_KEY)
|
|
47
|
+
session_key: str = field(default=DEFAULT_SESSION_KEY)
|
|
48
|
+
commit_mode: "CommitMode" = field(default=DEFAULT_COMMIT_MODE)
|
|
49
|
+
extra_commit_statuses: "Optional[set[int]]" = field(default=None)
|
|
50
|
+
extra_rollback_statuses: "Optional[set[int]]" = field(default=None)
|
|
51
|
+
enable_correlation_middleware: bool = field(default=True)
|
|
52
|
+
connection_provider: "Callable[[State, Scope], AsyncGenerator[ConnectionT, None]]" = field( # pyright: ignore[reportGeneralTypeIssues]
|
|
53
|
+
init=False, repr=False, hash=False
|
|
54
|
+
)
|
|
55
|
+
pool_provider: "Callable[[State,Scope], Awaitable[PoolT]]" = field(init=False, repr=False, hash=False) # pyright: ignore[reportGeneralTypeIssues]
|
|
56
|
+
session_provider: "Callable[[Any], AsyncGenerator[DriverT, None]]" = field(init=False, repr=False, hash=False) # pyright: ignore[reportGeneralTypeIssues]
|
|
57
|
+
before_send_handler: "BeforeMessageSendHookHandler" = field(init=False, repr=False, hash=False)
|
|
58
|
+
lifespan_handler: "Callable[[Litestar], AbstractAsyncContextManager[None]]" = field(
|
|
59
|
+
init=False, repr=False, hash=False
|
|
60
|
+
)
|
|
61
|
+
annotation: "type[Union[SyncConfigT, AsyncConfigT]]" = field(init=False, repr=False, hash=False) # type: ignore[valid-type] # pyright: ignore[reportGeneralTypeIssues]
|
|
62
|
+
|
|
63
|
+
def __post_init__(self) -> None:
|
|
64
|
+
if not self.config.supports_connection_pooling and self.pool_key == DEFAULT_POOL_KEY: # type: ignore[union-attr,unused-ignore]
|
|
65
|
+
self.pool_key = f"_{self.pool_key}_{id(self.config)}"
|
|
66
|
+
if self.commit_mode == "manual":
|
|
67
|
+
self.before_send_handler = manual_handler_maker(connection_scope_key=self.connection_key)
|
|
68
|
+
elif self.commit_mode == "autocommit":
|
|
69
|
+
self.before_send_handler = autocommit_handler_maker(
|
|
70
|
+
commit_on_redirect=False,
|
|
71
|
+
extra_commit_statuses=self.extra_commit_statuses,
|
|
72
|
+
extra_rollback_statuses=self.extra_rollback_statuses,
|
|
73
|
+
connection_scope_key=self.connection_key,
|
|
74
|
+
)
|
|
75
|
+
elif self.commit_mode == "autocommit_include_redirect":
|
|
76
|
+
self.before_send_handler = autocommit_handler_maker(
|
|
77
|
+
commit_on_redirect=True,
|
|
78
|
+
extra_commit_statuses=self.extra_commit_statuses,
|
|
79
|
+
extra_rollback_statuses=self.extra_rollback_statuses,
|
|
80
|
+
connection_scope_key=self.connection_key,
|
|
81
|
+
)
|
|
82
|
+
else:
|
|
83
|
+
msg = f"Invalid commit mode: {self.commit_mode}"
|
|
84
|
+
raise ImproperConfigurationError(detail=msg)
|
|
85
|
+
self.lifespan_handler = lifespan_handler_maker(config=self.config, pool_key=self.pool_key)
|
|
86
|
+
self.connection_provider = connection_provider_maker(
|
|
87
|
+
connection_key=self.connection_key, pool_key=self.pool_key, config=self.config
|
|
88
|
+
)
|
|
89
|
+
self.pool_provider = pool_provider_maker(config=self.config, pool_key=self.pool_key)
|
|
90
|
+
self.session_provider = session_provider_maker(
|
|
91
|
+
config=self.config, connection_dependency_key=self.connection_key
|
|
92
|
+
)
|