sqlspec 0.26.0__py3-none-any.whl → 0.28.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 +7 -15
- sqlspec/_serialization.py +55 -25
- sqlspec/_typing.py +155 -52
- sqlspec/adapters/adbc/_types.py +1 -1
- sqlspec/adapters/adbc/adk/__init__.py +5 -0
- sqlspec/adapters/adbc/adk/store.py +880 -0
- sqlspec/adapters/adbc/config.py +62 -12
- sqlspec/adapters/adbc/data_dictionary.py +74 -2
- sqlspec/adapters/adbc/driver.py +226 -58
- sqlspec/adapters/adbc/litestar/__init__.py +5 -0
- sqlspec/adapters/adbc/litestar/store.py +504 -0
- sqlspec/adapters/adbc/type_converter.py +44 -50
- sqlspec/adapters/aiosqlite/_types.py +1 -1
- sqlspec/adapters/aiosqlite/adk/__init__.py +5 -0
- sqlspec/adapters/aiosqlite/adk/store.py +536 -0
- sqlspec/adapters/aiosqlite/config.py +86 -16
- sqlspec/adapters/aiosqlite/data_dictionary.py +34 -2
- sqlspec/adapters/aiosqlite/driver.py +127 -38
- sqlspec/adapters/aiosqlite/litestar/__init__.py +5 -0
- sqlspec/adapters/aiosqlite/litestar/store.py +281 -0
- sqlspec/adapters/aiosqlite/pool.py +7 -7
- sqlspec/adapters/asyncmy/__init__.py +7 -1
- sqlspec/adapters/asyncmy/_types.py +1 -1
- sqlspec/adapters/asyncmy/adk/__init__.py +5 -0
- sqlspec/adapters/asyncmy/adk/store.py +503 -0
- sqlspec/adapters/asyncmy/config.py +59 -17
- sqlspec/adapters/asyncmy/data_dictionary.py +41 -2
- sqlspec/adapters/asyncmy/driver.py +293 -62
- sqlspec/adapters/asyncmy/litestar/__init__.py +5 -0
- sqlspec/adapters/asyncmy/litestar/store.py +296 -0
- sqlspec/adapters/asyncpg/__init__.py +2 -1
- sqlspec/adapters/asyncpg/_type_handlers.py +71 -0
- sqlspec/adapters/asyncpg/_types.py +11 -7
- sqlspec/adapters/asyncpg/adk/__init__.py +5 -0
- sqlspec/adapters/asyncpg/adk/store.py +460 -0
- sqlspec/adapters/asyncpg/config.py +57 -36
- sqlspec/adapters/asyncpg/data_dictionary.py +48 -2
- sqlspec/adapters/asyncpg/driver.py +153 -23
- sqlspec/adapters/asyncpg/litestar/__init__.py +5 -0
- sqlspec/adapters/asyncpg/litestar/store.py +253 -0
- sqlspec/adapters/bigquery/_types.py +1 -1
- sqlspec/adapters/bigquery/adk/__init__.py +5 -0
- sqlspec/adapters/bigquery/adk/store.py +585 -0
- sqlspec/adapters/bigquery/config.py +36 -11
- sqlspec/adapters/bigquery/data_dictionary.py +42 -2
- sqlspec/adapters/bigquery/driver.py +489 -144
- sqlspec/adapters/bigquery/litestar/__init__.py +5 -0
- sqlspec/adapters/bigquery/litestar/store.py +327 -0
- sqlspec/adapters/bigquery/type_converter.py +55 -23
- sqlspec/adapters/duckdb/_types.py +2 -2
- sqlspec/adapters/duckdb/adk/__init__.py +14 -0
- sqlspec/adapters/duckdb/adk/store.py +563 -0
- sqlspec/adapters/duckdb/config.py +79 -21
- sqlspec/adapters/duckdb/data_dictionary.py +41 -2
- sqlspec/adapters/duckdb/driver.py +225 -44
- sqlspec/adapters/duckdb/litestar/__init__.py +5 -0
- sqlspec/adapters/duckdb/litestar/store.py +332 -0
- sqlspec/adapters/duckdb/pool.py +5 -5
- sqlspec/adapters/duckdb/type_converter.py +51 -21
- sqlspec/adapters/oracledb/_numpy_handlers.py +133 -0
- sqlspec/adapters/oracledb/_types.py +20 -2
- sqlspec/adapters/oracledb/adk/__init__.py +5 -0
- sqlspec/adapters/oracledb/adk/store.py +1628 -0
- sqlspec/adapters/oracledb/config.py +120 -36
- sqlspec/adapters/oracledb/data_dictionary.py +87 -20
- sqlspec/adapters/oracledb/driver.py +475 -86
- sqlspec/adapters/oracledb/litestar/__init__.py +5 -0
- sqlspec/adapters/oracledb/litestar/store.py +765 -0
- sqlspec/adapters/oracledb/migrations.py +316 -25
- sqlspec/adapters/oracledb/type_converter.py +91 -16
- sqlspec/adapters/psqlpy/_type_handlers.py +44 -0
- sqlspec/adapters/psqlpy/_types.py +2 -1
- sqlspec/adapters/psqlpy/adk/__init__.py +5 -0
- sqlspec/adapters/psqlpy/adk/store.py +483 -0
- sqlspec/adapters/psqlpy/config.py +45 -19
- sqlspec/adapters/psqlpy/data_dictionary.py +48 -2
- sqlspec/adapters/psqlpy/driver.py +108 -41
- sqlspec/adapters/psqlpy/litestar/__init__.py +5 -0
- sqlspec/adapters/psqlpy/litestar/store.py +272 -0
- sqlspec/adapters/psqlpy/type_converter.py +40 -11
- sqlspec/adapters/psycopg/_type_handlers.py +80 -0
- sqlspec/adapters/psycopg/_types.py +2 -1
- sqlspec/adapters/psycopg/adk/__init__.py +5 -0
- sqlspec/adapters/psycopg/adk/store.py +962 -0
- sqlspec/adapters/psycopg/config.py +65 -37
- sqlspec/adapters/psycopg/data_dictionary.py +91 -3
- sqlspec/adapters/psycopg/driver.py +200 -78
- sqlspec/adapters/psycopg/litestar/__init__.py +5 -0
- sqlspec/adapters/psycopg/litestar/store.py +554 -0
- sqlspec/adapters/sqlite/__init__.py +2 -1
- sqlspec/adapters/sqlite/_type_handlers.py +86 -0
- sqlspec/adapters/sqlite/_types.py +1 -1
- sqlspec/adapters/sqlite/adk/__init__.py +5 -0
- sqlspec/adapters/sqlite/adk/store.py +582 -0
- sqlspec/adapters/sqlite/config.py +85 -16
- sqlspec/adapters/sqlite/data_dictionary.py +34 -2
- sqlspec/adapters/sqlite/driver.py +120 -52
- sqlspec/adapters/sqlite/litestar/__init__.py +5 -0
- sqlspec/adapters/sqlite/litestar/store.py +318 -0
- sqlspec/adapters/sqlite/pool.py +5 -5
- sqlspec/base.py +45 -26
- sqlspec/builder/__init__.py +73 -4
- sqlspec/builder/_base.py +91 -58
- sqlspec/builder/_column.py +5 -5
- sqlspec/builder/_ddl.py +98 -89
- sqlspec/builder/_delete.py +5 -4
- sqlspec/builder/_dml.py +388 -0
- sqlspec/{_sql.py → builder/_factory.py} +41 -44
- sqlspec/builder/_insert.py +5 -82
- sqlspec/builder/{mixins/_join_operations.py → _join.py} +145 -143
- sqlspec/builder/_merge.py +446 -11
- sqlspec/builder/_parsing_utils.py +9 -11
- sqlspec/builder/_select.py +1313 -25
- sqlspec/builder/_update.py +11 -42
- sqlspec/cli.py +76 -69
- sqlspec/config.py +331 -62
- sqlspec/core/__init__.py +5 -4
- sqlspec/core/cache.py +18 -18
- sqlspec/core/compiler.py +6 -8
- sqlspec/core/filters.py +55 -47
- sqlspec/core/hashing.py +9 -9
- sqlspec/core/parameters.py +76 -45
- sqlspec/core/result.py +234 -47
- sqlspec/core/splitter.py +16 -17
- sqlspec/core/statement.py +32 -31
- sqlspec/core/type_conversion.py +3 -2
- sqlspec/driver/__init__.py +1 -3
- sqlspec/driver/_async.py +183 -160
- sqlspec/driver/_common.py +197 -109
- sqlspec/driver/_sync.py +189 -161
- sqlspec/driver/mixins/_result_tools.py +20 -236
- sqlspec/driver/mixins/_sql_translator.py +4 -4
- sqlspec/exceptions.py +70 -7
- sqlspec/extensions/adk/__init__.py +53 -0
- sqlspec/extensions/adk/_types.py +51 -0
- sqlspec/extensions/adk/converters.py +172 -0
- sqlspec/extensions/adk/migrations/0001_create_adk_tables.py +144 -0
- sqlspec/extensions/adk/migrations/__init__.py +0 -0
- sqlspec/extensions/adk/service.py +181 -0
- sqlspec/extensions/adk/store.py +536 -0
- sqlspec/extensions/aiosql/adapter.py +69 -61
- sqlspec/extensions/fastapi/__init__.py +21 -0
- sqlspec/extensions/fastapi/extension.py +331 -0
- sqlspec/extensions/fastapi/providers.py +543 -0
- sqlspec/extensions/flask/__init__.py +36 -0
- sqlspec/extensions/flask/_state.py +71 -0
- sqlspec/extensions/flask/_utils.py +40 -0
- sqlspec/extensions/flask/extension.py +389 -0
- sqlspec/extensions/litestar/__init__.py +21 -4
- sqlspec/extensions/litestar/cli.py +54 -10
- sqlspec/extensions/litestar/config.py +56 -266
- sqlspec/extensions/litestar/handlers.py +46 -17
- sqlspec/extensions/litestar/migrations/0001_create_session_table.py +137 -0
- sqlspec/extensions/litestar/migrations/__init__.py +3 -0
- sqlspec/extensions/litestar/plugin.py +349 -224
- sqlspec/extensions/litestar/providers.py +25 -25
- sqlspec/extensions/litestar/store.py +265 -0
- sqlspec/extensions/starlette/__init__.py +10 -0
- sqlspec/extensions/starlette/_state.py +25 -0
- sqlspec/extensions/starlette/_utils.py +52 -0
- sqlspec/extensions/starlette/extension.py +254 -0
- sqlspec/extensions/starlette/middleware.py +154 -0
- sqlspec/loader.py +30 -49
- sqlspec/migrations/base.py +200 -76
- sqlspec/migrations/commands.py +591 -62
- sqlspec/migrations/context.py +6 -9
- sqlspec/migrations/fix.py +199 -0
- sqlspec/migrations/loaders.py +47 -19
- sqlspec/migrations/runner.py +241 -75
- sqlspec/migrations/tracker.py +237 -21
- sqlspec/migrations/utils.py +51 -3
- sqlspec/migrations/validation.py +177 -0
- sqlspec/protocols.py +106 -36
- sqlspec/storage/_utils.py +85 -0
- sqlspec/storage/backends/fsspec.py +133 -107
- sqlspec/storage/backends/local.py +78 -51
- sqlspec/storage/backends/obstore.py +276 -168
- sqlspec/storage/registry.py +75 -39
- sqlspec/typing.py +30 -84
- sqlspec/utils/__init__.py +25 -4
- sqlspec/utils/arrow_helpers.py +81 -0
- sqlspec/utils/config_resolver.py +6 -6
- sqlspec/utils/correlation.py +4 -5
- sqlspec/utils/data_transformation.py +3 -2
- sqlspec/utils/deprecation.py +9 -8
- sqlspec/utils/fixtures.py +4 -4
- sqlspec/utils/logging.py +46 -6
- sqlspec/utils/module_loader.py +205 -5
- sqlspec/utils/portal.py +311 -0
- sqlspec/utils/schema.py +288 -0
- sqlspec/utils/serializers.py +113 -4
- sqlspec/utils/sync_tools.py +36 -22
- sqlspec/utils/text.py +1 -2
- sqlspec/utils/type_guards.py +136 -20
- sqlspec/utils/version.py +433 -0
- {sqlspec-0.26.0.dist-info → sqlspec-0.28.0.dist-info}/METADATA +41 -22
- sqlspec-0.28.0.dist-info/RECORD +221 -0
- sqlspec/builder/mixins/__init__.py +0 -55
- sqlspec/builder/mixins/_cte_and_set_ops.py +0 -253
- sqlspec/builder/mixins/_delete_operations.py +0 -50
- sqlspec/builder/mixins/_insert_operations.py +0 -282
- sqlspec/builder/mixins/_merge_operations.py +0 -698
- sqlspec/builder/mixins/_order_limit_operations.py +0 -145
- sqlspec/builder/mixins/_pivot_operations.py +0 -157
- sqlspec/builder/mixins/_select_operations.py +0 -930
- sqlspec/builder/mixins/_update_operations.py +0 -199
- sqlspec/builder/mixins/_where_clause.py +0 -1298
- sqlspec-0.26.0.dist-info/RECORD +0 -157
- sqlspec-0.26.0.dist-info/licenses/NOTICE +0 -29
- {sqlspec-0.26.0.dist-info → sqlspec-0.28.0.dist-info}/WHEEL +0 -0
- {sqlspec-0.26.0.dist-info → sqlspec-0.28.0.dist-info}/entry_points.txt +0 -0
- {sqlspec-0.26.0.dist-info → sqlspec-0.28.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import logging
|
|
4
4
|
from contextlib import asynccontextmanager
|
|
5
|
-
from typing import TYPE_CHECKING, Any, ClassVar,
|
|
5
|
+
from typing import TYPE_CHECKING, Any, ClassVar, TypedDict
|
|
6
6
|
|
|
7
7
|
from typing_extensions import NotRequired
|
|
8
8
|
|
|
@@ -15,30 +15,31 @@ from sqlspec.adapters.aiosqlite.pool import (
|
|
|
15
15
|
AiosqlitePoolConnection,
|
|
16
16
|
)
|
|
17
17
|
from sqlspec.config import AsyncDatabaseConfig
|
|
18
|
+
from sqlspec.utils.serializers import from_json, to_json
|
|
18
19
|
|
|
19
20
|
if TYPE_CHECKING:
|
|
20
|
-
from collections.abc import AsyncGenerator
|
|
21
|
+
from collections.abc import AsyncGenerator, Callable
|
|
21
22
|
|
|
22
23
|
from sqlspec.core.statement import StatementConfig
|
|
23
24
|
|
|
24
|
-
__all__ = ("AiosqliteConfig", "AiosqliteConnectionParams", "AiosqlitePoolParams")
|
|
25
|
+
__all__ = ("AiosqliteConfig", "AiosqliteConnectionParams", "AiosqliteDriverFeatures", "AiosqlitePoolParams")
|
|
25
26
|
|
|
26
27
|
logger = logging.getLogger(__name__)
|
|
27
28
|
|
|
28
29
|
|
|
29
|
-
class AiosqliteConnectionParams(TypedDict
|
|
30
|
+
class AiosqliteConnectionParams(TypedDict):
|
|
30
31
|
"""TypedDict for aiosqlite connection parameters."""
|
|
31
32
|
|
|
32
33
|
database: NotRequired[str]
|
|
33
34
|
timeout: NotRequired[float]
|
|
34
35
|
detect_types: NotRequired[int]
|
|
35
|
-
isolation_level: NotRequired[
|
|
36
|
+
isolation_level: NotRequired[str | None]
|
|
36
37
|
check_same_thread: NotRequired[bool]
|
|
37
38
|
cached_statements: NotRequired[int]
|
|
38
39
|
uri: NotRequired[bool]
|
|
39
40
|
|
|
40
41
|
|
|
41
|
-
class AiosqlitePoolParams(AiosqliteConnectionParams
|
|
42
|
+
class AiosqlitePoolParams(AiosqliteConnectionParams):
|
|
42
43
|
"""TypedDict for aiosqlite pool parameters, inheriting connection parameters."""
|
|
43
44
|
|
|
44
45
|
pool_size: NotRequired[int]
|
|
@@ -48,21 +49,42 @@ class AiosqlitePoolParams(AiosqliteConnectionParams, total=False):
|
|
|
48
49
|
extra: NotRequired[dict[str, Any]]
|
|
49
50
|
|
|
50
51
|
|
|
52
|
+
class AiosqliteDriverFeatures(TypedDict):
|
|
53
|
+
"""Aiosqlite driver feature configuration.
|
|
54
|
+
|
|
55
|
+
Controls optional type handling and serialization features for SQLite connections.
|
|
56
|
+
|
|
57
|
+
enable_custom_adapters: Enable custom type adapters for JSON/UUID/datetime conversion.
|
|
58
|
+
Defaults to True for enhanced Python type support.
|
|
59
|
+
Set to False only if you need pure SQLite behavior without type conversions.
|
|
60
|
+
json_serializer: Custom JSON serializer function.
|
|
61
|
+
Defaults to sqlspec.utils.serializers.to_json.
|
|
62
|
+
json_deserializer: Custom JSON deserializer function.
|
|
63
|
+
Defaults to sqlspec.utils.serializers.from_json.
|
|
64
|
+
"""
|
|
65
|
+
|
|
66
|
+
enable_custom_adapters: NotRequired[bool]
|
|
67
|
+
json_serializer: "NotRequired[Callable[[Any], str]]"
|
|
68
|
+
json_deserializer: "NotRequired[Callable[[str], Any]]"
|
|
69
|
+
|
|
70
|
+
|
|
51
71
|
class AiosqliteConfig(AsyncDatabaseConfig["AiosqliteConnection", AiosqliteConnectionPool, AiosqliteDriver]):
|
|
52
72
|
"""Database configuration for AioSQLite engine."""
|
|
53
73
|
|
|
54
74
|
driver_type: "ClassVar[type[AiosqliteDriver]]" = AiosqliteDriver
|
|
55
75
|
connection_type: "ClassVar[type[AiosqliteConnection]]" = AiosqliteConnection
|
|
76
|
+
supports_transactional_ddl: "ClassVar[bool]" = True
|
|
56
77
|
|
|
57
78
|
def __init__(
|
|
58
79
|
self,
|
|
59
80
|
*,
|
|
60
|
-
pool_config: "
|
|
61
|
-
pool_instance: "
|
|
62
|
-
migration_config: "
|
|
63
|
-
statement_config: "
|
|
64
|
-
driver_features: "
|
|
65
|
-
bind_key: "
|
|
81
|
+
pool_config: "AiosqlitePoolParams | dict[str, Any] | None" = None,
|
|
82
|
+
pool_instance: "AiosqliteConnectionPool | None" = None,
|
|
83
|
+
migration_config: "dict[str, Any] | None" = None,
|
|
84
|
+
statement_config: "StatementConfig | None" = None,
|
|
85
|
+
driver_features: "AiosqliteDriverFeatures | dict[str, Any] | None" = None,
|
|
86
|
+
bind_key: "str | None" = None,
|
|
87
|
+
extension_config: "dict[str, dict[str, Any]] | None" = None,
|
|
66
88
|
) -> None:
|
|
67
89
|
"""Initialize AioSQLite configuration.
|
|
68
90
|
|
|
@@ -73,20 +95,42 @@ class AiosqliteConfig(AsyncDatabaseConfig["AiosqliteConnection", AiosqliteConnec
|
|
|
73
95
|
statement_config: Optional statement configuration.
|
|
74
96
|
driver_features: Optional driver feature configuration.
|
|
75
97
|
bind_key: Optional unique identifier for this configuration.
|
|
98
|
+
extension_config: Extension-specific configuration (e.g., Litestar plugin settings)
|
|
76
99
|
"""
|
|
77
100
|
config_dict = dict(pool_config) if pool_config else {}
|
|
78
101
|
|
|
79
102
|
if "database" not in config_dict or config_dict["database"] == ":memory:":
|
|
80
103
|
config_dict["database"] = "file::memory:?cache=shared"
|
|
81
104
|
config_dict["uri"] = True
|
|
105
|
+
elif "database" in config_dict:
|
|
106
|
+
database_path = str(config_dict["database"])
|
|
107
|
+
if database_path.startswith("file:") and not config_dict.get("uri"):
|
|
108
|
+
logger.debug(
|
|
109
|
+
"Database URI detected (%s) but uri=True not set. "
|
|
110
|
+
"Auto-enabling URI mode to prevent physical file creation.",
|
|
111
|
+
database_path,
|
|
112
|
+
)
|
|
113
|
+
config_dict["uri"] = True
|
|
114
|
+
|
|
115
|
+
processed_driver_features: dict[str, Any] = dict(driver_features) if driver_features else {}
|
|
116
|
+
|
|
117
|
+
if "enable_custom_adapters" not in processed_driver_features:
|
|
118
|
+
processed_driver_features["enable_custom_adapters"] = True
|
|
119
|
+
|
|
120
|
+
if "json_serializer" not in processed_driver_features:
|
|
121
|
+
processed_driver_features["json_serializer"] = to_json
|
|
122
|
+
|
|
123
|
+
if "json_deserializer" not in processed_driver_features:
|
|
124
|
+
processed_driver_features["json_deserializer"] = from_json
|
|
82
125
|
|
|
83
126
|
super().__init__(
|
|
84
127
|
pool_config=config_dict,
|
|
85
128
|
pool_instance=pool_instance,
|
|
86
129
|
migration_config=migration_config,
|
|
87
130
|
statement_config=statement_config or aiosqlite_statement_config,
|
|
88
|
-
driver_features=
|
|
131
|
+
driver_features=processed_driver_features,
|
|
89
132
|
bind_key=bind_key,
|
|
133
|
+
extension_config=extension_config,
|
|
90
134
|
)
|
|
91
135
|
|
|
92
136
|
def _get_pool_config_dict(self) -> "dict[str, Any]":
|
|
@@ -138,7 +182,7 @@ class AiosqliteConfig(AsyncDatabaseConfig["AiosqliteConnection", AiosqliteConnec
|
|
|
138
182
|
|
|
139
183
|
@asynccontextmanager
|
|
140
184
|
async def provide_session(
|
|
141
|
-
self, *_args: Any, statement_config: "
|
|
185
|
+
self, *_args: Any, statement_config: "StatementConfig | None" = None, **_kwargs: Any
|
|
142
186
|
) -> "AsyncGenerator[AiosqliteDriver, None]":
|
|
143
187
|
"""Provide an async driver session context manager.
|
|
144
188
|
|
|
@@ -151,7 +195,11 @@ class AiosqliteConfig(AsyncDatabaseConfig["AiosqliteConnection", AiosqliteConnec
|
|
|
151
195
|
An AiosqliteDriver instance.
|
|
152
196
|
"""
|
|
153
197
|
async with self.provide_connection(*_args, **_kwargs) as connection:
|
|
154
|
-
yield self.driver_type(
|
|
198
|
+
yield self.driver_type(
|
|
199
|
+
connection=connection,
|
|
200
|
+
statement_config=statement_config or self.statement_config,
|
|
201
|
+
driver_features=self.driver_features,
|
|
202
|
+
)
|
|
155
203
|
|
|
156
204
|
async def _create_pool(self) -> AiosqliteConnectionPool:
|
|
157
205
|
"""Create the connection pool instance.
|
|
@@ -165,7 +213,7 @@ class AiosqliteConfig(AsyncDatabaseConfig["AiosqliteConnection", AiosqliteConnec
|
|
|
165
213
|
idle_timeout = config.pop("idle_timeout", 24 * 60 * 60)
|
|
166
214
|
operation_timeout = config.pop("operation_timeout", 10.0)
|
|
167
215
|
|
|
168
|
-
|
|
216
|
+
pool = AiosqliteConnectionPool(
|
|
169
217
|
connection_parameters=self._get_connection_config_dict(),
|
|
170
218
|
pool_size=pool_size,
|
|
171
219
|
connect_timeout=connect_timeout,
|
|
@@ -173,6 +221,28 @@ class AiosqliteConfig(AsyncDatabaseConfig["AiosqliteConnection", AiosqliteConnec
|
|
|
173
221
|
operation_timeout=operation_timeout,
|
|
174
222
|
)
|
|
175
223
|
|
|
224
|
+
if self.driver_features.get("enable_custom_adapters", False):
|
|
225
|
+
self._register_type_adapters()
|
|
226
|
+
|
|
227
|
+
return pool
|
|
228
|
+
|
|
229
|
+
def _register_type_adapters(self) -> None:
|
|
230
|
+
"""Register custom type adapters and converters for SQLite.
|
|
231
|
+
|
|
232
|
+
Called once during pool creation if enable_custom_adapters is True.
|
|
233
|
+
Registers JSON serialization handlers if configured.
|
|
234
|
+
|
|
235
|
+
Note: aiosqlite uses the same sqlite3 module type registration as the
|
|
236
|
+
sync adapter, so this shares the implementation.
|
|
237
|
+
"""
|
|
238
|
+
if self.driver_features.get("enable_custom_adapters", False):
|
|
239
|
+
from sqlspec.adapters.sqlite._type_handlers import register_type_handlers
|
|
240
|
+
|
|
241
|
+
register_type_handlers(
|
|
242
|
+
json_serializer=self.driver_features.get("json_serializer"),
|
|
243
|
+
json_deserializer=self.driver_features.get("json_deserializer"),
|
|
244
|
+
)
|
|
245
|
+
|
|
176
246
|
async def close_pool(self) -> None:
|
|
177
247
|
"""Close the connection pool."""
|
|
178
248
|
if self.pool_instance and not self.pool_instance.is_closed:
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
"""SQLite-specific data dictionary for metadata queries via aiosqlite."""
|
|
2
2
|
|
|
3
3
|
import re
|
|
4
|
-
from typing import TYPE_CHECKING,
|
|
4
|
+
from typing import TYPE_CHECKING, Any, cast
|
|
5
5
|
|
|
6
6
|
from sqlspec.driver import AsyncDataDictionaryBase, AsyncDriverAdapterBase, VersionInfo
|
|
7
7
|
from sqlspec.utils.logging import get_logger
|
|
8
8
|
|
|
9
9
|
if TYPE_CHECKING:
|
|
10
|
+
from collections.abc import Callable
|
|
11
|
+
|
|
10
12
|
from sqlspec.adapters.aiosqlite.driver import AiosqliteDriver
|
|
11
13
|
|
|
12
14
|
logger = get_logger("adapters.aiosqlite.data_dictionary")
|
|
@@ -20,7 +22,7 @@ __all__ = ("AiosqliteAsyncDataDictionary",)
|
|
|
20
22
|
class AiosqliteAsyncDataDictionary(AsyncDataDictionaryBase):
|
|
21
23
|
"""SQLite-specific async data dictionary via aiosqlite."""
|
|
22
24
|
|
|
23
|
-
async def get_version(self, driver: AsyncDriverAdapterBase) -> "
|
|
25
|
+
async def get_version(self, driver: AsyncDriverAdapterBase) -> "VersionInfo | None":
|
|
24
26
|
"""Get SQLite database version information.
|
|
25
27
|
|
|
26
28
|
Args:
|
|
@@ -97,6 +99,36 @@ class AiosqliteAsyncDataDictionary(AsyncDataDictionaryBase):
|
|
|
97
99
|
type_map = {"uuid": "TEXT", "boolean": "INTEGER", "timestamp": "TIMESTAMP", "text": "TEXT", "blob": "BLOB"}
|
|
98
100
|
return type_map.get(type_category, "TEXT")
|
|
99
101
|
|
|
102
|
+
async def get_columns(
|
|
103
|
+
self, driver: AsyncDriverAdapterBase, table: str, schema: "str | None" = None
|
|
104
|
+
) -> "list[dict[str, Any]]":
|
|
105
|
+
"""Get column information for a table using SQLite PRAGMA.
|
|
106
|
+
|
|
107
|
+
Args:
|
|
108
|
+
driver: AioSQLite driver instance
|
|
109
|
+
table: Table name to query columns for
|
|
110
|
+
schema: Schema name (unused in SQLite)
|
|
111
|
+
|
|
112
|
+
Returns:
|
|
113
|
+
List of column metadata dictionaries with keys:
|
|
114
|
+
- column_name: Name of the column
|
|
115
|
+
- data_type: SQLite data type
|
|
116
|
+
- nullable: Whether column allows NULL
|
|
117
|
+
- default_value: Default value if any
|
|
118
|
+
"""
|
|
119
|
+
aiosqlite_driver = cast("AiosqliteDriver", driver)
|
|
120
|
+
result = await aiosqlite_driver.execute(f"PRAGMA table_info({table})")
|
|
121
|
+
|
|
122
|
+
return [
|
|
123
|
+
{
|
|
124
|
+
"column_name": row["name"] if isinstance(row, dict) else row[1],
|
|
125
|
+
"data_type": row["type"] if isinstance(row, dict) else row[2],
|
|
126
|
+
"nullable": not (row["notnull"] if isinstance(row, dict) else row[3]),
|
|
127
|
+
"default_value": row["dflt_value"] if isinstance(row, dict) else row[4],
|
|
128
|
+
}
|
|
129
|
+
for row in result.data or []
|
|
130
|
+
]
|
|
131
|
+
|
|
100
132
|
def list_available_features(self) -> "list[str]":
|
|
101
133
|
"""List available SQLite feature flags.
|
|
102
134
|
|
|
@@ -4,7 +4,7 @@ import asyncio
|
|
|
4
4
|
import contextlib
|
|
5
5
|
import datetime
|
|
6
6
|
from decimal import Decimal
|
|
7
|
-
from typing import TYPE_CHECKING, Any
|
|
7
|
+
from typing import TYPE_CHECKING, Any
|
|
8
8
|
|
|
9
9
|
import aiosqlite
|
|
10
10
|
|
|
@@ -12,7 +12,18 @@ from sqlspec.core.cache import get_cache_config
|
|
|
12
12
|
from sqlspec.core.parameters import ParameterStyle, ParameterStyleConfig
|
|
13
13
|
from sqlspec.core.statement import StatementConfig
|
|
14
14
|
from sqlspec.driver import AsyncDriverAdapterBase
|
|
15
|
-
from sqlspec.exceptions import
|
|
15
|
+
from sqlspec.exceptions import (
|
|
16
|
+
CheckViolationError,
|
|
17
|
+
DatabaseConnectionError,
|
|
18
|
+
DataError,
|
|
19
|
+
ForeignKeyViolationError,
|
|
20
|
+
IntegrityError,
|
|
21
|
+
NotNullViolationError,
|
|
22
|
+
OperationalError,
|
|
23
|
+
SQLParsingError,
|
|
24
|
+
SQLSpecError,
|
|
25
|
+
UniqueViolationError,
|
|
26
|
+
)
|
|
16
27
|
from sqlspec.utils.serializers import to_json
|
|
17
28
|
|
|
18
29
|
if TYPE_CHECKING:
|
|
@@ -26,6 +37,15 @@ if TYPE_CHECKING:
|
|
|
26
37
|
|
|
27
38
|
__all__ = ("AiosqliteCursor", "AiosqliteDriver", "AiosqliteExceptionHandler", "aiosqlite_statement_config")
|
|
28
39
|
|
|
40
|
+
SQLITE_CONSTRAINT_UNIQUE_CODE = 2067
|
|
41
|
+
SQLITE_CONSTRAINT_FOREIGNKEY_CODE = 787
|
|
42
|
+
SQLITE_CONSTRAINT_NOTNULL_CODE = 1811
|
|
43
|
+
SQLITE_CONSTRAINT_CHECK_CODE = 531
|
|
44
|
+
SQLITE_CONSTRAINT_CODE = 19
|
|
45
|
+
SQLITE_CANTOPEN_CODE = 14
|
|
46
|
+
SQLITE_IOERR_CODE = 10
|
|
47
|
+
SQLITE_MISMATCH_CODE = 20
|
|
48
|
+
|
|
29
49
|
|
|
30
50
|
aiosqlite_statement_config = StatementConfig(
|
|
31
51
|
dialect="sqlite",
|
|
@@ -61,7 +81,7 @@ class AiosqliteCursor:
|
|
|
61
81
|
|
|
62
82
|
def __init__(self, connection: "AiosqliteConnection") -> None:
|
|
63
83
|
self.connection = connection
|
|
64
|
-
self.cursor:
|
|
84
|
+
self.cursor: aiosqlite.Cursor | None = None
|
|
65
85
|
|
|
66
86
|
async def __aenter__(self) -> "aiosqlite.Cursor":
|
|
67
87
|
self.cursor = await self.connection.cursor()
|
|
@@ -74,7 +94,11 @@ class AiosqliteCursor:
|
|
|
74
94
|
|
|
75
95
|
|
|
76
96
|
class AiosqliteExceptionHandler:
|
|
77
|
-
"""Async context manager for
|
|
97
|
+
"""Async context manager for handling aiosqlite database exceptions.
|
|
98
|
+
|
|
99
|
+
Maps SQLite extended result codes to specific SQLSpec exceptions
|
|
100
|
+
for better error handling in application code.
|
|
101
|
+
"""
|
|
78
102
|
|
|
79
103
|
__slots__ = ()
|
|
80
104
|
|
|
@@ -84,38 +108,103 @@ class AiosqliteExceptionHandler:
|
|
|
84
108
|
async def __aexit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
|
|
85
109
|
if exc_type is None:
|
|
86
110
|
return
|
|
87
|
-
if issubclass(exc_type, aiosqlite.IntegrityError):
|
|
88
|
-
e = exc_val
|
|
89
|
-
msg = f"AIOSQLite integrity constraint violation: {e}"
|
|
90
|
-
raise SQLSpecError(msg) from e
|
|
91
|
-
if issubclass(exc_type, aiosqlite.OperationalError):
|
|
92
|
-
e = exc_val
|
|
93
|
-
error_msg = str(e).lower()
|
|
94
|
-
if "locked" in error_msg:
|
|
95
|
-
msg = f"AIOSQLite database locked: {e}. Consider enabling WAL mode or reducing concurrency."
|
|
96
|
-
raise SQLSpecError(msg) from e
|
|
97
|
-
if "syntax" in error_msg or "malformed" in error_msg:
|
|
98
|
-
msg = f"AIOSQLite SQL syntax error: {e}"
|
|
99
|
-
raise SQLParsingError(msg) from e
|
|
100
|
-
msg = f"AIOSQLite operational error: {e}"
|
|
101
|
-
raise SQLSpecError(msg) from e
|
|
102
|
-
if issubclass(exc_type, aiosqlite.DatabaseError):
|
|
103
|
-
e = exc_val
|
|
104
|
-
msg = f"AIOSQLite database error: {e}"
|
|
105
|
-
raise SQLSpecError(msg) from e
|
|
106
111
|
if issubclass(exc_type, aiosqlite.Error):
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
112
|
+
self._map_sqlite_exception(exc_val)
|
|
113
|
+
|
|
114
|
+
def _map_sqlite_exception(self, e: Any) -> None:
|
|
115
|
+
"""Map SQLite exception to SQLSpec exception.
|
|
116
|
+
|
|
117
|
+
Args:
|
|
118
|
+
e: aiosqlite.Error instance
|
|
119
|
+
|
|
120
|
+
Raises:
|
|
121
|
+
Specific SQLSpec exception based on error code
|
|
122
|
+
"""
|
|
123
|
+
error_code = getattr(e, "sqlite_errorcode", None)
|
|
124
|
+
error_name = getattr(e, "sqlite_errorname", None)
|
|
125
|
+
error_msg = str(e).lower()
|
|
126
|
+
|
|
127
|
+
if "locked" in error_msg:
|
|
128
|
+
msg = f"AIOSQLite database locked: {e}. Consider enabling WAL mode or reducing concurrency."
|
|
117
129
|
raise SQLSpecError(msg) from e
|
|
118
130
|
|
|
131
|
+
if not error_code:
|
|
132
|
+
if "unique constraint" in error_msg:
|
|
133
|
+
self._raise_unique_violation(e, 0)
|
|
134
|
+
elif "foreign key constraint" in error_msg:
|
|
135
|
+
self._raise_foreign_key_violation(e, 0)
|
|
136
|
+
elif "not null constraint" in error_msg:
|
|
137
|
+
self._raise_not_null_violation(e, 0)
|
|
138
|
+
elif "check constraint" in error_msg:
|
|
139
|
+
self._raise_check_violation(e, 0)
|
|
140
|
+
elif "syntax" in error_msg:
|
|
141
|
+
self._raise_parsing_error(e, None)
|
|
142
|
+
else:
|
|
143
|
+
self._raise_generic_error(e)
|
|
144
|
+
return
|
|
145
|
+
|
|
146
|
+
if error_code == SQLITE_CONSTRAINT_UNIQUE_CODE or error_name == "SQLITE_CONSTRAINT_UNIQUE":
|
|
147
|
+
self._raise_unique_violation(e, error_code)
|
|
148
|
+
elif error_code == SQLITE_CONSTRAINT_FOREIGNKEY_CODE or error_name == "SQLITE_CONSTRAINT_FOREIGNKEY":
|
|
149
|
+
self._raise_foreign_key_violation(e, error_code)
|
|
150
|
+
elif error_code == SQLITE_CONSTRAINT_NOTNULL_CODE or error_name == "SQLITE_CONSTRAINT_NOTNULL":
|
|
151
|
+
self._raise_not_null_violation(e, error_code)
|
|
152
|
+
elif error_code == SQLITE_CONSTRAINT_CHECK_CODE or error_name == "SQLITE_CONSTRAINT_CHECK":
|
|
153
|
+
self._raise_check_violation(e, error_code)
|
|
154
|
+
elif error_code == SQLITE_CONSTRAINT_CODE or error_name == "SQLITE_CONSTRAINT":
|
|
155
|
+
self._raise_integrity_error(e, error_code)
|
|
156
|
+
elif error_code == SQLITE_CANTOPEN_CODE or error_name == "SQLITE_CANTOPEN":
|
|
157
|
+
self._raise_connection_error(e, error_code)
|
|
158
|
+
elif error_code == SQLITE_IOERR_CODE or error_name == "SQLITE_IOERR":
|
|
159
|
+
self._raise_operational_error(e, error_code)
|
|
160
|
+
elif error_code == SQLITE_MISMATCH_CODE or error_name == "SQLITE_MISMATCH":
|
|
161
|
+
self._raise_data_error(e, error_code)
|
|
162
|
+
elif error_code == 1 or "syntax" in error_msg:
|
|
163
|
+
self._raise_parsing_error(e, error_code)
|
|
164
|
+
else:
|
|
165
|
+
self._raise_generic_error(e)
|
|
166
|
+
|
|
167
|
+
def _raise_unique_violation(self, e: Any, code: int) -> None:
|
|
168
|
+
msg = f"SQLite unique constraint violation [code {code}]: {e}"
|
|
169
|
+
raise UniqueViolationError(msg) from e
|
|
170
|
+
|
|
171
|
+
def _raise_foreign_key_violation(self, e: Any, code: int) -> None:
|
|
172
|
+
msg = f"SQLite foreign key constraint violation [code {code}]: {e}"
|
|
173
|
+
raise ForeignKeyViolationError(msg) from e
|
|
174
|
+
|
|
175
|
+
def _raise_not_null_violation(self, e: Any, code: int) -> None:
|
|
176
|
+
msg = f"SQLite not-null constraint violation [code {code}]: {e}"
|
|
177
|
+
raise NotNullViolationError(msg) from e
|
|
178
|
+
|
|
179
|
+
def _raise_check_violation(self, e: Any, code: int) -> None:
|
|
180
|
+
msg = f"SQLite check constraint violation [code {code}]: {e}"
|
|
181
|
+
raise CheckViolationError(msg) from e
|
|
182
|
+
|
|
183
|
+
def _raise_integrity_error(self, e: Any, code: int) -> None:
|
|
184
|
+
msg = f"SQLite integrity constraint violation [code {code}]: {e}"
|
|
185
|
+
raise IntegrityError(msg) from e
|
|
186
|
+
|
|
187
|
+
def _raise_parsing_error(self, e: Any, code: "int | None") -> None:
|
|
188
|
+
code_str = f"[code {code}]" if code else ""
|
|
189
|
+
msg = f"SQLite SQL syntax error {code_str}: {e}"
|
|
190
|
+
raise SQLParsingError(msg) from e
|
|
191
|
+
|
|
192
|
+
def _raise_connection_error(self, e: Any, code: int) -> None:
|
|
193
|
+
msg = f"SQLite connection error [code {code}]: {e}"
|
|
194
|
+
raise DatabaseConnectionError(msg) from e
|
|
195
|
+
|
|
196
|
+
def _raise_operational_error(self, e: Any, code: int) -> None:
|
|
197
|
+
msg = f"SQLite operational error [code {code}]: {e}"
|
|
198
|
+
raise OperationalError(msg) from e
|
|
199
|
+
|
|
200
|
+
def _raise_data_error(self, e: Any, code: int) -> None:
|
|
201
|
+
msg = f"SQLite data error [code {code}]: {e}"
|
|
202
|
+
raise DataError(msg) from e
|
|
203
|
+
|
|
204
|
+
def _raise_generic_error(self, e: Any) -> None:
|
|
205
|
+
msg = f"SQLite database error: {e}"
|
|
206
|
+
raise SQLSpecError(msg) from e
|
|
207
|
+
|
|
119
208
|
|
|
120
209
|
class AiosqliteDriver(AsyncDriverAdapterBase):
|
|
121
210
|
"""AIOSQLite driver for async SQLite database operations."""
|
|
@@ -126,15 +215,15 @@ class AiosqliteDriver(AsyncDriverAdapterBase):
|
|
|
126
215
|
def __init__(
|
|
127
216
|
self,
|
|
128
217
|
connection: "AiosqliteConnection",
|
|
129
|
-
statement_config: "
|
|
130
|
-
driver_features: "
|
|
218
|
+
statement_config: "StatementConfig | None" = None,
|
|
219
|
+
driver_features: "dict[str, Any] | None" = None,
|
|
131
220
|
) -> None:
|
|
132
221
|
if statement_config is None:
|
|
133
222
|
cache_config = get_cache_config()
|
|
134
223
|
statement_config = aiosqlite_statement_config.replace(enable_caching=cache_config.compiled_cache_enabled)
|
|
135
224
|
|
|
136
225
|
super().__init__(connection=connection, statement_config=statement_config, driver_features=driver_features)
|
|
137
|
-
self._data_dictionary:
|
|
226
|
+
self._data_dictionary: AsyncDataDictionaryBase | None = None
|
|
138
227
|
|
|
139
228
|
def with_cursor(self, connection: "AiosqliteConnection") -> "AiosqliteCursor":
|
|
140
229
|
"""Create async context manager for AIOSQLite cursor."""
|
|
@@ -144,7 +233,7 @@ class AiosqliteDriver(AsyncDriverAdapterBase):
|
|
|
144
233
|
"""Handle AIOSQLite-specific exceptions."""
|
|
145
234
|
return AiosqliteExceptionHandler()
|
|
146
235
|
|
|
147
|
-
async def _try_special_handling(self, cursor: "aiosqlite.Cursor", statement: "SQL") -> "
|
|
236
|
+
async def _try_special_handling(self, cursor: "aiosqlite.Cursor", statement: "SQL") -> "SQLResult | None":
|
|
148
237
|
"""Hook for AIOSQLite-specific special operations.
|
|
149
238
|
|
|
150
239
|
Args:
|
|
@@ -196,7 +285,7 @@ class AiosqliteDriver(AsyncDriverAdapterBase):
|
|
|
196
285
|
fetched_data = await cursor.fetchall()
|
|
197
286
|
column_names = [col[0] for col in cursor.description or []]
|
|
198
287
|
|
|
199
|
-
data = [dict(zip(column_names, row)) for row in fetched_data]
|
|
288
|
+
data = [dict(zip(column_names, row, strict=False)) for row in fetched_data]
|
|
200
289
|
|
|
201
290
|
return self.create_execution_result(
|
|
202
291
|
cursor, selected_data=data, column_names=column_names, data_row_count=len(data), is_select_result=True
|